一、为什么要使用 setState 方法,如何用?
在 React 中,组件分为 有状态组件 和 无状态组件,有状态组件就是能够定义 state 的组件,比如类组件,无状态组件反之,比如函数组件。state 就是用来描述事物在某时刻的数据,可以被改变,改变后与视图相映射,用来保存数据和响应视图。
虽然状态可以改变,但不是响应式的,动态改变并没有与视图响应,想要改变并响应视图则需要 setState 修改并更新视图。
使用方法:({ 修改的数据 })
setState并不会影响其他没有进行修改的数据,此方法是从Component中继承过来的
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
export default class App extends Component {
// 定义 state
state = {
count: 0,
list: [1, 2, 3],
person: {
name: 'jack',
age: 18,
},
}
changeCount = () => {
// ++
// 上面的写法并不能更新视图,想要双向更新只能利用 setState 方法
({
count: ,
})
}
changeList = () => {
// 同理
('Hello React')
({
list: ,
})
}
changePerson = () => {
= 'ifer'
= 19
({
person: ,
})
}
render() {
return (
<div>
<h3>count输出</h3>
<p>{}</p>
<button onClick={}>click</button>
<hr />
<h3>list输出</h3>
<ul>
{((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
<button onClick={}>click</button>
<hr />
<h3>person输出</h3>
<p>
{} {}
</p>
<button onClick={}>click</button>
</div>
)
}
}
(<App />, ('#root'))
状态有个特性是不可变性,就是不可以直接修改原数据,而是直接产生一个新的数据,通过 setState 方法把新的数据覆盖原有的数据,从而进行响应视图,这么做有利于性能优化和 SCU。
二、setState 使用触发的钩子函数
- render() ———— 每次组件渲染都会触发
注意:render()中不要调用 setState 方法,会发生死循环。
- componentDidUpdate() ———— 组件更新(完成DOM渲染)后触发
三、setState 是同步代码
虽说 setState 很多人说是异步的,但是它本身的执行过程和代码是同步的,只是它在合并数据与钩子函数的调用顺序在更新后无法拿到值,形成了 异步 ,我们可以通过第二个参数拿到更新后的结果。
changeText() {
({
age: 20
}, () => {
(); // 文本已经被改变,得到的是20
});
}
也可以通过钩子函数获取修改后的值
componentDidUpdate() {
();
}
什么时候“同步”,什么时候“异步”?
- 一般情况下(常见在生命周期或者是合成事件处理函数中),通过 setState() 方法来更新状态,表现是 异步 的。
state = { count: 1 } ({ count: + 1, }) () // 1 // 通过 DOM 不能立马获取更新后的值
- 当执行到 setState 时,React 出于性能考虑,并不会立马执行修改 state ,而是先把当前更新对象以及后续的合并到一起,放在一个更新队列里进行合并操作,这期间并不会影响后续代码执行。且多次调用会合并,以最后一次渲染为主,以此来进行性能优化。
({ count: + 1, }) ({ count: + 2, }) ({ count: + 3, }) // 最后实现更新的是最后一个代码,前面的都会被覆盖
如果想要实现一个一个实现,可以利用 setState 的第二个参数,第二个参数是一个回调函数,在更新完之后会执行
// setState 有两个参数,第二个参数可以立即拿到更新后的值 ({}, () => { ('这个回调函数会在状态更新后立即执行') })
(() => ({ count: + 1 })) // prevStart 是最近一次的更新的数据 ((prevStart) => ({ count: + 2 })) ((prevStart) => ({ count: + 3 }))
上面确实可以累加,把累加的结果渲染到页面上,但依然不能改变异步更新的表现形式
- 如果在 setTimeout/setInterval 或者原生事件(比如点击事件等)的回调中,或者是在 setState 前面遇到 await 后续表现都为 同步 的。
利用定时器
import React, { Component } from 'react' export default class App extends Component { state = { count: 1, } componentDidMount() { setTimeout(() => { ({ count: + 1, }) () // 2 }) } render() { return <div>{}</div> } }
原生事件
import React, { Component, createRef } from 'react' export default class App extends Component { state = { count: 1, } countRef = createRef() componentDidMount() { = () => { ({ count: + 1, }) () // 2 } } render() { return ( <div> <h2>{}</h2> <button ref={}>点击</button> </div> ) } }
async写法
import React, { Component } from 'react' export default class App extends Component { state = { count: 1, } async componentDidMount() { await ({ count: + 1, }) () // 2 } render() { return ( <div> <h2>{}</h2> </div> ) } }