일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
29 | 30 | 31 |
- 프로그래밍 언어론
- 프로그래머스 완전탐색
- JavaScript
- design pattern
- 자바스크립트
- react
- 코딩테스트 고득점 Kit
- NextJS
- 장고
- 코틀린
- 데이터모델링과마이닝
- React JS
- vanillaJS
- websocket
- 리액트 훅
- 리액트
- 백준
- 코딩테스트 고득점 Kit 완전탐색
- react hook
- Java
- 자바
- 자바 공부
- useState
- 디자인 패턴
- codesandbox
- 컴퓨터 네트워크
- useEffect
- 프로그래머스
- react firebase
- 프로그래머스 자바
- Today
- Total
기록하는 개발자
[Redux] React+TypeScript 환경에 Redux 적용하기 본문
나는 지금 Springboot+react환경에서 프론트엔드를 맡아 졸업 프로젝트를 진행 중이다.
전부터 프로젝트 진행 시 react에서 useState hook을 사용할 때,
하위 컴포넌트가 state를 사용하기 위해서는
해당 state가 쓰이지 않는 중간 컴포넌트에도 state를 전달해야하는 방식이
정말 비효율적이라는 생각이 들었다.
이 점을 해결하고자 검색해봤을 때, 이름만 알고 있었던 'Redux'가 바로 그 역할을 한다는 것을 깨달았다.
개발을 직접 해보니 왜 써야하는지 이유를 스스로 체감하게 되는 것 같다.
Redux
1. store
- 전역에서 state를 관리해주는 공간으로 객체 형태이다.
2. action
- 상태 변화를 일으키는 주체이다.
- 객체 형태이고 타입이라는 것이 필수로 들어간다. 액션 생성 함수로 발행한다.
3. reducer
- 형태 : reducer(prevProps,action) => newState
- reducer는 action을 실행시키는 순수함수로,
현재 state와 들어온 action을 인자로 받아 새로운 state를 만들어 반환한다.
순수함수는 동일한 props가 들어오면 동일한 결과를 return해야 하며, 함수 내 존재하는 변수 외에 외부의 값을 참조, 의존하거나 변경하지 않아야 함. |
4. dispatch
- store의 내장함수 중 하나로, 호출 시 store는 reducer 함수를 실행시켜 새로운 함수를 생성한다.
→'redux'대신 "react-redux"를 사용하면 dispatch대신
useDispatch라는 hook을 사용하여 더욱 쉽게 action을 실행시킬 수 있다.
5. Provider
- 생성된 app이 store를 사용할 수 있도록 wrapping한다.
- App에 props로 store를 전달한다.
설치(app생성 후 과정만 기록)
// react
npm i redux react-redux
// typescript
npm i --dev @types/react-redux
내가 만든 Redux 폴더 구조
내가 적용할 action은 게시판의 boardList를 수정하는 역할이다.
useState를 사용했을 때는 [boardList,setBoardList]였다.
게시판의 필터링이 변경되면 서버에 axios.get 요청을 보낸다.
서버로부터 변경된 필터링에 해당하는 BoardList를 정상적으로 받아오면 action을 통해서 boardList의 상태를 변경한다.
Type.tsx
import { setBoardList } from "./Actions/changeBoardListAction";
export type Image = {
src: string;
};
export interface boardItem {
id: number;
type: string;
name: string;
image: string;
reviews: Array<Object>;
}
export type BOARDLIST = {
boardlist: Array<boardItem>;
};
export type changeBoardListAction = ReturnType<typeof setBoardList>;
- type 재사용을 위해 Types.tsx 파일을 만들어 필요한 type들을 export로 선언해준다.
- action의 return type도 정해준다.
Actions>changeBoardListActions.tsx
import * as type from "../Types";
export const SET_BOARDLIST = "changeBoardList/SET_BOARDLIST" as const;
export const setBoardList = (boardItemList: type.boardItem[]) => ({
type: SET_BOARDLIST,
payload: boardItemList,
});
1) "../Types" import
- "../Types" 에서 type이라는 이름으로 미리 선언해 놓은 type들을 import 한다.
2) Action Type 정의
export const SET_BOARDLIST = "changeBoardListAction/SET_BOARDLIST" as const;
- 이때의 type은 typescript의 type이 아니다.
- Action Type : redux action에 들어갈 Type을 정의해 주는 것이다.
- 나는 일단 action을 한 가지만 만들기 위해 Action Type을 SET_BOARDLIST 하나만 선언했지만
action이 수정, 삭제 두 개인 경우 CLEAR_BOARDLIST 등의 이름으로 action을 한 개 더 만들면 된다.
3) Action 생성 함수
export const setBoardList = (boardItemList: type.boardItem[]) => ({
type: SET_BOARDLIST,
payload: boardItemList,
});
- type : action의 type
- payload : Action 함수의 parameter(인자)
- 위 action setBoardList는 boardItem[]이라는 type의 배열인 boardItemList를 인자로 받고
action type이 SET_BOARDLIST action을 할 것이다.
Reducers>handleBoardList.tsx
- reducer 에는 앞서 선언한 action들이 구체적으로 어떤 기능을 하는지 기술한다.
import * as type from "../Types";
import { SET_BOARDLIST } from "../Actions/changeBoardListAction";
import produce from "immer";
import { createReducer } from "typesafe-actions";
export const initialState: type.BOARDLIST = {
boardlist: [],
};
//draft : 기존의 state, action : 새로운 action
const boardlist = createReducer<type.BOARDLIST, type.changeBoardListAction>(
initialState,
{
[SET_BOARDLIST]: (state, action) =>
produce(state, (draft) => {
draft.boardlist = action.payload;
}),
}
);
export default boardlist;
1) import
import * as type from "../Types";
import { SET_BOARDLIST } from "../Actions/changeBoardListAction";
import produce from "immer";
import { createReducer } from "typesafe-actions";
- 가장 먼저 Types.tsx에 선언했던 type들을 import 한다.
- typesafe-actions에서 createReducer를 import 한다.
npm i typesafe-actions로 설치
기존 : reducer를 switch/case문을 통해 작성
createReducer : reducer를 object의 형식으로 작성 가능
- immer에서 produce를 import
기존 : reducer 함수 작성 시 불변성 유지를 위하여 ... 문법을 사용하였다.
그러나, 큰 규모나 복잡한 함수 사용에서는 불변성 유지가 어렵다.
produce : 2개의 인자만 설정해주면 불변성을 스스로 관리해준다.
2) initialState 정의
export const initialState: type.BOARDLIST = {
boardlist: [],
};
필요한 state는 게시판에 띄울 boardlist 이므로 boardlist : [] 와 같이 초기값을 빈 배열로 선언했다.
3) Reducer 함수 구현
const boardlist = createReducer<type.BOARDLIST, type.changeBoardListAction>(
initialState,
{
[SET_BOARDLIST]: (state, action) =>
produce(state, (draft) => {
draft.boardlist = action.payload;
}),
}
);
export default boardlist;
.1 createReducer함수
type : state에 대한 Type(type.BOARDLIST)과 Action에 대한 Type(type.changeBoardListAction)이 필요하다.
parameter: initialState와 object({ }안 내용)가 들어간다.
.2 object
//Action Type : SET_BOARDLIST
[SET_BOARDLIST] : (state, action)
action : new action(새로 호출된 action 자체)
→ action을 통해 앞선 changeBoardListActions.tsx의 Action 생성 함수에서 지정해줬던 payload를 사용할 수 있다.
Action의 Type을 정의 후 아래와 같이 produce함수를 사용한다.
produce(state, (draft)) => { 함 수 작 성 }),
draft : 기존의 state
produce(state, (draft) => {
draft.boardlist = action.payload;
}),
- 기존의 boardlist(draft.boardlist)를 action의 인자(payload)로 변경한다.
rootReducder.tsx
- RootState를 정의하고 구현한 reducer를 combineReducers를 통해 합쳐준다.
import { combineReducers } from "redux";
import handleBoardList from "./handleBoardList";
const rootReducer = combineReducers({ handleBoardList });
export type RootState = ReturnType<typeof rootReducer>;
export default rootReducer;
reducer가 여러 개인 경우 아래처럼 모두 combineReducers에 넣어준다.
const rootReducer = combineReducers({
reducer1,
reducer2,
reducer3,
...
});
index.tsx
- redux는 기본적으로 store를 통해 state를 공유한다.
보통 store는 가장 상위 폴더인 index.tsx에 선언하여 모든 하위 태그들이 공유할 수 있도록 한다.
import React from "react";
import ReactDOM from "react-dom";
import reportWebVitals from "./reportWebVitals";
import "./index.css";
import App from "./App";
import { Provider } from "react-redux";
import { createStore } from "redux";
import rootReducer from "./Redux/Reducers/rootReducer";
ReactDOM.render(
<React.StrictMode>
<Provider store={createStore(rootReducer)}>
<App />
</Provider>
,
</React.StrictMode>,
document.getElementById("root")
);
reportWebVitals();
1) import
createStore : store를 생성하는 함수
Provider : 생성된 store를 모든 하위 태그가 공유할 수 있도록 하는 class
2) store 공유
- 1. store에 parameter가 rootReducer인 createStore함수의 return값을 넣어준다.
- 2. Provider 태그의 parameter로 store를 넣어준다.
위 두 단계 만으로 모든 하위 태그들이 이 store를 공유하게 된다.
component에서의 redux 사용 모습
1. 상태관리 대상인 상태(boardlist) 가져오기
import React, { useState, useEffect } from "react";
import "./board.css";
import axios from "axios";
import * as type from "../Redux/Types";
import { useSelector, useDispatch } from "react-redux";
import { setBoardList } from "../Redux/Actions/changeBoardListAction";
import { RootState } from "../Redux/Reducers/rootReducer";
const Board = () => {
const boardList = useSelector((state: RootState) => state.handleBoardList.boardlist);
...
return <div>...</div>
};
export default Board;
- useSelector hook을 이용하여 Rootstate중 handleBoardList의 boardlist를 가져온다.
useSelector() : 리덕스 스토어에 저장된 데이터를 추출하는 Hook |
2. action 사용하기
import React, { useCallback } from "react";
import axios from "axios";
import { useDispatch } from "react-redux";
import * as type from "../Redux/Types";
import { setBoardList } from "../Redux/Actions/changeBoardListAction";
function useGetAlcoholList(filterObj: filterState) {
//dispatch를 최상단 함수에서만 사용
const dispatch = useDispatch();
const setBoard = useCallback(
(boardItemList: type.boardItem[]) => dispatch(setBoardList(boardItemList)),
[dispatch]
);
const GetAlcoholList = () => {
axios({
method: "GET",
url: `[생략]`,
})
.then((res) => {
setBoard(res.data);
})
.catch((err) => {
console.log("리스트 가져오기 에러", err);
});
};
return { GetAlcoholList };
}
export default useGetAlcoholList;
1) import
import * as type from "../Redux/Types";
import { setBoardList } from "../Redux/Actions/changeBoardListAction";
- type와 action으로 작성한 setBoardList를 import 해온다.
2) action 사용
const dispatch = useDispatch();
const setBoard = useCallback(
(boardItemList: type.boardItem[]) => dispatch(setBoardList(boardItemList)),
[dispatch]
);
- useCallback과 useDispatch를 이용하여 setBoard에 action으로 지정했던 setBoardList를 저장한다.
useCallback(function(), array[]) : useMemo처럼 함수를 memoization하기 위해서 사용되는 hook 이다. 첫번째 인자(함수)를 두번째 인자(배열) 내의 값이 변경될 때까지 저장해놓고 재사용할 수 있게 해준다. |
useDispatch() : 리덕스 스토어에 설정된 action에 대한 dispatch를 연결하는 Hook |
공식 문서 : https://redux.js.org/usage/usage-with-typescript
redux 적용에 도움 받은 곳 : https://devlog-h.tistory.com/33
useCallback : https://www.daleseo.com/react-hooks-use-callback/
'Web > Redux' 카테고리의 다른 글
[Redux] useDispatch hook 에러 해결기(Error: Invalid hook call) (0) | 2022.03.07 |
---|