1. 상태 관리의 필요성
React에서 상태는 상황에 다라 변하는 데이터이다. 단순한 웹 페이지라도 수많은 상태가 있고, 우리가 어떤 동작을 취하는냐에 따라 상태들이 계속해서 바뀐다.
하지만, 그동안 우리는 상태를 최상위 컴포넌트에 설정했고, 하위 컴포넌트로 내려주기 위해서는 props를 통한 state 끌어올리기 방식을 사용했었다.
참고
2023.04.06 - [코드스테이츠] - [FE_Bootcamp] 35일차_React 데이터 흐름
[FE_Bootcamp] 35일차_React 데이터 흐름
블로그를 무려 8일만에 작성하게 되었다. 여러 원인들이 있었지만,.,.,..,,, 일단 지금 배우고 있는 웹 서버가 너무 어렵다,..,.,.,,, 할일 많음 + 수업시간 외 공부의 연타로 블로그를 쓸 여유가 없었
kinggh.tistory.com
간단히 요약하자면, 상태 변경 함수를 props로 내린 뒤, 하위 컴포넌트에서 상태 변경함수를 실행하면 되는 것이었다.
하지만 자식 컴포넌트가 한두개가 아닌 여러개라면?
최상위 컴포넌트에서 노란색 컴포넌트로 가기 위해서는, 파란색과 보라색 컴포넌트를 거쳐가야 한다.
이러한 구조에서 state를 props로 전달하려면 파란색 컴포넌트에 props를 전달하고, 파란색 컴포넌트는 다시 보라색 컴포넌트에 props를 전달하고, 또 마지막으로 노란색 컴포넌트에 props를 전달해야 만 한다.
이렇게 컴포넌트를 계속 거쳐가며 props를 전달하는 것을 Props Drilling이라 한다.
컴포넌트가 적어 props의 전달 횟수가 적은 경우에는 상관 없지만, 아닌 경우 Props Drilling은 가독성을 해치고 유지보수를 어렵게 만든다는 단점이 있다. 또한 state 변경 시 Props 전달 과정에서 불필요하게 관여된 컴포넌트들 또한 리렌더링이 발생해 웹성능에 악영향을 줄 수 있다.
2. Redux
Props Drilling 문제를 해결하기 위해 등장한 것이 바로 Redux 라이브러리이다.
아래 이미지에서 빨간 컴포넌트의 state를 파란 컴포넌트로 전달한다고 해보자
props로 상태 변경 함수 내려주기 → state 끌어올리기를 통해 최상위 컴포넌트로 state를 전달 → 다시 props로 상태 전달
이라는 매우 복잡한 과정을 거친다.
하지만 Redux를 사용하면 위 과정을 간소화하고, 매우 간단하게 상태를 전달할 수 있다.
Redux는 'store'라는 상태 저장소를 이용해 상태를 관리한다.
이제부터 Redux가 어떻게 상태를 관리하는지 알아보자
Redux는 다음과 같은 순서로 상태를 관리한다.
- 상태가 변경되어야 하는 이벤트가 발생하면, 변경될 상태에 대한 정보가 담긴 Action 객체 생성
- Action 객체는 Dispatch 함수의 인자로 전달
- Dispatch 함수는 Action 객체를 Reducer 함수로 전달
- Reducer 함수는 Action 객체의 값을 확인하고, 그 값에 따라 전역 상태 저장소 Store의 상태를 변경
- 상태가 변경되면, React는 화면을 다시 렌더링
이를 그림으로 나타내보면 다음과 같다
Redux를 통해 버튼을 누르면 숫자가 올라가거나 내려가는 애플리케이션을 한번 만들어 보자.
A. Store
Store는 상태가 관리되는 저장소이다. 즉, state들이 저장되어 있는 공간이다.
Store는 reateStore 메서드를 활용해 Reducer를 연결해서 생성할 수 있다.
import { createStore } from 'redux';
const store = createStore(reducer);
import { Provider } from 'react-redux';
.
.
.
const store = createStore(reducer);
root.render(
<Provider store={store}>
<App />
</Provider>
);
B. Reducer
Reducer는 Dispatch에게서 전달받은 Action 객체의 type 값에 따라서 상태를 변경시키는 함수이다.
인자로는 변경할 state와 Action 객체를 받으며, if문이나 switch문을 통해 Action 객체의 type을 구분하고, 해당 type에 맞는 상태 변경을 실시한다.
const count = 0
const reducer = (state = count, action) =>{
if(action.type === 'PLUS'){
return state + 1
} else if(action.type === 'MINUS'){
return state - 1
} else {
return state
}
}
이 reducer가 리턴하는 값이 새로운 state가 된다.
이때, reducer은 특정 입력에 대해 같은 값을 내보내야 하기 때문에 '순수함수'가 되어야 한다.
만약 여러 개의 Reducer를 사용하는 경우, Redux의 combineReducers 메서드를 사용해서 하나의 Reducer로 합쳐줄 수 있다.
import { combineReducers } from 'redux';
const rootReducer = combineReducers({
counterReducer,
anyReducer,
...
});
C. Action
Action은 이벤트가 일어났을때, 상태를 어떻게 변경시킬지 정의해 놓은 객체이다.
Action 객체를 만들 때에는 type 은 필수로 지정을 해 주어야 한다. type은 Action 객체가 어떤 동작을 하는지 명시해 주고, Reducer에서도 이 type의 값을 통해 상태를 변경해주기 때문에 반드시 있어야 한다. 여기에 필요에 따라 payload를 작성해 구체적인 값을 전달해 준다.
보통 Action을 직접 작성하기보다는 액션 생성자(Action Creator)라고 부르는 Action 객체를 생성 함수를 만들어 사용하는 경우가 많다.
const plus = () =>{
return {
type : 'PLUS'
}
}
const minus = () =>{
return {
type : 'MINUS'
}
}
4. Dispatch
Dispatch는 Reducer로 Action을 전달해 주는 함수이다. Dispatch의 전달인자로 Action 객체가 전달되고, Dispatch를 통해 전달받은 Action 객체를 Reducer로 전달한다.
Dispatch는 useDispatch()를 통해 만들 수 있다. useDispatch()는 Action 객체를 Reducer로 전달해 주는 Dispatch 함수를 반환하는 메서드로, 객체를 Action 객체를 직접 넣거나 Action Creator를 넣어 사용할 수 있다.
dispatch는 주로 이벤트가 일어나는 곳에 사용된다. 예를 들어 우리는 버튼을 누르면 state가 변경되게 만들 것이기 때문에, 버튼을 클릭했을 때 Action 객체를 만들고 Dispatch로 Reducer에 보내주면 된다.
import { useDispatch } from 'react-redux'
const dispatch = useDispatch()
const clickPlusButton = () => {
dispatch(plus)
}
const clickMinusButton = () => {
dispatch(minus)
}
5. useSelector
useSelector()는 컴포넌트와 state를 연결하여 Redux의 state에 접근할 수 있게 해주는 메서드이다.
import { useSelector } from 'react-redux'
const count = useSelector(state => state)
현재 store에 저장되어 있는 state를 받아오고, 이를 count 변수에 저장한다.
전체 코드는 다음과 같다
//index.js
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
import { Provider } from 'react-redux';
import { legacy_createStore as createStore } from 'redux';
const rootElement = document.getElementById('root');
const root = createRoot(rootElement);
export const plus = () =>{
return {
type : 'PLUS'
}
}
export const minus = () =>{
return {
type : 'MINUS'
}
}
const count = 0
const reducer = (state = count, action) =>{
if(action.type === 'PLUS'){
return state + 1
} else if(action.type === 'MINUS'){
return state - 1
} else {
return state
}
}
const store = createStore(reducer);
root.render(
<Provider store={store}>
<App />
</Provider>
);
//App.js
import React from 'react';
import { plus, minus } from './index.js';
import { useDispatch, useSelector } from 'react-redux';
export default function App() {
const dispatch = useDispatch()
const count = useSelector((state) => state);
const plusNumber = () => {
dispatch(plus())
};
const minusNumber = () => {
dispatch(minus())
};
return (
<div className="container">
<h1>{`Count: ${count}`}</h1>
<div>
<button onClick={plusNumber}>
+
</button>
<button onClick={minusNumber}>
-
</button>
</div>
</div>
);
}
3. Redux의 3가지 원칙
1. Single source of truth
동일한 데이터는 항상 같은 곳에서 가지고 와야 한다. 즉, Redux에는 데이터를 저장하는 Store라는 단 하나뿐인 공간이 있음과 연결이 되는 원칙.
2. State is read-only
상태는 읽기 전용이라는 뜻으로, React에서 상태갱신함수로만 상태를 변경할 수 있었던 것처럼, Redux의 상태도 직접 변경할 수 없음을 의미한다. 즉, Action 객체가 있어야만 상태를 변경할 수 있음과 연결되는 원칙.
3. Changes are made with pure functions
변경은 순수함수로만 가능하다는 뜻으로, 상태가 엉뚱한 값으로 변경되는 일이 없도록 순수함수로 작성되어야 하는 Reducer와 연결되는 원칙.
'Frontend Study' 카테고리의 다른 글
[FE_Bootcamp] 53일차_Web 접근성 (0) | 2023.04.28 |
---|---|
[FE_Bootcamp] 52일차_Web 표준 (1) | 2023.04.28 |
[FE_Bootcamp] 31일차_클라이언트-서버 통신 (0) | 2023.04.24 |
[FE_Bootcamp] 46일차_Component Driven Development (0) | 2023.04.20 |
[FE_Bootcamp] 35일차_Effect Hook (0) | 2023.04.17 |