📍
아마 요즘 내가 가장 신경쓰고 있는 것 중 하나가 상태관리 일 것이다.
리액트 프로젝트를 하다보니 상태관리의 중요성을 알게 되었고
상태관리에 대해 알아보며 상태관리 라이브러리에 대해 관심을 갖게 되었다.
오늘 배운 리덕스는 사실 이제는 조금 지나간(?) 느낌의 라이브러리라는
소리만 듣고 안써야지 막연히 생각만 하고 있었는데 내가 어떤 라이브러리를
쓸 지 모르는 상황에서 해당 라이브러리의 아키텍처를 배워두는건 중요하다는 말을 들었다.
리덕스를 시작으로 다양한 라이브러리에 대해 배우고 정리해보려고 한다.
Redux 시작하기 | Redux
소개 > 시작하기: Redux를 배우고 사용하기 위한 자료
ko.redux.js.org
리액트 상태관리가 필요한 이유
- 상태, 상태 변경기능이 부모 컴포넌트에 집중되어 있기 때문이다.
- 자식 컴포넌트에서 이벤트가 발생하면 props로 전달받은 메서드가 호출되고 부모 컴포넌트의 상태변경이 일어나면서 리렌더링 된다.
- 리액트의 장점 중 하나가 상태변경 추적이 용이하다는 것인데 이렇게 하면 쉽게 UI를 예측할 수 있다.
- 하지만 프로젝트 규모가 커지기 시작하면 부모 컴포넌트에서 자식 컴포넌트로 props를 계속 전달해야하는 props drilling이 생기면서 전달만 하는 컴포넌트가 생기기 시작하고 코드를 유지보수하는데 악영향을 준다.
- 따라서 리액트에는 상태관리 기능이 필요하다.
💡 리액트 상태관리 라이브러리
- Redux
- Redux toolkit
- Mobx
- Recoil (참고로 Recoil은 개발자가 퇴사하는 이슈(?)로 더 이상 업데이트 하지 않는다는 이야기가 있다.)
- Zustand
이렇게 많은 상태관리 라이브러리가 있는데 그 중 무엇을 쓰는게 좋을까?
→ 모두 다 배워두는 것이 좋다. 어떤 프로젝트든 내 마음대로 기술 스택을 정할 수 있는 경우는 거의 없으니 어느 것이든 사용할 준비가 되어있어야 한다. 모든 라이브러리의 아키텍처를 이해하고 있는 것이 좋다.
Redux란?
- 현시점 가장 많이 쓰이고 있는 상태관리 라이브러리 중 하나이다.
- 리덕스를 이해하고 있다면 다른 상태관리 라이브러리들을 이해하는데 수월할 것이다.
- 리덕스는 리액트를 위한 상태관리 라이브러리는 아니다.
- 자바스크립트 앱에서 UI 상태, 데이터 상태를 관리하기 위한 도구 이다.
- Flux의 아키텍처를 발전시키면서 복잡성을 줄였다.
- Flux 패턴이란? 데이터를 중앙 집중형 스토어에 저장하고 Action을 통해 데이터를 조작하는 패턴
Redux가 제공하는 기능
- Flux 기능
- Hot Reloading ✨
- 시간 여행 디버깅 (Time Travel Debugging) ✨
Redux의 특징
- Store
- Hot Reloading 기능
- 상태와 상태 변경 기능인 Reducer을 분리하여 리듀서 코드가 변경되어도 상태가 초기화 되지 않고 유지됨
- 상태 변경
- 불변성 필수
- 상태 변경 추적
- 시간여행 디버깅 가능
- 리덕스는 별도의 객체를 가지고 있기 때문에 이전 상태로 돌아가 작업하는 것이 가능하기 때문에 상태 변경 추적이 용이하다.
- 불변성이 필요한 이유?
- 렌더링 최적화
- 상태 변경 추적 용이
리덕스의 장점과 단점
- 장점 : 에러가 나도 추적이 가능해서 안정성이 높아진다.
- 단점 : 코드량이 많아진다.
📌 컴포넌트 내에서 데이터 요청과 같은 로직을 처리하는것 보다 별도의 라이브러리를 사용해서 데이터를 처리하는 것이 좋다. → 비동기 요청 처리 작업은 리덕스 사가와 같은 것을 쓰는게 좋다.
Redux의 원리와 불변성
예를들어 이러한 사용할 수 있는 데이터가 있다고 하자.
{
name: "jay",
age: 20,
password: 1234
}
이 데이터는 조회 뿐만 아닌 수정, 쓰기, 삭제 등과 같이 데이터를 변경할 수 있다.
만약 내가 name을 변경하고 싶다면 리덕스에서는 action을 만들어 데이터를 변경할 수 있다.
{
// type은 액션의 이름
type : "CHANGE_NICKNAME",
data: "jayjay"
}
그리고 난 뒤 변경하려면 해당 action을 dispatch 해서 변경할 수 있다.
- 이렇게 해주면 neme이 babo에서 boogicho로 변경됨. 그리고 이 데이터를 사용하고 있던 컴포넌트들의 데이터도 자동으로 업데이트 됨
- 이때, action을 무작정 dispatch한다고 데이터가 변경되는 것이 아니라 어떻게 변경될지 사용자가 하나하나 정의해줘야되는데 그게 reducer 이다.
📌 리덕스는 redux devtools를 사용하면 앞과 전의 상태 추적이 가능하다. 예를들어 로그인 했을 때, 로그아웃 됐을 때 등과 같이 모든 기록이 히스토리로 쌓이기 때문에 테스트 하기 좋다.
불변성(Immutability)
{} === {} // false
const a = {};
const b = a;
a === b // true
- 객체를 새로 만든건 false 객체를 참조하면 true. 이 개념을 잘 생각해야한다.
switch (action.type) {
case "CHANGE_NICKNAME" :
return {
...state,
name: action.name
}
}
- {} === {} 과 같이 새로운 객체와 비교를 하면 false가 나오는 것 처럼 return 뒤에 있는 {} 객체는 항상 다른 객체를 반환하는 것이라고 생각하면 된다.
- 즉, …state로 기존이 데이터는 그대로 name:action.name 처럼 내가 바꾸고 싶은 것은 바꾸는거라고 생각하면 된다.
💡 객체를 항상 새로 만들어주는 이유?
const prev = { name: "zerocho" }
const next = { name: "bogicho" }
새롭게 객체를 생성해줘야 전 기록, 새로운 기록이 쌓여가기 때문이다.
예를들어,
const next = prev;
next.name = "boogicho";
prev.name; // "boogicho"
이처럼 참조형으로 데이터를 바꿔버리면 기존에 있던 데이터가 새로운 데이터로 덮어지는 것 즉, 전에 있던 기록이 사라지고 새로 만든 데이터만 쌓이기 때문에 히스토리가 사라진다.
💡기존 데이터를 다 쓰지않고 …state를 사용하는 이유는?
코드의 복잡성을 줄이는 것도 있지만 메모리를 절약할 수 있기 때문이다.
💡 Redux 아키텍처 처리 흐름
- 컴포넌트의 이벤트 핸들러 속성으로 주입된 함수가 호출된다.
- 이때, action(객체, 메세지)를 만들어 store로 전달(dispatch)한다.
- { type: "addTodo", payload : { todo: "야식먹기", desc:"치킨을 먹자" }}
- store는 전달받은 액션과 자신 상태를 Reducer 함수의 인자로 전달한다.
- (state, action) => { ... }
- Reducer는 인자로 전달받은 상태(state)를 변경하지 않고 action을 이용해 새로운 상태를 만든 뒤 return 한다.
- store는 reducer가 return한 상태를 새로운 상태로 설정한다.
- store의 새로운 상태가 연결되어 있는 컴포넌트에 전달되며 화면이 리렌더링 된다.
Redux의 구성요소
- store ✨
- 단일 스토어 (단 하나의 스토어가 존재)
- 내부 상태는 읽기 전용(read only)
- 모든 action은 store를 거쳐감
- 따라서 store만 확인하면 상태 변경 이력, 데이터 흐름등 상태 추적에 필요한 정보를 얻을 수 있음
- 모든 상태를 한곳에서 관리하면 상태와 변경하는 작업이 복잡해지기 때문에 상태만 store가 관리하고 상태 변경 작업은 모두 reducer에게 넘긴다.
- reducer ✨
- 다중 리듀서 (여러개의 리듀서 존재 가능)
- 계층적으로 구성해야하며 상태 트리 설계가 중요
- reducer는 순수 함수이다.
- 순수 함수란? 부수효과가 없는 함수
- 입력 인자가 동일하면 리턴 값도 동일해야 한다.
- 부작용 즉 부수효과가 없어야 한다.
- 외부의 값을 이용하거나 외부에 영향을 줄 수 없다.
- 함수에 전달된 인자는 불변성으로 여겨지기 때문에 변경할 수 없다.
- 가장 대표적인 순수함수는 Array의 reduce 메서드이다.
- Action Creators
- 액션을 생성하는 역할
- 액션이란 상태를 변경하기 위해 전달하는 객체 형태의 메세지이다.
- 다중 리듀서
- 애플리케이션의 상태가 더 복잡해지면 리듀서의 상태 변경기능도 많아지면서 복잡해진다.
- 결국 하나의 리듀서로 처리가 불가능한 상태가 된다.
- 따라서 여러 개의 리듀서(다중 리듀서)로 분리시켜야 한다.
- combineReducers()
Redux 컨테이너 컴포넌트
- store와 연결되는 컴포넌트는 표현 컴포넌트(Presentation Component)이다.
- 표현 컴포넌트에 store의 상태와 action을 전달해주는 기능을 넣을 수 있는 컨테이너 컴포넌트를 생성해야 한다.
- react-redux 라이브러리가 제공하는 고차함수 : connect()
- react-redux 라이브러리가 제공하는 훅 : useSelector() 등등...
💡 이때, 모든 표현 컴포넌트에 대해 컨테이너 컴포넌트를 생성해야 할까?
아니다. 주요 거점 컴포넌트에 대해서만 컨테이너 컴포넌트를 작성한다. 여기서 주요 거점 컴포넌트란 소규모 메뉴, 화면, 화면 레이아웃의 최상위 컴포넌트이다. 이때 재사용성을 고려해야한다.
react-redux 라이브러리가 제공하는 훅
- 보통 훅을 사용하는 것이 더 직관적이다.
- useStore() : 스토어 객체를 리턴한다. 스토어의 상태를 읽으려면 이 객체의 getState() 함수를 이용한다.
- useDispatch() : 스토어의 dispatch 함수를 리턴한다. 리턴받은 함수를 이용해 액션을 스토어로 전달한다.
- useSelector() : 스토어의 특정 상태를 선택하여 리턴한다.
Redux 사용하기
리덕스 설치하기
- npm install redux
- npm install react-redux
- npm install @reduxjs/toolkit
- 한번에 설치해도 됨 npm install redux react-redux @reduxjs/toolkit
Redux Devtools
- Redux를 이용한 앱을 개발할 때 개발을 강력하게 지원하는 개발 패키지 도구
- Redux의 상태와 액션 정보를 시각화하며, 상태 변경을 추적할 수 있도록 한다.
- 툴킷에는 이미 미들웨어로 등록이 되어있다.
- 개발 환경일 때만 사용하도록 하기위해 따로 스토어에 설정하면 된다.
- 시간여행 디버깅을 가능하게 한다.
예를들어 로그인기능에 데브툴즈를 사용한다고 해보자.
요렇게 기록이 됨!
여기서 State 탭, Diff 탭을 많이 사용하는데 state탭은 전체 상태, diff는 변경 내용
✨ 내가 로그인, 로그아웃 할때마다 기록이 쌓여서 히스토리를 확인할 수 있다.
ump를 클릭하면 과거 상태, 미래상태로 돌아갈수 있음 → 이렇게 테스트를 해볼수 있는것!
시간여행 기능을 통해 디버깅을 쉽게 할 수 있다. → 추적가능
💡 단, 불변성은 꼭 지켜줘야함! 그래야 변화를 감지하고 히스토리를 쌓을 수 있음!
📌
원형섭 강사님 실시간 강의
'Front-end > React' 카테고리의 다른 글
[React] Redux Middleware는 어떤 용도로 사용될까? (0) | 2024.01.18 |
---|---|
[React] 상태관리 라이브러리 - zustand (0) | 2024.01.17 |
[React] 리액트 불변성이 필요한 이유 (1) | 2024.01.10 |
리액트에서 checkbox 오류 해결하기 (0) | 2023.12.28 |
[React] CSR (클라이언트 사이드 렌더링) 과 SSR (서버 사이드 렌더링) (0) | 2023.03.07 |