처음으로
react

02. 지역 상태와 전역 상태 이용하기

다이시 카토의 리액트 훅을 활용한 마이크로 상태 관리 2부

2024-04-29

💡 리액트 컴포넌트는 트리 구조를 구성합니다. 트리 구조에서 하위 트리 내에 상태를 만드는 것은 간단한데, 단순히 트리의 상위 컴포넌트에서 지역 상태를 만들고 컴포넌트와 자식 컴포넌트에서 해당 상태를 사용하기만 하면 됩니다. 이런 방법은 지역성과 재사용성 측면에서 좋기 때문에 보통 이런 전략을 따르는 것이 권장됩니다.

언제 지역 상태를 사용할까?

💡 리액트 컴포넌트는 자바스크립트 함수이기 때문에 순수할 수 있지만 컴포넌트 내에서 상태를 사용할 경우 순수하지 않게 된다.

함수와 인수

다음은 같은 이수에 대해 항상 같은 값을 반환하는 순수 함수입니다.

const addOne = (n) => n + 1;

순수 함수는 동작을 예측할 수 있기 때문에 선호되는 경우가 많습니다.

다음은 전역 변수에 의존하는 함수입니다. base의 값이 변경되면 함수가 다르게 동작합니다.

let base = 1;

const addBase = (n) => n + base;

전역 변수에 의존하는 함수를 무조건 나쁘다고 볼 수는 없지만, 의도하지 않은 결과가 나올 수 있다는 위험성이 항상 존재합니다. 또한 전역 변수인 base가 싱글턴이기 때문에 재사용성이 떨어집니다.

다음은 컨테이너 객체를 통해 재사용성을 높인 접근 방식입니다.

const createContainer = () => {
	let base = 1;
	const addBase = (n) => n + base;
	const changeBase = (b) => { base = b };
	return { addBase, changeBase };
};

const { addBase, changeBase } = createContainer;

컨테이너 객체마다의 base는 격리돼 있으므로 서로 다른 컨테이너에 영향을 주지 않습니다. 따라서 재사용하기가 쉬워집니다.

리액트 컴포넌트와 props

리액트는 개념적으로 상태를 사용자 인터페이스(UI)로 변환하는 함수입니다. 리액트로 코드를 작성할 때 리액트 컴포넌트는 말 그대로 자바스크립트 함수이며 그것의 인수를 props라고 합니다.

const AddOne = ({ number }) => {
	return <div>{number + 1}</div>;
};

위 코드의 컴포넌트는 number를 받아 number + 1을 반환합니다. 이것은 위에 작성한 addOne 함수와 똑같이 작동하며 순수 함수입니다. 차이점이라고 하면 인수가 props 객체이고 반환값이 JSX 형식이라는 점입니다.

지역 상태에 대한 useState 이해하기

지역 상태에 대해 useState를 사용하면 어떻게 될까요?

const AddBase = ({ number }) => {
	const [base, changeBase] = useState(1);
	return <div>{number + base}</div>;
};

이 함수는 인수에 포함되지 않은 base에 의존하기 때문에 엄밀히 말하면 순수하지 않습니다.

useState의 동작을 추측하자면 변경되지 않는 한 base를 반환하므로 AddBase 함수는 createContainer 사례에서 봤듯이 멱등성을 가집니다.

useState가 포함된 AddBase 함수는 changeBase를 함수 선언 범위 내에서만 사용할 수 있기 때문에 억제됐다고 할 수 있습니다. 함수 밖에서 base를 변경하는 것은 불가능합니다. useState를 이렇게 사용하는 것은 지역 상태를 사용하는 것에 해당하며, 컴포넌트는 억제돼 있고 컴포넌트 외부의 그 어떤 것에도 영향을 미치지 않기 때문에 지역성을 보장합니다.

지역 상태의 한계

함수 컴포넌트 외부에서 상태를 변경해야 한다면 전역 상태가 필요합니다.

상태 변수는 개념적으로 전역 변수입니다. 전역 변수는 함수 외부에서 자바스크립트 함수의 동작을 제어할 때 유용하게 사용할 수 있습니다. 마찬가지로 전역 상태는 컴포넌트 외부에서 리액트 컴포넌트의 동작을 제어할 때 유용하게 사용할 수 있지만 컴포넌트 동작을 제어할 때 유용하게 사용할 수 있지만 컴포넌트 동작을 예측하기 어렵다는 장단점이 있습니다. 따라서 전역 상태를 과하게 사용해서는 안되며, 지역 상태를 기본으로 사용하고 전역 상태는 보조 수단으로 사용하는 것이 좋습니다.

지역 상태를 효과적으로 사용하는 방법

상태 끌어올리기(Lifting State Up)

const Component1 = ({ count, setCount }) => }
	return (
		<div>
			{count}
			<button onClick={() => setCount((c) => c + 1)}>
				Increment Count
			</button>
		</div>
	);
};

const Component1 = ({ count, setCount }) => }
	return (
		<div>
			{count}
			<button onClick={() => setCount((c) => c + 1)}>
				Increment Count
			</button>
		</div>
	);
};

const Parent = () => {
	const [count, setCount] = useState(0);
	return (
		<>
			<Component1 count={count} setCount={setCount} />
			<Component2 count={count} setCount={setCount} />
		</>
	);
};

count 상태는 Parent에서 단 한번만 정의되기 때문에 상태는 Component1과 Component2에 공유됩니다. count는 여전히 컴포넌트 내에 지역 상태로 존재합니다. 여기서 자식 컴포넌트는 부모 컴포넌트의 상태를 이용할 수 있습니다.

이 패턴은 지역 상태를 사용하는 대부분의 상황에서 작동하지만 성능 문제가 있을 수 있습니다. 상태를 상위 컴포넌트로 전달할 경우 Parent는 모든 자식 컴포넌트를 포함해 하위 트리 전체를 리렌더링할 것입니다. 이는 일부 상황에서 성능 문제를 일으킬 수 있습니다.

내용 끌어올리기(Lifting Content Up)

const AdditionalInfo = () => {
	return <p>Some information</p>
};

const Component1 = ({ count, setCount, children }) => }
	return (
		<div>
			{count}
			<button onClick={() => setCount((c) => c + 1)}>
				Increment Count
			</button>
			{children}
		</div>
	);
};

const Parent = ({ children }) => }
	const [count, setCount] = useState(0);
	return (
		<>
			<Component1 count={count} setCount={setCount}>
				{children}
			</Component1>
			<Component2 count={count} setCount={setCount} />
		</>
	);
};

const GrandParent = () => {
	return (
		<Parent>
			<AdditionalInfo />
		</Parent>
	);
};

children은 JSX 형식으로 중첩된 자식 요소를 표현하는 특별한 prop 이름입니다. 만약 여러 개의 요소를 전달해야 한다면 다른 이름을 사용하는 편이 적절합니다.

위와 같이 코드를 작성함으로써 AdditionalInfo 컴포넌트는 count가 변경될 때 리렌더링되지 않습니다.

전역 상태 사용하기

전역 상태란?

이 책에서 전역 상태는 단순히 지역 상태가 아님을 의미합니다. 개념적으로 하나의 컴포넌트에 속하고 컴포넌트에 의해 캡슐화된 상태를 지역 상태라고 합니다. 따라서 상태가 하나의 컴포넌트에만 속하지 않고 여러 컴포넌트에서 사용할 수 있다면 전역 상태라고 합니다.

모든 컴포넌트가 의존하는 애플리케이션 차원의 지역 상태가 있을 수 있습니다. 이 경우 애플리케이션 차원의 지역 상태는 전역 상태라고 볼 수 있습니다. 이런 측면에서 봤을 때는 지역 상태와 전역 상태를 명확하게 나눌 수 없습니다. 대부분의 경우 상태가 개념적으로 속한 곳이 어디인지 생각해 보면 상태가 지역 상태인지 전역 상태인지 알 수 있습니다.

전역 상태는 두 가지 측면이 있습니다.

  • 첫 번째는 싱글턴이며, 이는 특정 컨텍스트에서 상태가 하나의 값을 가지고 있다는 것을 의미합니다.
  • 두 번째는 공유 상태이며, 이는 상태 값이 다른 컴포넌트 간에 공유된다는 것을 의미하지만, 자바스크립트 메모리상에서 단일 값일 필요는 없습니다. 싱글턴이 아닌 전역 상태는 여러 값을 가질 수 있습니다.

언제 전역 상태를 사용할까?

리액트에서는 다음과 같은 두 가지 상황에서 전역 상태를 사용합니다.

  • prop을 전달하는 것이 적절하지 않을 때
  • 이미 리액트 외부에 상태가 있을 때