前言

React Hooks是React16新出的基于函数式组件的一组新的api,其不同于之前class组件的内层嵌套形式,利用hooks进行钩子形式的对数据进行了组件间的流向组织,sdwan我的项目中都是基于函数式组件的封装,本文为sdwan我的项目中的react hooks的利用实际

目录

  • 增加正告规定弹窗组件实际
  • React Hooks源码解读
  • React Fiber数据结构剖析

摸索案例

增加正告规定弹窗组件实际

[组件目录]

  • components

    • addRule.jsx
    • RuleList.jsx
  • index.jsx
  • index.less

[目录形容] addRule是点击弹窗后弹出的主体组件

[源码剖析] addRule是增加规定的弹窗,其中在告警规定一栏中,须要对列表中的行进行加减操作,这里最先想到的就是利用useState进行数据的治理,但其实useState是useReducer的语法糖,后续源码中会剖析,咱们看到应用了useState后能够将所有状态抽离到顶部,后续但凡须要应用trNum或setTrNum的便能够间接应用,这样就省去了在setState中的设置以及对相应this的绑定问题,使得数据的操作更加纯正而且清晰

const AddRule = (props) => {  const { children, title } = props;  ......  const [trNum, setTrNum] = useState(1);  const trLoop = (n) => {    let arr = [];    for(let i=0; i< n; i++) {      arr.push(        <tr>          <td>            <Select              placeholder='请抉择'              defaultValue='0'              style={{width:'120px'}}            >                {options.params.map((d) => (                    <Select.Option value={d.status} key={d.status}>                    {d.text}                    </Select.Option>                ))}            </Select>          </td>          <td>            <Select              placeholder='请抉择'              defaultValue='0'            >                {options.compare.map((d) => (                    <Select.Option value={d.status} key={d.status}>                    {d.text}                    </Select.Option>                ))}            </Select>          </td>          <td>            <Select              placeholder='请抉择'              defaultValue={currentType}              onChange={val => setTypeValue(val)}            >                {options.type.map((d) => (                    <Select.Option value={d.status} key={d.status}>                    {d.text}                    </Select.Option>                ))}            </Select>          </td>          <td>            { typeValue == options.type[1].status             ?               <span style={{display: 'inline-flex', verticalAlign: 'middle', lineHeight: '32px', width: '120px'}}>                <Input placeholder=""/>dBm              </span>            :               <Select                placeholder='请抉择'                defaultValue='0'                style={{width:'120px'}}              >                  {options.params.map((d) => (                      <Select.Option value={d.status} key={d.status}>                      {d.text}                      </Select.Option>                  ))}              </Select>            }          </td>          <td>            <PlusOutlined style={{color: '#1890ff'}} onClick={()=>setTrNum(trNum + 1)}/>          </td>          <td>            <CloseOutlined style={{color: '#ff4d4f'}} onClick={()=> trNum>1 && setTrNum(trNum - 1)}/>          </td>        </tr>      )    };    return arr;  };  ......  return (    <>      <span onClick={showModelHandler}>{children}</span>      <Modal        title={title}        visible={visible}        onCancel={hideModelHandler}        onOk={handleOk}        maskClosable={false}        destroyOnClose      >        <Form form={form} layout="vertical">          ......          <Form.Item name="告警规定" label="告警规定">              <div style={{width: '100%', backgroundColor: '#ececec', padding: '10px'}}>                <span>                  合乎以下&nbsp;<Select                    placeholder='请抉择'                    defaultValue='0'                    style={{width: '120px'}}                  >                      {options.rule.map((d) => (                          <Select.Option value={d.status} key={d.status}>                          {d.text}                          </Select.Option>                      ))}                  </Select>&nbsp;条件:                </span>                <div                   style={{                    border: '1px solid #ccc',                    width: '100%',                     background: '#fff',                     marginTop: '10px',                     padding: '4px'                  }}                >                  <table >                    <tbody >                      { trLoop(trNum) }                    </tbody>                  </table>                </div>              </div>          </Form.Item>          ......        </Form>      </Modal>    </>  );};

React Hooks源码解读

[组件目录]

  • packages

    • react

      • src

        • ReactHooks.js

这里仅仅是做了一个名称的导出包含:

  • useContext
  • useState
  • useReducer
  • useRef
  • useEffect
  • useLayoutEffect
  • useCallback
  • useMemo
  • useImperativeHandles
  • useDebugValue
  • useTransition
  • useDeferredValue
  • useOpaqueIdentifier
  • useMutableSource

这里真正的源码是放在了packages/react-reconciler/src/ReactFiberHooks.js里,能够看出其利用的依然是React的外围数据结构Fiber的调度作用

export function renderWithHooks<Props, SecondArg>(  current: Fiber | null,  workInProgress: Fiber,  Component: (p: Props, arg: SecondArg) => any,  props: Props,  secondArg: SecondArg,  nextRenderLanes: Lanes,): any {  renderLanes = nextRenderLanes;  currentlyRenderingFiber = workInProgress;  if (__DEV__) {    hookTypesDev =      current !== null        ? ((current._debugHookTypes: any): Array<HookType>)        : null;    hookTypesUpdateIndexDev = -1;    // Used for hot reloading:    ignorePreviousDependencies =      current !== null && current.type !== workInProgress.type;  }  workInProgress.memoizedState = null;  workInProgress.updateQueue = null;  workInProgress.lanes = NoLanes;  // The following should have already been reset  // currentHook = null;  // workInProgressHook = null;  // didScheduleRenderPhaseUpdate = false;  // TODO Warn if no hooks are used at all during mount, then some are used during update.  // Currently we will identify the update render as a mount because memoizedState === null.  // This is tricky because it's valid for certain types of components (e.g. React.lazy)  // Using memoizedState to differentiate between mount/update only works if at least one stateful hook is used.  // Non-stateful hooks (e.g. context) don't get added to memoizedState,  // so memoizedState would be null during updates and mounts.  if (__DEV__) {    if (current !== null && current.memoizedState !== null) {      ReactCurrentDispatcher.current = HooksDispatcherOnUpdateInDEV;    } else if (hookTypesDev !== null) {      // This dispatcher handles an edge case where a component is updating,      // but no stateful hooks have been used.      // We want to match the production code behavior (which will use HooksDispatcherOnMount),      // but with the extra DEV validation to ensure hooks ordering hasn't changed.      // This dispatcher does that.      ReactCurrentDispatcher.current = HooksDispatcherOnMountWithHookTypesInDEV;    } else {      ReactCurrentDispatcher.current = HooksDispatcherOnMountInDEV;    }  } else {    ReactCurrentDispatcher.current =      current === null || current.memoizedState === null        ? HooksDispatcherOnMount        : HooksDispatcherOnUpdate;  }  let children = Component(props, secondArg);  // Check if there was a render phase update  if (didScheduleRenderPhaseUpdateDuringThisPass) {    // Keep rendering in a loop for as long as render phase updates continue to    // be scheduled. Use a counter to prevent infinite loops.    let numberOfReRenders: number = 0;    do {      didScheduleRenderPhaseUpdateDuringThisPass = false;      invariant(        numberOfReRenders < RE_RENDER_LIMIT,        'Too many re-renders. React limits the number of renders to prevent ' +          'an infinite loop.',      );      numberOfReRenders += 1;      if (__DEV__) {        // Even when hot reloading, allow dependencies to stabilize        // after first render to prevent infinite render phase updates.        ignorePreviousDependencies = false;      }      // Start over from the beginning of the list      currentHook = null;      workInProgressHook = null;      workInProgress.updateQueue = null;      if (__DEV__) {        // Also validate hook order for cascading updates.        hookTypesUpdateIndexDev = -1;      }      ReactCurrentDispatcher.current = __DEV__        ? HooksDispatcherOnRerenderInDEV        : HooksDispatcherOnRerender;      children = Component(props, secondArg);    } while (didScheduleRenderPhaseUpdateDuringThisPass);  }  // We can assume the previous dispatcher is always this one, since we set it  // at the beginning of the render phase and there's no re-entrancy.  ReactCurrentDispatcher.current = ContextOnlyDispatcher;  if (__DEV__) {    workInProgress._debugHookTypes = hookTypesDev;  }  // This check uses currentHook so that it works the same in DEV and prod bundles.  // hookTypesDev could catch more cases (e.g. context) but only in DEV bundles.  const didRenderTooFewHooks =    currentHook !== null && currentHook.next !== null;  renderLanes = NoLanes;  currentlyRenderingFiber = (null: any);  currentHook = null;  workInProgressHook = null;  if (__DEV__) {    currentHookNameInDev = null;    hookTypesDev = null;    hookTypesUpdateIndexDev = -1;  }  didScheduleRenderPhaseUpdate = false;  invariant(    !didRenderTooFewHooks,    'Rendered fewer hooks than expected. This may be caused by an accidental ' +      'early return statement.',  );  return children;}

从中抽离出外围的hooks渲染,其余的具体的use办法能够在其上进行扩大,能够看出其实质是是基于Fiber的workInProgress的全局变量的更改与调度,其中蕴含记录以后hook状态的memoizedState以及须要更新的队列updateQueue,hooks的队列通过memoizedState及next形成了一个链表,整个hook的外围是基于Dispatcher的切换hook的调用,这里就波及到Fiber的整个数据结构,在下一节中进行形容

React Fiber数据结构剖析

[组件目录]

  • packages

    • react-reconciler

      • src

        • ReactFiber.js

简略来说React的Fiber数据结构是保护了一个如下的数据格式:

Fiber = {    // 标识 fiber 类型的标签,详情参看下述 WorkTag    tag: WorkTag,    // 指向父节点    return: Fiber | null,    // 指向子节点    child: Fiber | null,    // 指向兄弟节点    sibling: Fiber | null,    // 在开始执行时设置 props 值    pendingProps: any,    // 在完结时设置的 props 值    memoizedProps: any,    // 以后 state    memoizedState: any,    // Effect 类型,详情查看以下 effectTag    effectTag: SideEffectTag,    // effect 节点指针,指向下一个 effect    nextEffect: Fiber | null,    // effect list 是单向链表,第一个 effect    firstEffect: Fiber | null,    // effect list 是单向链表,最初一个 effect    lastEffect: Fiber | null,    // work 的过期工夫,可用于标识一个 work 优先级程序    expirationTime: ExpirationTime,};

该数据结构是一个通过链表实现的树的构造,整个React的阶段可分为Render Phase、Pre-Commit Phase以及Commit Phase,Fiber的设计初衷是利用浏览器渲染过程中残余的工夫碎片来进行render,而要达到这个目标须要可能对渲染过程的工作进行暂停、终止以及复用,Fiber便是利用数据结构实现了这样一个虚构堆栈帧。

这里不再对协调(Reconciliation)和调度(Scheduling)的具体过程,如expirationTime的权重设计、Effect lists的DFS算法设计等进行讲述,有趣味的同学能够参看这篇文章(React Fiber 源码解析)

基于React Hooks波及到的workInProgress,咱们重点看一下这里的设计

// This is used to create an alternate fiber to do work on.export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {  let workInProgress = current.alternate;  if (workInProgress === null) {    // We use a double buffering pooling technique because we know that we'll    // only ever need at most two versions of a tree. We pool the "other" unused    // node that we're free to reuse. This is lazily created to avoid allocating    // extra objects for things that are never updated. It also allow us to    // reclaim the extra memory if needed.    workInProgress = createFiber(      current.tag,      pendingProps,      current.key,      current.mode,    );    workInProgress.elementType = current.elementType;    workInProgress.type = current.type;    workInProgress.stateNode = current.stateNode;    if (__DEV__) {      // DEV-only fields      workInProgress._debugID = current._debugID;      workInProgress._debugSource = current._debugSource;      workInProgress._debugOwner = current._debugOwner;      workInProgress._debugHookTypes = current._debugHookTypes;    }    workInProgress.alternate = current;    current.alternate = workInProgress;  } else {    workInProgress.pendingProps = pendingProps;    // Needed because Blocks store data on type.    workInProgress.type = current.type;    // We already have an alternate.    workInProgress.subtreeTag = NoSubtreeEffect;    workInProgress.deletions = null;    // The effect list is no longer valid.    workInProgress.nextEffect = null;    workInProgress.firstEffect = null;    workInProgress.lastEffect = null;    if (enableProfilerTimer) {      // We intentionally reset, rather than copy, actualDuration & actualStartTime.      // This prevents time from endlessly accumulating in new commits.      // This has the downside of resetting values for different priority renders,      // But works for yielding (the common case) and should support resuming.      workInProgress.actualDuration = 0;      workInProgress.actualStartTime = -1;    }  }  // Reset all effects except static ones.  // Static effects are not specific to a render.  workInProgress.effectTag = current.effectTag & StaticMask;  workInProgress.childLanes = current.childLanes;  workInProgress.lanes = current.lanes;  workInProgress.child = current.child;  workInProgress.memoizedProps = current.memoizedProps;  workInProgress.memoizedState = current.memoizedState;  workInProgress.updateQueue = current.updateQueue;  // Clone the dependencies object. This is mutated during the render phase, so  // it cannot be shared with the current fiber.  const currentDependencies = current.dependencies;  workInProgress.dependencies =    currentDependencies === null      ? null      : {          lanes: currentDependencies.lanes,          firstContext: currentDependencies.firstContext,        };  // These will be overridden during the parent's reconciliation  workInProgress.sibling = current.sibling;  workInProgress.index = current.index;  workInProgress.ref = current.ref;  if (enableProfilerTimer) {    workInProgress.selfBaseDuration = current.selfBaseDuration;    workInProgress.treeBaseDuration = current.treeBaseDuration;  }  if (__DEV__) {    workInProgress._debugNeedsRemount = current._debugNeedsRemount;    switch (workInProgress.tag) {      case IndeterminateComponent:      case FunctionComponent:      case SimpleMemoComponent:        workInProgress.type = resolveFunctionForHotReloading(current.type);        break;      case ClassComponent:        workInProgress.type = resolveClassForHotReloading(current.type);        break;      case ForwardRef:        workInProgress.type = resolveForwardRefForHotReloading(current.type);        break;      default:        break;    }  }  return workInProgress;}

这里波及到的workInProgress和current两个树通过alternate这个指针的相互指引操作来实现首次渲染和非首次渲染的比照更新,保障两个队列都更新而不会失落,并且确保更新始终是workInProgress的一部分,这里还做了一个内存缓冲,奇次更新和偶次更新的循环复用

总结

通过学习React16对于Fiber源码及React Hooks的源码,咱们发现整个React16的底层外围是基于Fiber的优化与扩大,包含dom-diff的扩大等,相较于Vue3对于Vue2的更新,能够看出React的优化迭代思路更加充斥对计算机原理底层的思考与发现,当然这两个框架从出发点设计上也是有所不同,Vue是基于组件级的优化,因此并不需要这样一个Fiber的数据结构去构建,但从真正的设计来看Fiber的架构设计思维形式的确更加合乎国外程序员的办法与韵味。(ps: 想要理解Andrew Clark介绍Fiber的同学,能够参看这篇文章react-fiber-architecure)

参考

  • Hook 简介
  • 【第2044期】React Hooks 设计思维
  • 【第2037期】React Hooks 实际指南
  • ReactHooks源码解析之useEffect
  • react hooks 源码剖析 --- useState
  • 分析React Hooks底层源码
  • React Hook 的体系设计之一 - 分层
  • React Hooks 的体系设计之二 - 状态粒度
  • React Hooks 的体系设计之三 - 什么是 ref
  • React Hooks 源码解析(译)
  • [[译]深刻React fiber架构及源码](https://zhuanlan.zhihu.com/p/...
  • React Fiber 源码解析