useState와 클로저

송민준·2024년 9월 8일

프론트엔드

목록 보기
2/2

리액트는 함수형 프로그래밍

추상화의 단위를 함수로 사용하는 것이 함수형 프로그래밍이다.

interface Props {
	menu: ReactNode;
}
export const Layout = ({ menu }: Props) => {
  return <div>{menu}</div>;
}; 

const App = () => {
	return <Layout menu={<div>menu here</div>} />
}

리액트는 jsx, tsx 문법으로 컴포넌트를 구성한다.
사실 Layout 같은 태그는 실제 html 태그는 아니고 인자를 받아서 컴포넌트 안의 함수를 실행시킨다. 그리고 그 결과로 완성된 dom 구조를 얻을 수 있다.
그러므로 Layout 컴포넌트가 menu라는 ReactNode 타입의 인자를 받아서 리턴한다는 의미는 "Layout이라는 이름의 함수가 menu라는 이름의 함수를 인자로 받아서 실행"하는 "고차함수"라는 이야기가 된다.
여기서 menu 함수로 추상화했으므로 리액트는 함수형 프로그래밍이 될 수 있다.

상태 관리

하지만 위와 같은 코드로는 컴포넌트마다 상태를 가지고 있지 않는다. 메뉴를 보였다가 안보이게 하는 등, 상태 관리가 불가피할 때 리액트 hook 중 가장 기본이 되는 useState 를 사용할 수 있다.

export const Layout = ({ menu }: Props) => {
  const [isMenuActive, setIsMenuActive] = useState<boolean>(false);
  return (
    <div style={{ visibility: isMenuActive ? "visible" : "hidden" }}>
	    <button onClick={() => setIsMenuActive(!isMenuActive)}> menu button </button>
      {menu}
    </div>
  );
}; 
  • 여기서 Layout은 함수로 알고 있고 함수가 실행했을 때 false로 초기화된 이후로 변경은 어렵지 않을까라는 의문이 들었다.
  • ->
    - 상태는 리액트 내부에서 관리된다. 이 상태값은 컴포넌트의 특정 랜더링 시점의 데이터이다.
    - 컴포넌트는 useState를 호출했을 때 리액트로부터 받는 상태 값과 상태를 업데이트할 수 있는 함수를 가지고 상태를 관리한다.
    - setState(상태를 업데이트할 수 있는 함수)는 상태값 변경하고 리랜더링을 한다
    - 리액트가 컴포넌트를 랜더링할 때, 상태값과 setState는 랜더링 시점 상태값을 클로저로 캡처한다.

클로저 예시

아래 예시는 클로지를 캡처한 예시이다.
(콘솔 출력 시점은 가장 처음 버튼을 누른 시점이다.)

export const Counter = () => {
	const [count, setCount] = useState<number>(0);

	const countDouble = () => {
		console.log(count); // 0
		setCount(prev => prev + 1)
		console.log(count); // 0
	}

	return (
		<>
			<button onClick={countDouble}> count 2 </button>
			<span>{count}</span>  // <span>1</span>
		</>
	);
}
  • countDouble이 실행했을 시점에 count는 0이다. 현재 캡처된 값은 초기화 상태인 0이기 때문이다.
  • <span>1</span> 은 랜더링 시점의 상태값이므로 변경된 상태값이 출력되었다.
    *setCount(prev => prev + 1)setCount(count+1) 두 변경 방식이 있는데 전자 방식이 '리액트'가 제공하는 가장 최신 상태값을 참조하기 때문에 안전하다.

Batching

위 예시에서 살짝 다른 예시이다.

export const Counter = () => {
	const [count, setCount] = useState<number>(0);
--------------------------
	const count = () => {
		setCount(1)
		setCount(2)
	}
--------------------------
	return (
		<>
			<button onClick={count}> count </button>
			<span>{count}</span> //<span>2</span>
		</>
	);
}

setState가 두 번 불렸으니 두 번 리랜더링한다고 생각할 수 있지만 리액트는 배칭을 사용한다.
배칭은 성능을 최적화하기 위해 업데이트 큐에 상태 업데이트를 넣어 상태 업데이트를 한 번에 처리하는 방식이다.
랜더링은 시간이 걸리는 작업이므로 두 번 랜더링할 거 한 번에 해버리니 배칭은 중요한 작업임이 틀림없다.

profile
개발자

0개의 댓글