useState와 클로저 관계

fe_sw·2023년 8월 8일
0

React

목록 보기
10/10
post-thumbnail

클로저

클로저는 자바스크립트에서 함수와 그 함수가 선언될 때의 렉시컬 환경과의 조합이다.
이를 통해 함수는 선언된 환경 밖에서 실행되더라도 해당 환경의 변수에 접근할 수 있다.
클로저는 상태 관리, 데이터 캡슐화 등에 사용된다.


function outer() {
  let count = 0;
  function inner() {
    count++;
    return count;
  }
  return inner;
}

const counter = outer();
console.log(counter()); // 1
console.log(counter()); // 2

useState훅과 클로저의 관계

React Hook에서는 useState라는 Hook을 통해 컴포넌트 내의 상태를 관리한다.

함수형 컴포넌트에서 이전 상태와 현 상태의 변경이 있는지를 감지하기 위해서는 함수가 실행되었을 때 이전 상태에 대한 정보를 가지고 있어야 한다. React는 이 과정에서 클로저를 사용한다.

function useState(initialVal) {
  let _val = initialVal;
  const state = () => _val;
  const setState = (newVal) => {
    _val = newVal;
  };

  return [state, setState];
}

const [count, setCount] = useState(1);

console.log(count()); //1
setCount(2);
console.log(count()); //2

실제로 useState가 어떻게 동작하는지는 리액트의 내부 코드에 담겨 있으며, 위는 그 동작을 단순화한 예제 코드이다.
이때 클로저(state,setState)가 useState 내부 스코프 _val 값을 기억하고 있기 때문에 이후에도 접근이 가능하다.

하지만 위의 코드에서는 문제점이 존재한다. 상태값은 함수가아닌 변수로 존재하여야 한다.

오래된 클로저

오래된 크로저란 상태값에 변화가 발생해도 이를 감지하지못하고 예전 값을 바라보게 되는 현상이다.

function useState(initialValue) {
  var _val = initialValue
  function setState(newVal) {
    _val = newVal
  }
  return [_val, setState] 
}
const [count, setCount] = useState(0;
console.log(count) // 0
setCount(1) 
console.log(count) // 0

실제 React 훅과 동일하게 만들려면 state가 함수가 아닌 변수여야 한다.
단순히 _val을 함수로 감싸지 않고 노출하면 버그가 발생한다. //count 상태값이 변경되지않음

state를 함수가아닌 변수로 사용 + 현재상태를 반영하기위해 컴포넌트의 상태가 필요함
아 두가지를 만족시키기 위해 어떤식으로 구현해야 될까?

모듈 안에서의 클로저

const MyReact = (function() {
  let _val // 모듈 스코프 안에 state를 잡아놓습니다.
  return {
    render(Component) {
      const Comp = Component()
      Comp.render()
      return Comp
    },
    useState(initialValue) {
      _val = _val || initialValue // 매 실행마다 새로 할당됩니다.
      function setState(newVal) {
        _val = newVal
      }
      return [_val, setState]
    },
  }
})()

오래된 클로저의의 문제점을 클로저를 또 다른 클로저의 내부로 이동시켜 해결할 수 있다.

허나 해당 코드에서도 문제점은 존재하는데 useState를 사용하는 컴포넌트가 여러 개라면 문제가 생긴.
하나의 state 변수에 여러 컴포넌트가 접근하기 때문에 모든 컴포넌트의 state가 동일해지기 떄문이다.

최종코드

let state = [];
let setters = [];
let cursor = 0;
let firstrun = true;

const createSetter = (cursor) => {
  return (newValue) => {
    state[cursor] = newValue;
  };
};

const useState = (initialValue) => {
  if (firstrun) {
    state.push(initialValue);
    setters.push(createSetter(cursor));
    firstrun = false;
  }

  const resState = state[cursor];
  const resSetter = setters[cursor];
  cursor++;

  return [resState, resSetter];
};

useState로 선언된 state들은 배열에 순서대로 저장된다.
이러한 state 배열은 컴포넌트를 유일하게 구분 짓는 키를 통해 접근할 수 있게된다.

0개의 댓글