Предположим, у меня есть хранилище 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 заботится только о foos и должно его получить:
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;
}
Теперь это становится немного сложнее, если я использую версию «почти Константа», в которой список изменяется, но содержит тот же элемент.
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));
});
Но потом ведет себя точно так же, как наивная версия.
So:
- бессмысленно пытаться запоминать массив?
- может повторно выбрать справиться с этим?
- я должен изменить организацию государства?
- я должен просто реализовать shouldComponentUpdate в корне, используя тест "deepEqual"?
- должен ли я отказаться от того, что Root является подключенным компонентом, и сделать каждый LeafItems самим подключенным компонентом?
- может помочь immutable.js?
- действительно ли это не проблема, потому что React умен и не будет ничего перерисовывать после вычисления виртуального дома?
Возможно, то, что я пытаюсь сделать, бессмысленно и скрывает проблему в моем магазине redux, поэтому не стесняйтесь указывать очевидные ошибки.