Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

5. 在React中处理状态:要考虑的四种不变方法 #5

Open
mianyue502 opened this issue Apr 9, 2020 · 1 comment
Open

Comments

@mianyue502
Copy link
Owner

No description provided.

@mianyue502 mianyue502 changed the title 5. React Input 输入类型 5. js生成图片 Apr 16, 2020
@mianyue502 mianyue502 changed the title 5. js生成图片 5. 在React中处理状态:要考虑的四种不变方法 Apr 16, 2020
@mianyue502
Copy link
Owner Author

mianyue502 commented Apr 16, 2020

此文非原创,原作者写的太好,直接参考原作者文章做的大概的翻译:https://www.freecodecamp.org/news/handling-state-in-react-four-immutable-approaches-to-consider-d1f5c00249d5/

React中最容易混淆的就是:state。
假设有一个用于编辑用户的表单,通常创建一个更改处理函数来处理所有表单字段的更改。它可能像这样:

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});
}

问题在第4行上。第4行实际上是对状态进行突变,因为用户变量是对状态的引用。state应视为不可变的。
React文档:

切勿直接更改this.state,因为之后调用setState()可能会替换您所做的更改。视this.state为不可变的。

为什么呢?
1.setState批处理在后台工作。这意味着setState执行时可能覆盖手动状态突变。
2.如果声明了shouldComponentUpdate方法,则不能在内部使用===等式检查,因为对象引用不会更改。因此,上述方法也可能会对性能产生影响。

上面的示例通常可以正常工作,但是要避免出现极端情况,请将状态视为不可变的。 以下是将状态视为不可变的四种方法:

方法一:Object.assign
Object.assign创建对象的副本。第一个参数是目标对象,然后为要添加的属性指定一个或多个参数。因此,修复上面的示例涉及对第3行的简单更改:

updateState(event) {
   const {name, value} = event.target;
   let user = Object.assign({}, this.state.user);
   user[name] = value;
   return this.setState({user});
}

在第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可以对其进行编译。这种方法更简洁:

updateState(event) {
   const {name, value} = event.target;
   let user = {...this.state.user, [name]: value};
   this.setState({user});
}

在第3行中,使用this.state.user上的所有属性创建一个新对象,然后将[name]表示的属性设置为在event.target.value上传递的新值”。因此,此方法与Object.assign方法类似,但是有两个好处:
1.无需Pollfill,因为Babel可以转译
2.更简洁

方法三:Immutability Helper(不可变数据的辅助工具)
Immutability Helper是一个用于更改数据副本而不更改源的库。在React文档中也是建议用这个库的。

import update from 'immutability-helper';

updateState({target}) {
   let user = update(this.state.user, {$merge: {[target.name]: target.value}});
   this.setState({user});
}

在第4行,调用了merge,这是由Immutability Helper提供的许多命令之一。与Object.assign一样,我将目标对象作为第一个参数传递给它,然后指定要合并的属性。
Immutability Helper的功能远不止于此。它受MongoDB查询语言的启发,提供了多种处理不可变数据的强大方法。

方法四:Immutable.js
想要要以编程方式强制执行不变性?考虑immutable.js。该库提供了不变的数据结构。

import { Map } from 'immutable';
  constructor(){
      this.state = {
      user: Map({ firstName: 'Cory', lastName: 'House'})
  }
}
updateState({target}) {
   let user = this.state.user.set(target.name, target.value);
   this.setState({user});
}

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:

updateState({target}) {
   this.setState({user: {...this.state.user, [target.name]: target.value}});
   doSomething(this.state.user) //setState只是计划改变state, 因此this.state.user可能还是原来的值
}

如果要在setState调用完成后运行代码,请使用setState的函数式更新形式:

updateState({target}) {
     this.setState((prevState) => {
       const updatedUser = {...prevState.user, [target.name]: target.value}; 
       return { user: updatedUser };
    }, () => {this.doSomething(this.state.user)}
    );
 }

我的想法
我比较喜欢方法2的简单性和轻巧性:对象传播。它不需要polyfill或单独的库,我可以在一行上声明一个更改处理程序,并且可以对更改内容进行改造。在使用嵌套对象结构的情况下我目前更喜欢Immer。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant