1、为什么需要setState?
在react中并没有类似vue的数据劫持,即直接修改数据,页面跟着更新。所以我们需要显式的调用一个方法去修改数据,并且更新页面,这个方法就是setState。
2、其他的用法
我们在使用setState时,都是只传入需要修改的数据,对于其他不需要修改的数据,react是进行对象的合并,即用Object.assign。setState除了直接传入对象,还有其他的用法
(1)传入回调
在setState中传入回调函数,函数返回需要修改的数据,在这个回调中能拿到上一次setState修改后的state。
见下面代码,三个setState会依次打印上一次setState后的state结果。【但此时的setState不是同步的】
// 23 'set之后'
// 23
// 24
// 25
state = {
age: 23
}
changeAge = () => {
this.setState((state) => {
console.log(state.age)
return { age: state.age + 1 }
})
this.setState((state) => {
console.log(state.age)
return { age: state.age + 1 }
})
this.setState((state) => {
console.log(state.age)
return { age: state.age + 1 }
})
console.log(this.state.age,'set之后')
}
【使用useState的set方法也能传入回调,在回调中能拿到上一次更新的结果。即: setCount(preCount=>preCount+1)】
(2)第二个参数是回调
其实我们知道在react18中setState都是异步的,但是有时我们希望拿到更改state后最新的dom,类似vue中的$nextTick。这时我们就能在setState中传入第二个参数,这是一个回调,dom更新完毕后,react会去调用这个回调。
changeAge = () => {
this.setState({ age: 244 }, () => {
// 此时的dom是最新的
console.log(this.refs.myP)
})
(3) flushSync中的setState变为同步
从react-dom中引入flushSync,给这个函数传入回调,在回调中setState,在flushSync的外部打印的state就是最新的。【但是官网说慎用,不推荐】
import {flushSync} from 'react-dom' //注意是引入react-dom
flushSync(()=>{
this.setState({msg:'aa'})
})
console.log(this.state.msg) //'aa'
3、setState同步还是异步?
在react18中setState都是异步的
react18之前组件生命周期或者react合成事件中setState是异步的。
setTImeout、promise或者原生dom事件中是同步的
如果同时连续调用setState修改值,react会将这些调用批量处理,就是将其合成为一个更新(这就与多次setTimeout改一个变量的值不同了)。看下面的示例代码:
//count:0
this.setState({
count:this.state.count + 3
})
this.setState({
count:this.state.count + 2
})
this.setState({
count:this.state.count + 6
})
//批处理,进行合并,最后结果是6
所以推荐在使用setState时使用传入回调的方式。
为什么搞成成异步的?
总结下来就是能提高性能,同步的话频繁setState,会频繁render,其实不需要频繁render, 所以最好是最后一次更新就行(一次render)。
4、什么是数据不可变力量
就是告诉我们不要直接去修改state,而是用他提供的setState方法修改state。
可以看下面的例子来理解。
state = {
books:['1']
}
//修改
this.state.books.push('3')
this.setState({
books:this.state.books
})
在类组件继承PureComponent的情况下,执行上面代码,页面不变化。不刷新的原因很明显,PureComponent会对state进行浅层比较,而新旧books指向同一块地址,就会判定books的值没有发生变化,所以页面不更新。
所以我们在修改引用类型的数据应该浅拷贝变量,再修改拷贝好的数据,最后 setState。
5、传入同一个值-优化
当我们设置同一个值,最好页面不要进行更新,下面分析两种组件下的具体情况。
//count:0
this.setState({
count: 0
})
setState
在类组件中,用setState设置同一个值,页面也是会更新的,要想页面不更新,我们需要让这个类组件继承PureComponent,而不是之前的Component。
class App extends React.PureComponent {
//.....
}
其实我们还需要对props进行浅层比较,而PureComponent有做这些操作。
PureComponent的原理
在shouldCompoentUpdate这个生命周期中对state和props进行浅层比较,通过返回布尔值来决定是否更新。比如下面的代码:
//执行shouldComponentUpdate时state还没有修改
shouldComponentUpdate(nextProps,nextState){ //新的值
if(this.state.age !== nextState.age){
return true
}
return false
}
useState的set方法
在函数组件中,本身useState的set方法会进行浅层比较,即传入相同的值也不会更新页面。但是useState并不会去对props进行浅层比较,为了更好的优化,通常我们还需要使用memo这个高阶组件(即用memo去对props进行比较)。
const Home = memo(() => {
//....
})
memo除了第一个参数传入函数组件外,第二个参数还能传入自定义的比较函数,这个比较函数接收两个参数:前一个 props 和后一个 props,返回一个布尔值来决定是否重新渲染。