useReducer vs useState) 1. useToggle hook

Sal Jeong·2022년 9월 20일
0

3년차가 되어서야, 클린 코드의 중요성을 깨달았다. 이제부터라도 최대한 좋은 소스를 많이 보면서 작성하려고 하는데, 그것의 처음이 useToggle hook이었다.

tr;dr -
1. 인터넷에서 useToggle을 useReducer로 구현하는 방식을 봄.
2. 해당 pull request는 훅의 사용 수를 줄이는(기존: useCallback, useState -> useReducer 하나) optimization으로 제안되었고 merge되었는데 기존 방식에 비해 어떤게 나아졌는지 궁금해짐.
3. hooks가 어떻게 돌아가는지 알아야할 필요가 있음. 그것을 확인하려면 react fiber structure와 기존 structure를 좀 심도 있게 확인해야 할 필요가 있음.

1. useToggle()

import { useCallback, useState } from 'react';
// Usage
function App() {
    // Call the hook which returns, current value and the toggler function
    const [isTextChanged, setIsTextChanged] = useToggle();
    
    return (
        <button onClick={setIsTextChanged}>{isTextChanged ? 'Toggled' : 'Click to Toggle'}</button>
    );
}
// Hook
// Parameter is the boolean, with default "false" value
const useToggle = (initialState = false) => {
    // Initialize the state
    const [state, setState] = useState(initialState);
    
    // Define and memorize toggler function in case we pass down the component,
    // This function change the boolean value to it's opposite value
    const toggle = useCallback(() => setState(state => !state), []);
    
    return [state, toggle]
}

일반적으로 사용했던 useToggleHook은 다음과 같다.

그냥 별다를 것 없이 useState와 useCallback을 써서 state에 boolean값을 담고, toggle 함수를 callback으로 in-memory에 담아서 메모리 손실을 줄인다... 까지 사용하고 있었는데,

https://github.com/streamich/react-use/blob/master/src/useToggle.ts

에서 다른 사용법을 확인할 수 있었다.

import { Reducer, useReducer } from 'react';

const toggleReducer = (state: boolean, nextValue?: any) =>
  typeof nextValue === 'boolean' ? nextValue : !state;

const useToggle = (initialValue: boolean): [boolean, (nextValue?: any) => void] => {
  return useReducer<Reducer<boolean, any>>(toggleReducer, initialValue);
};

export default useToggle;

useState가 아닌, useReducer를 사용해서 boolean 값을 리턴한다.

pull request 항목을 확인해 보니, 예전 버전의 코드는 일반적인 그것과 같았다.

그런데 useReducer optimization, use a single hook 이라는 제목은 어떻게 된 것일까?

hooks는 하나만 사용하는 것이 무조건 최적화에 좋은 것일까?

아니, 사실 모든 코드라는 것이 많아지면 퍼포먼스에 부담을 주겠지만... 그렇게 깊게 생각해본 적이 없어서 잘 모르겠다.

2. 더 깊게 생각하기

https://dev.to/wuz/linked-lists-in-the-wild-react-hooks-3ep8

// Hooks are stored as a linked list on the fiber's memoizedState field. The
// current hook list is the list that belongs to the current fiber. The
// work-in-progress hook list is a new list that will be added to the
// work-in-progress fiber.

위 설명에 의하면,
react Hooks는 linked list의 형태로 fiber의 memoizedState에 저장된다.
현재 훅 리스트는 현재 fiber에 저장되고, work-in-progress 훅의 변동이 생긴다면 새로운 list를 생성해 work-in-progress fiber에 저장된다.

이걸 확인하려면 react fiber부터 다시 보아야 겠다.

원래 알고있던 내용.
1. 실제 dom rerender가 일어날 때, tree 구조 상 렌더의 숫자가 무분별하게 늘어날 수 있다는 것이다.

내가 알고 있는 가상 돔은..

a. DOM Tree를 자바스크립트 객체로 in-memory에 저장하고,(virtual DOM)
b. 변화가 일어나면 diffing algorithm을 통해 새 virtual DOM과 이전 virtual DOM을 비교하고,
c. 변화가 일어날 경우 변화가 일어난 부분만 DOM에 전달해 rendering 하는데, 이때 브라우저의 layout 단계가 생략되어 더 퍼포먼스가 좋다고 알고 있다.

그렇다면, react의 기존 diffing 메소드와 16버전에 소개된 fiber가 어떻게 퍼포먼스를 더 향상시켰는지, 어떻게 rerender를 줄였는지 확인해보면 되겠다.

4. 여기까지 보았다면, 다시

<-- 여기서부터 전부 다음 포스팅 -->

예전에 정리해야 한다고 생각만 했던, react hooks는 linked list를 사용한다는 url을 다시 살펴보기로 했다.

아래 함수는

function createWorkInProgressHook(): Hook {
  if (workInProgressHook === null) {
    // This is the first hook in the list
    if (firstWorkInProgressHook === null) {
      isReRender = false;
      currentHook = firstCurrentHook;
      if (currentHook === null) {
        // This is a newly mounted hook
        workInProgressHook = createHook();
      } else {
        // Clone the current hook.
        workInProgressHook = cloneHook(currentHook);
      }
      firstWorkInProgressHook = workInProgressHook;
    } else {
      // There's already a work-in-progress. Reuse it.
      isReRender = true;
      currentHook = firstCurrentHook;
      workInProgressHook = firstWorkInProgressHook;
    }
  } else {
    if (workInProgressHook.next === null) {
      isReRender = false;
      let hook;
      if (currentHook === null) {
        // This is a newly mounted hook
        hook = createHook();
      } else {
        currentHook = currentHook.next;
        if (currentHook === null) {
          // This is a newly mounted hook
          hook = createHook();
        } else {
          // Clone the current hook.
          hook = cloneHook(currentHook);
        }
      }
      // Append to the end of the list
      workInProgressHook = workInProgressHook.next = hook;
    } else {
      // There's already a work-in-progress. Reuse it.
      isReRender = true;
      workInProgressHook = workInProgressHook.next;
      currentHook = currentHook !== null ? currentHook.next : null;
    }
  }
  return workInProgressHook;
}
if (workInProgressHook === null) {

참고할 url

https://usehooks.com/useToggle/

https://dev.to/wuz/linked-lists-in-the-wild-react-hooks-3ep8

https://github.com/streamich/react-use/pull/1079

https://github.com/reduxjs/react-redux/issues/1468

https://github.com/reduxjs/react-redux/issues/1468

https://dzone.com/articles/understanding-of-react-fiber-architecture

https://www.freecodecamp.org/news/event-propagation-event-bubbling-event-catching-beginners-guide/#how-event-bubbling-happens-in-javascript

참고할 사항. react-dom 2022/09/20 기준 최신 버전(18.20)의 createWorkInProgressHook()

  function createWorkInProgressHook() {
    if (workInProgressHook === null) {
      // This is the first hook in the list
      if (firstWorkInProgressHook === null) {
        isReRender = false;
        firstWorkInProgressHook = workInProgressHook = createHook();
      } else {
        // There's already a work-in-progress. Reuse it.
        isReRender = true;
        workInProgressHook = firstWorkInProgressHook;
      }
    } else {
      if (workInProgressHook.next === null) {
        isReRender = false; // Append to the end of the list

        workInProgressHook = workInProgressHook.next = createHook();
      } else {
        // There's already a work-in-progress. Reuse it.
        isReRender = true;
        workInProgressHook = workInProgressHook.next;
      }
    }

    return workInProgressHook;
  }
profile
Can an old dog learn new tricks?

0개의 댓글