【问题标题】:React / Redux: how to update one cell of a grid without re-rendering all the othersReact / Redux:如何更新网格的一个单元格而不重新渲染所有其他单元格
【发布时间】:2020-04-25 08:23:35
【问题描述】:

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

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

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

我使用reselectre-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 函数中的“哪个单元格”日志会针对每个网格单元格写入控制台。

编辑:我在减速器中使用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));
    }

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

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

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

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

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

【问题讨论】:

  • 为什么不连接而不是连接MyGrid 而是将原始值作为简单的道具传递给GridCell 组件?我想说将每个GridCell 包装在connect() 中是这里的关键问题。
  • 您能告诉我们,您是如何更新这些值的吗?
  • 有时 redux 太“复杂”,不允许你做简单的事情。也许github.com/aralroca/fragmented-store 可以帮助您,认为如果您更新商店中的某个字段,则只有使用该特定字段的组件会被重新渲染,而不是商店的其余部分。

标签: reactjs optimization reselect


【解决方案1】:

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

但这并不意味着它会重新渲染该组件。 如果 mapStateToProps 之前和当前的返回值相同,则更新将被中止。

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

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

【讨论】:

  • 谢谢。网格单元格的 render() 方法中的 console.log 显示每个单元格都在重新渲染,但我不知道为什么,因为在我看来组件道具和 MapStateToProps 的返回没有改变。
  • 你能在沙盒中重现吗
  • 还有如何更新商店中的网格值?
  • 我的实际代码还有很多内容,包括几个数组属性。然而,那些并没有改变,只有简单的“价值”道具。
  • 我用沙盒创建了一个新问题,因为问题似乎与数组道具有关,而不是记忆:stackoverflow.com/questions/59641706/…
猜你喜欢
  • 2015-12-15
  • 2011-04-20
  • 2020-02-21
  • 2015-08-24
  • 1970-01-01
  • 2021-04-18
  • 2021-12-27
  • 2017-09-01
  • 2020-03-11
相关资源
最近更新 更多