React Hooks ソースコードの詳細な分析

著者: JD 小売鄭ビンイー

序文

React HooksReact16.8 で導入された新機能で、stateクラス コンポーネントを使用せずに関数コンポーネントで他の React 機能を使用できるようにします。Hooksは、よりシンプルでわかりやすいReact開発エクスペリエンスを提供するため、非常に重要な概念です。

React Hooksコア ソース コードには、主に 2 つの部分が含まれています。React内部Hookマネージャーと一連のプリセットHook機能です

Reactまず、内部のHookマネージャーを見てみましょう。このマネージャーは、Reactコンポーネント内のすべてを管理し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オブジェクトにqueueとの 2 つの重要なプロパティがありますcurrentqueueすべてのHook状態関数と更新関数をコンポーネントに保存し、current現在レンダリングされているコンポーネントのリンク リストを保存しますHookuseState関数と関数は、それぞれ新しい状態を作成し、それをコンポーネントで使用するuseHook責任がありますHookHook

プリセットフック機能

useState フック

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、その主な機能は update 関数で配列を返すことでありstate、状態の初期値は ですinitialState

この実装では、updateWorkInProgressHook()関数を使用して、現在実行中の関数コンポーネントのファイバー オブジェクトを取得し、対応するものがあるかどうかを判断します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()関数は、現在実行中の関数コンポーネントのオブジェクトを取得するために使用されfiberworkInProgressHook現在実行中のオブジェクトを格納するために使用されますhook関数コンポーネントでは、useState呼び出しごとに新しいフック オブジェクトが作成され、fiberオブジェクトのリンク リストに追加されますhooksこのhooksリンク リストは、fiberオブジェクトのmemoizedStateプロパティを通じて維持されます。

useState Hookの実装では、各hookオブジェクトには、queue更新する状態と update 関数を格納するためのオブジェクトが含まれていることにも注意する必要があります。関数は、タスクを実行する必要があることをスケジューラに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メソッドを呼び出し、配列を返すことであり、最初の要素は状態の値であり、2 番目の要素は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 フック

useEffectリモートデータへのアクセス、イベントリスナーの追加/削除、手動操作など、コンポーネントで副作用操作を実行するためにコンポーネントReactで一般的に使用されるHook関数です。のコア機能は、コンポーネントのレンダリング プロセスが終了した後にコールバック関数を非同期で実行することであり、その実装には React の非同期レンダリング メカニズムが含まれます。DOMuseEffect

以下は、useEffect フックの実装例です。

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

  // 根据当前的渲染批次判断是否需要执行回调函数
  if (shouldFireEffect(batch, dependencies)) {
    callback();
  }

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

この例では、useEffectコールバック関数と依存関係の配列の 2 つのパラメーターを受け取ります。依存関係配列のいずれかの値が変更されると、

ReactuseEffect渡されたコールバック関数は、次のレンダリングで再実行されます。

useEffect関数の実装は、主に のReact非同期レンダリング メカニズムに依存します。コンポーネントを再レンダリングする必要がある場合、Reactすべてのstate更新操作がキューに追加され、これらの更新操作は現在のレンダリング バッチの終了後に非同期で実行されるため、同じレンダリング バッチで複数の連続した更新操作が回避されます。

useEffect関数では、useContext(BatchContext)メソッドを呼び出して現在のレンダリング バッチを取得し、shouldFireEffectメソッドに従ってコールバック関数を実行する必要があるかどうかを判断します。コールバック関数が実行された後、後続のレンダリング バッチに影響を与えないようにclearEffect、メソッドを使用して現在のeffect状態情報をクリアする必要があります。

要約する

一般に、React Hooksシステムの実装原理は複雑ではなく、主にReact内部fiberデータ構造とスケジューリングシステムに依存し、これらのメカニズムを通じてコン​​ポーネントの状態の管理と更新を実現します。Hooksこれにより、関数コンポーネントで状態やその他の機能を使用できるようになりReact、関数コンポーネントがクラス コンポーネントに匹敵するようになります。

useStateuseEffectなどの ほかhook他 に 常用 し て い る もの がReactあ るそれらの実装原理は基本的に似ており、すべてアーキテクチャを使用して、状態管理やライフサイクル フックなどの機能を実装しています。useContextHookfiber

上記はhook単純な実装例であり、実際に で使用されるコードではありませんが、コア実装をReactよりよく理解するのに役立ちます。hook

{{o.name}}
{{m.name}}

おすすめ

転載: my.oschina.net/u/4090830/blog/8527395