建设网站 软件,南山免费做网站公司排名,网上电影网站怎么做的,更换dns能上国外网站吗前言
简单说下为什么React选择函数式组件#xff0c;主要是class组件比较冗余、生命周期函数写法不友好#xff0c;骚写法多#xff0c;functional组件更符合React编程思想等等等。更具体的可以拜读dan大神的blog。其中Function components capture the rendered values这句…前言
简单说下为什么React选择函数式组件主要是class组件比较冗余、生命周期函数写法不友好骚写法多functional组件更符合React编程思想等等等。更具体的可以拜读dan大神的blog。其中Function components capture the rendered values这句十分精辟的道出函数式组件的优势。
但是在16.8之前react的函数式组件十分羸弱基本只能作用于纯展示组件主要因为缺少state和生命周期。本人曾经在hooks出来前负责过纯函数式的react项目所有状态处理都必须在reducer中进行所有副作用都在saga中执行可以说是十分艰辛的经历了。在hooks出来后我在公司的一个小中台项目中使用落地效果不错代码量显著减少的同时提升了代码的可读性。因为通过custom hooks可以更好地剥离代码结构不会像以前类组件那样在cDU等生命周期堆了一大堆逻辑在命令式代码和声明式代码中有一个良性的边界。
useState在React中是怎么实现的 Hooks take some getting used to — and especially at the boundary of imperative and declarative code. 如果对hooks不太了解的可以先看看这篇文章:前情提要十分简明的介绍了hooks的核心原理但是我对useEffectuseRef等钩子的实现比较好奇所以开始啃起了源码下面我会结合源码介绍useState的原理。useState具体逻辑分成三部分mountStatedispatch updateState
hook的结构
首先的是hooks的结构hooks是挂载在组件Fiber结点上memoizedState的
//hook的结构
export type Hook {memoizedState: any, //上一次的statebaseState: any, //当前statebaseUpdate: Updateany, any | null, // update funcqueue: UpdateQueueany, any | null, //用于缓存多次actionnext: Hook | null, //链表
};renderWithHooks
在reconciler中处理函数式组件的函数是renderWithHooks其类型是
renderWithHooks(current: Fiber | null, //当前的fiber结点workInProgress: Fiber, Component: any, //jsx中用调用的函数props: any,refOrContext: any,nextRenderExpirationTime: ExpirationTime, //需要在什么时候结束
): any在renderWithHooks核心流程如下
//从memoizedState中取出hooks
nextCurrentHook current ! null ? current.memoizedState : null;
//判断通过有没有hooks判断是mount还是update两者的函数不同
ReactCurrentDispatcher.current nextCurrentHook null? HooksDispatcherOnMount: HooksDispatcherOnUpdate;
//执行传入的type函数
let children Component(props, refOrContext);
//执行完函数后的dispatcher变成只能调用context的
ReactCurrentDispatcher.current ContextOnlyDispatcher;return children;useState构建时流程
mountState
在HooksDispatcherOnMount中useState调用的是下面的mountState作用是创建一个新的hook并使用默认值初始化并绑定其触发器因为useState底层是useReducer所以数组第二个值返回的是dispatch。
type BasicStateActionS (S S) | S;function mountStateS(initialState: (() S) | S,
){const hook mountWorkInProgressHook();
//如果入参是func则会调用但是不提供参数带参数的需要包一层if (typeof initialState function) {initialState initialState();}
//上一个state和基本(当前)state都初始化hook.memoizedState hook.baseState initialState;const queue (hook.queue {last: null,dispatch: null,eagerReducer: basicStateReducer, // useState使用基础reducereagerState: (initialState: any),});
//返回触发器const dispatch: Dispatch//useState底层是useReducer所以type是BasicStateAction(queue.dispatch (dispatchAction.bind(null,//绑定当前fiber结点和queue((currentlyRenderingFiber: any): Fiber),queue,): any));return [hook.memoizedState, dispatch];
}mountWorkInProgressHook
这个函数是mountState时调用的构建hook的方法在初始化完毕后会连接到当前hook.next如果有的话
function mountWorkInProgressHook(): Hook {const hook: Hook {memoizedState: null,baseState: null,queue: null,baseUpdate: null,next: null,};if (workInProgressHook null) {// 列表中的第一个hookfirstWorkInProgressHook workInProgressHook hook;} else {// 添加到列表的末尾workInProgressHook workInProgressHook.next hook;}return workInProgressHook;
}dispatch分发函数
在上面我们提到useState底层是useReducer所以返回的第二个参数是dispatch函数其中的设计十分巧妙。
假设我们有以下代码:
相关参考视频讲解进入学习
const [data, setData] React.useState(0)
setData(first)
setData(second)
setData(third)在第一次setData后 hooks的结构如上图 在第二次setData后 hooks的结构如上图 在第三次setData后 hooks的结构如上图 在正常情况下是不会在dispatcher中触发reducer而是将action存入update中在updateState中再执行但是如果在react没有重渲染需求的前提下是会提前计算state即eagerState。作为性能优化的一环。
function dispatchActionS, A(fiber: Fiber,queue: UpdateQueueS, A,action: A,
) {const alternate fiber.alternate;{flushPassiveEffects();//获取当前时间并计算可用时间const currentTime requestCurrentTime();const expirationTime computeExpirationForFiber(currentTime, fiber);const update: UpdateS, A {expirationTime,action,eagerReducer: null,eagerState: null,next: null,};//下面的代码就是为了构建queue.last是最新的更新然后last.next开始是每一次的action// 取出lastconst last queue.last;if (last null) {// 自圆update.next update;} else {const first last.next;if (first ! null) {update.next first;}last.next update;}queue.last update;if (fiber.expirationTime NoWork (alternate null || alternate.expirationTime NoWork)) {// 当前队列为空我们可以在进入render阶段前提前计算出下一个状态。如果新的状态和当前状态相同则可以退出重渲染const lastRenderedReducer queue.lastRenderedReducer; // 上次更新完后的reducerif (lastRenderedReducer ! null) {let prevDispatcher;if (__DEV__) {prevDispatcher ReactCurrentDispatcher.current; // 暂存dispatcherReactCurrentDispatcher.current InvalidNestedHooksDispatcherOnUpdateInDEV;}try {const currentState: S (queue.lastRenderedState: any);// 计算下次stateconst eagerState lastRenderedReducer(currentState, action);// 在update对象中存储预计算的完整状态和reducer如果在进入render阶段前reducer没有变化那么可以服用eagerState而不用重新再次调用reducerupdate.eagerReducer lastRenderedReducer;update.eagerState eagerState;if (is(eagerState, currentState)) {// 在后续的时间中如果这个组件因别的原因被重渲染且在那时reducer更变后仍有可能重建这次更新return;}} catch (error) {// Suppress the error. It will throw again in the render phase.} finally {if (__DEV__) {ReactCurrentDispatcher.current prevDispatcher;}}}}scheduleWork(fiber, expirationTime);}
}useState更新时流程
updateReducer
因为useState底层是useReducer所以在更新时的流程(即重渲染组件后)是调用updateReducer的。
function updateStateS(initialState: (() S) | S,
): [S, DispatchBasicStateActionS] {return updateReducer(basicStateReducer, (initialState: any));
}所以其reducer十分简单
function basicStateReducerS(state: S, action: BasicStateActionS): S {return typeof action function ? action(state) : action;
}我们先把复杂情况抛开跑通updateReducer流程
function updateReducer(reducer: (S, A) S,initialArg: I,init?: I S,
){// 获取当前hook,queueconst hook updateWorkInProgressHook();const queue hook.queue;queue.lastRenderedReducer reducer;// action队列的最后一个更新const last queue.last;// 最后一个更新是基本状态const baseUpdate hook.baseUpdate;const baseState hook.baseState;// 找到第一个没处理的更新let first;if (baseUpdate ! null) {if (last ! null) {// 第一次更新时队列是一个自圆queue.last.next queue.first。当第一次update提交后baseUpdate不再为空即可跳出队列last.next null;}first baseUpdate.next;} else {first last ! null ? last.next : null;}if (first ! null) {let newState baseState;let newBaseState null;let newBaseUpdate null;let prevUpdate baseUpdate;let update first;let didSkip false;do {const updateExpirationTime update.expirationTime;if (updateExpirationTime renderExpirationTime) {// 优先级不足跳过这次更新如果这是第一次跳过更新上一个update/state是newBaseupdate/stateif (!didSkip) {didSkip true;newBaseUpdate prevUpdate;newBaseState newState;}// 更新优先级if (updateExpirationTime remainingExpirationTime) {remainingExpirationTime updateExpirationTime;}} else {// 处理更新if (update.eagerReducer reducer) {// 如果更新被提前处理了且reducer跟当前reducer匹配可以复用eagerStatenewState ((update.eagerState: any): S);} else {// 循环调用reducerconst action update.action;newState reducer(newState, action);}}prevUpdate update;update update.next;} while (update ! null update ! first);if (!didSkip) {newBaseUpdate prevUpdate;newBaseState newState;}// 只有在前后state变了才会标记if (!is(newState, hook.memoizedState)) {markWorkInProgressReceivedUpdate();}hook.memoizedState newState;hook.baseUpdate newBaseUpdate;hook.baseState newBaseState;queue.lastRenderedState newState;}const dispatch: DispatchA (queue.dispatch: any);return [hook.memoizedState, dispatch];
}export function markWorkInProgressReceivedUpdate() {didReceiveUpdate true;
}后记
作为系列的第一篇文章我选择了最常用的hooks开始抛开提前计算及与react-reconciler的互动整个流程是十分清晰易懂的。mount的时候构建钩子触发dispatch时按序插入update。updateState的时候再按序触发reducer。可以说就是一个简单的redux。