
React의 상태관리에 대해

React의 상태관리란 무엇인지, 리액트에 존재하는 다양한 상태관리 방법들과 장단점, 또한 지양해야 할 부분들이 무엇인지 등에 관해 다루어보려고 합니다.
부족한 점이 많지만, 해당 주제에 대해 조사하게 되면서 배우고 습득한 것들을 바탕으로, 최대한 이해하기 쉽게 설명해보도록 하겠습니다.
상태란 ?
먼저 "상태"가 무엇인지부터 알고 넘어가야 할 것 같습니다.
상태의 사전적 정의는 다음과 같습니다.

그렇다면 프론트엔드에서의 상태란 무엇을 의미할까요 ?
하나의 애플리케이션을 통해 사용자에게 직접 데이터를 표현해주고, 사용자와 다양한 데이터를 주고 받는 클라이언트 개발자의 입장으로서, 다음과 같은 의미를 가질 수 있을 것 같습니다.
유저와의 상호작용을 위한, 변할 수 있는 데이터.
예시를 들어보겠습니다. 다음은 '배달의 민족' 페이지 중 하나입니다.

해당 페이지에선 어떠한 상태들이 존재할까요 ?
누가 봐도 상태인 것 처럼 보이는 것들이 있습니다.
매장음식 사진, 매장 이름, 별점, 가격, 배달 팁 등등..하지만, 눈에 보이지는 않지만 해당 애플리케이션을 정상적으로 작동시키기 위해 존재하는 데이터, 기능 또한 엄연히 상태일 것입니다. 예를 들면 다음과 같습니다.
전화, 찜 누르기, 공유와 같이 눈에 보이지 않는 데이터
각종 API 호출의 성공, 실패, 보류 등의 상태
상태관리
프론트엔드에서의 상태의 개념에 대해 알아보았습니다.
이처럼 상태란 눈에 보이지 않더라도, 유저와의 상호작용을 정상적으로 수행하기 위해 변할 수 있는 모든 데이터들을 말합니다.
따라서 "상태를 관리한다" 라는 의미는 다음과 같을 것 입니다.
사용자와의 상호작용을 위해, 상태를 조작하고 다루는 모든 작업
상태관리가 중요한 이유
상태란, 유저와의 상호작용을 위해 정상적으로 하나의 프로젝트가 기능하게 하도록 존재하는 모든 가변 데이터라고 정의하였습니다.
처음에는 오로지 기능 구현만을 위해 상태를 선언하고 관리할 수 있지만, 프로젝트의 크기가 커지고 사용자가 많아질수록 상태 관리의 중요성은 높아집니다.
프로젝트가 거대해지다 보면 "언제, 어디서, 왜" 상태를 관리할 것인지 신경쓰지 않으면 정말 안될 것 같은 순간들을 맞이하기 마련이기 때문입니다.
실패한 상태관리는, 불필요한 리렌더링을 발생시키고, 유지보수를 힘들게 하며, 의도치 않은 UI / UX를 초래합니다.
또한 점점 디버깅이 힘들어지고, 코드를 확장하기가 어려워지며, 상태를 추적하기 힘들게 만듭니다.
"좋은 상태관리"에 대해 정의하기 보다, 먼저 실패한 상태관리의 특징을 살펴보았습니다. 아마도 좋은 상태관리의 특징은, 이러한 실패한 상태관리의 단점들이 발생하지 않도록 정성을 들인( ? ) 상태관리가 아닐까 싶습니다.
리액트의 역사
리액트의 상태관리에 대해서 알아보기 전에, 프론트엔드의 역사를 잠시 살펴보고 넘어가겠습니다.
과거에는 HTML, CSS, JS가 탄생하고, 이후에 jQuery를 사용하게 되면서 자바스크립트를 통해 직접 DOM을 조작하는 방식으로 데이터를 관리해왔습니다. data-* 속성을 사용하여 데이터를 표현하였고, 직접 DOM을 수정하게 되면서 상태 변화의 추적이 어려웠던 시기였습니다.
시간이 지나 HTML + JS를 함께 다루는 MVC 아키텍처를 표방한 형태의, DOM을 조작하는 로직을 하나로 관리하려는 움직임이 생겼고, 따라서 화면 단위에서 컴포넌트 단위로 발전하게 되었다고 합니다.
따라서 이러한 선언적인 프로그래밍을 지향하는 움직임이 더욱 발전하여, 리액트 프레임워크가 탄생하게 되었습니다.
리액트는 SPA(Single Page Application) 환경을 구축하고 있는데, 이러한 특징으로 페이지의 깜빡임 없이 자연스러운 웹페이지 탐색이 가능해졌고, UI의 변경사항을 즉각즉각 보여줄 수 있게 되었습니다.
이전 프론트엔드의 특징과는 달리, SPA 환경을 장착한 리액트에서는 DOM에 접근하지 않아도 데이터가 변경되면 값이 변경되었고, 데이터 중심의 상태관리 로직이 반영되었으며, 어디서 상태가 변경되었는지 추적하기 유리해졌습니다.
그렇다면 이제 비로소 리액트 상태관리의 특징을 볼까요 ?
리액트의 상태관리 & Props drilling
리액트는 단방향 데이터 흐름의 특징을 가지고 있습니다. 컴포넌트 단위로 이루어져 있는 리액트에서, 컴포넌트는 자신의 state 를 자신의 컴포넌트에 가두고, 이러한 데이터들을 다른 컴포넌트에서도 사용할 수 있도록 전달해주기 위해 props 를 사용하게 됩니다.
따라서 필연적으로 상위 컴포넌트의 props 들이 하위 컴포넌트들로 전달되는 과정에서 중간에 있는 컴포넌트들을 실제 사용하지도 않고 하위에 props 를 전달해주기 위해서 데이터를 유통하기 위한 props 가 필요하게 되었습니다.
이러한 현상을 "props drilling" 이라고 합니다. 한 번 코드로 살펴보겠습니다.
function Toggle() {
const [on, setOn] = React.useState(false)
const toggle = () => setOn(o => !o)
return <Switch on={on} onToggle={toggle} />
}
function Switch({on, onToggle}) {
return (
<div>
<SwitchMessage on={on} />
<SwitchButton onToggle={onToggle} />
</div>
)
}
function SwitchMessage({on}) {
return <div>The button is {on ? 'on' : 'off'}</div>
}
function SwitchButton({onToggle}) {
return <button onClick={onToggle}>Toggle</button>
}Switch 컴포넌트는 실제로 on state와 onToggle 핸들러를 필요로 하지 않지만, 자식 컴포넌트인 SwitchMessage 와 SwitchButton 컴포넌트에 props를 전달해주기 위해 props를 받고 있는 모습입니다.
컴포넌트의 독립성과 재사용성을 위해서 컴포넌트를 분리하였지만, 중간에 오직 props 를 유통하기 위한 컴포넌트가 생겼고, 따라서 상위 컴포넌트와 하위 컴포넌트는 더욱 단단하게 결합을 하는 현상이 발생하게 된 것입니다.
단순히 컴포넌트 1개에서 2개 정도를 거치는 수준이라면 문제가 되지 않을 수도 있지만, component 의 부모 자식 관계의 깊이가 깊어질수록 더욱 문제는 심해지고 상태의 추적 또한 극히 어려워집니다.
따라서 리액트 개발자들은 애플리케이션 전체적으로 관리해야할 상태가 있다면, 이를 어떻게 효율적으로 관리하고, 해당 상태가 필요한 쪽에서는 빠르게 반응할 수 있는 모델에 대한 고민이 시작되었습니다.
해당 전역 상태를 어디에다 둘 것인가(전역 변수로 ? 클로저로 ?)
그 상태가 유효한 범위는 어떻게 제한할 것인가
상태의 변화에 따라 변경돼야 하는 모든 자식들은, 어떻게 해당 상태의 변화를 감지할 것인가
상태가 변함에 따라, 연간된 모든 자식 요소들이 변경되면서 애플리케이션이 찢어지는 현상(tearing)을 어떻게 방지할 것인가
리액트 개발자들이 좀 더 효율적으로 상태를 관리하고, 앞서 언급한 'props drilling'을 비롯한 여러 기존 상태 관리의 단점들을 해결하기 위해, 어떠한 방법을 활용하였는지 알아보고자 합니다.
상태 관리의 역사
리액트 개발자들의 고민의 결과들을, 상태 관리의 역사로 되짚어보고자 합니다.
Flux 패턴 + Redux의 등장
Flux 패턴은, 순수 리액트에서 제공하는 전역 상태 관리 방법인 context API 가 등장하기 전에, 리덕스(Redux)와 비슷한 시기에 소개되었습니다.
당시 웹 개발 상황은, 애플리케이션이 비대해짐에 따라 어떤 일이 일어나서 이 상태가 변했는지 등을 추적하기가 굉장히 어려운 상황이었습니다.
이러한 문제의 원인으로 '양방향 데이터 바인딩'이 언급되었고, 따라서 비즈니스 로직을 굳이 컴포넌트 계층구조로 만들 필요가 없다라는 것을 알게 되었습니다. 따라서 데이터를 조작하는 로직( Store )을 별개로 두고, 매개체( Action ) 를 통해서 데이터를 변경하면, 이를 View (리액트의 컴포넌트에 해당하는 부분)에 연결하여 화면에 렌더링하는 '단방향 데이터 구조'의 FLUX 패턴이 등장한 것입니다. (액션을 스토어에 보내는 역할을 dispatcher라고 지칭)

이렇게 단방향 데이터 흐름이 점점 두각을 나타내면서 등장한 것이 바로 리덕스입니다. 리덕스는 model , view , update 로 데이터 흐름을 분류하고, 이를 단방향으로 강제해 웹 애플리케이션의 상태를 안정적으로 관리하고자 하였습니다.
하나의 글로벌 상태 객체를 통해 상태를 하위 컴포넌트에 전파할 수 있기 때문에 'props drilling' 문제를 해결할 수 있었고, 스토어가 필요한 컴포넌트라면 단지 connect 만 호출하여 스토어에 바로 접근할 수 있도록 하였습니다.
하지만, 단순히 하나의 상태를 바꾸고 싶어도 해야 할 일이 너무 많았습니다. 어떠한 액션인지 타입을 선언해야 하고, 액션을 수행할 함수를 정의해야 하며, dispatcher와 selector를 필요로 하는 등등.. 너무나도 많은 보일러플레이트가 필요했던 것입니다.
따라서 React는 더 간결한 문법과 hooks 를 통해 외부 비즈니스 로직을 쉽게 가져다 쓸 수 있도록 '전역 상태를 하위 컴포넌트에 주입' 할 수 있는 context API 를 출시하게 됩니다.
Context API
리액트 팀에서는 당시 리덕스를 사용할 수도 있었지만 조금 더 단순히 상태를 참조하여 보일러플레이트를 줄이고, props로 상태를 넘겨주지 않더라도 원하는 곳에서 사용 가능하도록 Context API 를 출시하였습니다.
props를 사용하지 않더라도 Context API를 원하는 곳에서 사용하면 Context Provider 가 주입하는 상태를 사용할 수 있게 되었습니다.
하지만 Context API는 상태 관리가 아닌 상태를 주입하는 기능이며, 렌더링을 막아주는 기능 또한 존재하지 않기 때문에, 컨텍스트를 무분별하게 사용하는 것은 지양해야 합니다.
Recoil, Zustand, Jotai ...
훅이라는 새로운 패러다임의 등장에 따라, 훅을 사용하여 상태를 가져오거나 관리할 수 있는 다양한 라이브러리가 등장하게 됩니다.
기존의 리덕스와 같은 라이브러리와의 차이점은, 바로 훅을 활용해 작은 크기의 상태를 효율적으로 관리한다는 점입니다. 페이스 북 팀이 만든 Recoil 을 필두로 다양한 라이브러리가 탄생하였습니다.
따라서 기존 상태 관리 라이브러리의 아쉬운 점으로 지적받던 전역 상태 관리 패러다임에서 벗어나 개발자가 원하는 만큼의 상태를 지역적으로 관리하는 것을 가능하게 만들었고, 훅을 지원하기 때문에 함수형 컴포넌트를 사용하는 리액트에서 손쉽게 사용할 수 있다는 장점 또한 가지고 있었습니다.
Recoil과 Jotai는 리덕스와는 달리 Atomic 방식을 사용하고 있습니다. 이는 중앙 집중화된 Flux 방식과는 달리, 상태를 원자로 나누고 각 원자들은 독립적으로 사용 가능하다는 특징이 있습니다. 따라서 모듈화가 쉽고 코드의 재사용성을 높힐 수 있다는 장점이 있습니다. Zustand는 리덕스처럼 한 곳에서 모든 상태를 관리하는 방식을 취하고 있습니다.
효율적인 상태관리란 ..
지금까지 상태관리의 중요성과, 다양한 상태 관리 라이브러리들을 소개하였습니다. 그렇다면 어떻게 실제로 리액트에서 보다 효율적으로 상태관리를 할 수 있는지에 대해 알아보려고 합니다. 특히 리액트의 훅과 함수형 컴포넌트 패러다임에 맞춰 useState 와 useReducer , 또한 Context API 등의 전역 상태 관리 방법에 관한 효율적인 사용법과 안티패턴에 대해 알아보고자 합니다.
useState와 useReducer
먼저 리액트하면 대표적으로 떠오르는 useState 훅을 어떻게 사용해야 할까요 ? useState 훅의 등장으로 여러 컴포넌트에 걸쳐 손쉽게 동일한 인터페이스의 상태를 생성하고 관리할 수 있게 되었습니다. 기본적인 사용법은 사실 모두 아실 것이라 생각하니, 대표적인 3가지 지양해야 할 안티패턴을 소개하려고 합니다.
이전 상태를 고려하여 작성하자
const [counter, setCounter] = useState(0); const incrementCounter = () => { setCounter(counter + 1); }위와 같이 setState 함수를 작성한다면, 예기치 못한 에러를 발생시킬 수도 있습니다. 리액트는 렌더링 성능을 조금 더 높이기 위해, 발생하는 모든 state의 변경에 대응하여 리렌더링하지 않고, 여러 setState 함수 호출을
batching하여 처리합니다. 따라서 현재counter값을 기준으로 state를 업데이트한다면, 상태가 업데이트되지 않은 상태에서 현재의 상태값을 참조할 가능성이 생기기 때문에 예기치 못한 에러가 발생할 수도 있습니다.이는 다음과 같은 state를 업데이트하려는 코드가 정상적으로 작동하지 않는 이유기도 합니다.
const incrementCounter = () => { setCounter(counter + 1); setCounter(counter + 1); }따라서 다음과 같이 작성하는 것이 더욱 좋습니다.
const incrementCounter = () => { setCounter(prev => prev + 1); }
상태의 불변성을 고려하자 (중첩 구조의 상태 업데이트)
상태의 불변성을 고려하자는 말은, state의 원본을 직접 수정하는 것이 아니라, 새로운 객체 혹은 배열을 만들어 해당 값으로 업데이트하자는 말입니다. 이를 위해 복사본을 생성하여 상태를 업데이트하거나, 스프레드 연산자를 활용할 수 있습니다.
const [profile, setProfile] = useState({name: 'John', age: 30}); const updateAge = () => { profile.age = 31 ❌ setProfile(profile) } const updateAge = () => { setProfile({...profile, age: 31}); ✅ }그렇다면 아래와 같이 중첩되어있다면 어떻게 해야 할까요 ?
const [person, setPerson] = useState({ name: 'John', artwork: { title: 'Blue NaNa', city: 'Hamburg', } })해당 객체를 새로운 객체를 만들어서 기존 값을 덮어씌우기 위해서는 다음과 같이 여러 번에 나누어 스프레드 연산자를 사용해야 합니다.
setPerson({ ...person, artwork: { ...person.artwork, title: 'Blue Shop', } })프로그래밍 시에 위와 같은
immutable(불변성의) 객체를 다루는데 실수를 덜기 위해Immer와 같은 라이브러리를 사용할 수 있습니다.불필요한 prop 미러링을 피하자
흔히 발견되는 중복 상태 값은 다음과 같이 상위 컴포넌트로부터 내려온
props를useState의 기본값으로 사용하는 것입니다. 이 패턴을 'props mirror'라고 합니다.const Message = ({initialColor}) => { const [color, setColor] = useState(initialColor); }문맥상 상위 컴포넌트에서 전달된 색상이 수정되었을 때도, 동일하게 Message 컴포넌트의 상태가 수정될 것처럼 보이지만,
initialColor가 변경되더라도color는 업데이트 되지 않습니다. 이러한 오류는 컴파일 에러로도 잡히지 않기 때문에 버그 원인 검출에서도 시간이 많이 소요될 수 있습니다."부모 컴포넌트로부터 전달 받은 initialColor가 변경되는 상황에서만 자식 컴포넌트의 상태 값을 업데이트 해야한다." 라는 문맥상
useEffect를 생각하실 수도 있지만, 이 또한 useEffect의 안티패턴으로 여겨지고 있습니다.다음은 부모로부터 전달 받은
props의 변경이 일어났을 경우에만 특정 상태값을 초기화하기 위해, useState를 이용해 미러링하는 방법입니다.const List = ({items}) => { const [selection, setSelection] = useState(null); const [prevItems, setPrevItems] = useState(items); if(items !== prevItems){ setPrevItems(items); setSelection(null); } }이는 prop인 items가 변경되었을 때만
selection상태가 리셋되어야 하는 경우입니다. useState로 전달되는 인자가 첫 렌더링 이후 무시된다는 특징을 이용하여, 불필요한 useEffect로 인한 리렌더링을 줄이고 필요한state를 업데이트할 수 있습니다.
useReducer 를 사용한다면, 여러 상태값을 useState로 업데이트할 때보다 조금 더 효율적으로 상태값을 관리할 수 있습니다. 예시 코드를 보고 넘어가겠습니다.
import React, { useReducer } from 'react';
function todoReducer(state, action) {
switch (action.type) {
case 'ADD_TODO':
return [...state, action.payload];
case 'REMOVE_TODO':
return state.filter((todo) => todo.id !== action.payload);
default:
return state;
}
}
function TodoList() {
const [todos, dispatch] = useReducer(todoReducer, []);
const addTodo = (text) => {
dispatch({ type: 'ADD_TODO', payload: { id: Date.now(), text } });
};
const removeTodo = (id) => {
dispatch({ type: 'REMOVE_TODO', payload: id });
};
return (
<div>
<ul>
{todos.map((todo) => (
<li key={todo.id}>
{todo.text}
<button onClick={() => removeTodo(todo.id)}>Remove</button>
</li>
))}
</ul>
<button onClick={() => addTodo('New Todo')}>Add Todo</button>
</div>
);
}리듀서를 통해 상태를 관리한다면 다음과 같은 장점이 있습니다.
복잡한 상태 변경과 액션에 대해 유연하게 대응할 수 있다.
다수의 연관된 상태값들을 보다 간단하게 관리할 수 있다.
따라서, useState는 간단한 상태 관리에 적합하다면, useReducer는 좀 더 복잡한 상태 변경 시나리오에 적합하다고 볼 수 있을 것 같습니다.
Context API
컨텍스트를 활용한다면 전역 상태의 props를 원하는 하위 컴포넌트에 주입할 수 있습니다. 컨텍스트로 선언한 상태값을 useContext 훅을 사용하여, 컨텍스트에서 내려주는 데이터를 쉽게 사용할 수 있습니다.
하지만, 조심해야 할 것이 있습니다. 앞서 말했다시피, 컨텍스트는 단지 상태를 주입해주는 역할만을 하며 불필요한 렌더링을 막아주는 기능이 없습니다.
리액트 공식문서에서는, 다양한 레벨에 네스팅된 많은 컴포넌트에게 데이터를 전달하는 것이 컨텍스트의 주된 용도라고 설명하고 있습니다. 즉, 컨텍스트를 사용하면 컴포넌트를 재사용하기가 어려워지므로 꼭 필요할 때만 사용하라고 언급하고 있습니다.
context 의 사용법은 다음과 같습니다.

Provider 는 context 를 구독하는 컴포넌트들에게 context의 변화를 알리는 역할을 합니다. Provider 컴포넌트는 value prop을 받아서 이 값을 하위에 있는 컴포넌트들에게 전달합니다.
Provider 하위에서 context를 구독하는 모든 컴포넌트는 Provider의 value prop이 변경될 때마다 다시 렌더링됩니다. 따라서 글로벌 스코프에서 Context 를 사용하는 것을 지양하고 있습니다.
전역 상태는 전체 애플리케이션의 상태를 관리하기 위해 존재하지만, 전체 상태 관리를 위해 context provider 를 전체 애플리케이션에 래핑하는 구조는 피해야 할 안티 패턴이라고 합니다.
그 대신, 앞서 설명한 리덕스나 Recoil, Jotai, Zustand 와 같은 라이브러리를 사용하되, 컨텍스트는 경계가 확실한 파트에서 범위를 좁혀 사용해야 한다고 말하고 있습니다.
다시 렌더링할지 여부를 정할 때 참조(reference)를 확인하기 때문에, Provider의 부모가 렌더링 될 때마다 불필요하게 하위 컴포넌트가 다시 렌더링 되는 문제가 생길 수도 있습니다.
예를 들어 아래 코드는 value가 바뀔 때마다 매번 새로운 객체가 생성되므로 Provider가 렌더링 될 때마다 그 하위에서 구독하고 있는 컴포넌트 모두가 다시 렌더링 될 것입니다.
class App extends React.Component {
render() {
return (
<MyContext.Provider value={{something: 'something'}}>
<Toolbar />
</MyContext.Provider>
);
}
}효율적인 렌더링 관리 ?
지금까지 효율적인 상태관리 방법들과, 대표적인 훅들의 사용법, 안티패턴 등을 살펴보았습니다. 당연히 상태를 잘 관리하는 것은 당연히 불필요한 리렌더링을 방지하며, 효율적인 렌더링에 관여할 수 있게 해줍니다.
하지만 추가적으로 리액트의 렌더링을 효율적으로 관리할 수 있게 해주는 요소들에 대해서 간략하게 살펴보고 넘어가겠습니다.
React Developer Tools
효율적인 렌더링을 하게끔 도와주는 대표적인 도구입니다. 현 프로젝트의 컴포넌트 트리를 대상으로, 어떠한 컴포넌트가 어떠한 이유에 의해 렌더링 되었는지 파악할 수 있게 해주며, 구조뿐만 아니라
props와 내부hooks등 다양한 정보를 확인할 수 있게 도와줍니다.React의 자체 API, Hooks
리액트에서 제공해주는 렌더링 최적화 요소들이 무엇이 있는지에 대해 살펴보겠습니다. 대표적으로 고차 컴포넌트인
memo와useMemo,useCallback훅 등이 있습니다. 해당 요소들에 대해 자세하게 설명하고 넘어가지는 않겠지만, 중요하게 짚고 넘어갈 점이 있습니다.바로 '섣부른 최적화의 독'과 '렌더링 과정의 비용' 사이에서의 선택입니다.
먼저 꼭 필요한 곳에만 신중히 메모이제이션을 해야한다는 입장을 살펴보겠습니다. 메모이제이션 또한 어디까지나 비용이 드는 작업이기 때문에, 비용을 지불하기 전에는 항상 신중해야 한다는 것입니다. 메모이제이션이란 단어에서 알 수 있듯이 값을 비교하고 렌더링 또는 재계산이 필요한지 확인하는 작업, 혹은 이전의 결과물을 저장해 두었다가 다시 꺼내와야 한다는 비용이 있기 때문에, 조심해서 사용할 필요가 있습니다.
이와는 반대로, 메모이제이션은 하지 않았을 때보다 섣부른 최적화라 할지라도 했을 때 누릴 수 있는 이점이 더 많다는 입장입니다. 리렌더링이 발생할 때 메모이제이션과 같은 별도 조치가 없다면 모든 객체는 재생성되고, 결과적으로 참조는 달라지게 되는 것입니다. 따라서 실수로 최적화를 하지 않았을 때 치러야 할 위험 비용이 더 크기 때문에, 최적화에 대한 확신이 없다면 가능한 한 모든 곳에 메모이제이션을 활용한 최적화를 하는 것이 더 좋을 필요가 있다는 점입니다.
저의 사견으로 어떤 것이 더 좋다고 확신하지는 못하지만, 최적화를 가능케 해주는 요소들에 대해 깊게 공부해보고 비교해보며, 지속적인 모니터링을 통해 적용을 시도해보는 것이 좋을 것 같다고 생각합니다.
마치며
'리액트의 상태관리', '효율적인 상태관리'를 주제로 리액트의 상태 관리 구조의 특징과, 다양한 상태 관리 라이브러리들, 또한 어떻게 조금이나마 효과적으로 상태를 관리할 수 있는지에 대해 알아보았습니다. 늘 리액트를 통해 개발하며 곁에 두고 있었던 상태(state)이지만, 깊게 탐구해보고 찾아보며 배울 것이 정말 많았던 포스팅이였던 것 같습니다.
'상태 관리를 잘한다'라는 말은, 아마도 이번 포스팅에서 언급한 나쁜 상태관리의 특징 + 안티패턴들을 최대한 지양하고, 불필요한 렌더링을 최대한 발생시키지 않으려고 하는 자세가 아닐까 싶습니다. 또한 꽤( ? ) 자세히 알아보았던 'props drilling'과도 관련이 있지 않을까요 ?
많은 인사이트를 얻을 수 있었던 시간이였고, 앞으로 개발에 임할 시 갖추어야 할 태도 또한 어깨 너머 배운 것 같습니다. 저의 글을 읽어주셔서 감사합니다.
참고자료
https://react.dev/blog/2023/03/16/introducing-react-dev
https://medium.com/gitconnected/4-usestate-mistakes-you-should-avoid-in-react-0d9d676869e2
https://velog.io/@jay/do-not-put-everything-in-usestate#상태-그룹핑
https://www.youtube.com/watch?v=jqir73Lourk
