React 中 setState 使用注意事项
Posted 诗渊
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了React 中 setState 使用注意事项相关的知识,希望对你有一定的参考价值。
1. 不能直接设置this.state
这个基本学习过 react 的读者都不会犯这样的错,直接设置 this.state 的值并不能触发组件 render()
,正确的是调用 setState()
函数来处理。
2. setState()
回调函数
我们在用 setState()
时,疑惑比较多的地方就是 setState()
可能不会立即生效。基于这一点,慢慢地我们就形成了 setState()
总是异步执行的假象。其实官方文档里也说的是可能不会立即生效
,后面会讲到立即生效的场景。其实 setState()
会批量推迟更新。这使得在调用 setState() 后立即读取 this.state 成为了隐患。例如以下示例:
hanldClick = () =>
// 初始counter=0
this.setState(
counter: this.state.counter + 1
);
console.log(this.state.counter); // 0
;
在 setState()
之后直接调用取 counter
的值的话,其实取到的还是生效之前的值,如果我们想要在 counter
生效之后取值的话,可以使用 componentDidUpdate 或者 setState 的回调函数(setState(updater, callback)),这两种方式都可以保证在应用更新后触发。我们常用的方式就是 setState 的第二个入参(回调函数)。上述的示例就可以修改成如下:
hanldClick = () =>
// 初始counter=0
this.setState(
counter: this.state.counter + 1
,()=>
console.log(this.state.counter); // 1
);
;
如果在 render 中也加上打印信息,可以发现,上述的打印信息会在 render 之后打印。
3. setState()
批量执行
我们将上面的示例变更一下:
hanldClick = () =>
this.setState(
counter: this.state.counter + 1
,
() =>
console.log(this.state.counter);
);
this.setState(
counter2: this.state.counter2 + 2
,
() =>
console.log(this.state.counter2);
);
;
在 render 里也加上打印信息,我们可以发现上述操作并不会出发两次 render ,而是将两次 setState()
合并在一起执行了,与下面是等效的:
hanldClick = () =>
this.setState(
counter: this.state.counter + 1,
counter2: this.state.counter2 + 2
,
() =>
console.log(this.state.counter);
console.log(this.state.counter2);
);
;
再看下面一个示例:
hanldClick = () =>
// 初始counter=0
this.setState(
counter: this.state.counter + 1
);
this.setState(
counter: this.state.counter + 1
);
this.setState(
counter: this.state.counter + 1
);
;
上述操作生效后,counter 的值为 1,而不是 3。这是因为 setState()
不一定是立即生效的,而且是批量执行,所以上述的 三个 setState()
中 拿到的 this.state.counter
值都是 0,所以合并起来就等效与:
hanldClick = () =>
// 初始counter=0
this.setState(
counter: this.state.counter + 1
);
;
如果要实现上述的操作,可以采用以下 setState()
第一个入参为函数的方法。
4. setState()
第一个入参为函数
从[官方文档(https://zh-hans.reactjs.org/docs/react-component.html#setstate)中可知,setState()
第一个参数除了可以传一个对象(nextState,nextState会与当前state做浅 merge 操作),还可以传函数,该函数中接收的 state 和 props 都保证为最新,且返回值会与 state 进行浅 merge 操作。:
void setState (
function|object nextState,
[function callback]
)
因此要实现上述的场景,就可以改写成如下:
hanldClick = () =>
// 初始counter=0
this.setState((state,props)=>(
counter: state.counter + 1
),()=>
console.log(this.state.counter); // 3
);
this.setState((state, props) => (
counter: state.counter + 1
),()=>
console.log(this.state.counter); // 3
);
this.setState((state, props) => (
counter: state.counter + 1
),()=>
console.log(this.state.counter); // 3
);
console.log(this.state.counter); // 0
;
5. 原生事件中修改状态
先看一个示例:
class Index extends Component
constructor(props)
super();
this.state =
counter: 0,
counter2: 0
;
componentDidMount()
let elem = document.getElementById("btn");
elem.addEventListener("click", this.changeValue, false);
changeValue = () =>
this.setState(
counter: this.state.counter+1
);
console.log(this.state.counter)
hanldClick = () =>
this.setState(
counter: this.state.counter + 1
);
console.log(this.state.counter)
;
render()
console.log("======== indexPage render ========");
console.log(this.state);
return (
<div>
<p>counter: this.state.counter</p>
<button onClick=this.hanldClick>增加</button>
<button id="btn">原生事件</button>
</div>
);
通过原生事件方式添加的点击事件中 changeValue
中的打印信息可以直接打印出变更之后的 counter
, 而通过react的 onClick
事件中的打印信息却还是变更之前的值。
在 React 的 setState 函数实现中,会根据一个变量 isBatchingUpdates 判断是直接更新 this.state 还是放到队列中回头再说,而 isBatchingUpdates 默认是 false,也就表示 setState 会同步更新 this.state,但是,有一个函数 batchedUpdates,这个函数会把 isBatchingUpdates 修改为 true,而当 React 在调用事件处理函数之前就会调用这个 batchedUpdates,造成的后果,就是由 React 控制的事件处理过程 setState 不会同步更新 this.state。
-
也就是说上述的
onClick
是 React 控制的事件处理过程,所以 isBatchingUpdates 修改为 true,导致不会同步更新this.state; -
而添加的原生事件不会修改 isBatchingUpdates 的值,还是默认 false, 所以会同步更新 this.state。
6. setTimeout
我们在上面的 hanldClick 中加一个 setTimeout 逻辑:
hanldClick = () =>
// 初始counter=0
this.setState(
counter: this.state.counter + 1
);
console.log(this.state.counter); // 0
setTimeout(() =>
this.setState(
counter: this.state.counter + 1
);
console.log(this.state.counter); // 2
this.setState(
counter: this.state.counter + 1
);
console.log(this.state.counter); // 3
, 0);
;
上述第一个 setState()
之后打印 0 的原因上面已经提到过了。至于后面 setTimeout
中的打印结果,可能会有一些疑惑。其实还是 isBatchingUpdates 值的原因,因为 setTimeout 里面的回调函数已经不是是 React 控制的事件处理过程了, isBatchingUpdates 的值还是默认的 false, 所以会同步更新 this.state。
reference:
1、https://zh-hans.reactjs.org/docs/react-component.html#setstate
2、https://www.zhihu.com/question/66749082/answer/246217812
以上是关于React 中 setState 使用注意事项的主要内容,如果未能解决你的问题,请参考以下文章