43

READMEの例を見る:

「悪い」構造を考えると:

[{
  id: 1,
  title: 'Some Article',
  author: {
    id: 1,
    name: 'Dan'
  }
}, {
  id: 2,
  title: 'Other Article',
  author: {
    id: 1,
    name: 'Dan'
  }
}]

新しいオブジェクトを追加するのは非常に簡単です。私がしなければならないのは、次のようなものです

return {
  ...state,
  myNewObject
}

減速機で。

「良い」ツリーの構造を考えると、どのようにアプローチすればよいかわかりません。

{
  result: [1, 2],
  entities: {
    articles: {
      1: {
        id: 1,
        title: 'Some Article',
        author: 1
      },
      2: {
        id: 2,
        title: 'Other Article',
        author: 1
      }
    },
    users: {
      1: {
        id: 1,
        name: 'Dan'
      }
    }
  }
}

私が考えたすべてのアプローチには、複雑なオブジェクト操作が必要であり、normalizr が私の人生を楽にしてくれるはずなので、正しい軌道に乗っていないように感じます。

この方法で正規化ツリーを操作している誰かの例をオンラインで見つけることができません。公式の例では追加と削除を行っていないため、これも役に立ちませんでした。

正規化ツリーに正しい方法で追加/削除する方法を教えてもらえますか?

4

5 に答える 5

35

以下は、redux/normalizr 作成者の投稿から直接引用したものです

したがって、状態は次のようになります。

{
  entities: {
    plans: {
      1: {title: 'A', exercises: [1, 2, 3]},
      2: {title: 'B', exercises: [5, 1, 2]}
     },
    exercises: {
      1: {title: 'exe1'},
      2: {title: 'exe2'},
      3: {title: 'exe3'}
    }
  },
  currentPlans: [1, 2]
}

レデューサーは次のようになります

import merge from 'lodash/object/merge';

const exercises = (state = {}, action) => {
  switch (action.type) {
  case 'CREATE_EXERCISE':
    return {
      ...state,
      [action.id]: {
        ...action.exercise
      }
    };
  case 'UPDATE_EXERCISE':
    return {
      ...state,
      [action.id]: {
        ...state[action.id],
        ...action.exercise
      }
    };
  default:
    if (action.entities && action.entities.exercises) {
      return merge({}, state, action.entities.exercises);
    }
    return state;
  }
}

const plans = (state = {}, action) => {
  switch (action.type) {
  case 'CREATE_PLAN':
    return {
      ...state,
      [action.id]: {
        ...action.plan
      }
    };
  case 'UPDATE_PLAN':
    return {
      ...state,
      [action.id]: {
        ...state[action.id],
        ...action.plan
      }
    };
  default:
    if (action.entities && action.entities.plans) {
      return merge({}, state, action.entities.plans);
    }
    return state;
  }
}

const entities = combineReducers({
  plans,
  exercises
});

const currentPlans = (state = [], action) {
  switch (action.type) {
  case 'CREATE_PLAN':
    return [...state, action.id];
  default:
    return state;
  }
}

const reducer = combineReducers({
  entities,
  currentPlans
});

それで、ここで何が起こっているのですか?まず、状態が正規化されていることに注意してください。他のエンティティ内にエンティティが存在することはありません。代わりに、ID によって相互に参照します。そのため、オブジェクトが変更されるたびに、更新が必要な場所が 1 か所だけになります。

次に、plans レデューサーに適切なエンティティを追加し、その ID を currentPlans レデューサーに追加することで、CREATE_PLAN にどのように反応するかに注意してください。これは重要。より複雑なアプリでは、リレーションシップがある場合があります。たとえば、プラン レデューサーは、プラン内の配列に新しい ID を追加することで、同じ方法で ADD_EXERCISE_TO_PLAN を処理できます。ただし、演​​習自体が更新された場合、ID が変更されていないため、plans reducer がそれを知る必要はありません。

第 3 に、エンティティ リデューサー (計画と演習) には、action.entities を監視する特別な節があることに注意してください。これは、すべてのエンティティを更新して反映させたい「既知の真実」を含むサーバー応答がある場合です。アクションをディスパッチする前にこの方法でデータを準備するには、normalizr を使用できます。Redux リポジトリの「実世界」の例で使用されていることがわかります。

最後に、エンティティ レデューサーがどのように似ているかに注意してください。それらを生成する関数を書きたいと思うかもしれません。それは私の回答の範囲外です。柔軟性を高めたい場合もあれば、ボイラープレートを少なくしたい場合もあります。同様のレデューサーを生成する例については、「実際の」レデューサーの例でページネーション コードを確認できます。

ああ、私は { ...a, ...b } 構文を使用しました。ES7 プロポーザルとして、Babel ステージ 2 で有効になります。これは「オブジェクト拡散演算子」と呼ばれ、Object.assign({}, a, b) と書くのと同じです。

ライブラリに関しては、Lodash (変更しないように注意してください。たとえば、merge({}, a, b} は正しいが、merge(a, b) は正しくありません)、updeep、react-addons-update などを使用できます。ただし、深い更新を行う必要がある場合は、おそらく状態ツリーが十分にフラットではなく、機能構成を十分に活用していないことを意味します.最初の例でも:

case 'UPDATE_PLAN':
  return {
    ...state,
    plans: [
      ...state.plans.slice(0, action.idx),
      Object.assign({}, state.plans[action.idx], action.plan),
      ...state.plans.slice(action.idx + 1)
    ]
  };

次のように書くことができます

const plan = (state = {}, action) => {
  switch (action.type) {
  case 'UPDATE_PLAN':
    return Object.assign({}, state, action.plan);
  default:
    return state;
  }
}

const plans = (state = [], action) => {
  if (typeof action.idx === 'undefined') {
    return state;
  }
  return [
    ...state.slice(0, action.idx),
    plan(state[action.idx], action),
    ...state.slice(action.idx + 1)
  ];
};

// somewhere
case 'UPDATE_PLAN':
  return {
    ...state,
    plans: plans(state.plans, action)
  };
于 2016-01-22T21:01:13.160 に答える
1

パーティーには何年も遅れましたが、ここに行きます—

normalized-reducerを使用すると、ボイラープレートなしで正規化されたレデューサーの状態を簡単に管理できます。リレーションシップを記述したスキーマを渡すと、状態のスライスを管理するためのレデューサー、アクション、およびセレクターが返されます。

import makeNormalizedSlice from 'normalized-reducer';

const schema = {
  user: {
    articles: {
      type: 'article', cardinality: 'many', reciprocal: 'author'
    }
  },
  article: {
    author: {
      type: 'user', cardinality: 'one', reciprocal: 'articles'
    }
  }
};

const {
  actionCreators,
  selectors,
  reducer,
  actionTypes,
  emptyState
} = makeNormalizedSlice(schema);

アクションを使用すると、基本的な CRUD ロジックだけでなく、リレーショナル アタッチメント/デタッチメント、カスケード削除、バッチ アクションなどのより複雑なロジックを実行できます。

例を続けると、状態は次のようになります。

{
  "entities": {
    "user": {
      "1": { 
        "id": "1", 
        "name": "Dan",
        "articles": ["1", "2"]
      }
    },
    "article": {
      "1": { 
        "id": "1",
        "author": "1",
        "title": "Some Article",
      },
      "2": {
        "id": "2",
        "author": "1",
        "title": "Other Article",
      }
    }
  },
  "ids": {
    "user": ["1"],
    "article": ["1", "2"]
  }
}

Normalized Reducer は、normalizr とも統合されます。

import { normalize } from 'normalizr'
import { fromNormalizr } from 'normalized-reducer'

const denormalizedData = {...}
const normalizrSchema = {...}

const normalizedData = normalize(denormalizedData, normalizrSchema);
const initialState = fromNormalizr(normalizedData);

normalizr 統合の別の例

于 2020-04-18T20:22:16.207 に答える