setState 是干嘛的
- 在 React 中,状态被认为是可读的,我们应始终替换它,而不是直接修改它。
- this.state 通常是用来初始化 state 数据的,this.setState 是修改 state 的。
- 直接修改 state 是不会触发视图变化的,调用 setState 传入新对象用于修改。
- 代码分析:
kotlin
this.setState({
key1: newState1,
key2: newState2, //将要设置的新状态
...
}, callback) // 第二个参数是 state 更新完成后的回调函数
setState 的调用原理
- 调用流程图展示:
-
源码 级详解:
- 进入 setState 这个入口函数后,根据有无回调函数,进入不同的处理队列
kotlinReactComponent.prototype.setState = function (partialState, callback) { this.updater.enqueueSetState(this, partialState); if (callback) { this.updater.enqueueCallback(this, callback, 'setState'); } };
- enqueueSetState 方法是通过拿到组件实例中的 state 数组,将其放到状态队列里,并调用 enqueueUpdate 方法来处理接下来要更新的对象
- 在 enqueueUpdate 方法中引出了一个 "锁管理器"------ batchingStrategy,这里的 "锁" 指的是全局唯一的 isBatchingUpdates 变量,这个锁决定了是走更新流程,还是应该排队等待。当等到该状态更新时,调用 batchedUpdates 方法来直接立即更新组件。这个全局变量初始值是 "false",意味着队列中没有数据要去更新,即可以直接调用 batchedUpdate 去执行更新操作,执行该更新操作时,再把 "锁" 给锁上(将isBatchingUpdates置为true)。
js
enqueueSetState: function (publicInstance, partialState) {
// 根据 this 拿到对应的组件实例
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
// 这个 queue 对应的就是一个组件实例的 state 数组
var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
queue.push(partialState);
// enqueueUpdate 用来处理当前的组件实例
enqueueUpdate(internalInstance);
}
js
. function enqueueUpdate(component) {
ensureInjected();
// 注意这一句是问题的关键,isBatchingUpdates标识着当前是否处于批量创建/更新组件的阶段
if (!batchingStrategy.isBatchingUpdates) {
// 若当前没有处于批量创建/更新组件的阶段,则立即更新组件
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
// 否则,先把组件塞入 dirtyComponents 队列里,让它"再等等"
dirtyComponents.push(component);
if (component.\_updateBatchNumber == null) {
component.\_updateBatchNumber = updateBatchNumber + 1;
}
}
.
-
额外思考: React 中的 setState 批量更新的过程是什么?
当我们处理状态高频改变这种场景,调用 setState 时,出于性能原因,state不会立即改变,根据批处理队列,将多次 setState 的状态修改合并成一次状态修改,最终只产生一次组件渲染。
setState 调用之后发生了什么?是同步还是异步的?
-
setState 调用之后发生了什么:
首先,会将传入的参数和组件当前状态合并,触发调和过程。
其次,在调和过程中,React 会根据新状态构建 React 状态树。
然后,React 得到元素树后,会自动计算新老树的节点差异。根据这些差异算法,能精确定位到哪些位置发生改变,保证了按需更新。
最后,根据改变位置重新渲染整个页面。
注:如果短时间内,频繁更新 setState,会将那些新 state 压入栈中,在合适的时机实现批量更新,达到提升性能的效果。
-
setState 是同步的还是异步的:
同步状态:每执行一次 setState 时,都会进行 vnode diff + dom 修改。
异步状态:可以通过一个同步代码实现多个 setState 合并成一次组件更新。
总的来说, setState 不是单纯的同步/异步的,这取决于它的调用场景。以 源码 来说, 通过 isBatchingUpdates 来判断 setState 是先排队还是直接更新,如果要排队(true),那就执行异步操作,不排队就直接更新。以具体适用场景来说, 在 React 可以控制的地方就走异步设计,在 React 无法控制的地方比如原生事件,就只能同步更新。
State 是怎么注入到组件的,从 reducer 到组件经历了什么样的过程
-
通过 connect 和 mapStateToProps 将state注入到组件中
-
reducer 到组件经历了什么样的过程:
Reducer 对 action 对象进行管理,更新组件状态,将新的状态值返回 store 。
通过 connect(mapStateToProps,mapDispatchToProps)高阶组件,此时将状态值从 store 取出并作为 props 参数传递到组件
源码实现:
javascriptimport React from 'react' import PropTypes from 'prop-types' // 高阶组件 contect export const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => { class Connect extends React.Component { // 通过对context调用获取store static contextTypes = { store: PropTypes.object } constructor() { super() this.state = { allProps: {} } } // 第一遍需初始化所有组件初始状态 componentWillMount() { const store = this.context.store this._updateProps() store.subscribe(() => this._updateProps()); // 加入_updateProps()至store里的监听事件列表 } // 执行action后更新props,使组件可以更新至最新状态(类似于setState) _updateProps() { const store = this.context.store; let stateProps = mapStateToProps ? mapStateToProps(store.getState(), this.props) : {} // 防止 mapStateToProps 没有传入 let dispatchProps = mapDispatchToProps ? mapDispatchToProps(store.dispatch, this.props) : { dispatch: store.dispatch } // 防止 mapDispatchToProps 没有传入 this.setState({ allProps: { ...stateProps, ...dispatchProps, ...this.props } }) } render() { return <WrappedComponent {...this.state.allProps} /> } } return Connect }
React 中的 props 和 state 有什么区别
区别 | props | state |
---|---|---|
特性 | 只读性和不可变性Props 是传递给组件的 | 只能在constructor中初始化,是组件的私有属性只能通过组件内部的 this.setState来修改 |
作用 | 父组件向子组件传递数据 | 组件保存,控制以及修改自己的状态 |
总结
setState
通过声明式状态管理驱动 UI 更新,其异步合并机制优化了渲染效率。开发者需注意函数式更新、嵌套对象合并及副作用处理,以构建高效可靠的 React 组件。