setState()更新的数据和自己预期的不一致

对 React 新手来说,使用 setState 是一件很复杂的事情。即使是熟练的 React 开发,也很有可能因为 React 的一些机制而产生一些bug,react文档 中也说明了当使用 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 。下面这个例子很好的展示了这个问题:

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 。 onSelect 方法永远传递的是之前的 state.selection 值,因为当 fireOnSelect 调用的时候, setState 还没有完成它的工作。我认为 React 至少要把 setState 改名为 scheduleState 或者把回掉函数设为必须参数。

2. setState会造成不必要的渲染

setState 造成的第二个问题是:每次调用都会造成重新渲染。很多时候,这些重新渲染是不必要的。你可以用 React performance tools 中的 printWasted 来查看什么时候会发生不必要渲染。但是,大概的说,不必要的渲染有以下几个原因:

  • 新的 state 其实和之前的是一样的。这个问题通常可以通过 shouldComponentUpdate 来解决。也可以用 pure render 或者其他的库赖解决这个问题。
  • 通常发生改变的 state 是和渲染有关的,但是也有例外。比如,有些数据是根据某些状态来显示的。
  • 第三,有些 state 和渲染一点关系都没有。有一些 state 可能是和事件、 timer ID 有关的。

3.setState并不能很有效的管理所有的组件状态

基于上面的最后一条,并不是所有的组件状态都应该用 setState 来进行保存和更新的。复杂的组件可能会有各种各样的状态需要管理。用 setState 来管理这些状态不但会造成很多不需要的重新渲染,也会造成相关的生命周期钩子一直被调用,从而造成很多奇怪的问题。

总结

基于上面提出的三点,我认为新手应该注意的地方是:

setState 是不保证同步的

setState 是不保证同步的,是不保证同步的,是不保证同步的。重要的事情说三遍。之所以不说它是异步的,是因为 setState 在某些情况下也是同步更新的。 可以参考这篇文章

如果需要在 setState 后直接获取修改后的值,那么有几个方案:

1.传入对应的参数,不通过 this.state 获取

针对于之前的例子,完全可以在调用 fireOnSelect 的时候,传入需要的值。而不是在方法中在通过 this.state 来获取

2.使用回调函数

setState 方法接收一个 function 作为回调函数。这个回掉函数会在 setState 完成以后直接调用,这样就可以获取最新的 state 。对于之前的例子,就可以这样:

this.setState({
selection: value
}, this.fireOnSelect)

3.使用setTimeout

在 setState 使用 setTimeout 来让 setState 先完成以后再执行里面内容。这样子:

this.setState({
selection: value
}); setTimeout(this.fireOnSelect, 0);

直接输出,回调函数, setTimeout 对比

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

4.和渲染无关的状态尽量不要放在 state 中来管理

通常 state 中只来管理和渲染有关的状态 ,从而保证 setState 改变的状态都是和渲染有关的状态。这样子就可以避免不必要的重复渲染。其他和渲染无关的状态,可以直接以属性的形式保存在组件中,在需要的时候调用和改变,不会造成渲染。

避免不必要的修改,当 state 的值没有发生改变的时候,尽量不要使用 setState 。虽然 shouldComponentUpdatePureComponent 可以避免不必要的重复渲染,但是还是增加了一层 shallowEqual 的调用,造成多余的浪费。

参考:https://www.tuicool.com/articles/zEfEfua

react的setState使用中遇到的问题的更多相关文章

  1. React的setState分析

    前端框架层出不穷,不过万变不离其宗,就是从MVC过渡到MVVM.从数据映射到DOM,angular中用的是watcher对象,vue是观察者模式,react就是state了. React通过管理状态实 ...

  2. React中setState同步更新策略

    setState 同步更新 我们在上文中提及,为了提高性能React将setState设置为批次更新,即是异步操作函数,并不能以顺序控制流的方式设置某些事件,我们也不能依赖于this.state来计算 ...

  3. 初学React,setState后获取到的thisstate没变,还是初始state?

    问题:(javascript)初学React,setState后获取到的thisstate没变,还是初始state?描述: getInitialState(){ return {data:[]}; } ...

  4. React中setState学习总结

    react中setState方法到底是异步还是同步,其实这个是分在什么条件下是异步或者同步. 1.先来回顾一下react组件中改变state的几种方式: import React, { Compone ...

  5. React的setState学习及应用

    React的setState学习及应用 一:作用: setState() 将对组件 state 的更改排入队列,并通知 React 需要使用更新后的 state 重新渲染此组件及其子组件.这是用于更新 ...

  6. 「React Native笔记」在React的 setState 中操作数组和对象的多种方法(合集)

    运用在React 中 setState的对象.数组的操作时是不能用类似array.push()等方法,因为push没有返回值,setState后会出现state变成Number,为了方便他人和自己查看 ...

  7. React中setState 什么时候是同步的,什么时候是异步的?

    class Example extends React.Component { constructor() { super(); this.state = { val: 0 }; } componen ...

  8. React的setState如何实现同步处理数据

    React里面的使用setState来进行状态的更新,为了性能的提升,此时的过程是异步操作的,那我们如果在一个进程里面想同步操作改变了状态的值怎么办呢,这里需要使用回调函数了: this.setSta ...

  9. [React] Safely setState on a Mounted React Component through the useEffect Hook

    In the class version of this component, we had a method called safeSetState which would check whethe ...

随机推荐

  1. koa1创建项目

    1.一定要全局安装(koa1.2和koa2都己经支持)npm install koa-generator -g 2.koa1.2 生成一个test项目,切到test目录并下载依赖 koa testcd ...

  2. leetcode-002

    给定两个非空链表来表示两个非负整数.位数按照逆序方式存储,它们的每个节点只存储单个数字.将两数相加返回一个新的链表. 你可以假设除了数字 0 之外,这两个数字都不会以零开头. 示例: 输入:(2 -& ...

  3. Entity Framework Code-First(9.8):DataAnnotations - Column Attribute

    DataAnnotations - Column Attribute: Column attribute can be applied to properties of a class. Defaul ...

  4. Struts2学习第七课 通配符映射

    一个WEB应用可能有长百上千个action声明,可以利用struts提供的通配符映射机制吧多个彼此相识的映射关系简化为一个映射关系. 通配符映射规则: --若找到多个匹配,没有通配符的那个将胜出(精确 ...

  5. 28.【转载】挖洞技巧:APP手势密码绕过思路总结

    说到APP手势密码绕过的问题,大家可能有些从来没接触过,或者接触过,但是思路也就停留在那几个点上,这里我总结了我这1年来白帽子生涯当中所挖掘的关于这方面的思路,有些是网上已经有的,有些是我自己不断摸索 ...

  6. 2. DVWA亲测CSRF漏洞

    DVWA登陆      用户名:admin   密码:password Low级: 查看源代码: <?php if (isset($_GET['Change'])) { // Turn requ ...

  7. 洛谷P2607 [ZJOI2008]骑士

    P2607 [ZJOI2008]骑士 题目描述 Z国的骑士团是一个很有势力的组织,帮会中汇聚了来自各地的精英.他们劫富济贫,惩恶扬善,受到社会各界的赞扬. 最近发生了一件可怕的事情,邪恶的Y国发动了一 ...

  8. Java Web之分页的实现(通用)

    一.用到的工具类的封装 为了实现代码的重用性,我们将经常用到的代码封装到工具类中,以便在任何地方都可以调用 1.获取路径工具 在jsp页面中,我们经常会向Servlet发送请求,并通过反射,实现通过传 ...

  9. ios开发 xcode6以上安装Alcatraz管理插件

    在终端上输入如下命令即可完成安装: curl -fsSL https://raw.github.com/supermarin/Alcatraz/master/Scripts/install.sh |  ...

  10. Unity---MonoBehaviour9大生命周期

    1.MonoBehaviour9大生命周期 MonoBehaviour是一个基类,所有Unity脚本都派生自该类. Awake():在脚本实例化时被调用. Start():在Awake之后,Updat ...