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 只渲染当前可见的网格项,这将大大提高你的性能。
我有一个 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 只渲染当前可见的网格项,这将大大提高你的性能。