개발

React useState에 대해 궁금해졌다 1

배우겠습니다 2023. 12. 15. 23:58

궁금해서 node_modules의 react 폴더를 직접 들어가봤다.

모듈을 볼땐 index.js를 먼저 보는 편이다.

// index.js
'use strict';

if (process.env.NODE_ENV === 'production') {
  module.exports = require('./cjs/react.production.min.js');
} else {
  module.exports = require('./cjs/react.development.js');
}

 

dev모드랑 production모드가 무엇인가 차이가 있나보다.

일단 react.development.js부터 보자.

useState가 언급돼있을까?

있다!

  function useState(initialState) {
    var dispatcher = resolveDispatcher();
    return dispatcher.useState(initialState);
  }
  // 생략
   exports.useState = useState;

useState를 파일 내에서 검색해봤더니 해당 부분에서 언급돼있다.

수천줄의 코드가 있는 코드라 흐름은 알기 어렵다.

우선 resolveDispathcer()이 무엇인지 봐야겠다.

// resolveDispatcher()가 사용된 곳

더보기
function useState(initialState) {
    var dispatcher = resolveDispatcher();
    return dispatcher.useState(initialState);
  }
  function useReducer(reducer, initialArg, init) {
    var dispatcher = resolveDispatcher();
    return dispatcher.useReducer(reducer, initialArg, init);
  }
  function useRef(initialValue) {
    var dispatcher = resolveDispatcher();
    return dispatcher.useRef(initialValue);
  }
  function useEffect(create, deps) {
    var dispatcher = resolveDispatcher();
    return dispatcher.useEffect(create, deps);
  }
  function useInsertionEffect(create, deps) {
    var dispatcher = resolveDispatcher();
    return dispatcher.useInsertionEffect(create, deps);
  }
  function useLayoutEffect(create, deps) {
    var dispatcher = resolveDispatcher();
    return dispatcher.useLayoutEffect(create, deps);
  }
  function useCallback(callback, deps) {
    var dispatcher = resolveDispatcher();
    return dispatcher.useCallback(callback, deps);
  }
  function useMemo(create, deps) {
    var dispatcher = resolveDispatcher();
    return dispatcher.useMemo(create, deps);
  }
  function useImperativeHandle(ref, create, deps) {
    var dispatcher = resolveDispatcher();
    return dispatcher.useImperativeHandle(ref, create, deps);
  }
  function useDebugValue(value, formatterFn) {
    {
      var dispatcher = resolveDispatcher();
      return dispatcher.useDebugValue(value, formatterFn);
    }
  }
  function useTransition() {
    var dispatcher = resolveDispatcher();
    return dispatcher.useTransition();
  }
  function useDeferredValue(value) {
    var dispatcher = resolveDispatcher();
    return dispatcher.useDeferredValue(value);
  }

useState뿐만 아니라 우리가 쓰는 hook에 들어가있는 무엇인가이다.

function resolveDispatcher() {
    var dispatcher = ReactCurrentDispatcher.current;

    {
      if (dispatcher === null) {
        error('Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' + ' one of the following reasons:\n' + '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' + '2. You might be breaking the Rules of Hooks\n' + '3. You might have more than one copy of React in the same app\n' + 'See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.');
      }
    } // Will result in a null access error if accessed outside render phase. We
    // intentionally don't throw our own error because this is in a hot path.
    // Also helps ensure this is inlined.


    return dispatcher;
  }

 

익숙한 에러메시지가 보인다. 주석을 읽어보자.

1. 렌더 페이즈 외부에서 접근할 때 null access error 발생

2. (성능이) 매우 중요한 경로에 있으므로 자체 오류를 발생시키지 않는다.

3. 이것이 인라인임을 보장하는데 도움된다.

 

1번과 2번은 연관시킬 수 있을 것 같다.

오류가 발생하고 오류를 처리하고 오류를 던지는 것은 성능 저하를 발생시킨다. 따라서 자체오류를 발생시키지 않는다. 그대신, 예상가능한 null access error는 발생시키게 한다.

3번은 잘 모르겠다.

inline은 <div style={{width: '100%',height:'500px'}}/>와 같은 인라인 스타일을 사용할때, 또는 <script ></script>안에 js를 작성하는 자바스크립트 작성 방식에서 언급된 단어이다.

React가 랜더링 되는 동안 작성되는 인라인 함수도 있겠다.

아! useState가 컴포넌트 외부가 아니라 내부에서 작성된다는걸 저렇게 표현한 것인 것 같다

 

이제 ReactCurrentDispatcher가 궁금해졌다.

// ReactCurrentDispatcher가 사용된 곳(후략)

더보기
/**
   * Keeps track of the current dispatcher.
   */
  var ReactCurrentDispatcher = {
    /**
     * @internal
     * @type {ReactComponent}
     */
    current: null
  };


 var ReactSharedInternals = {
    ReactCurrentDispatcher: ReactCurrentDispatcher,
    ReactCurrentBatchConfig: ReactCurrentBatchConfig,
    ReactCurrentOwner: ReactCurrentOwner
  };
  
  //위에서 본 것
  function resolveDispatcher() {
    var dispatcher = ReactCurrentDispatcher.current;

    {
      if (dispatcher === null) {
        error('Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' + ' one of the following reasons:\n' + '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' + '2. You might be breaking the Rules of Hooks\n' + '3. You might have more than one copy of React in the same app\n' + 'See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.');
      }
    } // Will result in a null access error if accessed outside render phase. We
    // intentionally don't throw our own error because this is in a hot path.
    // Also helps ensure this is inlined.


    return dispatcher;
  }
  
  // 그리고 $1이라는 추가적인 변수도 보았다. 생략한다.
  var ReactCurrentDispatcher$1 = ReactSharedInternals.ReactCurrentDispatcher;

일단 이것 부터 보자.

/**
   * Keeps track of the current dispatcher.
   */
  var ReactCurrentDispatcher = {
    /**
     * @internal
     * @type {ReactComponent}
     */
    current: null
  };

internal: 내부에서만 쓰이는 코드

type: current는 react component이다.

즉 current는 현재 dispatcher가 다루고 있는(?) 컴포넌트를 나타낸다.

근데.. 여기서 끝난다.

keeps track of the current dispatcher이라는 주석 뿐이다.

하지만 index.js말고도 다른 파일들이 있다.

continue;