一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第17天,点击查看活动详情。
先说一下React中的state和props
React中组件的显示形态可以由数据状态和外部参数所决定,而数据状态就是state,需要修改state的状态需要通过调用setState来改变,从而达到更新组件内部数据的作用,并且会重新调用组件render方法
这个外部参数就是props,React的核心思想就是组件化思想,页面会被切分成一些独立的、可复用的组件。
组件从概念上看就是一个函数,可以接受一个参数作为输入值,这个参数就是props,所以可以把props理解为从外部传入组件内部的数据
-
react具有单向数据流的特性,所以props的主要作用是从父组件向子组件中传递数据 -
props除了可以传字符串,数字,还可以传递对象,数组和回调函数 -
props发生改变,也会导致render函数重新调用,页面更新 在使用setState的时候一定遇到过这种情况import React from 'react'
class APP extends React.Component { constructor(props) { super(props)
this.state = { msg: '黑夜来临' } }
changeText=()=>{ this.setState({ msg: '又是新的一天' }) console.log(this.state.msg) }
render() { return
} } export default APP{this.state.msg}
点我
我们希望点击button按钮调用函数,将state里的msg修改成'又是新的一天',页面实现同步更新
这里有一个注意点
changeText=()=>{
this.setState({
msg: '又是新的一天'
})
console.log(this.state.msg)
}
这里如果写的不是箭头函数的形式,写的是这种changeText(){}普通函数,this指向的不是react实例,会报找不到state的错误,打印this也是undefined
所以如果要写普通函数形式,就要手动的去修改this指向
- 在 super(props)下面通过bind修改this指向
- 在按钮点击事件里修改
这两种方法都可以,不过为了避免出现this指向问题,还是建议大家在类组件里声明的函数写成箭头函数的形式
接下来就是很容易出现的情况,当我们点击按钮通过setState修改state的值时
我们发现虽然页面是同步修改了,但是打印console.log(this.state.msg)得到的还是旧值,如果我需要在函数中接着对新值操作的时候,这里就不行了,因为拿到的不是新值而是旧值
那如果我们不通过setState来修改state的状态,直接this.state修改会有什么结果呢?
changeText = () => {
// this.setState({
// msg: '又是新的一天'
// })
this.state.msg = '又是新的一天'
console.log(this.state.msg)
}
可以看到虽然state的状态是已经发生了改变,打印出来的是新值 ,但是页面却没有更新。这是因为React并不像vue2中调用Object.defineProperty数据响应式或者Vue3调用Proxy监听数据的变化
react中必须通过setState方法来告知react组件state已经发生了改变
关于state方法的定义是从React.Component中继承,定义的源码如下:
从上面可以看到setState第一个参数可以是一个对象,或者是一个函数,而第二个参数是一个回调函数,在这个函数里可以实时的获取到更新之后的数据
changeText = () => {
this.setState({
msg: '又是新的一天'
}, () => {
console.log(this.state.msg)
})
}
我们可以看到,
在setState里传入第二个参数函数,在这个函数里面可以拿到state更新之后的数据,为什么setState外面获取不到最新的值呢?因为setState的更新类型
setState的更新类型
-
异步更新
-
同步更新 异步更新就是我们上面的例子,必须在setState第二个参数里获取更新后的值
changeText = () => { this.setState({ msg: '又是新的一天' }, () => { console.log(this.state.msg) }) }
同步更新
再定时器setTimeout中,setState是同步的
changeText = () => {
setTimeout(() => {
this.setState({
msg: '又是新的一天'
})
console.log(this.state.msg)
}, 0)
}
把setState写在定时器里,页面会同步更新,并且可以获取到最新的数据
通过原生DOM事件,不把点击事件写在button上,setState也是同步的
// componentDidMountdom加载渲染完毕
componentDidMount() {
const btn = document.getElementById('btn')
btn.addEventListener('click', () => {
this.setState({
msg: '又是新的一天'
})
console.log(this.state.msg)
})
}
总结
- 在组件生命周期或React合成事件中,setState是异步,
(一般我们写在jsx语法也就是直接在结构上写的事件就是React的合成事件,使用驼峰命名法的事件) - 在setTimeout或者原生dom事件中,setState是同步
注意点:批量更新
changeText = () => {
this.setState({
count: this.state.count + 1,
})
this.setState({
count: this.state.count + 1,
})
this.setState({
count: this.state.count + 1,
})
this.setState({
count: this.state.count + 1,
})
console.log(this.state.count)
}
render() {
return <div>
<h1>{this.state.count}</h1>
<button onClick={this.changeText}>点我</button>
</div>
}
对同一个值进行多次
setState, setState 的批量更新策略会对其进行覆盖,取最后一次的执行结果
如果是下一个state依赖前一个state的话,可以给setState参数传入一个function
changeText = () => {
this.setState((state, props) => {
return {
count: state.count + 1
}
})
this.setState((state, props) => {
return {
count: state.count + 1
}
})
this.setState((state, props) => {
return {
count: state.count + 1
}
})
this.setState((state, props) => {
return {
count: state.count + 1
}
})
console.log(this.state.count)
}
这样下面的setState操作的state就是上一次setState返回的结果,就不会出先覆盖
在setTimeout或者原生dom事件中,由于是同步的操作,所以并不会进行覆盖现象