본문 바로가기
Coding/[Web] Frontend

[React] 더 나은 상태(state) 관리하기

by Gofo 2024. 11. 8.

목표

상태를 여러개 사용하다보면 상태 간 "꼬임"이 발생할 수도 있고 동기화를 놓쳐버려서 오류를 야기할 수도 있다.

따라서 오류를 최소화하기 위해서 state를 단순화시키고 불필요한 state는 제거하는 것이 좋다.

 

데이터베이스 구조를 정규화해서 버그 발생 가능성을 줄이는 것과 유사하다.

 


방법들

더 나은 상태를 관리하는 가장 좋은 방법은 state를 최대한 단순하게 만드는 것 이다.

 

  • 연관된 state 그룹화 : 항상 한번에 업데이트해야하는 상태값들이 있다면 여러 개의 state로 분리하는 것 보다 하나의 object로 묶어서 하나의 state로 관리하자.
  • state의 모순 피하기 : 서로 다른 상태 간에 논리적으로 불가능한 값을 가질 수 있는 환경을 제거하자.
  • 불필요한 state 제거하기 : 렌더링 중에 컴포넌트의 props나 기존 state 변수에서 계산할 수 있는 정보들은 state로 관리하기보다는 일반 변수로 사용하자.
  • state의 중복 피하기 : 동기화를 최소화할 수 있도록 상태 간에 중복된 정보는 최소화하자.
  • 깊게 계층화된 state 구조는 피하기 : state는 가급적 단순한 구조로 관리하자.

 

State가 아닌 경우

아래의 경우는 리액트 공식 문서에서 명확하게 state로 관리해서는 안된다고 말하는 케이스들이다.

  • 시간이 지나도 변하지 않는 것
  • 부모로부터 props를 통해 전달되는 것
  • 컴포넌트 안의 다른 state나 props를 가지고 계산 가능한 것

 

연관된 state 그룹화

그룹화란 여러 개의 state로 분리하지 않고 하나의 state로 묶어서 관리하는 것을 말한다.

 

아래의 경우 그룹화하는 것이 좋다.

  • 항상 같이 업데이트 해야하는 변수들
  • 요소 수가 가변적으로 변할 수 있는 배열이나 필드가 유동적으로 추가/삭제될 수 있는 객체

 

예를 들어, 대부분 같이 업데이트해야하는 두 개의 변수 <pre>x</pre>, <pre>y</pre>가 있다고 하자.

그 경우 <pre>x</pre>와 <pre>y</pre>를 별도의 상태로 관리하기 보다는 하나의 상태로 관리하는 것이 좋다.

 

나쁜 예시 1

const [x, setX] = useState(0);
const [y, setY] = useState(0);

 

좋은 예시 1

const [position, setPosition] = useState({ x: 0, y: 0 });

 

또는 <pre>type User = { id: string; name: string; }</pre> 형태를 가지고 배열이 증가/감소 할 수 있는 경우도 하나의 staet로 관리하는 것이 좋다.

 

나쁜 예시 2

const [user1, setUser1] = useState({ id: "user1", name: "Amy" });
const [user2, setUser2] = useState({ id: "user2", name: "Kale" });

 

좋은 예시 2

cosnt [users, setUsers] = useStaet([
  { id: "user1", name: "Amy" },
  { id: "user2", name: "Kale" },
])

 

State의 모순 피하기

모순되는 상태가 발생할 가능성이 있는 상태들은 만들지 않는 것이 좋다.

 

예를 들어, 결과 상태를 나타내는 변수 <pre>isSuccess</pre>와 <pre>isError</pre> 가 있을 때,

논리적으로는 <pre>isSuccess</pre>와 <pre>isError</pre>는 동시에 같은 값을 가지지 않는 것이 좋다.

 

나쁜 예시

아래 코드는 <pre>isSuccess</pre>와 <pre>isError</pre>가 같은 값을 가질 수 있을 뿐더러,

<pre>isSuccess</pre>나 <pre>isError</pre> 중 하나라도 동기화를 깜빡하면 논리적인 오류이다. (동작은 하겠지만)

const [isSuccess, setIsSuccess] = useState(false);
const [isError, setIsError] = useState(false);

function sentSucceed() {
	setIsSuccess(true);
    setIsError(false);
}

 

좋은 예시

아래처럼 state 간에 모순이 발생하지 않도록 하고, 가독성을 위해서 상수를 선언하는 것이 낫다.

const [status, setStatus] = useState<"none" | "success" | "error">("none");

const isSuccess = status === "success";
const isError = status === "error";

 

불필요한 state 제거하기

렌더링 과정 중에 계산할 수 있는 정보라면 상태로 관리하기 보다는 컴포넌트 내부에 변수(const)로 관리하는 것이 좋다.

 

예를 들어

  • props나 다른 state에서 가져오면 되거나
  • 그 정보들로 단순히 계산만 하면 되는 값들

이다.

 

두 개의 상태 <pre>firstName</pre>, <pre>lastName</pre>가 있고 fullName을 사용자에게 노출하거나 필요한 경우

<pre>fullName</pre> 라는 상태를 추가로 두기보다 firstName과 lastName을 조합해서 fullName을 만드는 것이 낫다.

 

렌더링 과정 중에 계산이 되는 것이기에 불필요하게 상태를 관리할 필요가 없기 때문이다.

 

나쁜 예시 1

const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [fullName, setFullName] = useState('');

 

좋은 예시 1

const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');

const fullName = firstName + ' ' + lastName;

 

특히 의도된 일부의 경우를 제외하고는 props를 state에 미러링하지 않아야 한다.

 

최초에 props로 전달된 값이 state의 initialValue로 세팅만 되고,

이후에 props의 값이 바뀌더라도 state는 업데이트 되지 않음을 유의해야한다.

 

나쁜 예시 2

function Message({ messageColor }) {
  const [color, setColor] = useState(messageColor);
  // ...
}

 

좋은 예시 2

function Message({ messageColor }) {
  const color = messageColor;
  // ...
}

 

State의 중복 피하기

동일한 내용을 여러군데의 상태에서 관리하지 않는 것이 좋다.

 

아래 예시처럼, 선택된 값이 무엇인지 관리하기 위한 상태로 <pre>const selectedItem: Item = {...}</pre>을 유지하기 보다는

선택된 아이템이 무엇인지 식별할 수 있는 key값만을 가지는 것이 좋다.

 

만약 <pre>items</pre>가 편집 가능하고 선택된 아이템 정보를 다 들고 있다면 items 편집 시 selectedItem도 같이 업데이트 해야줘야한다.

반대로 말하자면 items는 업데이트를 했지만, selectedItem을 업데이트 하지 않았다면 문제가 발생하게 된다는 것이다.

즉, 향후 발생할 수 있는 에러를 제거하기 위함이다.

 

나쁜 예시

const initialItems = [
	{ title: 'pretzels', id: 0 },
	{ title: 'crispy seaweed', id: 1 },
	{ title: 'granola bar', id: 2 },
];

const [items, setItems] = useState(initialItems);
const [selectedItem, setSelectedItem] = useState(
	items[0]
);

 

좋은 예시

selectedItem도 결국 렌더링 과정 중에 items와 selectedItemId를 이용하여 계산할 수 있는 값이다.

동기화 위험을 줄이고 불필요한 상태를 제거하기 위해서는 상태간 중복되는 정보를 최소화하는 것이 좋다.

const initialItems = [
	{ title: 'pretzels', id: 0 },
	{ title: 'crispy seaweed', id: 1 },
	{ title: 'granola bar', id: 2 },
];

const [items, setItems] = useState(initialItems);
const [selectedItemId, setSelectedItemId] = useState(0);

const selectedItem = items.find(item => item.id === selectedId);

 

깊게 계층화된 state 구조는 피하기

depth가 깊게 유지되는 state 구조는 피하고, 단순한 구조의 state를 유지하는 것이 좋다.

 

중첩된 구조의 state를 업데이트 하는 것은 변경된 부분부터 그 아래 단의 모든 객체의 복사본을 만들어야 함이고,

이는 코드 동작의 복잡성을 증가시킨다.

 

나쁜 예시

const [items, setItems] = useState({
  id: 0,
  title: '(Root)',
  childPlaces: [{
    id: 1,
    title: 'Earth',
    childPlaces: [{
      id: 2,
      title: 'Africa',
      childPlaces: [{
        id: 3,
        title: 'Botswana',
        childPlaces: []
      }]
    }]
  }]
});

 

좋은 예시

중첩된 구조보다는 flat한 구조가 state 업데이트를 쉽게할 수 있다.

const [items, setItems] = useState({
  0: {
    id: 0,
    title: '(Root)',
    childIds: [1, 42, 46],
  },
  1: {
    id: 1,
    title: 'Earth',
    childIds: [2, 10, 19, 26, 34]
  },
  2: {
    id: 2,
    title: 'Africa',
    childIds: [3, 4, 5, 6 , 7, 8, 9]
  },
});

 


참고

React 공식 문서 : https://ko.react.dev/learn/choosing-the-state-structure

 

State 구조 선택하기 – React

The library for web and native user interfaces

ko.react.dev

 

 

 

댓글