17

假设我有一个具有这种状态结构的 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 视图列表的 Root 视图,该视图仅提取状态的一部分。例如,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) 发生变化时会发生什么。甚至,当我收到一个项目的新值时,既foo没有也没有bar改变:

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;
}

现在,如果我使用 List 更改但包含相同元素的“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 作为连接组件,并让每个 LeafItems 本身成为连接组件吗?
  • immutable.js 有帮助吗?
  • 这实际上不是问题吗,因为 React 很聪明,一旦计算了虚拟 dom 就不会重新绘制任何东西?

我试图做的事情可能毫无意义,并在我的 redux 商店中隐藏了一个问题,所以请随意陈述明显的错误。

4

1 回答 1

21

对于导致重新渲染的新数组引用,您绝对是正确的,并且您的选择器在正确的轨道上,但您确实需要改变一些方法

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,第 6 部分:连接列表、表单和性能我的React/Redux 链接列表的Redux Techniques#Selectors and NormalizationPerformance#Redux Performance部分也有很多相关文章。

于 2017-06-01T17:17:15.337 回答