React Hooks 소스 코드 심층 분석

저자: JD 소매 Zheng Bingyi

머리말

React Hooks클래스 구성 요소를 사용하지 않고도 다른 React 기능을 함수 구성 요소에서 사용할 수 있도록 React16.8에 도입된 새로운 기능 입니다 . 더 간단하고 이해하기 쉬운 개발 경험을 제공하기 때문에 매우 중요한 개념입니다 .stateHooksReact

React Hooks핵심 소스 코드는 주로 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및 가 있습니다 current. queue모든 Hook상태 및 업데이트 기능을 구성 요소에 저장하고 current현재 렌더링 중인 구성 요소의 연결된 목록을 저장합니다 Hook. useStateuseHook함수는 각각 새 상태를 만들고 Hook구성 요소에서 사용하는 역할을 합니다 Hook.

프리셋 훅 기능

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있으며 주요 기능은 업데이트 기능으로 배열을 반환하는 것이며 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()fiber이 함수는 현재 실행 중인 함수 구성 요소의 개체를 가져오는 데 사용되며 workInProgressHook현재 실행 중인 개체를 저장하는 데 사용됩니다 hook. 함수 구성 요소에서 useState호출할 때마다 새 후크 개체가 만들어지고 연결된 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반환하는 이라는 함수를 호출합니다. 의 구현은 더 복잡하고 많은 세부 사항을 포함하므로 여기서는 소개하지 않겠습니다.dispatchupdateReducer

useEffect 후크

useEffect원격 데이터 액세스, 이벤트 리스너 추가/제거, 수동 작업 등과 같은 구성 요소의 부작용 작업을 수행하기 위해 구성 요소 React에서 일반적으로 사용되는 Hook기능 입니다 . 의 핵심 기능은 컴포넌트의 렌더링 프로세스가 종료된 후 콜백 함수를 비동기적으로 실행하는 것이며, 그 구현에는 React의 비동기 렌더링 메커니즘이 포함됩니다.DOMuseEffect

다음은 useEffect Hook 구현의 예입니다.

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

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

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

이 예에서는 useEffect콜백 함수와 종속성 배열이라는 두 개의 매개변수가 수신됩니다. 종속성 배열의 값이 변경되면

ReactuseEffect전달된 콜백 함수는 다음 렌더링에서 다시 실행됩니다 .

useEffect함수의 구현은 주로 의 React비동기 렌더링 메커니즘에 따라 달라집니다. 구성 요소를 다시 렌더링해야 하는 경우 React모든 state업데이트 작업이 대기열에 추가되고 이러한 업데이트 작업은 현재 렌더링 일괄 처리가 끝난 후 비동기적으로 실행되므로 동일한 렌더링 일괄 처리에서 여러 개의 연속적인 업데이트 작업을 방지할 수 있습니다.

useEffect함수 에서 useContext(BatchContext)메서드를 호출하여 현재 렌더링 배치를 가져오고 shouldFireEffect메서드에 따라 콜백 함수를 실행해야 하는지 여부를 판단합니다. 콜백 함수가 실행된 후 후속 렌더링 배치에 영향을 주지 않도록 clearEffect현재 상태 정보를 지우는 메서드를 사용해야 합니다 .effect

요약하다

일반적으로 React Hooks시스템의 구현 원리는 복잡하지 않으며 주로 React내부 fiber데이터 구조 및 스케줄링 시스템에 의존하며 이러한 메커니즘을 통해 구성 요소 상태의 관리 및 업데이트를 실현합니다. Hooks이를 통해 함수 구성 요소에서 상태 및 기타 기능을 사용할 수 있으므로 React함수 구성 요소를 클래스 구성 요소와 비교할 수 있습니다.

useState이외에도 일반적으로 사용 useEffect되는 것들이 hook있습니다 . 구현 원칙은 기본적으로 유사하며 모두 아키텍처를 사용하여 상태 관리 및 수명 주기 후크와 같은 기능을 구현합니다.ReactuseContextHookfiber

위의 내용은 간단한 구현 예이며 에서 사용된 실제 코드는 hook아니지만 핵심 구현을 더 잘 이해하는 데 도움이 될 수 있습니다.Reacthook

{{o.이름}}
{{이름}}

추천

출처my.oschina.net/u/4090830/blog/8527395