1、获取setState异步更新后的结果
1、setState更新后一个回调函数,可以通过回调函数获取更新后的值
this.setState({
counter: this.state.counter + 1
}, () => { //
console.log('更新完成!')
})
2、componentDidUpdate() 生命周期函数可以获取回更新后的值
componentDidUpdate() {
console.log(this.state.counter);
}
2、实现setState同步更新
1、setState用定时器包裹,设置定时器时间为0
setTimeout(() => {
this.setState({
counter: this.state.counter + 1
}, () => {
});
console.log(this.state.counter);
}, 0);
2、通过监听原生的DOM事件会滴
componentDidMount() {
document.getElementById("btn").addEventListener("click", ()=> {
this.setState({
counter: this.state.counter + 1
}, () => {
})
console.log(this.state.counter);
});
}
小结:
- 在组件生命周期或React合成事件中,setState是异步
- 在setTimeout或者原生dom事件中,setState是同步
例如
import React, { Component } from 'react'
export default class App extends Component {
constructor(props) {
super(props)
this.state = {
message: "好消息啊"
}
}
componentDidUpdate() {
}
componentDidMount() {
document.getElementById("btn").addEventListener("click", ()=> {
this.setState({
message: "你好啊,领导们"
}, () => {
})
console.log(this.state.message)
})
}
render() {
return (
<div style={{flexDirection: "column"}}>
<h2>{this.state.message}</h2>
{/*react 的合成事件*/}
<button onClick={e => this.changeText()}>改变文本</button>
<button id={"btn"} >改变文本2</button>
</div>
)
}
changeMessage() {
setTimeout(() => {
this.setState({
message: "你好啊,领导们"
}, () => {
})
console.log(this.state.message)
}, 0);
}
}
源码分析setState
平时看源码时主要是看 react、react-dom、react-reconciler 三个文件夹
setState的调用入口
// react/src/ReactBaseClasses.js line 57
Component.prototype.setState = function(partialState, callback) {
invariant(
typeof partialState === 'object' ||
typeof partialState === 'function' ||
partialState == null,
'setState(...): takes an object of state variables to update or a ' +
'function which returns an object of state variables.',
);
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
this.updater
调用的是 ReactFiberClassComponent.new.js
中的enqueueSetState
方法
// react-reconciler/src/forks/ReactFiberClassComponent.new.js line194
const classComponentUpdater = {
isMounted,
enqueueSetState(inst, payload, callback) {
const fiber = getInstance(inst);
const eventTime = requestEventTime();
// line 199 返回确定是使用同步还是异步执行的方式
const lane = requestUpdateLane(fiber);
const update = createUpdate(eventTime, lane); // 返回记录要更新的内容
update.payload = payload;
if (callback !== undefined && callback !== null) {
if (__DEV__) {
warnOnInvalidCallback(callback, 'setState');
}
update.callback = callback;
}
enqueueUpdate(fiber, update); // 将本次需要update的内容加入到队列中
scheduleUpdateOnFiber(fiber, lane, eventTime);
... // 省略
},
};
查看requestUpdateLane
调用方法,返回SyncLane
同步或者SyncBatchedLane
批处理
// react-reconciler/src/forks/ReactFiberWorkLoop.new.js line 506
function requestRetryLane(fiber: Fiber) {
// This is a fork of `requestUpdateLane` designed specifically for Suspense
// "retries" — a special update that attempts to flip a Suspense boundary
// from its placeholder state to its primary/resolved state.
// Special cases
const mode = fiber.mode;
if ((mode & BlockingMode) === NoMode) {
return (SyncLane: Lane); // 返回执行方式
} else if ((mode & ConcurrentMode) === NoMode) {
return getCurrentPriorityLevel() === ImmediateSchedulerPriority
? (SyncLane: Lane)
: (SyncBatchedLane: Lane); // 批处理
}
// See `requestUpdateLane` for explanation of `currentEventWipLanes`
if (currentEventWipLanes === NoLanes) {
currentEventWipLanes = workInProgressRootIncludedLanes;
}
return findRetryLane(currentEventWipLanes);
}
查看enqueueUpdate
方法
// react-reconciler/src/forks/ReactUpdateQueue.new.js line 506
export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
const updateQueue = fiber.updateQueue;
if (updateQueue === null) {
// Only occurs if the fiber has been unmounted.
return;
}
const sharedQueue: SharedQueue<State> = (updateQueue: any).shared;
const pending = sharedQueue.pending;
if (pending === null) {
// This is the first update. Create a circular list.
update.next = update; // 将需要update的内容组成一个链表
} else {
update.next = pending.next;
pending.next = update;
}
sharedQueue.pending = update;
}
3、setState数据合并
同时执行多条setState的时候底层源码会进行合并操作
1、执行的setState为对象
this.setState({
couner: prevState.couner + 1
})
this.setState({
couner: prevState.couner + 1
})
this.setState({
couner: prevState.couner + 1
})
2、执行setState是一个函数
this.setState((prevState, props) => {
return {
couner: prevState.couner + 1
}
})
this.setState((prevState, props) => {
return {
couner: prevState.couner + 1
}
})
this.setState((prevState, props) => {
return {
couner: prevState.couner + 1
}
})
源码分析
1)、执行的setState为对象时其实是通过do..while循环多次调用getStateFromUpdate
方法
执行 Object.assign({}, prevState, partialState)
,每次执行的时候prevState都是原来的state,而不是上一条state,因此最终合并成最后一次执行的setState方法。
2)、执行setState是一个函数,getStateFromUpdate
会判断typeof payload === 'function'
是否是一个函数,如果是一个函数,直接执行方法payload.call(instance, prevState, nextProps);
,对每一条count进行+1操作
// react-reconciler/src/forks/ReactUpdateQueue.new.js line 384
// line 311
function getStateFromUpdate<State>(
workInProgress: Fiber,
queue: UpdateQueue<State>,
update: Update<State>,
prevState: State,
nextProps: any,
instance: any,
): any {
switch (update.tag) {
case ReplaceState: {
const payload = update.payload;
if (typeof payload === 'function') {
// Updater function
const nextState = payload.call(instance, prevState, nextProps);
return nextState;
}
// State object
return payload;
}
case CaptureUpdate: {
workInProgress.flags =
(workInProgress.flags & ~ShouldCapture) | DidCapture;
}
// Intentional fallthrough
case UpdateState: {
const payload = update.payload;
let partialState;
if (typeof payload === 'function') {
// Updater function
partialState = payload.call(instance, prevState, nextProps);
} else {
// Partial state object
partialState = payload;
}
if (partialState === null || partialState === undefined) {
// Null and undefined are treated as no-ops.
return prevState;
}
// Merge the partial state and the previous state.
return Object.assign({}, prevState, partialState); // 合并state数据
}
case ForceUpdate: {
hasForceUpdate = true;
return prevState;
}
}
return prevState;
}
processUpdateQueue
方法的do..while循环操作
// react-reconciler/src/forks/ReactUpdateQueue.new.js line 394
export function processUpdateQueue<State>(
workInProgress: Fiber,
props: any,
instance: any,
renderLanes: Lanes,
): void {
// This is always non-null on a ClassComponent or HostRoot
const queue: UpdateQueue<State> = (workInProgress.updateQueue: any);
hasForceUpdate = false;
let firstBaseUpdate = queue.firstBaseUpdate;
let lastBaseUpdate = queue.lastBaseUpdate;
// Check if there are pending updates. If so, transfer them to the base queue.
let pendingQueue = queue.shared.pending;
if (pendingQueue !== null) {
queue.shared.pending = null;
// The pending queue is circular. Disconnect the pointer between first
// and last so that it's non-circular.
const lastPendingUpdate = pendingQueue;
const firstPendingUpdate = lastPendingUpdate.next;
lastPendingUpdate.next = null;
// Append pending updates to base queue
if (lastBaseUpdate === null) {
firstBaseUpdate = firstPendingUpdate;
} else {
lastBaseUpdate.next = firstPendingUpdate;
}
lastBaseUpdate = lastPendingUpdate;
// If there's a current queue, and it's different from the base queue, then
// we need to transfer the updates to that queue, too. Because the base
// queue is a singly-linked list with no cycles, we can append to both
// lists and take advantage of structural sharing.
// TODO: Pass `current` as argument
const current = workInProgress.alternate;
if (current !== null) {
// This is always non-null on a ClassComponent or HostRoot
const currentQueue: UpdateQueue<State> = (current.updateQueue: any);
const currentLastBaseUpdate = currentQueue.lastBaseUpdate;
if (currentLastBaseUpdate !== lastBaseUpdate) {
if (currentLastBaseUpdate === null) {
currentQueue.firstBaseUpdate = firstPendingUpdate;
} else {
currentLastBaseUpdate.next = firstPendingUpdate;
}
currentQueue.lastBaseUpdate = lastPendingUpdate;
}
}
}
// These values may change as we process the queue.
if (firstBaseUpdate !== null) {
// Iterate through the list of updates to compute the result.
let newState = queue.baseState;
// TODO: Don't need to accumulate this. Instead, we can remove renderLanes
// from the original lanes.
let newLanes = NoLanes;
let newBaseState = null;
let newFirstBaseUpdate = null;
let newLastBaseUpdate = null;
let update = firstBaseUpdate;
do {
const updateLane = update.lane;
const updateEventTime = update.eventTime;
if (!isSubsetOfLanes(renderLanes, updateLane)) {
// Priority is insufficient. Skip this update. If this is the first
// skipped update, the previous update/state is the new base
// update/state.
const clone: Update<State> = {
eventTime: updateEventTime,
lane: updateLane,
tag: update.tag,
payload: update.payload,
callback: update.callback,
next: null,
};
if (newLastBaseUpdate === null) {
newFirstBaseUpdate = newLastBaseUpdate = clone;
newBaseState = newState;
} else {
newLastBaseUpdate = newLastBaseUpdate.next = clone;
}
// Update the remaining priority in the queue.
newLanes = mergeLanes(newLanes, updateLane);
} else {
// This update does have sufficient priority.
if (newLastBaseUpdate !== null) {
const clone: Update<State> = {
eventTime: updateEventTime,
// This update is going to be committed so we never want uncommit
// it. Using NoLane works because 0 is a subset of all bitmasks, so
// this will never be skipped by the check above.
lane: NoLane,
tag: update.tag,
payload: update.payload,
callback: update.callback,
next: null,
};
newLastBaseUpdate = newLastBaseUpdate.next = clone;
}
// Process this update.
// 合并所有的setState方法,将其合并成一个state
newState = getStateFromUpdate(
workInProgress,
queue,
update,
newState,
props,
instance,
);
const callback = update.callback;
if (callback !== null) {
workInProgress.flags |= Callback;
const effects = queue.effects;
if (effects === null) {
queue.effects = [update];
} else {
effects.push(update);
}
}
}
update = update.next;
if (update === null) {
pendingQueue = queue.shared.pending;
if (pendingQueue === null) {
break;
} else {
// An update was scheduled from inside a reducer. Add the new
// pending updates to the end of the list and keep processing.
const lastPendingUpdate = pendingQueue;
// Intentionally unsound. Pending updates form a circular list, but we
// unravel them when transferring them to the base queue.
const firstPendingUpdate = ((lastPendingUpdate.next: any): Update<State>);
lastPendingUpdate.next = null;
update = firstPendingUpdate;
queue.lastBaseUpdate = lastPendingUpdate;
queue.shared.pending = null;
}
}
} while (true);
if (newLastBaseUpdate === null) {
newBaseState = newState;
}
queue.baseState = ((newBaseState: any): State);
queue.firstBaseUpdate = newFirstBaseUpdate;
queue.lastBaseUpdate = newLastBaseUpdate;
// Set the remaining expiration time to be whatever is remaining in the queue.
// This should be fine because the only two other things that contribute to
// expiration time are props and context. We're already in the middle of the
// begin phase by the time we start processing the queue, so we've already
// dealt with the props. Context in components that specify
// shouldComponentUpdate is tricky; but we'll have to account for
// that regardless.
markSkippedUpdateLanes(newLanes);
workInProgress.lanes = newLanes;
workInProgress.memoizedState = newState;
}
}
4、setState的不可变力量
使用延展操作符对数组进行深拷贝
handleClick() {
this.setState(state => ({
words: [...state.words, 'marklar'],
}));
};
网友评论