web-dev-qa-db-ja.com

配列をメモ化するために `reselect`をどのように使用しますか?

この状態構造のreduxストアがあるとします。

{
  items: {
    "id1" : {
      foo: "foo1",
      bar: "bar1"
    },
    "id2": {
      foo: "foo2",
      bar: "bar2"
    } 
  }
}

このストアは、アイテムのまったく新しい価値を受け取ることによって進化します。

const reduceItems = function(items = {}, action) {
  if (action.type === 'RECEIVE_ITEM') {
    return {
      ...items,
      [action.payload.id]: action.payload,
    };
  }
  return items;
};

状態の一部のみを抽出するSubItemビューのリストをレンダリングするルートビューを表示したいと思います。たとえば、SubItemビューはfooのみを考慮し、それを取得する必要があります。

function SubItem({ id, foo }) {
  return <div key={id}>{foo}</div>
}

私は状態の「サブパート」のみを気にしているので、それを「ダム」ルートビューに渡したいと思います。

const Root = function({ subitems }) {
  // subitems[0] => { id: 'id1', foo: "foo1" }
  // subitems[1] => { id; 'id2', foo : "foo2" }
  const children = subitems.map(SubItem);
  return <div>{children}</div>;
};

このコンポーネントを簡単に接続して、状態の変更をサブスクライブできます。

function mapStatesToProps(state) {    
 return {
    subitems: xxxSelectSubItems(state)
 }
}
return connect(mapStatesToProps)(Root)

私の根本的な問題は、私が気にしない状態の部分(bar)が変化したときに何が起こるかです。または、アイテムの新しい値を受け取ったときでも、foobarも変更されていません。

setInterval(() => {
    store.dispatch({
      type: 'RECEIVE_ITEM',
      payload: {
        id: 'id1',
        foo: 'foo1',
        bar: 'bar1',
      },
    });
  }, 1000);

「ナイーブ」セレクター実装を使用する場合:

// naive version
function toSubItem(id, item) {
  const foo = item.foo;
  return { id, foo };
}

function dumbSelectSubItems(state) {
  const ids = Object.keys(state.items);
  return ids.map(id => {
    const item = state.items[id];
    return toSubItem(id, item);
  });
}

その場合、リストは呼び出されるたびに完全に新しいオブジェクトになり、私のコンポーネントは毎回無料でレンダリングされます。

もちろん、常に同じリストを返す「定数」セレクターを使用すると、接続されたコンポーネントは純粋であるため、再レンダリングされます(ただし、接続されたコンポーネントが純粋であることを示すためだけです)。

// fully pure implementation
const SUBITEMS = [
  {
    id: 'id0',
    foo: 'foo0',
  },
];
function constSelectSubItems(state) {
  return SUBITEMS;
}

リストが変更されるが同じ要素が含まれている「almostConst」バージョンを使用する場合、これは少し注意が必要です。

const SUBITEM = {
  id: 'id0',
  foo: 'foo0',
};
function almostConstSelectSubItems(state) {
  return [SUBITEM];
}

さて、予想通り、リストが異なるため、内部のアイテムは同じでも、コンポーネントは毎秒再レンダリングされます。

ここで「再選択」が役立つかもしれませんが、要点を完全に見逃していないのではないかと思います。これを使用してreselectを動作させることができます。

const reselectSelectIds = (state, props) => Object.keys(state.items);
const reselectSelectItems = (state, props) => state.items;
const reselectSelectSubItems = createSelector([reSelectIds, reSelectItems], (ids, items) => {
  return ids.map(id => toSubItem(id, items));
});

しかし、それはナイーブバージョンとまったく同じように動作します。

そう:

  • 配列をメモ化しようとするのは無意味ですか?
  • これを処理するために再選択できますか?
  • 州の組織を変更する必要がありますか?
  • 「deepEqual」テストを使用して、ルートにshouldComponentUpdateを実装する必要がありますか?
  • rootが連結成分であることをあきらめて、各LeafItemを連結成分自体にする必要がありますか?
  • immutable.jsは役に立ちますか?
  • Reactは賢く、virtual-domが計算されると何も再描画しないため、実際にはnot問題ですか?

私が彼の無意味なことをしようとしている可能性があり、私のreduxストアに問題を隠しているので、明らかなエラーを自由に述べてください。

9
phtrivier

あなたは間違いなく、再レンダリングを引き起こす新しい配列参照について正しいです、そしてsortはセレクターで正しい軌道に乗っていますが、変更する必要がありますあなたのアプローチはいくつかあります。

Object.keys(state.item)をすぐに返すセレクターを用意するのではなく、オブジェクト自体を処理する必要があります。

const selectItems = state => state.items;

const selectSubItems = createSelector(
    selectItems,
    (items) => {
        const ids = Object.keys(items);
        return ids.map(id => toSubItem(id, items));
    }
);

そうすれば、配列はstate.itemsオブジェクトが置き換えられたときにのみ再計算されます。

それ以外にも、個々のリストアイテムコンポーネントを接続して、各コンポーネントがIDで独自のデータを検索するようにすることも検討してください。例については、私のブログ投稿 Practical Redux、Part 6:Connected Lists、Forms、and Performance を参照してください。 Redux Techniques#Selectors and Normalization および Performance#Redux Performance セクションの React/Reduxリンクリスト にも関連記事がたくさんあります。 。

12
markerikson