函数组件
函数组件是无状态的,每一次更新数据都会重新调用函数,生成新的函数执行上下文
所以在hook出现之前,函数组件大都只能用来当作纯展示组件,因为它内部没有存储状态(state),没有生命周期,并且逻辑不能复用
编写函数组件称为函数式编程
class组件
class组件是基于es6中的类的,编写class组件称为面向对象编程
class组件有状态,有自己的生命周期
在hook出现之前,逻辑比较复杂或者需要依赖状态的组件都采用class组件的形式
所以他们的本质区别是什么?
直接看代码
class组件
class Index extends React.Component{
constructor(props){
super(props)
this.state={
number:0
}
}
handerClick=()=>{
for(let i = 0 ;i<5;i++){
setTimeout(()=>{
this.setState({ number:this.state.number+1 })
console.log(this.state.number)
},1000)
}
}
render(){
return
}
}
函数组件
function Index(){
const [ num ,setNumber ] = React.useState(0)
const handerClick=()=>{
for(let i=0; i<5;i++ ){
setTimeout(() => {
setNumber(num+1)
console.log(num)
}, 1000)
}
}
return
}
上面两个代码片段会分别打印什么结果?
第一个例子: 0 0 0 0 0
第二个例子: 0 0 0 0 0
先来解释第一个例子
第一个例子是用class组件的形式
初始化一个名为number
的state,初始值为0
点击按钮执行handerClick
方法,触发number的更新,在更新函数中使用了循环递增的方式,并且加了1000ms的延时
在react18版本中,react对状态更新做了自动批处理,也就是说在很短时间内多次更新同一个状态,react只会以更新前的state值为基础进行状态更新
this.state = {
number: 0
}
this.setState({number:this.state.number+1})
this.setState({number:this.state.number+1})
this.setState({number:this.state.number+1})
//此时number的值:1
上述代码相当于执行了三次
this.setState({number:0 +1})
因为在短时间内this.state.number的值始终为0!(归根结底还是因为setState是异步的)
所以我们就能理解了
短时间内打印number 始终为0
但是我们假设react没有对状态做自动分批处理
输出结果会是怎样?
答案是: 1 2 3 4 5
归根结底还是state被保存起来了,每次setState都会以上一次state为基础进行递增
不论react对状态进行了什么处理,我们要知道的是,class组件是有状态的,他的状态会在组件生命周期中一直保存
我们再来看第二个例子:函数组件
这个对熟悉闭包的人应该比较容易理解
在for循环中使用setTimeOut回调会产生闭包,闭包中的num会指向原函数作用域中的num,也就是0
所以我们每次执行setNumber(num + 1)
实际上都是执行setNumber(0 + 1)
,执行console.log(num)
;实际都是执行console.log(0)
在循环结束后 num的值会变成1,因为setNumber(0 + 1)
执行后将num置为1
我们梳理一下执行一次setNumber的流程:
执行setNumber后,会导致组件function重新,所有语句会被重新调用执行
走到useState的时候,react内部其实走了updateState,拿到最新的状态:1,此时一切正常
走到handerClick,此时handerClick被重新创建,即handerClick指向了新的内存空间,值得注意的是:for循环接着往下走的时候,num所在的上下文并不是当前函数(新创建的函数),而是第一次初始化时创建的函数,而那个函数的上下文中,num永远都是0,所以console.log输出都是0
相反:class写法,state发生变化handerClick没有被重新创建而已,并且this指向也没发生改变