리액트와 클로저 1편

창건·2022년 7월 16일
3

리액트

목록 보기
3/5

리액트와 클로저

리액트의 useState는 모듈 패턴을 바탕으로 구현되어 있습니다.

모듈 패턴을 이해하려면 클로저를 이해해야 되는데, 클로저에 대해 헷갈린다면 여기를 참조하면 이해가 쉬울 것 같습니다.

저도 헷갈리던 것들을 잘 정리할 수 있었습니다.

1~2편에 걸쳐서 리액트 모듈을 구현해 보겠습니다.

참고한 유튜브 영상을 보시면 이해하는 데 더더욱 도움이 될 것 같습니다.

useState 구현하기

state를 보관하고, render와 useState 메소드를 가진 MyReact를 먼저 구현해 보겠습니다.

const MyReact = (function () {
  let _val; // 우리가 관리할 state에 해당합니다.

  return {
    render(Component) {
      const comp = new Component();
      comp.render();
      return comp;
    },

    useState(initialValue) {
      _val = _val || initialValue;
      function setState(newValue) {
        _val = newValue;
      }
      return [_val, setState];
    },
  };
})();
  • 즉시실행함수로 모듈패턴을 통해 MyReact를 구현했습니다.

    render 메서드는 Component를 인자로 받아 해당 컴포넌트를 렌더링할텐데요,

    아래 코드를 보시면 알겠지만 브라우저가 아닌 콘솔에 찍을 예정입니다.

  • useState는 _val이라는 변수를 참조하고 있습니다.

    이렇게 되면 클로저가 형성됩니다. 우리가 컴포넌트 내에서 useState를 호출하면,

    "지금" 형성된 외부 렉시컬 환경인 MyReact 내에서 _val을 찾을 것입니다.

다음은 컴포넌트입니다.

function Counter() {
  const [count, setCount] = MyReact.useState(0);

  return {
    render() {
      console.log('render: ', count);
    },
    click() {
      setCount(count + 1);
    },
  };
}
  • 위에서 언급한 대로, 렌더링은 콘솔로 대체하고 있습니다.

    useState를 MyReact 모듈에서 가져와 반환값인 state와 setState를 count와 setCount에 할당했습니다.

useState의 작동 방식

전체 코드를 통해, useState가 어떻게 동작하는지 보겠습니다.

const MyReact = (function () {
  let _val;

  return {
    render(Component) {
      // 2. Counter 컴포넌트 함수의 코드가 한줄 한줄 실행됩니다.
      const comp = new Component();
      // 4. 컴포넌트가 렌더링(콘솔에 찍기)됩니다.
      comp.render();
      return comp;
    },

    useState(initialValue) {
      _val = _val || initialValue;
      function setState(newValue) {
        _val = newValue;
      }
      return [_val, setState];
    },
  };
})();

function Counter() {
  // 3. useState가 호출되어, _val에 0을 할당하고 setState와 함께 반환합니다.
  const [count, setCount] = MyReact.useState(0);

  return {
    render() {
      console.log('render: ', count);
    },
    click() {
      // 7. setCount는 외부 렉시컬 환경(MyReact)의 _val을 찾아 값을 수정합니다.
      setCount(count + 1);
    },
  };
}

// 1. MyReact 모듈의 render메서드를 Counter를 인자로 전달하여 호출했습니다.
// 5. App 변수에 Counter 함수가 반환한 객체가 할당됩니다.
let App = MyReact.render(Counter);

// 6. click을 통해 setCount가 호출되었습니다.
App.click();

// 8. render메소드를 다시 해봅니다.
App.render(); // 9. render: 0 ??
  • 천천히 코드를 따라가 보겠습니다.
    1. MyReact 모듈의 render메서드를 Counter를 인자로 전달하여 호출했습니다.
    2. Counter 컴포넌트 함수의 코드가 한줄 한줄 실행됩니다.
    3. useState가 호출되어, _val에 0을 할당하고 setState와 함께 반환합니다.
    4. 컴포넌트가 렌더링(콘솔에 찍기)됩니다.
    5. App 변수에 Counter 함수가 반환한 객체가 할당됩니다.
    6. click을 통해 setCount가 호출되었습니다.
    7. setCount는 외부 렉시컬 환경(MyReact)의 _val을 찾아 값을 수정합니다.
    8. render메소드를 다시 해봅니다.
    9. 엥.. 1이 출력되어야 할 것 같은데 0이 출력됩니다.

0이 출력되는 이유가 무엇일까요?

컴포넌트 객체(App)이 render를 통해 count라는 변수를 참조하고 있습니다.

하지만, 해당 count는 아직 업데이트 되지 않은 값입니다.

count가 업데이트가 되려면, 함수가 재호출 되어야 합니다. 아래 코드처럼요.

App = MyReact.render(Counter); // render: 1

실제로 리액트에서 함수형 컴포넌트는 state가 업데이트 되면서 함수 전체가 다시 호출됩니다.

그래야 useState가 다시 한번 호출되고, 새 state를 받아 올 수 있기 때문입니다.

다음 2편에서는 useEffect를 구현해 보겠습니다.

profile
피곤한만큼 성장할 수 있으면

0개의 댓글