背景
在端午假期时写的,把之前学习的知识做了整理,想分享给大家看看,知识点有没有理解不到位的地方,还有没有需要改进的地方,希望借助大家的慧眼,提升自己,同时,也希望能帮助同学们,对react的理解更加深入;
useReducer
1.mountReducer
页面加载时,走的是mountReducer;mountReducer会创建hook对象,得到初始状态,创建queue,生成dispatch供用户调用;
- 创建hook对象,形成或加入hook单向链表;(mountWorkInProgressHook)
- initialArg和init生成initialState初始状态,存到hook对象的memoizedState和baseState里;
- 创建queue对象给hook.queue,queue用于存储当前状态,当前reducer,更新队列pending,赛道lanes,dispatch等;(调用dispatch时会用到queue)
- 绑定dispatch函数,dipatch的前两个参数绑定为,dispatch函数所在的函数组件fiber,和用于存储更新状态的queue;(之所以要绑定fiber和queue,是在用户同时多次调用同一个dispatch,所更新的是同一个组件下的同一个状态)
- 返回初始状态和绑定过的dispatch函数(return [hook.memoizedState, dispatch]);
function mountReducer(reducer, initialArg, init) {
var hook = mountWorkInProgressHook();
//创建hook对象,形成hook链表或加入已经创建好的hook链表
var initialState;
if (init !== undefined) {//根据initialArg, init得到初始状态
initialState = init(initialArg);
} else {
initialState = initialArg;
}
hook.memoizedState = hook.baseState = initialState;
//将初始状态存储到hook对象的memoizedState和baseState
var queue = {
pending: null,//存储更新环状链表
interleaved: null,
lanes: NoLanes,//赛道
dispatch: null,
lastRenderedReducer: reducer,
lastRenderedState: initialState //上一次渲染的状态
};
hook.queue = queue; //创建queue对象,也存储到hook对象中
var dispatch = queue.dispatch = dispatchReducerAction.bind(null, currentlyRenderingFiber$1, queue);
//用函数组件fiber(currentlyRenderingFiber$1),queue对象生成dispatch;
//用户调用同一个dispatch,拿到的fiber和queue是一样的;
//用户同时调用多次同一个dispatch,会将更新对象,添加queue.pending环状列表;
return [hook.memoizedState, dispatch];
//生成dispath函数,和初始状态一起返回给用户调用;
}
2.dispatch
用户调用dispatch,会触发更新;创建更新对象,添加到hook对象的queue.pending环状列表中,开始调度更新;
- 拿到更新的赛道lane,创建更新对象update,将update对象加入到queue.pending环状链表里;
- 开始调度更新(scheduleUpdateOnFiber(fiber, lane, eventTime));
- 拿到root节点,从root节点开始更新工作,将performSyncWorkOnRoot同步更新工作添加到syncQueue中,有添加过的就不用添加,只添加一次,做批量更新;
- scheduleMicrotask添加一个微任务(默认用queueMicrotask,除了IE,其余新版本的浏览器均有这个api),用于处理syncQueue;(多次调用dispatch,也只执行一次,dispatch是同步任务)
- 同步任务执行完后,就会执行scheduleMicrotask添加的微任务,开始renderRootSync,workLoopSync工作循环,到renderWithHooks时执行函数组件,执行updateReducer,拿到新状态,返回给用户,生成新的jsx,与老fiber进行比对,生成新的fiber,将新状态渲染到页面中
function dispatchReducerAction(fiber, queue, action) {
//dispatchReducerAction就是用户调用的dispatch函数,
//fiber为当前hook所在的函数组件fiber;
//queue是在mountReducer时绑定的
//action是用户传的;
var lane = requestUpdateLane(fiber);//得到更新的lane
var update = {//创建update更新对象
lane: lane, //更新赛道
action: action, //用户传入的action
hasEagerState: false,
eagerState: null,
next: null
};
if (isRenderPhaseUpdate(fiber)) {
//根据fiber判断是否是加载渲染时的更新
//很明显,用户手动调用dispatch是更新,不是加载阶段,所以不会走这里
enqueueRenderPhaseUpdate(queue, update);
} else {
enqueueUpdate$1(fiber, queue, update);
//将update,加入到queue.pending环状链表中
//多次调用dispatch,创建的update都会加入到这个queue.pending环状链表中;
var eventTime = requestEventTime();//事件时间,更新时用
var root = scheduleUpdateOnFiber(fiber, lane, eventTime);
//开始更新调度
}
}
3.updateReducer
- 根据上次更新或加载后,存储的组件fiber的hook对象,创建新的hook对象;
- 拿到hook的更新对象环状链表queue.pending,循环环状链表,算出新的状态;
- 判断新老状态,如果不一样就标记更新;
- 把新的状态存储到hook对象;
- 把新的状态和dispatch返回给用户;
function updateReducer(reducer, initialArg, init) {
var hook = updateWorkInProgressHook();
//根据上次更新或加载后,存储的组件fiber的hook对象,创建新的hook对象
var queue = hook.queue;
queue.lastRenderedReducer = reducer;//更新reducer
var current = currentHook; // The last rebase update that is NOT part of the base state.
var pendingQueue = queue.pending;//拿到环状链表
// We have a queue to process.
var first = pendingQueue.next;// 拿到第一个更新对象
var newState = current.baseState;
var update = first;
do { //循环queue.pending环状链表,拿到新的状态
if (update.hasEagerState) {
// If this update is a state update (not a reducer) and was processed eagerly,
// we can use the eagerly computed state
newState = update.eagerState;
} else {
var action = update.action;
newState = reducer(newState, action);
//调用用户给的reducer和action,得到新的状态
}
update = update.next;
} while (update !== null && update !== first);
// 如果 update === first,意味着环状链表循环结束,
// 所有的update对象均已使用,可以跳出循环;
if (!objectIs(newState, hook.memoizedState)) {
//比对新老状态,如果不相等,标记更新;
markWorkInProgressReceivedUpdate();
}
hook.memoizedState = newState;//更新hook对象的状态;
queue.lastRenderedState = newState;
var dispatch = queue.dispatch;
return [hook.memoizedState, dispatch];//返回给用户
}
useState
1.mountState
- 创建hook对象,形成或加入hook单向链表;(mountWorkInProgressHook)
- initialState初始状态,存到hook对象的memoizedState和baseState里;
- 创建queue给hook.queue,用于存储当前状态,当前reducer,环状链表pending,赛道lanes,dispatch等;
- 绑定dispatch函数,更新时调用dispatch,dipatch的前两个参数绑定为,dispatch函数所在的函数组件fiber,和fiber的更新队列queue;
- 返回初始状态和绑定过的dispatch函数(return [hook.memoizedState, dispatch]);
function mountState(initialState) {
var hook = mountWorkInProgressHook();
if (typeof initialState === 'function') {
// $FlowFixMe: Flow doesn't like mixed types
initialState = initialState();
//如果initialState是函数,就执行它,返回的值就是初始状态
}
hook.memoizedState = hook.baseState = initialState;
var queue = {
pending: null,// 更新环状链表
interleaved: null,
lanes: NoLanes,// 更新赛道
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: initialState //上一次渲染的状态
};
hook.queue = queue;
var dispatch = queue.dispatch = dispatchSetState.bind(null, currentlyRenderingFiber$1, queue);
//生成dispatch函数;
return [hook.memoizedState, dispatch];
}
2.dispatch(setState)
用户调用dispatch,会触发更新
- 拿到更新的赛道lane,创建更新对象update,将update对象加入到queue.pending更新队列里;
- 拿到老状态,算出新的状态,新老状态进行比对,如果一样,就return,不会调度更新;
- 开始调度更新(scheduleUpdateOnFiber(fiber, lane, eventTime));(这块和useReducer的dispatch是一样的)
function dispatchSetState(fiber, queue, action) {
//dispatchSetState就是用户调用的setState函数,
//dispatch的参数和useReducer的dispatch是一样的
var lane = requestUpdateLane(fiber);
var update = {
lane: lane,
action: action,
hasEagerState: false,
eagerState: null,
next: null
};
enqueueUpdate$1(fiber, queue, update);
//将update对象添加到queue.pending环状链表
var currentState = queue.lastRenderedState; //拿到旧的状态
var eagerState = lastRenderedReducer(currentState, action);
//得到新的状态
update.eagerState = eagerState;
//更新新的状态
if (objectIs(eagerState, currentState)) {
//判断新老状态是否相等
return;
}
var eventTime = requestEventTime();
var root = scheduleUpdateOnFiber(fiber, lane, eventTime);
//开始调度更新
}
3.updateState
调用的就是updateReducer,基于updateReducer,传入basicStateReducer,也就是useReducer的reducer,用于计算用户的新状态;
function updateState(initialState) {
return updateReducer(basicStateReducer);
}
function basicStateReducer(state, action) {
// $FlowFixMe: Flow doesn't like mixed types
return typeof action === 'function' ? action(state) : action;
//如果用户调用的setState传入的参数是一个函数,就执行这个函数,返回新的state;
//不是函数就返回action,action为用户传入的新state;
}
4.多次调用同一个dispatch
也就是多次调用setState,由于绑定的是同一个dispatch,每次调用dispatch都会创建一个update对象,所以将update对象添加到queue.pending时,添加的是同一个环状链表;在执行updateReducer时,会循环这个环状链表,更新状态,得到最新状态
const [number, setNumber] = useReducer(reducer, 0);
const [age,setAge] = useState(0)
return (
<>
<div>{number}</div>
<div>{age}</div>
<button onClick={() => {
debugger
setNumber({type: 'add'})
setAge(()=>age+1) //update1
setAge(3) //update2
}}>add</button>
</>
)
总结
1.useState与useRender的相同点和不同点?
相同点
- 都是返回一个长度为2的数组,状态与dispatch方法;
- 在mount阶段,都创建了hook对象和queue对象,以hook所在的组件fiber和queue为前两个参数,生成了dispatch函数;
- 调用dispatch时,都要创建update对象,将update对象加入到queue.pending环状链表中;
- 在update阶段,updateState是基于updateReducer的,只是给了一个默认的reducer;
不同点
- 用法不同,useState只需传入一个状态即可;而useReducer需要reducer和状态;
- 调用dispatch时,useState会判断新老状态是否相等,再决定调度更新;useReducer直接调度更新;
- update阶段,useState的reducer是内部固定的;useReducer的reducer是用户传的;
2.对useReducer和useState和设计的个人理解
- 可以将相关联的状态放在一个state里,不相关的另写一个useState,不用像this.setState一样,所有状态放在一起;这块官方有说明zh-hans.reactjs.org/docs/hooks-…
- dispatch时,将更新对象添加到环状链表,将更新处理添加到一个微任务,这样可以多次调用dispatch,而只执行一次更新;这个处理不像老版的this.setState那样,利用一个批量更新的开关控制批量更新,将更新放到事件函数执行完以后执行,但更新执行的优先级还是要高于宏任务和微任务的,如果将this.setState放到一个宏任务或者微任务中执行,批量更新先执行完,将批量更新的开关置为false,而this.setState还没有被执行,批量更新已被破坏;而将多个dispatch放到一个宏任务或者微任务下,执行dispatch,等于重新添加了一个处理更新的微任务,在同一个宏任务下所有的同步dispatch执行完,触发更新,批量更新没有被破坏,完美解决批量更新不被异步任务破坏的问题
- 每个useState返回一个dispatch,可以利用组件通信的方式,在所有的函数组件调用修改同一个状态,灵活性很高;
- 在mountState或mountReducer时,利用闭包可以保存和读取函数内部变量的特性,返回的dispatch函数,读取和修改的都是各自独立的,对应各自的hook对象和queue对象,状态等
3.学习方法
和我在写解读useEffect和useLayoutEffect原理总结的一样,只关注当下所学的知识,抓住知识的核心点,这些学会就行,其余的咱不看,没有那么多的时间和精力