持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情
在类组件中,setState可以是同步也可以是异步,结合class-setState问题食用。进行hooks中useState同步/异步问题与类组件中setState进行对比。
正文
- 异步--合成函数中
const [count,setCount] = useState(0)
let handleClick=()=>{
setCount(count+1)
console.log(count,"count");// 0 异步
}
return(
<div>
<h1>{count}</h1>
<button onClick={()=>{handleClick()}}>click</button>
</div>
)
点击按钮时,页面count变为1,打印还是0,说明合成函数中的事件是异步。
- 批量更新---合成函数内
const [count,setCount] = useState(0)
let handleClick=()=>{
setCount(count+1)
setCount(count+2)
setCount(count+3)
console.log(count,"count");// 0 异步
}
console.log("render");
即使方法调用了三次,但是render只执行了一次。内部也会等a更新完再去执行render渲染a状态。所以,即使改变了三次a值,也只执行一次render。
2-1 依赖累加---合成函数内
const [count1, setCount1] = useState(0)
let handleClick = () => {
setCount1(pre=>pre+1)
setCount1(pre=>pre+1)
setCount1(pre=>pre+1) //每次依赖上次累加之后的count
}
console.log("render");
- 批量更新---合成函数内
const [count1, setCount1] = useState(0)
const [count2, setCount2] = useState(0)
const [count3, setCount3] = useState(0)
let handleClick = () => {
setCount1(count1 + 1)
setCount2(count2 + 1)
setCount3(count3 + 1)
console.log(count1, "count1");// 0 异步
console.log(count2, "count1");// 0 异步
console.log(count3, "count1");// 0 异步
}
console.log("render");
即使方法执行了三次,但是render只执行了一次。内部也会等count1,count2,count3更新完再去执行render渲染状态。所以,即使改变了三次值,也只执行一次render。
- 不同点----settimeout内
setTimeout(()=>{
setCount1(count1+1);
console.log(count1,"count1"); //0
},0)
在类函数组件中,定时器內部setState为同步,而hooks钩子函数中仍为异步。
- 无批量更新--settimeout内
setTimeout(() => {
setCount1(count1 + 1);
setCount2(count2 + 1)
setCount3(count3 + 1)
}, 0)
在定时器內部,原本的批量更新也不会触发。但是在定时器外部会批量触发哦。
- 原生事件--异步
let handleClick2=()=>{
setCount1(count1 + 1);
console.log(count1,"count1");
}
document.getElementById("button2"), addEventListener('click', handleClick2,false)
新--浅比较问题:
当使用setName去修改name的值时,第一次会触发render,第二次第三次由于修改的值相同,就不会触发render了。只一次render,这是react18中fiber数据结构內部机制自动优化。
但class组件中没有数据优化方案,改几次值,即使为同样的值,也会同样render几次。
在链表结构中setState为单项指向的链表,不可以被修改顺序,所以自然不可以将钩子函数用于if/while语句中,如果使用该语句则会导致setState执行顺序发生改变,与链表结构冲突。
关于多个setState被合并成一次渲染的问题。并不是说在最后一次更改之前的setState()就不被渲染了,此前的多个setState它其实还是被执行了,只不过是由于多个setState处于环形链表中內部的优化机制去判断了是初始值还是新渲染的值,从而得出最优的一项。才会让我们拿到这个链表中最新的一项。
补充拓展:
类组件中render性能解决方案:
场景:初始name值为xiaohua,点击更改为lily。
class App extends React.Component{
//state
state={
name:"xiaohua",
}
//方法
handleName=(name)=>{
this.setState({
name
})
}
//显示
<h1>{this.state.name}</h1>
<button onClick={()=>{this.handleName("lily")}}>name</button>
}
方案1: shouldComponentUpdate(nextProps,nextState)生命周期在render之前运行,判断render是否应该更新。
ps:触发更新的数据条件,內部state改变/外部传值了。
shouldComponentUpdate(nextProps,nextState){
if(this.state.name===nextState.name){
return false
}
return true
}
结果优化成功,当数据没有发生改变时,点击按钮不触发render。
官方文档用的是Object.is()去判断是否发生改变,是否应该触发render
ps:
object.is,==,===区别
console.log(""==false);//true
console.log(""===false); //false
console.log(Object.is("",false)); //false
console.log(-0===+0); //true
console.log(Object.is(-0,+0)); //false
方案2 :利用PureComonent
class App extends React.PureComponent{ //调用
state={
name:"xiaohua",
age:18,
sex:"boy"
}
}
相当于是shouldComponentUpdate的效果,自动优化render是否执行。
方案3
更换场景,给子组件传值
class Child extends React.Component{
render(){
console.log("child render");
return(
<div>
child:{this.props.count}
</div>
)
}
}
<Child count={this.state.count} />
当点击按钮时,去改变name/age时,即使child组件的count没有变,但也会跟着重复render。导致性能浪费。
解决方法:
- shouldComponentUpdate(nextProps,nextState)去判断;
在子组件中写:
shouldComponentUpdate(nextProps,nextState){
if(nextProps.count===this.props.count){
return false
}
return true
}
- PrueComponent;
class Child extends React.PureComponent{
}
那如果换成是函数组件呢?
function Child(props){
console.log("child render");
return <div>
child:{props.count}
</div>
}
处理函数组件有一个专门的方法,react中的memo
const MemoChild = React.memo(Child)
//父组件中直接使用被包裹之后的memo组件
<MemoChild count={this.state.count} />
效果与类组件中的PureComponent一样。达到render优化内部机制的效果。
续更:复杂数据类型
当useState中是复杂数据类型时又会是什么情呢?先设置为对象的形式。
let initStudent ={name:'lily',age:18}
const [student,setStudent] = useState(initStudent)
于是在页面中显示name和age两个数据,并设置一个按钮,点击的时候去改变这两个数据。
let handleClick =()=>{
initStudent.name="xiaohua",
initStudent.age=20
setStudent(initStudent)
console.log(initStudent);
}
测试,是否修改成功呢?
失败,无论点几次,都没有render。
原因,在let initStudent初始会有一个地址,而setStudent时(initStudent)里面的地址与初始let时的地址相同,所以由于钩子函数内部机制去判断地址没有改变,所以没有修改页面。
解决方法:
将initStudent赋给一个新地址:
initStudent={name:'xiaohua',age:20}
setStudent(initStudent)
然后再去刷新页面,就可以看见render啦!
那如果初始是一个数组呢?是什么情况?
let initStudent = [{name:'lily',age:18},{name:'lily1',age:18},{name:'lily2',age:18},]
const [students,setStudent] = useState(initStudent)
显示方法:
<div>
<ul>
{
students.map(student=>{
return<li key={student.name}>{student.name}---{student.age}</li> })
}
<button onClick={()=>{handleClick()}}>按钮</button>
</ul>
</div>
当点击按钮时,向数组中push一个对象,并调用setStudent方法,查看结果:
let handleClick=()=>{
initStudent.push({name:"lily:4",age:18})
setStudent(initStudent)
console.log(initStudent);
}
结果一样,没有render
也是地址相同的问题,触发了钩子函数的隐藏技能,使其自动优化,于是还把它赋给新地址,来改变数组。
initStudent=[...initStudent,{name:"lily:4",age:18}] //新地址
setStudent(initStudent)
解决啦。
要注意复杂数据类型在改变之前与之后的地址是否相同,如果相同的话会导致改变数据失败,要更改地址之后才能成功设置数据。