补充说明:setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。
setState
表现为异步的场景如下:setState
react自己封装了一套事件机制,代理了原生的事件,像在jsx中常见的onClick、onChange这些都是合成事件。
class SetStatus extends Component {state = { val: 0 }increment = () => {debuggerthis.setState({val: this.state.val +1})console.log('---------'. val) // 0}render() {return ({`Counter is: ${this.state.val}`})}
}
只有当increment函数执行完以后,才会更新state,执行performSyncWorkOnRoot
setState
class StudySetState extends Component {state = {val: 0}componentDidMount() {debuggerthis.setState({val: this.state.val + 1})console.log('-----', this.state.val) // 0}render() {return ( {`Counter is ${this.state.val}`} )}
}
原理:其实还是和合成事件一样,当 componentDidmount
执行的时候,react内部并没有更新,执行完componentDidmount
后才去 commitUpdateQueue
更新。这就导致你在 componentDidmount
中 setState
完去console.log拿的结果还是更新前的值。源码真相:
if ( finishedWork.mode & ProfileMode) { try { startLayoutEffectTimer(); instance.componentDidMount(); // 执行完componentDidMount() } finally { recordLayoutEffectDuration(finishedWork); }} else { instance.componentDidMount();
}
var updateQueue = finishedWork.updateQueue;
if (updateQueue !== null) { { if (finishedWork.type === finishedWork.elementType && !didWarnAboutReassigningProps) {if (instance.props !== finishedWork.memoizedProps) { error('Expected %s props to match memoized props before ' + 'processing the update queue. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance'); }if (instance.state !== finishedWork.memoizedState) { error('Expected %s state to match memoized state before ' + 'processing the update queue. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.state`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance');} }
} // We could update instance props and state here,// but instead we rely on them being set during last render.// TODO: revisit this when we implement resuming.
commitUpdateQueue(finishedWork, updateQueue, instance); // 更新props和state
}
setState
表现为同步的场景如下:class StudySetState extends Component {state = { val: 0 }changeValue = () => {this.setState({val: this.state.val + 1})console.log('-----', this.state.val) // 1}componentDidMount() {document.body.addEventListener('click', this.changeValue, false)}render() {return ({`Counter is: ${this.state.val}`})}
}
原生事件是指非react
合成事件,原生自带的事件监听 addEventListener
,或者也可以用原生js、jq
直接 document.querySelector().onclick
这种绑定事件的形式都属于原生事件。
原生事件的调用栈就比较简单了,因为没有走合成事件的那一大堆,直接触发click
事件,到 requestWork
,在requestWork
里由于 expirationTime === Sync
的原因,直接走了 performSyncWork
去更新,并不像合成事件或钩子函数中被return
,所以当你在原生事件中setState
后,能同步拿到更新后的state
值。
class App extends Component {state = { val: 0 }
componentDidMount() {setTimeout(_ => {this.setState({ val: this.state.val + 1 })console.log(this.state.val) // 输出更新后的值 --> 1}, 0)
}render() {return ({`Counter is: ${this.state.val}`})}
}
在 setTimeout
中的 setState
并不算是一个单独的场景,它是随着你外层去决定的,因为你可以在合成事件中 setTimeout
,可以在钩子函数中 setTimeout
,也可以在原生事件setTimeout
,但是不管是哪个场景下,基于事件循环机制,setTimeout中里去setState总能拿到最新的state值。
在react18之前
在react18之后
class StudySetState extends Component {state = { val: 0}handleClick = () => {flushSync(() => {this.setState({val: 2})})console.log('-------', this.state.val) // 2}render() {return ()}
}
最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。
有需要的小伙伴,可以点击下方卡片领取,无偿分享