微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

直接改变状态是不好的创建副本资源太重怎么办?

如何解决直接改变状态是不好的创建副本资源太重怎么办?

这是我正在开发的一个小游戏。 Screenshot

这些方块中的每一个都由一个对象表示。

{
    row: i,col: j,isWall: false,isVisited: false,isPath: false,parentRow: null,parentCol: null,distance: Infinity
}

isWall 属性与黑色方块相关。

这些都存储在状态中的二维数组中。

this.state = {
    grid: getGrid()
}

我知道我们不应该直接改变状态,所以每次我必须将一个正方形从白色变为黑色时,我都会复制网格,将该正方形的 isWall 属性更改为 true,最后调用 setState。

getGridcopy = () => this.state.grid.map(row => row.map(square => ({...square})));

turnBlack = (row,col) => {
    const grid = this.getGridcopy();
    grid[row][col].isWall = true;
    this.setState({
        grid
    })
}

代码被精简,只显示相关部分)

现在想象一下,我必须一次将整个网格从白色变为完全黑色一个正方形。网格中有数百个方块,我必须将它们中的每一个都更改为黑色,而仅更改一个方块,我必须复制整个 2D 对象数组。 事实证明,这非常耗费资源,我可以明显地看到动画中的断断续续。直接改变状态不复制,动画真的很流畅。

你有什么建议? 我没有太多的开发经验,所以欢迎提出任何建议。这是第一个真正有价值的项目。你能建议用其他方式来存储这些对象而不是二维数组吗?

编辑:

如果只有一个组件以整个网格作为其状态...

我有一个 Board 组件,它具有网格二维数组的这种状态。 我在电路板组件的渲染方法中映射了网格,并为每个单元渲染了一个 Node 组件。 节点组件没有状态。它接收属性作为 props,将相应的 className 应用于 div 并呈现它们。

//Board component
render() {
    return(
        <div className="node-group">
        {
             grid.map((row,i) => (
                 <div key={i} className="node-row">
                 { row.map((node,j) => <Node {...node} key={j} ></Node> ) }
                 </div>
             ))
        }
        </div>
    )
}

//Node component
render() {
    let className = 'node';
    if (this.props.isWall) className += ' node-wall';
    return( <div className={className} ></div> )
}

这是否符合每个单元格都是它自己的反应组件的条件?

解决方法

技术本身没有好坏之分。

让你的状态不可变允许任何数据绑定框架几乎立即检测变化,通过比较旧状态和新状态的引用,而不必深入进行逐个比较。然而,当状态更新时,显然需要付出代价。

另一方面,可变状态的计算量较小,但需要付出大量努力来检测变化。

这里的关键问题是您的视图是如何组织的。如果只有一个组件以整个网格作为其状态,则您将不得不付出代价 - 无论是在更新状态时还是在尝试检测更改时。

但是,如果网格的每个单元格都是一个单独的视图(React 组件),则只需通过将数组的相应元素替换为新元素来更新此对象就足够了。像这样:

function handleGridUpdate(changedRow,changedCol,changes) {
  const newGrid = grid.map(
    (row,i) => rows.map(
      (cell,j) => i === changedRow && j === changedCol 
         ? { ...cell,changes }
         : cell
    )
  );
  setGrid(newGrid);
}

...然后像这样调用这个函数:

handleGridUpdate(row,col,{ isWall: true } )

在此示例中,handleGridUpdate 函数获取更改的网格元素的坐标,以及应应用的更改。但诀窍是,虽然更新列表引用(因为 map 返回数组的新副本),但对于每个未更改的元素,它们的元素保持不变。这在复制和重新渲染组件方面节省了大量时间。

可以不使用索引,而是传递原始对象(因此网格的每个元素都将与其进行比较)。但重要的是,只有这个对象会被一个新的实例替换。

查看 this article 了解更多详情。尽管它需要您处理一个列表,而不是一个网格,但那里展示的关键思想也可以应用于您的案例。

,

在大多数情况下,状态很小,完全复制它们然后更新它的一部分不是问题。

然而,如果状态变大,或者你有一些深度组合的状态,它会影响性能,并且编码变得乏味。这就是持久性变得重要的地方。持久性确保不可变数据结构保持对不改变的数据部分的引用,并在可能的情况下返回引用相同“旧”数据的对象副本,并在需要时返回新数据。这使得这些结构在内存和性能方面都非常高效。

有些库可以为您处理不变性和持久性。最值得注意的是:

  • Immutable.js:内置持久性的不可变集合库。

  • Immer:一种像往常一样更新对象的智能方式,无需担心修改原始对象。

我自己正在为 TypeScript 开发一个名为 Rimbu 的不可变持久集合库。它提供了大量不可变的持久集合,您可以在您的状态中使用这些集合,而不必担心意外修改您的数据。

因为我喜欢你的问题,并且需要材料来测试我自己的库,所以我使用 example in CodeSandbox 为状态创建了一个小型的 Rimbu Table 基本游戏板。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。