본문 바로가기
Development/Front-end

Typescript에서 Redux를 사용해보자

by 차암새 2020. 11. 6.

안녕하세요 휴몬랩 초보 개발자 차암새입니다.

 

이번 시간에는 Typescript 환경에서 Redux를 사용해보겠습니다.

Redux는 React 상에서 state관리가 힘들 수 있는 부분을 보완하기 위해 만들어졌습니다.

※혹시라도 Redux에 대한 개념이 부족하신 분은 devlog-h.tistory.com/26를 참고해주세요

 

이 튜토리얼에서는 TODO리스트를 만들지만 기존의 다른 예제들과는 조금 다른 방식으로 만들어 볼 예정입니다.

바로 'typesafe-actions' 패키지를 활용한 방법입니다! 기존의 redux-saga 등을 활용하는 방식은 사용하다 보면 불편한 점이 많아 이 패키지를 이용하시는 것 또한 추천드립니다. 그럼 시작해보겠습니다.

github.com/piotrwitek/typesafe-actions (typesafe-actions링크)


1. 프로젝트 생성하기

우선 제일 먼저 프로젝트를 생성해봅시다.

기본적으로 저희는 typescript 환경에서 redux를 사용할 것이기 때문에 typescript기반의 react파일을 생성해야 합니다.

npx creat-react-app [fileName] --template typescript
cd [fileName]
npm install @types/react-redux
npm install typesafe-actions

기존에는 npm install react-redux로 설치가 가능했지만 저희는 typescript 환경에서 사용할 것이기 때문에

앞에 '@types/'를 붙여줌으로써

 


2. Redux 파일 구성하기

자, 이번에는 redux용 module 파일을 만들어주고, 그 안에 차례로 action, reducer, type 파일을 만들어 봅시다.

위 사진과 같이 src 하위 폴더로 modules폴더를 만든 뒤, index.tsx를 생성하고

modules의 하위 폴더로 todo폴더를 만든 뒤, actions.ts, reducer.ts, types.ts를 생성해줍니다.

 


3. actions 구현

이제 폴더와 파일이 모두 만들어졌으니 action을 정의할 차례입니다.

저희가 필요한 action은 todo를 새로 생성할 ADD_TODO, todo를 삭제할 DELETE_TODO입니다. 

1)  typesafe-actions에서 deprecated를 import

 

사실은 deprecated가 아닌 createStandardAction을 바로 import 해줘도 무방하지만 만약 많은 기능을 불러와야 하는 경우 deprecated만 불러온 후 const객체로 필요한 객체를 받을 수 있습니다.

 

2) Action Type 정의 

const ADD_TODO = "todo/ADD_TODO";
const DELETE_TODO = "todo/DELETE_TODO";

이때 type이라고 해서 typescript의 type을 생각하시면 안 됩니다. Action Type이란 리덕스 액션에 들어갈 Type을 정의해 주는 것입니다. 만약 typesafe-actions를 사용하지 않으신다면 뒤에 as const를 붙여주셔야 합니다.

 

3) Action 생성 함수 구현

export const addTodo = createStandardAction(ADD_TODO)<{
  todo: string;
}>();

export const deleteTodo = createStandardAction(DELETE_TODO)();

기존의 방식에서는 일일이 action 함수를 정의해주어야 하지만 typesafe-actions의 createStandardAction을 통해 손쉽게 Action 생성 함수를 구현했습니다.

createStandardAction에는 첫 번째로 Action Type을, 두 번째로는 payload를 정의해줍니다.

(세 번째 ()는 Action의 Type이지만, 기본적으로 Type을 우선 구현했기 때문에 자동으로 적용됩니다.)

 

이때 payload란 Action 함수의 파라미터라고 생각하시면 됩니다.

 


4. Types 정의

위에서 Action의 Type을 정의했다고 했지만 사실 온전히 정의가 된 것이 아닙니다. 온전하게 Type을 정의하기 위해서는 typesafe-actions의 ActionType을 사용해야 합니다.

 

굳이 types.ts를 이용하여 이를 구현하는 이유는 typescript에서 type을 따로 정의하는 것은 나중에 코드를 볼 때에도 type에 대한 이해가 빠르며 파일을 분리함으로써 한 파일에서는 하나의 역할만 함으로서 파일을 역할 단위로 쪼갤 수 있게 됩니다.

1) ActionType 및 actions import

 

ActionType을 통해서 actions.ts에서 정의한 Action Type 들을 이제 온전히 Action Type으로 정의할 수 있게 되었습니다.

또한 밑의 ToDo Type은 이제 store에서 관리할 state에 대한 type을 정의해준 부분입니다.


5. Reducer 구현

reducer.ts에서는 실질적으로 Action들이 어떤 기능을 하는지 구현하는 파일입니다.

1) 필요한 기능 및 객체 import 

 

가장 먼저 types.ts에서 정의한 type들을 import 해주고, actions.ts에서의 Action Type들도 import 해줍니다.

또한 typesafe-actions에서 createReducer 또한 import 해줍니다. createReducer를 통해 저희는 기존에는 switch/case문을 통해 작성했던 reducer를 object의 형식으로 구현할 수 있습니다.

 

특이한 점은 immer에서 produce를 import 한 점입니다. 

 

기존에는 저희가 reducer함수를 작성할 때 ...문법을 사용했습니다. 이는 불변성을 유지하기 위해 객체를 만들어주기 위함인데, 사실 todo 리스트를 만들 때에는 ...을 통해 객체를 생성하는 방법을 선택해도 무방합니다.

하지만, 규모가 점점 커지거나 함수의 기능이 복잡해지기 시작하면 불변성을 유지하기가 상당히 까다롭습니다.

 

이때 유용한 함수가 바로 produce입니다. produce함수는 2개의 인자만 설정해주시면, 이 불변성을 '알아서' 관리해줍니다. 아주 고마운 함수죠. ㅎㅎ

 

2) initialState 정의

 

const intitailState : Todo = {
 todo : [],
};

저희가 필요한 state는 todo의 list이므로 간단하게 todo : []로 정의했습니다.

 

3) Reducer 함수 구현

const todo = createReducer<Todo,TodoAction>(intitailState,{
    [ADD_TODO] : (state,action)=>
    produce(state,draft => {
        draft.todo.push(action.payload.todo);
    }),
    [DELETE_TODO] : (state,aciton) =>
    produce(state,draft => {
        draft.todo.pop();
    })
})

 createReducer함수에는 우선 두 가지 type이 들어갑니다. 첫 번째로, state에 대한 Type과 두 번째로는 Action에 대한 Type입니다. 

 

그 이후 파라미터로는 initialState와 object가 들어갑니다.

 

이 object를 살펴보면 우선 [ADD_TODO]와 같이 Action의 Type을 정의해주고 위와 같이 produce함수를 사용해줍니다.

이때 draft : 기존의 state, action : 새로운 action 이라고 생각하시면 됩니다.


 

6. index.ts 구현

자, 이렇게 reducer 관련한 모든 코드는 작성했습니다. 이제 필요한 것은 이 3개의 파일을 한 곳으로 묶어내는 것입니다.

index.ts에서 해야 할 일은 RootState를 정의하는 것과, 구현한 reducer를 combineReducers를 통해 합쳐주는 것입니다.

 


7. index.tsx 수정하기 - store 생성

redux는 기본적으로  store를 통해 state를 공유합니다. 그렇다면 store를 만들어야겠죠?

이 store는 가장 상위 폴더에 선언하여 모든 하위 태그들이 공유할 수 있도록 해야 합니다.

필요한 것들은 createStore, Provider, rootReducer입니다. 

 

createStore는 말 그대로 store를 생성해주는 함수입니다.

Provider는 생성된 store를 모든 태그가 공유할 수 있도록 하는 클래스입니다.

 

위와 같이 store를 rootReducer를 파라미터로  하는 createStore함수의 반환 값으로 선언한 뒤, Provider 태그의 파라미터로 store를 넣어줍니다.

 

이렇게만 해주면 모든 하위 태그들이 이 store를  공유하게 됩니다.

 

다음은 App.tsx에서 사용할 component들을 태그로 구현해놓습니다.

 

자, 이제 모든 준비는 끝났고 component만 구현하면 끝입니다....!


8. Todo 및 TodoList Component 구현

이제는 생성한 store를 실제로 활용할 겁니다.

저희한테 필요한 Component는 Todo.tsx, TodoList.tsx입니다.

 

Todo.tsx : todo를 저장할 input과 button이 구현된 component

TodoList.tsx : 저장된 todo들을 불러와서 화면에 띄워주는 component

 

Component를 구현하기 전에!! 

Redux의 Hook 두 가지를 우선 짚고 넘어가겠습니다.

 

useSelector()

-> 리덕스 스토어에 저장된 데이터를 추출하는 Hook입니다. 개념적으로는 mapStateToProps와 흡사합니다.

 

useDispatch()

-> 리덕스 스토어에 설정된 action에 대한 dispatch를 연결하는 Hook입니다.

 

이 두 가지 Hook을 사용하는 이유는 바로 코드의 간결화입니다. 

 

이전 방식으로 구현할 때에는 mapStateToProps를 이용해서 props 내 state를 정의하고, connect를 이용해서 props를 바인딩을 해야 하는 아주 귀찮은(?) 일이 발생합니다. 하지만 이러한 Hook들을 적시적소에 사용한다면, 코드를 이해하기도 편하고 코드를 간결하게 유지할 수 있습니다.

 

 

자, 그럼 이제 Todo.tsx부터 구현해보도록 하겠습니다.

 

[Todo.tsx]

전체 코드는 위와 같습니다.

리덕스 스토어에 접근하기 위해 useDispatchaddTodo를 import 해줍니다.

 

form의 input에서 텍스트를 임시로 저장하고 화면에 띄우기 위해 react의 useState를 통해 간단한 state 관리를 합니다.

이후 dispatch를 사용할 수 있도록 addTodo와 연동하여 객체로 선언해주었습니다.

 

그리고 onSubmit과 onChange를 위해서 두 가지 객체를 선언했습니다.

 

onChange의 경우 input내에서 입력한 텍스트를 띄우기 위해 state와 연동하였습니다.

onSubmit의 경우 자체적으로 refrash 되는 것을 방지하기 위해 preventDefault() 함수를 사용했고, 이후 입력한 내용을 dispatch 한 뒤, 제출 이후 입력 칸이 비워질 수 있도록 state값을 비웠습니다.

 

return으로는 form을 활용하여 입력과 제출 기능을 가진 간단한  input과 button을 구현했습니다.

 

결과는 다음과 같습니다.

(css는 따로 적용하지 않았습니다.)

 

 

[TodoList.tsx]

 

전체 코드는 위와 같습니다.

TodoList.tsx에서 필요한 것들은 useSelector, useDispatch Hook들과

리덕스 스토어의 state타입을 정의하기 위한 RootState,

미리 구현해놓은 deleteTodo함수입니다.

 

우선 useSelector를 통해 todolist 객체에 리덕스 스토어에 저장된 값을 불러옵니다.

그리고 useDispatch를 통해 deleteTodo함수를 연동합니다.

 

return으로는 todolist를 map함수를 통해 차례로 띄울 수 있도록 했습니다. 또한 (x)를 누르면 todo가 하나씩 사라질 수 있도록 했습니다.

 

결과는 다음과 같습니다.


 

이번 시간에는 typescript환경에서 Redux를 사용하는 방식에 대해 간략하게 배워보았습니다.

아주 정직하게 react-redux만을 이용하지는 않았지만, Redux의 개념이 쉽지 않은 만큼 구현하는 데 있어서는 조금이라도 접근성을 높이기 위해 다양한 모듈을 사용했습니다.

 

Redux는 여러 컴포넌트 혹은 컨테이너끼리 state를 공유해야 할 때 아주 유용하게 사용됩니다. 기존의 state는 파라미터로 계속 전달해야 사용이 가능하지만 Redux는 그냥 Hook 2개와 store만으로도 쉽게 state에 접근할 수 있기 때문입니다. 

 

본 포스팅을 통해 Redux에 대해 이해하고, 평소에도 요긴하게 사용하셨으면 좋겠습니다!!

이상 초보 개발자 차암새였습니다 :D 

댓글