updateState(event) {
const {name, value} = event.target;
let user = this.state.user; // this is a reference, not a copy...
user[name] = value; // so this mutates state ?
return this.setState({user});
}
import update from 'immutability-helper';
updateState({target}) {
let user = update(this.state.user, {$merge: {[target.name]: target.value}});
this.setState({user});
}
此文非原创,原作者写的太好,直接参考原作者文章做的大概的翻译:https://www.freecodecamp.org/news/handling-state-in-react-four-immutable-approaches-to-consider-d1f5c00249d5/
React中最容易混淆的就是:state。 假设有一个用于编辑用户的表单,通常创建一个更改处理函数来处理所有表单字段的更改。它可能像这样:
问题在第4行上。第4行实际上是对状态进行突变,因为用户变量是对状态的引用。state应视为不可变的。 React文档:
为什么呢? 1.setState批处理在后台工作。这意味着setState执行时可能覆盖手动状态突变。 2.如果声明了shouldComponentUpdate方法,则不能在内部使用===等式检查,因为对象引用不会更改。因此,上述方法也可能会对性能产生影响。
上面的示例通常可以正常工作,但是要避免出现极端情况,请将状态视为不可变的。 以下是将状态视为不可变的四种方法:
方法一:Object.assign Object.assign创建对象的副本。第一个参数是目标对象,然后为要添加的属性指定一个或多个参数。因此,修复上面的示例涉及对第3行的简单更改:
在第3行中,创建了state下存储的user对象的单独副本。现在,我可以安全地更改第4行上的user对象,它是与state对象完全独立的对象。 由于IE不支持Object.assign,并且Babel尚未对其进行转译,要考虑4个方面: 1.object-assign 2.The MDN docs 3.Babel Polyfill 4.Polyfill.io
方法二:对象传播 对象传播目前是第3阶段的功能,Babel可以对其进行编译。这种方法更简洁:
在第3行中,使用this.state.user上的所有属性创建一个新对象,然后将[name]表示的属性设置为在event.target.value上传递的新值”。因此,此方法与Object.assign方法类似,但是有两个好处: 1.无需Pollfill,因为Babel可以转译 2.更简洁
方法三:Immutability Helper(不可变数据的辅助工具) Immutability Helper是一个用于更改数据副本而不更改源的库。在React文档中也是建议用这个库的。
在第4行,调用了merge,这是由Immutability Helper提供的许多命令之一。与Object.assign一样,我将目标对象作为第一个参数传递给它,然后指定要合并的属性。 Immutability Helper的功能远不止于此。它受MongoDB查询语言的启发,提供了多种处理不可变数据的强大方法。
方法四:Immutable.js 想要要以编程方式强制执行不变性?考虑immutable.js。该库提供了不变的数据结构。
immutable.js的优点:如果尝试直接改变状态,它将失败。使用上面其他方法,很容易忘记,当你直接更改状态时,React不会告警。 immutable.js的弊端: 1.膨胀。Immutable.js在依赖包中添加至少57K。对于替换React的Preact只有3K,加入这么大的库是很难接受的。 2.语法。必须通过字符串和方法调用而不是直接引用对象属性 3.任何加入团队的人都需要学习另一个API来获取和设置数据,以及一系列新的数据类型
还可以考虑以下两个有趣的替代方法: seamless-immutable react-copy-write
注意:留心嵌套对象 方法一和方法2只能进行浅复制,所以如果你的对象中有嵌套对象,这些嵌套对象将通过引用而不是值进行复制。因此,如果更改嵌套对象,将改变原始对象。 只对要克隆的内容进行更改,因此不要克隆所有东西,克隆已更改的对象。Immutability-helper将使这一过程变得很容易。或者也可以选择every,updeep等多种方式。
你可能很想接触深层合并工具,例如clone-deep或lodash.merge,但要避免盲目深层克隆。因为: 1.deep clone 成本很高; 2.deep clone通常是浪费的(相反,仅克隆实际发生变化的对象) 3.deep clone会导致不必要的渲染,因为React认为一切都已更改,而实际上可能只有特定的子对象已更改。
最后建议:使用 setState 的函数式更新形式 setState()不会立即使this.state改变,而是创建一个挂起的状态转换。调用此方法后访问this.state可能会返回原有值。 由于setState调用是批处理的,因此这样的代码会导致bug:
如果要在setState调用完成后运行代码,请使用setState的函数式更新形式:
我的想法 我比较喜欢方法2的简单性和轻巧性:对象传播。它不需要polyfill或单独的库,我可以在一行上声明一个更改处理程序,并且可以对更改内容进行改造。在使用嵌套对象结构的情况下我目前更喜欢Immer。