React 공식 문서를 보면 다음과 같은 문장이 나온다:
For each unique piece of state, you will choose the component that “owns” it. This principle is also known as having a “single source of truth”.
나는 이 문장이 상태를 어디에 둘 것인지 고민할 때 단일 진실의 원천(SSOT: Single Source of Truth) 개념을 반드시 생각해야 한다는 의미라고 생각했다.
내가 이해한 SSOT는, 어떤 데이터가 여러 컴포넌트에서 공통으로 사용된다면 그 데이터의 '공급원'은 반드시 하나여야 한다는 원칙이다.
예를 들어, 나에 대한 정보가 저장된 시스템이 두 군데 있다고 해보자. 내가 한쪽에서 개명을 했는데, 다른 한쪽에는 여전히 이전 이름이 남아 있다면?
그 순간부터는 누구도 “진짜 내 이름이 뭔지” 확신할 수 없게 된다. 이런 문제를 방지하려면, 모든 데이터는 하나의 진실된 출처를 가져야 한다.
위키백과에서는 단일 진실 공급원을 다음과 같이 정의하고 있다:
정보 시스템 설계 및 이론에서의 단일 진실 공급원(Single Source of Truth, SSOT)은, 모든 데이터 요소를 한 곳에서만 제어하거나 편집하도록 조직하는 관례를 말한다. 이 데이터를 사용하는 다른 모든 곳은 하나의 공급원만을 참조하며, 이 공급원만 수정하면 그 변경 사항은 전체 시스템에 자동으로 반영된다.
따라서 데이터 사본이 누락되거나 동기화되지 않을 위험도 사라진다.
이러한 SSOT 개념은 React에서도 그대로 적용된다. React 공식 문서에서는 상태 공유와 관련된 내용에서 이 원칙을 언급하며, 같은 데이터를 여러 컴포넌트가 공유해야 할 경우, 반드시 하나의 공급원에서 상태를 관리해야 한다"고 설명한다.
❓왜 그럴까?
React에서는 UI의 변경을 상태(state)의 변경으로 본다. 상태가 바뀌면 그에 따라 UI도 즉시 변경되기 때문에, UI는 상태를 그대로 반영하는 결과물이라고 할 수 있다. 그런데 동일한 상태를 여러 컴포넌트에서 따로따로 관리하면, 각 컴포넌트가 서로 다른 UI를 보여주는 일관성이 없는 상황이 발생할 수 있다. 이렇게 상태가 중복되면, 어떤 값이 진짜인지 알기 어려워지고, 결국 버그로 이어질 가능성도 커진다.
내 정보를 입력하는 컴포넌트가 2개 있고, 그 정보를 화면에 보여주는 컴포넌트가 하나 있다고 해보자. 각 컴포넌트가 모두 자신만의 상태로 내 정보를 저장하고 있다면, 초기에는 세 컴포넌트 모두 ericajeong이라는 이름을 보여줄 것이다. 하지만 첫 번째 입력 컴포넌트에서 이름을 youjeong으로 바꾸면, 해당 컴포넌트만 바뀌고 나머지는 여전히 ericajeong이다. 두 번째 입력 컴포넌트에서 jtomatoj로 바꾸면? 이제는 세 컴포넌트가 서로 다른 이름을 갖고 있다
ericajeong, youjeong, jtomatoj이제 생각해보자 "진짜 내 이름은 뭘까?"
여기에 그 누구도 답을 할 수 없을 것이다.
이런 상황은 단일 진실의 원천이 없기 때문에 생기는 문제이다.
React에서는 상태가 UI의 결과를 결정한다. 그래서 상태의 출처는 반드시 하나여야 하며, 다른 컴포넌트들은 그 상태를 참조만 하도록 설계해야 한다. React에서 상태 관리가 어려운 이유는, 상태가 많아서가 아니라 "상태가 어디에 있어야 할지 모호할 때" 발생한다고 생각한다. 그래서 공식 문서에서도 SSOT 개념을 강조하는 것이 아닐까?
React 공식 문서에서는 SSOT를 지키기 위한 몇 가지 방법을 소개하고 있다.
여러 컴포넌트가 같은 데이터를 공유해야 할 경우, 해당 상태를 가장 가까운 공통 조상 컴포넌트로 끌어올려 관리하는 방식이다.
이렇게 하면 상태가 중복되지 않고, 단일한 출처에서 일관되게 관리된다.
예를 들어, 컴포넌트 A와 B가 같은 정보를 사용한다면, 부모 컴포넌트에서 useState를 선언하고 그 상태를 props로 내려주는 것이 SSOT 원칙에 부합하는 설계이다.
컴포넌트 트리가 깊을수록 props를 통해 상태를 전달하기는 어렵다.
이럴 때는 Context를 사용해 공급원은 하나로 유지하면서도, 필요한 컴포넌트들이 직접 그 상태에 접근할 수 있도록 만들 수 있다.
이 방식은 상태의 위치를 분산시키지 않으면서, props drilling을 줄이고 효율적으로 상태 공유를 가능하게 한다.
예를 들어, 사용자 인증 정보, 언어 설정처럼 앱 전역에서 사용되는 상태는 Context로 관리할 수 있다.
이미 있는 상태에서 계산할 수 있는 값이라면, 그것을 별도의 상태로 만들지 않아야 한다.
파생 상태를 별도로 관리하면 원본 상태와의 동기화가 깨지기 쉬워지고, 그렇게 되면 SSOT 원칙이 무너진다
예를 들어, items 배열이 있다면, items.length나 totalPrice를 상태로 저장하지 말고 필요한 시점에 계산해서 사용하는 것이 안전하다.
SSOT는 상태의 출처를 하나로 제한하는 개념이고, 조합은 UI를 작게 나누고 조립하여 구성하는 방식이다.
이 두 가지는 함께 사용할 때 더욱 강력한 상태 관리 구조를 만든다고 한다.
상태는 상위 컴포넌트에서 하나로 관리하고, 하위 컴포넌트들은 해당 상태를 props나 context를 통해 읽기만 하도록 구성하면 된다.
<CardProvider value={cardState}>
<Card.Preview />
<Card.Input />
<Card.SubmitButton />
</CardProvider>
이런 컴파운드 컴포넌트 구조는, 단일한 상태를 분산하지 않으면서 참조할 수 있는 구조를 만들어주며, SSOT를 유지하면서도 UI 조립은 유연하게 할 수 있다.
React에서 SSOT를 지키는 것은 단지 기술적인 원칙이 아니라, 복잡한 상태들을 예측 가능하고 일관성 있게 관리하기 위한 것이라고 생각한다.
요약하면
React에서 상태는 곧 UI다.
그렇기 때문에 상태를 하나의 진실된 출처로 유지하는 SSOT 원칙은 React의 핵심인 UI의 신뢰성과 직결된다고 생각한다.그래서 결국 우리는 상태를 어디에 두는 게 가장 적절한지, 그리고 그 상태를 어떻게 컴포넌트에 더 잘 전달할 수 있을지를 끊임없이 고민하는 것이 아닐까?