전역적으로 상태를 관리하는 것. 웹 어플리케이션을 개발할 때에 상태(데이터)를 어떤 유형의 UI로 가공하는지가 핵심적인 문제이다. 리액트는 데이터를어떻게 돔으로 잘 변형할 것인가를 보면 상태(데이터)관리가 굉장이 중요하다.
개별 Component는 외부와 dependency가 없어야 한다. 컴포넌트는 재사용성의 구조인데 dependency가 있다면 재사용이 힘들어진다. dependency가 있다면 큰 규모의 프로젝트에서는 뎁스가 깊어져 불필요한 프랍들링이 생기게 된다. 변하니까 상태, 변하지 않으면 데이터. 이런 표현의 차이가 있다.
action type
어떤 액션이 있으면 좋을지 정의를 해놓는다.
export const INC_COUNT = "INC_COUNT";
export const DEC_COUNT = "DEC_COUNT";
문자열이 아니고 변수로 선언한 이유는 오타방지를 할 수 있다. 디버깅툴이 에러를 표시해주기 때문이다.
{ type: INC_COUNT, payload: ~~ }
인 action 객체를 reducer
로 보내는 행위를 dispatch
라고 표현한다. action 생성함수는 dispatch 에 action 객체를 return 하는 함수를 전달한다. 왜 생성함수를 쓰냐면 넘길 데이터를 명시적으로 보기 위해서다.
export function incCount(diff) {
return {
type: INC_COUNT,
payload: {diff},
}
}
export function decCount(diff) {
return {
type: DEC_COUNT,
payload: {diff},
}
}
dispatch(incCount(2))
// reducer
function counter(state = initialState, action) {
switch (action.type) {
case INC_COUNT:
return state + 1
case DEC_COUNT:
return state - 1;
default:
return state;
}
}
store
는 하나의 app에는 하나의 스토어가 있는게 규칙이다. store란 앞서 만든 리듀서와 상태를 담아놓은 것을 말한다. store 를 Provider 라는 것을 통해서 컴포넌트에 공유함으로써, 어떤 컴포넌트에서든 리듀서와 상태를 가져와서 사용할 수 있도록 코드를 작성한다.
// store
import { legacy_createStore as createStore } from "redux";
import counter from "./reducers/counter";
const store = createStore(counter);
export default store;
// app.js
import { Provider } from "react-redux";
import store from './redux';
function App() {
return (
<Provider store={store}>
</Provider>
);
}
export default App;
legacy_createStore 가 아닌 그냥 createStore 의 경우 deprecate 되었다. redux 개발자들이 redux toolkit 사용을 권장하기 위해서 해당 함수를 deprecate 시켰으나, 사용 빈도를 보면 createStore 를 쓰는 경우도 아직 많은 것으로 보인다고 한다.
해당 컴포넌트에서 쓰게 된다. useSelector
는 redux 로 관리 중인 상태를 가져오기 위한 Hook이다. useDispatch
는 dispatch 함수를 실행할 수 있도록 해주는 Hook이다.
데이터를 처리하고, 받아오는 부분 (기능) 은 Container 컴포넌트
에서 담당하고, 데이터를 보여주는 부분 (UI) 은 Presenter 컴포넌트
에서 담당하도록 분리한다.
redux를 쓰면 꼭 이 패턴을 써야하는 건 아니다. 파일을 계속 따로 만드는게 개발자의 피로도를 올릴 수도 있기 때문에 취향에 맞게 디자인 패턴을 쓰면 될거 같다.
ducks 패턴이란 파일을 분리하지 않고 하나의 파일에다가 모든 코드를 작성한다.
Container Presenter 패턴에서는 파일3개가 필요했던게 ducks 패턴에서는 한개 파일만 있으면 된다.
// 액션 타입
const INC_COUNT = "counter/INC_COUNT";
const DEC_COUNT = "counter/DEC_COUNt";
// 액션 생성 함수
export function incCount(diff) {
return {
type: INC_COUNT,
payload: {diff},
}
}
export function decCount(diff) {
return {
type: DEC_COUNT,
payload: {diff},
}
}
// 초기값
const initialState = {number: 0};
// 리듀서 선언
export default function counter(state = initialState, action) {
switch (action.type) {
case INC_COUNT:
return {
...state,
number: state.number + action.payload.diff
}
case DEC_COUNT:
return {
...state,
number: state.number - action.payload.diff
};
default:
return state;
}
}
store 파일까지
import { legacy_createStore as createStore } from "redux";
import counter from "./counter";
const store = createStore(counter);
export default store;
콘솔창에 기록을 남겨주는 역할을 담당하는 리덕스 미들웨어중에 하나다. (미들웨어라고 해서 특별한 것이 아니고, 그냥 리덕스가 작동하는 과정에서 store, action 객체를 다루면서 특정한 기능을 수행하는 함수를 말합니다)
redux-thunk 는 리덕스에서 비동기 작업을 처리할 수 있도록 도와주는 미들웨어입니다. 정확히 말하자면, 해당 미들웨어를 사용하면 액션 객체가 아니라 함수 그 자체를 디스패치할 수 있도록 해줍니다.
기존 액션 생성함수는 액션객체만 리턴하고 끝이였는데 axios로 데이터를 받아서 리턴하고자 한다.
export const fetchData = async () => {
const response = await axios.get('url')
return {
type: 'FETCH_DATA',
payload: response.data
}
}
// action must be plain objects. Use custom middleware for async actions
그럼 에러가 뜨게되서 redux-thunk가 필요하다.
redux-thunk 가 작동하는 방식에 대해서 풀어서 설명하자면,
export const fetchData = () => {
return async function(dispatch, getState) {
const response = await axios.get('url')
return {
type: 'FETCH_DATA',
payload: response.data
}
}
}