关于源码:React-Hooks源码深度解析

42次阅读

共计 4322 个字符,预计需要花费 11 分钟才能阅读完成。

作者:京东批发 郑炳懿

前言

React HooksReact16.8 引入的一个新个性,它容许函数组件中应用state 和其余 React 个性,而不用应用类组件。Hooks是一个十分重要的概念,因为它们提供了更简略、更易于了解的 React 开发体验。

React Hooks的外围源码次要包含两个局部:React外部的 Hook 管理器和一系列预置的 Hook 函数

首先,让咱们看一下 React 外部的 Hook 管理器。这个管理器是 React 外部的一个重要机制,它负责管理组件中的所有Hook,并确保它们在组件渲染期间以正确的顺序调用。

外部 Hook 管理器

示例:

const Hook = {queue: [],
  current: null,
};

function useState(initialState) {const state = Hook.current[Hook.queue.length];
  if (!state) {
    Hook.queue.push({state: typeof initialState === 'function' ? initialState() : initialState,
      setState(value) {
        this.state = value;
        render();},
    });
  }
  return [state.state, state.setState.bind(state)];
}

function useHook(callback) {
  Hook.current = {__proto__: Hook.current,};
  try {callback();
  } finally {Hook.current = Hook.current.__proto__;}
}

function render() {useHook(() => {const [count, setCount] = useState(0);
    console.log('count:', count);
    setTimeout(() => {setCount(count + 1);
    }, 1000);
  });
}

render();

在这个示例中,Hook对象有两个重要属性:queuecurrentqueue 存储组件中所有 Hook 的状态和更新函数,current存储以后正在渲染的组件的 Hook 链表。useStateuseHook 函数则别离负责创立新的 Hook 状态和在组件中应用Hook

预置 Hook 函数

useState Hook

以下是 useState Hook 的实现示例:

function useState(initialState) {const hook = updateWorkInProgressHook();
  if (!hook.memoizedState) {
    hook.memoizedState = [typeof initialState === 'function' ? initialState() : initialState,
      action => {
        hook.queue.pending = true;
        hook.queue.dispatch = action;
        scheduleWork();},
    ];
  }
  return hook.memoizedState;
}

上述代码实现了 useState Hook,其次要作用是返回一个state 和更新函数的数组,state 初始值为initialState

在这个实现中,updateWorkInProgressHook()函数用来获取以后正在执行的函数组件的 fiber 对象并判断是否存在对应的hook。它的实现如下:

function updateWorkInProgressHook() {const fiber = getWorkInProgressFiber();
  let hook = fiber.memoizedState;
  if (hook) {
    fiber.memoizedState = hook.next;
    hook.next = null;
  } else {
    hook = {
      memoizedState: null,
      queue: {
        pending: null,
        dispatch: null,
        last: null,
      },
      next: null,
    };
  }
  workInProgressHook = hook;
  return hook;
}

getWorkInProgressFiber()函数用来获取以后正在执行的函数组件的 fiber 对象,workInProgressHook则用来存储以后正在执行的 hook 对象。在函数组件中,每一个 useState 调用都会创立一个新的 hook 对象,并将其增加到 fiber 对象的 hooks 链表中。这个 hooks 链表是通过 fiber 对象的 memoizedState 属性来保护的。

咱们还须要留神到在 useState Hook 的实现中,每一个 hook 对象都蕴含了一个 queue 对象,用来存储待更新的状态以及更新函数。scheduleWork()函数则用来告诉 React 调度器有工作须要执行。

React 的源码中,useState函数实际上是一个叫做 useStateImpl 的外部函数。

上面是 useStateImpl 的源码:

function useStateImpl<S>(initialState: (() => S) | S): [S, Dispatch<SetStateAction<S>>] {const dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}

能够看到,useStateImpl函数的作用就是获取以后的 dispatcher 并调用它的 useState 办法,返回一个数组,第一个元素是状态的值,第二个元素是一个 dispatch 函数,用来更新状态。这里的 resolveDispatcher 函数用来获取以后的dispatcher,其实现如下:

function resolveDispatcher(): Dispatcher {
  const dispatcher = currentlyRenderingFiber?.dispatcher;
  if (dispatcher === undefined) {throw new Error('Hooks can only be called inside the body of a function component. (https://fb.me/react-invalid-hook-call)');
  }
  return dispatcher;
}

resolveDispatcher函数首先尝试获取以后正在渲染的 fiber 对象的 dispatcher 属性,如果获取不到则说

明以后不在组件的渲染过程中,就会抛出一个谬误。

最初,咱们来看一下 useState 办法在具体的 dispatcher 实现中是如何实现的。咱们以 useReducer

dispatcher为例,它的实现如下:

export function useReducer<S, A>(reducer: (prevState: S, action: A) => S,
  initialState: S,
  initialAction?: A,
): [S, Dispatch<A>] {const [dispatch, currentState] = updateReducer<S, A>(
    reducer,
    // $FlowFixMe: Flow doesn't like mixed types
    [initialState, initialAction],
    // $FlowFixMe: Flow doesn't like mixed types
    reducer === basicStateReducer ? basicStateReducer : updateStateReducer,
  );
  return [currentState, dispatch];
}

能够看到,useReducer办法实际上是调用了一个叫做 updateReducer 的函数,返回了一个蕴含以后状态和 dispatch 函数的数组。updateReducer的实现比较复杂,波及到了很多细节,这里不再开展介绍。

useEffect Hook

useEffectReact 中罕用的一个 Hook 函数,用于在组件中执行副作用操作,例如拜访近程数据、增加 / 移除事件监听器、手动操作 DOM 等等。useEffect的外围性能是在组件的渲染过程完结之后异步执行回调函数,它的实现形式波及到 React 中的异步渲染机制。

以下是 useEffect Hook 的实现示例:

function useEffect(callback, dependencies) {
  // 通过调用 useLayoutEffect 或者 useEffect 办法来获取以后的渲染批次
  const batch = useContext(BatchContext);

  // 依据以后的渲染批次判断是否须要执行回调函数
  if (shouldFireEffect(batch, dependencies)) {callback();
  }

  // 在组件被卸载时革除以后 effect 的状态信息
  return () => clearEffect(batch);
}

在这个示例中,useEffect接管两个参数:回调函数和依赖项数组。当依赖项数组中的任何一个值发生变化时,

React会在下一次渲染时从新执行 useEffect 中传入的回调函数。

useEffect函数的实现形式次要依赖于 React 中的异步渲染机制。当一个组件须要从新渲染时,React会将所有的 state 更新操作退出到一个队列中,在以后渲染批次完结之后再异步执行这些更新操作,从而防止在同一个渲染批次中间断执行屡次更新操作。

useEffect 函数中,咱们通过调用 useContext(BatchContext) 办法来获取以后的渲染批次,并依据 shouldFireEffect 办法判断是否须要执行回调函数。在回调函数执行结束后,咱们须要通过 clearEffect 办法来革除以后 effect 的状态信息,防止对后续的渲染批次产生影响。

总结

总的来说,React Hooks的实现原理并不简单,它次要依赖于 React 外部的 fiber 数据结构和调度零碎,通过这些机制来实现对组件状态的治理和更新。Hooks可能让咱们在函数组件中应用状态和其余 React 个性,使得函数组件的性能能够和类组件媲美。

除了 useStateuseEffecthookReact还有 useContext 等罕用的 Hook。它们的实现原理也根本类似,都是利用fiber 架构来实现状态治理和生命周期钩子等性能。

以上是 hook 简略实现示例,它们并不是 React 中理论应用的代码,然而能够帮忙咱们更好地了解 hook 的外围实现形式。

正文完
 0