React / Redux:如何在不重新渲染所有其他单元格的情况下更新网格的一个单元格

React / Redux: how to update one cell of a grid without re-rendering all the others

我有一个 React / Redux 应用程序,我在其中绘制了一个二维网格。网格数据在 Redux 存储中,作为数组的数组。例如对于 3x2 网格,它可能是这样的:

state.grid = [
[{value: 1},{value: 2}],
[{value: 0},{value: 1}],
[{value: 1},{value: 3}]
]

用户可以单击网格方块来更改其值。由于网格可能很大,我只想更新已更改的单元格。但无论我尝试什么,整个网格都会更新。

我正在使用 reselect with re-reselect 来记住各个选择器,但我认为它不起作用。这是我的代码的简化版本:

GridCell.js:

class GridCell extends PureComponent {
    render() {
        const { valueObj } = this.props;
        return <span>valueObj.value</span>
}

function mapStateToProps(state, ownProps) {
    const { rowIndex, columnIndex } = ownProps;
    const valueObj = getValueCached(state, `${columnIndex}_${rowIndex}`);
console.log('which cell', `${columnIndex}_${rowIndex}`);

    return {valueObj};
}

export default connect(mapStateToProps)(GridCell);

还有一个父组件绘制 GridCells 的网格,将其 columnIndex 和 rowIndex 作为 props 传递给每个组件:

class MyGrid extends PureComponent {
    const columns = 3;
    const rows = 2;

    render() {
        columns.map((column) => {
            rows.map((row) => {
                <GridCell
                    columnIndex={column}
                    rowIndex={row}
                />
        }
}

在我的 Redux 商店中:

import createCachedSelector from 're-reselect';

export const getValueFromCache = (state, identifier) => {
    const identifiers = identifier.split('_');

    return state.grid[identifiers[0]][identifiers[1]];
};

export const getValueCached = createCachedSelector(
  getValueFromCache,

  // resultFunc
  (value) => value,
)(
  // re-reselect keySelector (receives selectors' arguments)
  // Use "columnIndex_rowIndex" as cacheKey
  // re-reselect only accepts string as cacheKey
  (_state_, identifier) => identifier
);

当用户单击网格正方形时,mapStateToProps 函数中的 "which cell" 日志会针对每个网格单元写入控制台。

编辑:我在我的 reducer 中使用 updeep 来更新值,像这样:

case UPDATE_GRID_CELL: {
    { newValue } = action.payload;
    return updeep({
        'grid': { [columnIndex]: { [rowIndex]: newValue } },
state);
}

我也尝试过使用 connect:

将值作为一个简单的 prop 来获取
function mapStateToProps(state, ownProps) {
    const { rowIndex, columnIndex } = ownProps;

    return {state.grid[columnIndex][rowIndex].value};
}

并且我检查了是否有任何道具或状态发生了变化:

componentDidUpdate(prevProps, prevState) {
        console.log('componentWillUpdate');
        console.log('new', JSON.stringify(this.props));
        console.log('old', JSON.stringify(prevProps));
        console.log('changed props', JSON.stringify(this.props) !== JSON.stringify(prevProps));

        console.log('state');
        console.log('new', JSON.stringify(this.state));
        console.log('old', JSON.stringify(prevState));
        console.log('changed state', JSON.stringify(this.state) !== JSON.stringify(prevState));
    }

如控制台日志所示,此记录为 false,但 render() 方法仍在运行。我很困惑,因为我认为 pureComponent 的意义在于它只有在其状态或道具发生变化时才会呈现?

我希望记忆为未受影响的单元格提供相同的值,并且不会触发组件更新。但我一定是漏掉了什么。

我不知道如何测试记忆是否有效。如果我在 getValueCached() 中放置一个 console.log,它会为每个单元格写入,但我不知道它是否会这样做......?

有什么方法可以防止在更改单个值时每个网格单元组件都更新吗?感谢您的任何建议。

编辑:从评论/回答和进一步调查来看,重新渲染似乎是由数组道具引起的。我创建了一个 ,用一个最小的沙盒示例来说明这一点。

我认为您误解了 mapStateToProps 函数。 每次 redux 状态更新都会调用它,并且它必须弄清楚它的值是否发生了变化。所以它必须为每个单元格调用每个 mapStateToProps。

但这并不意味着它会重新呈现该组件。 如果 mapStateToProps 的先前和当前 return 值相同,则更新将中止。

所以调用mapStateToProps是正确的。检查单元格是否实际重新渲染。您还可以删除 getValueCached 包装器并使用 state.grid[identifiers[0]][identifiers[1]] 直接访问网格。这将比缓存版本更快,因为它只是直接访问。而且您不会从缓存版本中获得任何好处,因为它不是繁重的计算。

连接最高组件甚至可能更快,如果需要让它重新渲染。另一种选择是使用 react-window 只渲染当前可见的网格项,这将大大提高你的性能。