浅谈使用React.setState需要注意的三点
GPT4.0+Midjourney绘画+国内大模型 会员永久免费使用!
【 如果你想靠AI翻身,你先需要一个靠谱的工具! 】
前言
这篇文章原标题是 3 Reasons why I stopped using React.setState ,但是我对原文作者提出的论点不是很感冒,但是作者提出的三点对 React 新手来说是很容易忽略的地方,所以我在这里只提出部分内容,而且把标题改为 使用React.setState需要注意的三点 。
正文
对 React 新手来说,使用 setState 是一件很复杂的事情。即使是熟练的 React 开发,也很有可能因为 React 的一些机制而产生一些bug,比如下面这个例子:
文档 中也说明了当使用 setState 的时候,需要注意什么问题:
注意:
绝对不要 直接改变 this.state ,因为之后调用 setState() 可能会替换掉你做的改
变。把 this.state 当做是不可变的。
setState() 不会立刻改变 this.state ,而是创建一个即将处理的 state 转变。在调用该方法之后访问 this.state 可能会返回现有的值。
对 setState 的调用没有任何同步性的保证,并且调用可能会为了性能收益批量执行。
setState() 将总是触发一次重绘,除非在 shouldComponentUpdate() 中实现了条件渲染逻辑。如果可变对象被使用了,但又不能在 shouldComponentUpdate() 中实现这种逻辑,仅在新 state 和之前的 state 存在差异的时候调用 setState() 可以避免不必要的重新渲染。
总结出来,当使用 setState 的时候,有三个问题需要注意:
1. setState是异步的(译者注:不保证同步的)
很多开发刚开始没有注意到 setState 是异步的。如果你修改一些 state ,然后直接查看它,你会看到之前的 state 。这是 setState 中最容易出错的地方。 setState 这个词看起来并不像是异步的,所以如果你不假思索的用它,可能会造成 bugs 。下面这个例子很好的展示了这个问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | class Select extends React.Component { constructor(props, context) { super (props, context) this .state = { selection: props.values[0] }; } render() { return ( <ul onKeyDown={ this .onKeyDown} tabIndex={0}> { this .props.values.map(value => <li className={value === this .state.selection ? 'selected' : '' } key={value} onClick={() => this .onSelect(value)} > {value} </li> )} </ul> ) } onSelect(value) { this .setState({ selection: value }) this .fireOnSelect() } onKeyDown = (e) => { const {values} = this .props const idx = values.indexOf( this .state.selection) if (e.keyCode === 38 && idx > 0) { /* up */ this .setState({ selection: values[idx - 1] }) } else if (e.keyCode === 40 && idx < values.length -1) { /* down */ this .setState({ selection: values[idx + 1] }) } this .fireOnSelect() } fireOnSelect() { if ( typeof this .props.onSelect === "function" ) this .props.onSelect( this .state.selection) /* not what you expected..*/ } } ReactDOM.render( <Select values={[ "State." , "Should." , "Be." , "Synchronous." ]} onSelect={value => console.log(value)} />, document.getElementById( "app" ) ) |
第一眼看上去,这个代码似乎没有什么问题。两个事件处理中调用 onSelect 方法。但是,这个 Select 组件中有一个 bug 很好的展现了之前的 GIF 图。 onSelect 方法永远传递的是之前的 state.selection 值,因为当 fireOnSelect 调用的时候, setState 还没有完成它的工作。我认为 React 至少要把 setState 改名为 scheduleState 或者把回掉函数设为必须参数。
这个bug很容易修改,最难的地方在于你要知道有这个问题。
2. setState会造成不必要的渲染
setState 造成的第二个问题是:每次调用都会造成重新渲染。很多时候,这些重新渲染是不必要的。你可以用 React performance tools 中的 printWasted 来查看什么时候会发生不必要渲染。但是,大概的说,不必要的渲染有以下几个原因:
- 新的 state 其实和之前的是一样的。这个问题通常可以通过 shouldComponentUpdate 来解决。也可以用 pure render 或者其他的库赖解决这个问题。
- 通常发生改变的 state 是和渲染有关的,但是也有例外。比如,有些数据是根据某些状态来显示的。
- 第三,有些 state 和渲染一点关系都没有。有一些 state 可能是和事件、 timer ID 有关的。
3.setState并不能很有效的管理所有的组件状态
基于上面的最后一条,并不是所有的组件状态都应该用 setState 来进行保存和更新的。复杂的组件可能会有各种各样的状态需要管理。用 setState 来管理这些状态不但会造成很多不需要的重新渲染,也会造成相关的生命周期钩子一直被调用,从而造成很多奇怪的问题。
后话
在原文中作者推荐了一个叫做 MobX 的库来管理部分状态,我不是很感冒,所以我就不介绍。如果感兴趣的,可以通过最上面的链接看看原文中的介绍。
基于上面提出的三点,我认为新手应该注意的地方是:
setState 是不保证同步的
setState 是不保证同步的,是不保证同步的,是不保证同步的。重要的事情说三遍。之所以不说它是异步的,是因为 setState 在某些情况下也是同步更新的。 可以参考这篇文章
如果需要在 setState 后直接获取修改后的值,那么有几个方案:
传入对应的参数,不通过 this.state 获取
针对于之前的例子,完全可以在调用 fireOnSelect 的时候,传入需要的值。而不是在方法中在通过 this.state 来获取
使用回调函数
setState 方法接收一个 function 作为回调函数。这个回掉函数会在 setState 完成以后直接调用,这样就可以获取最新的 state 。对于之前的例子,就可以这样:
使用setTimeout
在 setState 使用 setTimeout 来让 setState 先完成以后再执行里面内容。这样子:
直接输出,回调函数, setTimeout 对比
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | componentDidMount(){ this .setState({val: this .state.val + 1}, ()=>{ console.log( "In callback " + this .state.val); }); console.log( "Direct call " + this .state.val); setTimeout(()=>{ console.log( "begin of setTimeout" + this .state.val); this .setState({val: this .state.val + 1}, ()=>{ console.log( "setTimeout setState callback " + this .state.val); }); setTimeout(()=>{ console.log( "setTimeout of settimeout " + this .state.val); }, 0); console.log( "end of setTimeout " + this .state.val); }, 0); } |
如果val默认为0, 输入的结果是:
Direct call 0
In callback 1
begin of setTimeout 1
setTimeout setState callback 2
end of setTimeout 2
setTimeout of settimeout 2
和渲染无关的状态尽量不要放在 state 中来管理
通常 state 中只来管理和渲染有关的状态 ,从而保证 setState 改变的状态都是和渲染有关的状态。这样子就可以避免不必要的重复渲染。其他和渲染无关的状态,可以直接以属性的形式保存在组件中,在需要的时候调用和改变,不会造成渲染。
避免不必要的修改,当 state 的值没有发生改变的时候,尽量不要使用 setState 。虽然 shouldComponentUpdate 和 PureComponent 可以避免不必要的重复渲染,但是还是增加了一层 shallowEqual 的调用,造成多余的浪费。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。
微信公众号搜索 “ 脚本之家 ” ,选择关注
程序猿的那些事、送书等活动等着你
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 reterry123@163.com 进行投诉反馈,一经查实,立即处理!
相关文章
react-router-dom6(对比 router5)快速入门指南
这篇文章主要介绍了快速上手react-router-dom6(对比 router5),通过本文学习最新的react-router-dom v6版本的路由知识,并且会与v5老版本进行一些对比,需要的朋友可以参考下2022-08-08Shopee在React Native 架构方面的探索及发展历程
这篇文章主要介绍了Shopee在React Native 架构方面的探索,本文会从发展历史、架构模型、系统设计、迁移方案四个方向逐一介绍我们如何一步步地满足多团队在复杂业务中的开发需求,需要的朋友可以参考下2022-07-07React Router 5.1.0使用useHistory做页面跳转导航的实现
本文主要介绍了React Router 5.1.0使用useHistory做页面跳转导航的实现,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下2021-11-11React 正确使用useCallback useMemo的方式
这篇文章主要介绍了React 正确使用useCallback useMemo的方式,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的朋友可以参考一下2022-08-08
最新评论