시작하는 중

useState(함수)??? 본문

react

useState(함수)???

싱욱 2023. 4. 20. 16:15

오픈 단톡방에서 신기한 것을 봐서 정리하려고 한다.

 

바로.. useState에 함수를 넣는 것

https://github.com/vinitus/useStateHook/blob/master/src/App.tsx

 

GitHub - vinitus/useStateHook: useState 훅 깊게 파보기

useState 훅 깊게 파보기. Contribute to vinitus/useStateHook development by creating an account on GitHub.

github.com

내가 만든 예시이다.

 

중요한 부분을 살펴보자면

// App.tsx
...
  function one1(): number {
    console.log('One1 function is called');
    return 1;
  }

  function one2(): number {
    console.log('One2 function is called');
    return 1;
  }

  const [test, setTest] = useState(1);
  const [test1, setTest1] = useState(one1);
  const [test2, setTest2] = useState(one2());
  const [test3, setTest3] = useState((): number => {
  console.log('One3 function is called');
  return 1;
  });
...

여기서 useState에 넘어가는 인자들이다.

 

0. test의 경우에는 평소처럼 숫자 1을 넣는 것이다.

1. 콜백함수로 one1를 넘긴다

2. 함수의 실행한 결과를 인자로 넘긴다.

3. 익명 콜백 함수를 넘긴다.

 

문제는 이 모든 것들이 정상적으로 작동한다는 것이다.

하지만, 조금은 다른 점이 있다. reset 버튼을 누르게 된다면, one2함수는 버튼을 누를 때마다 계속 실행된다.

그렇기 때문에 첫 상태가 복잡한 로직의 결과라면, 함수를 만들고 이 함수를 useState의 콜백 함수로 넘기는 것이 가능하다.

 

이것이 어떻게 유용하냐면 ..

// 첫 번째 예제

...
  let longWorkResult = 0;
  for (let i = 0; i < 100; i += 1) {
    longWorkResult += i;
  }

  const [result, setResult] = useState(longWorkResult);
...

// 두 번째 예제

...
  function longWork(): number {
    let result = 0;
    for (let i = 0; i < 100; i += 1) {
      result += i;
    }
    return result;
  }

  const [result, setResult] = useState(longWork);
...

지금은 0~99까지 단순한 더하기이지만, 더 복잡한 로직들이 있다면 우리는 매번 이 함수가 렌더링될 때마다 해야한다.

 

useState로 들어가는 인자는 그 복잡한 로직 연산 결과이다. 그러므로 연산이 반드시 필요하다.

때문에, 이벤트 루프의 메인 스레드를 반드시 막는다. 이것이 중요한 것

 

리렌더링시 사용되지 않음에도 불구하고, 계속 계산되어야한다. useCallback을 사용할 수도 있겠지만, useState에 콜백함수로 넘기는 지연 초기화를 사용한다면 쉽게 할 수 있다!


사실 여기까지만 알고, 지연 초기화란 이런 것이고 어떻게 사용할 수 있다 정도로 끝내도 상관없다.

 

나는 근데 이런거 못참음.

 

1. 왜 콜백함수로 넘기면 이 값이 계산되는 것인지도 신기했고 왜 한번만 실행되는가도 신기했다.

2. 왜 함수를 호출하여 넘기면 계속 실행되는 것인지가 너무 궁금했다. 그냥 애초에 useState에 인자로 넘기면, 다음 렌더링에서 인자로 넘긴 함수의 호출은 실행될 필요가 없는거 아닌가? 싶었다.

 

그래서 찾아보고 생각하기로 했다.


1. useState(one1)는 왜 유효한 것일까

이를 위해선 useState를 React.js에서 찾는 여행을 떠나야한다. 너무 좋은 블로그 글이 있어서 이를 참고하기로 했다.

React 모듈의 react.development.js

useState는 initialState라는 인자를 받는다. dispatcher라는 변수에 resolveDispatcher의 리턴을 할당해준다.

우선, 이 resolveDispatcher는 또 ReactCurrentDispatcher의 current가 할당된 dispatcher를 반환할 뿐이다.

 

그럼 ReactCurrentDispatcher가 뭔지 알아야한다.

current라는 객체 하나가 끝이다..  근데 https://github.com/facebook/react/blob/v16.12.0/packages/react/src/ReactCurrentDispatcher.js#L15

/**
 * Keeps track of the current dispatcher.
 */
const ReactCurrentDispatcher = {
  /**
   * @internal
   * @type {ReactComponent}
   */
  current: (null: null | Dispatcher),
};


여기서 보면, crurrent는 Dispatcher가 될 수 있고 이 타입은 ReactFiberHooks에서 온다.

 

즉, 여기가 끝이 아니라는 것...

 

https://github.com/facebook/react/blob/v16.12.0/packages/react-reconciler/src/ReactFiberHooks.js#L1362

export const ContextOnlyDispatcher: Dispatcher = {
  readContext,
  ...
  useState: throwInvalidHookError,
  ///
};

const HooksDispatcherOnMount: Dispatcher = {
  readContext,
  ...
  useState: mountState,
  ...
};

const HooksDispatcherOnUpdate: Dispatcher = {
  readContext,
  ...
  useState: updateState,
  ...
};

여기서 보면 Dispatcher는 updateState, mountState, throwInvalidHookError가 올 수 있다.

 

우리는 초기 렌더링이니까, mountState를 살펴보면

https://github.com/facebook/react/blob/v16.12.0/packages/react-reconciler/src/ReactFiberHooks.js#L823

이 부분이 핵심이다. 즉, 인자로 들어온 값이 "함수"라면 -> 실행하는 것

 

그렇기에, 콜백함수를 넘기면 실행되고 그 인자는 함수에서 함수가 실행된 값으로 업데이트되는 것이다.


2. useState(one2())는 왜 자꾸 실행될까

참 많은 고민을 했다. react.dev도 찾아보고, 거의 모든 블로그 글도 찾아봤다. 거진 2시간 찾아본 듯

이거 찾아보다가 React 깃허브도 읽어본 것이다.

 

하지만, JS 자체로 생각해보니까 답이 나왔다.

React는 useState의 인자가 쓸모 없다는 것을 알지만, JS 자체는 알지 못한다는 것

 

너무 리액트의 관점에서 생각했던 것 같다..

 

리렌더링시 JS는 해당 함수형 컴포넌트를 실행시키면서 코드 한줄한줄 실행한다. 그렇기에 JS는 useState(one2())를 만나면 useState를 실행하기 위해, one2()가 실행되고 결과를 도출해내서 어떤 값이 정해져야 실행된다.

하지만, React는 useState의 인자는 더 이상 쓸모 없기에 쓰이지 않는다는 것을 React는 안다. 때문에, 함수의 결과 도출되지만, 이를 신경쓰지 않고 상태만 업데이트될 뿐인 것이다.

 

당연한 것이었으니깐..


reference

https://velog.io/@jjunyjjuny/React-useState%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%8F%99%EC%9E%91%ED%95%A0%EA%B9%8C

https://goidle.github.io/react/in-depth-react-hooks_1/

'react' 카테고리의 다른 글

react router의 원리  (0) 2023.05.04
yarn dev를 하면 vite에서 일어나는 일  (0) 2023.05.02
내 코드 리팩토링 하기  (0) 2023.04.14
react router v6.4에 생긴 기능들  (0) 2023.04.12
react-redux의 dispatch의 return은 무엇일까  (0) 2023.03.27