React - 상태 관리

이번 포스팅에서는 리액트에서의 상태 관리에 대해 공부해 보려고 한다.
앞서, React - State & Props 포스팅에서 State와 Props에 대해 공부했었다.
상태 관리 공부를 하기 전에, State와 Props에 간단히 정리해보자.
State는, 특정 컴포넌트 내부 안에서 선언되고 특정 컴포넌트가 갖는 상태(값)을 변경하기 위해 사용한다.
Props는, 부모 컴포넌트에서 자식 컴포넌트로 데이터(상태)를 전달하기 위해 사용한다. (데이터 변경 불가 only 전달용)
왜 상태 관리 공부를 하기 전에 State & Props에 대해 먼저 짚어본 것일까?
상태 관리란, State & Props 사용시 발생되는 단점을 보완하기 위해 사용하는 것이기 때문이다.

리액트는, 페이지를 수많은 컴포넌트들로 쪼개어 렌더링한다는 특징이 있다. 만약, 최상단에 위치한 컴포넌트에서부터 최하단에 위치한 컴포넌트로 특정 데이터(상태)를 보내고 싶을 경우엔 어떻게 하면 될까? 최상단에 위치한 컴포넌트에서 최하단에 위치한 컴포넌트로 props를 이용해 데이터를 전달해야 한다. 그러기 위해서는, 최상단 컴포넌트와 최하단 컴포넌트 사이에 위치한 중간 컴포넌트들이 props를 계속해서 넘겨받아야 하며, 중간 컴포넌트들은 사용하지도 않는 데이터를 계속해서 주고받아야 한다는 단점이 생긴다. 그런 데이터가 1개가 아니라 여러 개라면? 매우 지저분하고 비효율적일 것이다. 또한, 데이터가 변경된다면? 유지보수하기에도 힘들 것이다.
state는 특정 컴포넌트 내부 안에서 선언되어 그 상태(값)을 변경하기 위해 사용한다. 특정 컴포넌트 내부 안에서만 사용되기 때문에, 그 데이터를 여러 컴포넌트에서 or 전체 컴포넌트에서 사용하고 싶은 경우엔 사용할 수 없다.
리액트의 State & Props만 사용해서는 불편한 점이 꽤 많기 때문에, 이번 포스팅에서는 리액트에서의 효율적인 상태 관리에 대해 공부해볼 것이다. 그렇다고 해서 State & Props 사용을 지양하는 것은 아니다. 상황에 따라서 적절하게 사용하면 된다.
Cross-Component State와 App-Wide State일 때 Context API나 Redux 등을 사용한 상태 관리가 필요하다.
- Cross-Component State: 2개 이상의 컴포넌트 간에 공유되는 상태를 의미하여 props를 통해 상태를 전달
- App-Wide State: 애플리케이션의 전체 영역에서 사용되는 상태. 여러 컴포넌트 or 전체 영역에서 공유되어야 할 때 사용됨
리액트의 상태관리
- 라이브러리 사용(npm~ 설치 필요)
- redux, recoil, react-query, mobX
- 리액트 기본 기능 context API 사용
- useContext


context API
- 리액트에서 제공하는 기본적인 상태관리 기능
- useContext()라는 기본 hook 사용
- createContext(초기값)를 이용하여 새로운 context 생성
- context: 리액트에서 컴포넌트간 특정 값을 공유할 수 있도록 하는 기능 → state를 전역적으로 다룰 수 O
- 값을 사용할 컴포넌트를 context의 Provider로 감싸줌
- provider의 value props로 전역적으로 사용할 값 전달
- context를 사용하면 재사용하기 어려워질 수 있기 때문에 반드시 필요한 경우에만 사용할 것
이제, 라이브러리를 활용한 상태 관리에 대해 알아보자.
대표적으로 많이 쓰이는 라이브러리로는 Redux와 Recoil이 있다. 차이점으로는 Redux는 코드가 길고 대규모 프로젝트에 유리하다는 것이고 Recoil은 짧은 코드로 간편하게 상태 관리가 가능하다는 것이다. 현재 실무에서는 Redux가 더 많이 쓰이고 있는 추세이다.
이 포스팅에서는 Redux에 대해서만 자세히 공부해볼 것이다.

Redux
- Javascript 상태관리 라이브러리
- 리액트 뿐만 아니라 Angular, Vue 등..에서도 사용 가능
- 리액트의 상태 관리 라이브러리로 가장 많이 사용
- Redux를 사용하면, 전역으로 상태를 관리할 수 있게 되어 state를 props를 통해 전달하지 않고도 store라는 곳에서 꺼내어 언제 어디서든지 사용이 가능하게 됨
- Store, Action, Reducer, Dispatch
- 별도로 설치 필요: npm install redux react-redux
- redux 복잡성을 줄이기 위한 도구 설치: npm install @reduxjs/toolkit

Store(스토어)
- state가 관리되는 오직 하나의 공간
- store 안에는 현재 애플리케이션 state와 reducer가 들어가 있음
- 1개의 프로젝트는 단 1개의 store만 가질 수 있음
- store에 있는 데이터는 컴포넌트에서 직접 조작하지 않음 → reducer 함수 사용!
Action(액션)
- 상태 변경을 일으키는 이벤트에 대한 정적인 정보
- 타입과 데이터를 가진 하나의 객체
- 타입: 액션을 묘사하는 string (보통, domain명/이벤트내용 이런식으로 타입만으로 유추가 가능하게끔 짓는다)
- 데이터: payload
- dispatch 함수의 인자로 전달되어 dispatch를 통해 reducer에게 도착함
- reducer가 수행할 작업에 대한 정보를 담고 있음
Dispatch(디스패치)
- Reducer에게 Action을 전달해주는 함수
- Action 객체를 전달받은 Dispatch 함수는 Reducer를 호출함
- useDispatch()를 통해 생성
- dispatch를 실행하는 hook 함수
- 인자로 원하는 action 객체를 넘겨줘야 함
Reducer(리듀서)
- Dispatch에게서 전달받은 Action 객체의 type 값에 따라서 상태를 변경시키는 함수
- 첫 번째 매개변수는 현재 상태값, 두 번째 매개변수는 Action을 받음
- 항상 새로운 상태 객체 반환
- Http 요청, 데이터 저장 XXX ==>?
정리)

+) useSelector()는 컴포넌트와 state를 연결하여 Redux의 state에 접근할 수 있게 해주는 메서드로, 자주 사용된다.

useSelector()
- redux store의 state값을 조회하기 위한 hook
- 인자로 함수를 넘겨줘야 함
- 함수는 state를 매개변수로 받을 수 있고, return 값은 원하는 state 변수 값으로 설정하면 됨
Redux 원칙
- 애플리케이션 내에 store는 단 하나만 존재 가능
- state는 reducer만 변경 가능( state 직접 변경 불가능 → Action 객체가 있어야만 상태를 변경 가능)
- Reducer은 기존의 state를 변경하는 것이 아니라 새로운 state 객체를 리턴하는 것
참고
https://kindjjee.tistory.com/106