web-dev-qa-db-ja.com

Redux状態のオブジェクトにメソッドを配置する方法は?

docs によると、reactアプリの状態はシリアライズ可能なものでなければなりません。クラスはどうですか?

ToDoアプリがあるとします。これまでのところ、Todoアイテムのそれぞれには、namedateなどのプロパティがあります。今、私は非シリアライズ可能なオブジェクトのメソッドを持ちたいです。つまりTodo.rename()は、todoの名前を変更し、他の多くのことを行います。

私が理解している限り、どこかで関数を宣言してrename(Todo)を実行するか、おそらくその関数をprops this.props.rename(Todo)を介してコンポーネントに渡すことができます。

私は.rename()をどこかに宣言すると2つの問題があります:1)どこ?減速機で?アプリのリデューサーのどこかに_would be instance_メソッドをすべて見つけるのは難しいでしょう。 2)この関数を渡します。本当に?を介してすべての高レベルのコンポーネントから手動で渡す必要があります。さらにメソッドを追加するたびに、大量のボイラープレートを追加して渡すだけですか?または、常に1つのタイプのオブジェクトに対して1つの名前変更メソッドしか持たないことを望みます。 Todo.rename()Task.rename()およびEvent.rename()ではありません

それは私には馬鹿げているようです。オブジェクトは、それに対して何ができるのか、またどのようにしてできるのかを知っている必要があります。そうではありませんか?

ここに何が欠けていますか?

46
Yan

Reduxでは、カスタムモデルは実際にはありません。状態はプレーンオブジェクト(または不変レコード)である必要があります。カスタムメソッドは想定されていません。

モデルにメソッドを配置する代わりに(例:TodoItem.renameアクションを処理するレデューサーを書くことが期待されています。それがReduxの重要なポイントです。

// Manages single todo item
function todoItem(state, action) {
  switch (action.type) {
    case 'ADD':
      return { name: action.name, complete: false };

    case 'RENAME':
      return { ...state, name: action.name };

    case 'TOGGLE_COMPLETE':
      return { ...state, complete: !state.complete };

    default:
      return state;
  }
}

// Manages a list of todo items
function todoItems(state = [], action) {
  switch (action.type) {
    case 'ADD':
      return [...state, todoItem(undefined, action)];

    case 'REMOVE':
      return [
        ...state.slice(0, action.index),
        ...state.slice(action.index + 1)
      ];

    case 'RENAME':
    case 'TOGGLE_COMPLETE':
      return [
        ...state.slice(0, action.index),
        todoItem(state[action.index], action),
        ...state.slice(action.index + 1)
      ];
  }
}

それでも意味がわからない場合は、 Reduxの基本チュートリアル をお読みください。Reduxアプリケーションの構造について間違った考えを持っているようです。

36
Dan Abramov

Danの答えは、アプリケーションをゼロから作成するときにreduxを使用する方法に関してはもちろん正しいです。私の答えは、OPの状況に似ているかもしれない非理想的な動機付けの状況に基づいており、私はそれをどのように扱うかについて考えています。

メソッドを使用して入力オブジェクトの操作を実行するreduxレデューサー内のサードパーティライブラリに依存するreduxアプリケーションを作成しようとしています。これらの入力オブジェクトをredux状態に保ちたいと思いました。擬似コードでの動機付けの状況と、lodashのassignメソッドを使用してそれをどのように「解決」するかを以下に示します。

// Library A is a large 3rd party library I don't want to change
// Library A expects instances of MyClass to have a method 'complexOperation', and also to have property 'a'
LibraryA = class {
  static reliesOnComplexOperation(myClassInstance) {
    if (myClassInstance.complexOperation() && myClassInstance.a < 100) {
      return true;
    } else {
      return false;
    }
  }
}

// MyClass is my own class that I control, and I want to store it's state and make changes to it using the redux reducer.
MyClass = class {
  constructor() {
    this.a = 0;
  }
  myComplexOperation() {
    return a > 10;
  }
}

//and here is my redux reducer, making use of Library A
function reducer(state, action) {
  switch (action.type) {
    case CHANGE_MY_CLASS:
      const myClassInstancePlain = state.myClassInstance;
      // redux has converted myClassInstance into a plain object with no methods, so we need to create a new instance of MyClass, and assign property values from the plain object to the new instance. Using the assign function from lodash library to achieve this - likely not the most efficient way
      const myClassInstance = _.assign(new MyClass(), myClassInstancePlain);

      // now I can pass myClassInstance off to LibraryA, and the complexOperation method will be available
      if (LibraryA.reliesOnComplexOperation(myClassInstance)) {
        myClassInstance.a = 50;
      }
      // I can return myClassInstance as part of the new state, and redux will convert it to a plain object
      return {
        ...state,
        myClassInstance
      };
    default:
      return state;
  }
}

したがって、この例では、redux状態のメソッドを持つオブジェクトをプレーンオブジェクトとして組み込み、reduxレデューサー内で使用できるようにメソッドを追加し直す方法の1つを示しています。このパターンまたはそれをどのように改善できるかについて、ダンまたは他の誰かの考えを聞いてみたいと思います。いくつかの場所でダンが投稿したのを見て、reduxではオブジェクトをメソッドで保存するのは悪い考えなので、ここでの目標はオブジェクトをプレーンなオブジェクトとして保存し、必要なときにメソッドを効率的にアタッチする方法を見つけることです。

5
adailey

これが正確に関連するかどうかはわかりません(コンテキストはReactでReduxを使用しています)が、似たようなものを検索中にこのページを見つけたので、興味がある場合にこれを書いています。

私の動機は、状態そのものではなく、その状態の表現を公開したい状態オブジェクトがあることです。たとえば、(Immutable.jsを使用して)1つ以上のfieldsで構成される状態(schema)があり、それぞれが一意の識別子で識別され、Mapに保存されます、およびフィールドの順序を指定する識別子のリスト。

私は本当に次のようなものを書きたくありません

state.get('field').get(state.get('order').get(nth))

内部表現が非表示になり、簡単に変更できるように、レデューサーに近い以外の場所でn番目のフィールドを取得します。

私が現在していること(そしてこれはちょっとした実験です)は、リデューサーと同じファイルに関数を追加することです:

schema.mapVisibleState = function (state) {
    return {
        getOrderedFields: () => state.get('order').map((fid) => state.get('fields').get(fid)
    }
}

これは、対応するReactコンポーネント(同様に動機付けられたschema.bindActionCreators関数とともに)で使用されます:

export const Schema = connect(
    (state) => schema.mapVisibleState (state),
    (dispatch) => schema.bindActionCreators (dispatch)
)(_Schema) ;

this.props.getOrderedFields()のようにコンポーネント内のフィールドに正しい順序でアクセスでき、基礎となる表現を心配する必要はありません。また、getOrderedFields()関数の依存性注入を簡単に行える追加のNiceプロパティもあります。

また、フィールドの状態についても同様の構造があるため、これを拡張できます。

schema.mapVisibleState = function (state) {
    return {
        getOrderedFields: () => state.get('order').map((fid) => field.mapVisibleState(state.get('fields').get(fid)),
        getNthField (nth) => field.mapVisibleState(state.get('fields').get(state.get('order').get(nth)))
    }
}
4
Mike Richardson