티스토리 뷰
목차
1. Redux
2. 리덕스 언제 써야 할까?
3. 리덕스 vs Context API
4. 리덕스에서 사용되는 키워드
5. 리덕스의 3가지 규칙
6. 리덕스 사용하기
1. Redux
리덕스는 리액트 생태계에서 가장 사용률이 높은 상태관리 라이브러리이다. 리덕스를 사용하면 컴포넌트들의 상태 관련 로직들을 다른 파일들로 분리시켜서 더욱 효율적으로 관리 할 수 있으며 글로벌 상태 관리도 손쉽게 할 수 있다.
리덕스에서는 리듀서와 액션이라는 개념을 사용한다. 따라서 개발 방식이 Context API와 useReducer Hook과 매우 유사하다.
참고로 redux 는 리액트에서 사용하기 위해 만들어진 라이브러리이긴 하지만 무조건 리액트와 함께 사용 할 필요는 없다. 일반 JavaScript 환경에서 사용 할 수도 있고 Angular 와 같은 다른 프레임워크에서도 사용되기도 한다.
2. 리덕스 언제 써야 할까?
- 프로젝트의 규모가 큰가?
- Yes: 리덕스
- No: Context API
- 비동기 작업을 자주 하게 되는가?
- Yes: 리덕스
- No: Context API
- 리덕스를 배워보니까 사용하는게 편한가?
- Yes: 리덕스
- No: Context API 또는 MobX
3. 리덕스 vs Context API
1. 미들웨어
리덕스에는 미들웨어(Middleware)라는 개념이 존재한다. 리덕스로 상태 관리를 할 때에는 useReducer에서 접했던 리듀서 함수를 사용한다.
또한 리덕스의 미들웨어를 사용하면 액션 객체가 리듀서에서 처리되기 전에 우리가 원하는 작업들을 수행 할 수 있다.
예시
- 특정 조건에 따라 액션이 무시되게 만들 수 있다.
- 액션을 콘솔에 출력하거나, 서버쪽에 로깅을 할 수 있다.
- 액션이 디스패치 됐을 때 이를 수정해서 리듀서에게 전달되도록 할 수 있다.
- 특정 액션이 발생했을 때 이에 기반하여 다른 액션이 발생되도록 할 수 있다.
- 특정 액션이 발생했을 때 특정 자바스크립트 함수를 실행시킬 수 있다.
미들웨어는 주로 비동기 작업을 처리 할 때 많이 사용된다.
2. 유용한 함수와, Hooks
리덕스에서는 connect 함수를 사용하면 리덕스의 상태 또는 액션 생성 함수를 컴포넌트의 props 로 받아올 수 있으며, useSelector, useDispatch, useStore 과 같은 Hooks를 사용하면 손쉽게 상태를 조회하거나 액션을 디스패치 할 수도 있다.
connect 함수와 useSelector 함수에는 내부적으로 최적화가 잘 이루어져있어서 실제 상태가 바뀔때만 컴포넌트가 리렌더링된다. 반면에 Context API를 사용할 때에는 그러한 최적화가 자동으로 이루어져있지 않기 때문에 Context 가 지니고 있는 상태가 바뀌면 해당 Context 의 Provider 내부 컴포넌트들이 모두 리렌더링된다.
3. 하나의 커다란 상태
Context API 를 사용해서 글로벌 상태를 관리 할 때에는 기능별로 Context를 만들어서 사용하는 것이 일반적이다 (물론 꼭 그렇게 할 필요는 없습니다). 반면 리덕스에서는 모든 글로벌 상태를 하나의 커다란 상태 객체에 넣어서 사용하는 것이 필수이다. 때문에 매번 Context를 새로 만드는 수고로움을 덜 수 있다.
4. 리덕스에서 사용되는 키워드
1. 액션 (Action)
상태에 어떠한 변화가 필요하게 될 때 발생시킨다. 하나의 객체로 표현되며 다음과 같은 형식을 띄고 있다.
{
type: "TOGGLE_VALUE"
}
액션 객체는 type 필드를 필수적으로 가지고 있어야하고 그 외의 값들은 개발자 마음대로 넣어줄 수 있다.
{
type: "ADD_TODO",
data: {
id: 0,
text: "리덕스 배우기"
}
}
2. 액션 생성함수 (Action Creator)
액션을 만드는 함수이다. 파라미터를 받아와서 액션 객체 형태로 만들어준다.
export function addTodo(data) {
return {
type: "ADD_TODO",
data
};
}
// 화살표 함수로도 만들 수 있습니다.
export const changeInput = text => ({
type: "CHANGE_INPUT",
text
});
이러한 액션 생성함수를 만들어서 사용하는 이유는 나중에 컴포넌트에서 더욱 쉽게 액션을 발생시키기 위함이다. 그래서 보통 함수 앞에 export 키워드를 붙여서 다른 파일에서 불러와서 사용한다.
리덕스를 사용 할 때 액션 생성함수를 사용하는것이 필수적이진 않다.
3. 리듀서 (Reducer)
리듀서는 변화를 일으키는 함수이다. 리듀서는 두가지의 파라미터가 있다.
function reducer(state, action) {
// 상태 업데이트 로직
return alteredState;
}
리듀서는 현재의 상태와 전달 받은 액션을 참고하여 새로운 상태를 만들어서 반환한다. 이 리듀서는 useReducer 를 사용할때 작성하는 리듀서와 똑같은 형태를 가지고 있다.
4. 스토어 (Store)
리덕스에서는 한 애플리케이션당 하나의 스토어를 만든다. 스토어 안에는 현재의 앱 상태와 리듀서가 들어가있고 추가적으로 몇가지 내장 함수들이 있다.
5. 디스패치 (dispatch)
디스패치는 스토어의 내장함수 중 하나이다. 디스패치는 액션을 발생 시키는 역할을 하며 dispatch 라는 함수에는 액션을 파라미터로 전달한다. ex) dispatch(action)
6. 구독 (subscribe)
구독 또한 스토어의 내장함수 중 하나이다. subscribe 함수는 함수 형태의 값을 파라미터로 받아온다. subscribe 함수에 특정 함수를 전달해주면 액션이 디스패치 되었을 때 마다 전달해준 함수가 호출한다.
리액트에서 리덕스를 사용하게 될 때 보통 이 함수를 직접 사용하는 일은 별로 없다. 그 대신에 react-redux 라는 라이브러리에서 제공하는 connect 함수 또는 useSelector Hook 을 사용하여 리덕스 스토어의 상태에 구독한다.
5. 리덕스의 3가지 규칙
1. 하나의 애플리케이션 안에는 하나의 스토어를 사용하자.
여러개의 스토를 사용하는 것이 가능하나 개발 도구를 활용하지 못하게 되기 때문에 권장되지 않는다.
하지만 특정 업데이트가 너무 빈번하게 일어나거나, 애플리케이션의 특정 부분을 완전히 분리시키게 될 때 여러개의 스토어를 만들 수도 있다.
2. 상태는 읽기전용이다.
리액트에서 state 를 업데이트 해야 할 때, setState 를 사용하고, 배열을 업데이트 해야 할 때는 배열 자체에 push 를 직접 하지 않고, concat 같은 함수를 사용하여 기존의 배열은 수정하지 않고 새로운 배열을 만들어서 교체하는 방식으로 업데이트를 한다.
리덕스에서도 기존의 상태는 건들지 않고 새로운 상태를 생성하여 업데이트 해주는 방식으로 해주면 나중에 개발자 도구를 통해서 뒤로 돌릴 수도 있고 다시 앞으로 돌릴 수도 있다.
리덕스에서 불변성을 유지해야 하는 이유는 내부적으로 데이터가 변경 되는 것을 감지하기 위하여 shallow equality 검사를 하기 때문이다. 이를 통하여 객체의 변화를 감지 할 때 객체의 깊숙한 안쪽까지 비교를 하는 것이 아니라 겉핥기 식으로 비교를 하여 좋은 성능을 유지할 수 있다.
Redux는 Shallow equality 검사로 상태의 변화를 감지한다.
서로 다른 객체(a, b)인 경우 단순히 a === b 인지만 체크해도 다른지 알 수 있고 이것을 shallow equality 검사라고 한다. 반대로 deep equality 검사는, 두 객체(a, b) 내부 프로퍼티까지 모두 같은지 일일이 체크를 하는 것을 말한다.
따라서 두 가지 방식 중 성능적으로 당연히 shallow equality 가 우위일 수 밖에 없다. 그러므로 리듀서에서는 상태를 변화시킬 때 이전 상태 객체의 값을 변경시키는 것이 아니라 아예 새로운 객체를 반환하는 것이다. 그리고 여기서 이전 상태 객체는 변경하지 않았으므로 불변성을 지켰다고 하는 것이다.
• 리덕스에서 shallow equalitiy checking을 하는 이유 :
https://redux.js.org/faq/immutable-data#how-do-shallow-and-deep-equality-checking-differ
3. 변화를 일으키는 함수, 리듀서는 순수한 함수여야 한다.
순수함수를 한마디로 정의해보자면 동일한 인자가 주어졌을 때 항상 동일한 결과를 반환해야 하며 외부의 상태를 변경하지 않는 함수이다. 쉽게 말하면 함수 내 변수 외에 외부의 값을 참조, 의존하거나 변경하지 않아야 한다.
아래 예제의 함수들은 모두 순수함수가 아니다.
let value = '123';
function func1(abc) {
value = '1234';
return abc + 1;
}
function func2(abc) {
return abc + value;
}
function func3(abc) {
abc = 123;
return abc + 1;
}
console.log(func1(12)); //13
console.log(func2(12)); //'12123'
console.log(func3(12)); //124
func1은 함수 외부의 value 변수를 수정하였고, func2는 함수 외부의 value 변수를 참조했다. 그리고 func3는 함수의 인자를 수정하였기 때문에 순수함수라고 할 수 없다.
이외 간단한 다른 경우는 서버에 데이터를 요청하여 그 값을 반환하는 함수역시 외부의 값을 참조하기 때문에 순수함수라고 할 수 없다.
그렇다면 Redux의 Reducer는 왜 순수함수로 구현해야 할까?
Redux의 State는 불변해야 한다는 특징을 가지고 있다. 불변한다는 것은 state가 변경되면 안된다는 뜻이 아니라 state가 수정되면 안된다는 뜻이다.
아래는 action에 따라 숫자를 증감시키는 counter reducer이다.
function counter(state = initialState, action) {
switch(action.type) {
case types.INCREMENT:
return { ...state, number: state.number + 1 };
case types.DECREMENT:
return { ...state, number: state.number - 1 };
default:
return state;
}
}
state의 값을 변경할때는 Spread Operator(...)으로 기존 state의 값들을 복사한 뒤 number라는 항목만 변경하여 state를 반환한다. 그 외에 default case에서는 state를 수정할 필요가 없기때문에 state 그대로를 반환한한다.
이렇게 reducer를 구현할 때 인자로 들어온 state를 직접 수정하지 않고 복사본을 만들어 수정하는 이유는 redux의 변경 감지 알고리즘 때문이다.
redux는 reducer를 거친 state가 변경됐는지를 검사하기 위해 state 객체의 주소를 비교한다. state의 복제본을 만들어 반환하면 이전의 state와 다른 주소값을 가르키기 때문에 state가 변경되었다고 판단한다. 반대로 state를 복제하는것이 아닌 속성만 수정하여 반환하면 기존의 state 객체와 가리키는 주소값이 같기 때문에 변경감지가 되지 않는다.
Redux가 이렇게 설계 된 이유?
그렇다면 왜 객체의 속성 값들을 비교하지 않고 주소를 비교하는걸까 라는 의문이 생긴다. 그 이유는 성능과 복잡성에 있다.
객체의 속성으로 비교하는 것은 깊은 비교(deep-compare)라고 한한다. 이는 개발자에게 더 편리할 수 있지만 state 객채에 모든 속성에 대해 변화를 감지하기 위해선 복잡하고 무거운 알고리즘이 필요하다.
6. 리덕스 사용하기
1. react-redux
yarn add react-redux 또는 npm install react-redux
- state를 조회하기 위한 useSelector를 사용할 수 있다.
- action을 발생시키기 위한 useDispatch를 사용할 수 있다.
2. useSelector
- connect함수를 이용하지 않고 리덕스의 state를 조회할 수 있다.
import { useSelector } from 'react-redux'
const user = useSelector(state => state.user);
3. useDispatch
- 생성한 action을 useDispatch를 통해 발생시킬 수 있다.
- 만들어둔 액션생성 함수를 import한다.
import { change_user } from '../modules/user'
import { useDispatch } from 'react-redux'
const dispatch = useDispatch();
const User = () => {
...
dispatch(change_user(user));
...
}
Todo List에 적용해보기
참고
'Client > React.js' 카테고리의 다른 글
[React] Context API (0) | 2023.08.31 |
---|---|
[React] Axios 인터셉터 사용법 (0) | 2023.08.30 |
[React] useCallback (0) | 2023.08.25 |
[React] 1. React-app 설치방법 (0) | 2023.08.21 |
[React] useMemo (0) | 2023.08.18 |
- Total
- Today
- Yesterday
- 객체
- 비제어 컴포넌트
- react
- 스프린트프론트엔드6기
- javascript
- hydrationboundary
- CSS
- 동기
- map
- 코드잇 스프린트
- html
- Next.js
- Git
- 코드잇스프린트
- GitHub
- currentTarget
- 배열
- 제어 컴포넌트
- 비동기
- 유사배열객체
- innerhtml
- 프론트엔드
- rest parameter
- 리액트
- arguments
- 중급 프로젝트
- Target
- js
- 취업까지달린다
- tanstackquery
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |