什么是setState
setState()
将对组件 state 的更新排入队列,并通知 React 需要使用更新后的 state 重新渲染此组件及其子组件。这是用于更新用户界面以响应事件处理器和处理服务器数据的主要方式
setState()
并不总是立即更新组件。它会批量推迟更新。这使得在调用 setState()
后立即读取 this.state
成为了隐患。为了消除隐患,请使用 componentDidUpdate
或者 setState
的回调函数(setState(updater, callback)
),这两种方式都可以保证在应用更新后触发
除非 shouldComponentUpdate()
返回 false
,否则 setState()
将始终执行重新渲染操作。如果可变对象被使用,且无法在 shouldComponentUpdate()
中实现条件渲染,那么仅在新旧状态不一致调用 setState()
可以避免不必要的重新渲染
是干嘛的
用来修改页面的数据的
怎么使用
setState(updater, [callback])
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
}, () => {
vue的 $nextTick
console.log('count by callback', this.state.count) // 回调
})
-
通过
setState
去修改message
,是不会对其他state
中的数据产生影响的 -
源码中其实是有对 原对象 和 新对象 进行合并的
return Object.assign({}, prevState, parialState);
-
当我们的多次调用了
setState
, 只会生效最后一次state
为什么不能这么那么使用
this.state.count + +
不能修改state自身,必须通过setState()
React 不能直接修改 state 自身的原因是为了确保数据的一致性和可预测性,以及配合 React 的更新机制进行正确的 DOM 更新。
一致性和可预测性:
- 直接修改 state 自身可能会导致数据变化的不可控和不一致性。React 鼓励使用不可变数据(immutable data)的概念,通过创建新的数据副本来表示数据的变化。
- 不可变数据可以让我们更容易跟踪数据的改变,避免出现意外的数据变化,从而提高代码的可维护性和可预测性。
配合 React 的更新机制:
- 直接修改 state 自身可能会绕过 React 的更新机制,导致视图与状态不同步。
- 通过
setState
方法来修改 state,可以触发 React 的更新流程,确保视图能够及时地根据最新的状态进行更新。因此,使用
setState
方法来修改 state 是 React 设计的一部分,它强调了数据的不可变性和通过统一的方式来更新状态,以确保应用的稳定性和可维护性。当你调用
setState
方法时,React 会在遵循特定的更新流程后,根据新的状态重新渲染组件,从而使得视图与状态保持同步。总的来说,React 不能直接修改 state 自身是为了保证数据的一致性、可预测性,并配合 React 的更新机制进行正确的 DOM 更新。
setState特性
不可变值
可能是异步更新
可能会被合并
1. 不可变值
React 不能直接修改 state 自身的原因是为了确保数据的一致性和可预测性,以及配合 React 的更新机制进行正确的 DOM 更新。
一致性和可预测性:
- 直接修改 state 自身可能会导致数据变化的不可控和不一致性。React 鼓励使用不可变数据(immutable data)的概念,通过创建新的数据副本来表示数据的变化。
- 不可变数据可以让我们更容易跟踪数据的改变,避免出现意外的数据变化,从而提高代码的可维护性和可预测性。
配合 React 的更新机制:
- 直接修改 state 自身可能会绕过 React 的更新机制,导致视图与状态不同步。
- 通过
setState
方法来修改 state,可以触发 React 的更新流程,确保视图能够及时地根据最新的状态进行更新。因此,使用
setState
方法来修改 state 是 React 设计的一部分,它强调了数据的不可变性和通过统一的方式来更新状态,以确保应用的稳定性和可维护性。当调用
setState
方法时,React 会在遵循特定的更新流程后,根据新的状态重新渲染组件,从而使得视图与状态保持同步。总的来说,React 不能直接修改 state 自身是为了保证数据的一致性、可预测性,并配合 React 的更新机制进行正确的 DOM 更新。
this.setState({
list1: this.state.list1.concat(100), // 追加
list2: [...this.state.list2, 100], // 追加
list3: this.state.list3.slice(0, 3) // 截取
list4: this.state.list4.filter(item => item>100), // 筛选
list5: list5Copy // 其他操作
})
注意:不能直接对 this.state.list进行push pop splice等,这样违反不可变
不可变值-对象
this.setState({
obj1: Obiect.assign({}, this.state.obj1, {a: 100}),
obj2: {...this.state.obj2, a: 100}
})
-
源码中对 原对象 和 新对象 进行合并的
2. 同步异步
其实可以分成两种情况:
在 组件生命周期 或 React合成事件 中, setState是异步的
this.setState({
count: this.state.count + 1
})
它是一个异步的操作
在上下文,拿不到修改后的值
console.log('count', this.state.count) // 这样是拿不到这个this.state.count修改后的值的
必须在后面加一个箭头函数才能拿到
this.setState({
count: this.state.count + 1
}, () => {
vue的 $nextTick
console.log('count by callback', this.state.count) // 回调
})
在 setTimeou 或 原生DOM事件 中, setState是同步的
验证一: 在setTimeout中的更新
changeText() {
setTimeout(() => {
this.setState({ message: '你好' })
console.log(this.state.message) // -> 你好
}, 0)
}
验证二:在原生DOM事件 -> 同步更新
componentDidMount() {
document.getElementById('btn),addEventListener('click', e => {
this.setState({ message: '你好' })
console.log(this.state.message)
})
}
React18之后,所有操作都是异步的了,如果想让setState同步处理任务,需要加一个flushSync,就如vue中的async await
flushSync(() => {
this.setState({ message: "你好啊" })
})
// 这里获取就是同步的
console.log(this.state.message) // 你好啊
2. setState的基本过程
setState的调用会引起React的更新生命周期的4个函数执行:
shouldComponentUpdate componentWillUpdate render componentDidUpdate
当shouldComponentUpdate执行时,返回true,进行下一步,this.state没有被更新
返回false,停止,更新this.state当componentWillUpdate被调用时,this.state也没有被更新,直到render被调用时候,this.state才被更新。
总之,直到下一次render函数调用(或者下一次shouldComponentUpdate返回false时)才能得到更新后的this.state
setState更新原理
触发状态更新:
- 当调用
setState(newState, callback)
时,React 会将新的状态 newState 存储在内部的更新队列中,并标记组件为“脏”(dirty)。批量更新:
- 如果在一个事件处理函数中多次调用
setState
,React 会对这些状态更新进行合并,只执行一次更新操作,以提高性能。调度更新:
- React 使用异步更新机制来调度状态更新,它会利用事务机制、批量更新等技术来确保更新的顺序和性能。
执行 render:
- 在下一个事件循环中,React 根据更新队列中的状态变化,重新调用组件的
render
方法生成虚拟 DOM。比较与更新:
- React 使用虚拟 DOM 的 diff 算法比较新旧虚拟 DOM 的差异,然后只更新有变化的部分到实际 DOM 中。
生命周期方法调用:
- 更新完成后,React 会调用相应的生命周期方法(如
componentDidUpdate
),让开发者有机会做一些额外的操作。
源码
1在class中能够使用setState的原因,如下面源码中的,继承了component
Component.prototype.setState = function(partialState, callback) {
invariant(
typeof partialState === 'object' ||
typeof partialState === '' ||
partialState == null,
'setState(...): takes an object of state variables to update or a' +
'function which returns on object of state variables.',
);
this.updater.enqueueSetState(this, partialState, callback, 'setState');
}
2setState同步异步的原因
const classCompenentUpdater = {
isMounted,
enqueueSetState(inst, payload, callback){
const fiber = getInstance(inst);
const currentTime = requestCurrentTimeForUpdate();
const suspenseConfig = requestCurrentSuspenseConfig();
const expirationTime = computeExpirationForFiber(
currentTime,
fiber,
suspenseConfig,
);
const update = createUpdate(expirationTime, suspenseConfig);
update.payload = payload;
if (callback !==undefined && callback !==null) {
if (__DEV__) {
warnOnInvalidCallback(callback, 'setState');
}
update.callback = callback;
}
enqueueUpdate(fiber, update);
scheduleWork(fiber, expirationTime);
},
enqueueReplaceState(inst, payload, callback){...
},
enqueueForceUpdate(inst, callback){...
}
}
函数组件和class组件的使用区别
深入浅出 setState 原理篇 https://zhuanlan.zhihu.com/p/470429343
React学习-setState的执行机制 https://www.cnblogs.com/zhilili/p/16067832.html
从 setState 聊到 React 性能优化 https://baijiahao.baidu.com/sid=1714389526829756241&wfr=spider&for=pc
12_React扩展(setState的两种写法、lazyLoad、Hooks等) https://blog.csdn.net/weixin_42152058/article/details/130619775
React中的setState使用细节和原理解析 https://blog.csdn.net/m0_71485750/article/details/126649380