• Stars
    star
    127
  • Rank 282,790 (Top 6 %)
  • Language
    TypeScript
  • Created about 5 years ago
  • Updated almost 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

타입스크립트를 + 리덕스를 프로처럼 사용해봅시다!

ts-react-redux-tutorial

velog 에 이에 관한 포스트가 조만간 올라올 예정입니다.

타입스크립트를 사용하는 리액트 프로젝트에서 리덕스를 프로처럼 사용해봅시다!

이 프로젝트는 총 10개의 브랜치로 나뉘어져있습니다.

  1. basics/counter: 가장 기본적인 카운터 예시입니다
  2. basics/todos: 조금 더 다양한 액션을 다루는 투두리스트 예시입니다
  3. refactor/counter: 카운터 리덕스 모듈을 typesafe-actions 를 사용하여 리팩토링하는 예시입니다.
  4. refactor/counter2: 카운터 리듀서를 typesafe-actions의 createReducer 함수를 사용하여 메서드 체이닝 방식을 사용하여 리팩토링하는 예시입니다.
  5. refactor/todos: 투두리스트 리덕스 모듈을 typesafe-actions 로 리팩토링한 예시입니다.
  6. refactor/todos2: 투두리스트 리덕스 모듈을 여러개의 파일로 분리하여 리팩토링한 예시입니다.
  7. middleware/thunk: redux-thunk 미들웨어를 사용하는 예시입니다.
  8. middleware/thunk-refactor: 비동기 작업을 관리하는 thunk 함수와, 비동기 관련 액션에 따라 상태를 업데이트하는 리듀서를 조금 더 짧은 코드로 구현 할 수 있도록 createAsyncThunkcreateAsyncReducer 유틸 함수를 만들어서 코드를 리팩토링하는 예시입니다.
  9. middleware/saga: redux-saga 미들웨어를 사용하는 예시입니다
  10. middleware/saga-refactor: Promise 를 기반으로 액션을 발생시키는 saga 를 쉽게 만들어주는 createAsyncSaga 유틸 함수를 만들어서 코드를 리팩토링하는 예시입니다.

Highlights

주목할만한 코드는 다음과 같습니다.

기초

액션 타입을 선언 할 때에는 다음과 같이 선언합니다.

const INCREASE_BY = 'counter/INCREASE_BY' as const;

as const 를 함으로써 추후 액션 객체의 타입을 유추 할 수 있게 됩니다.

액션 생성 함수를 만들 떄에는 다음과 같이 선언합니다.

export const increaseBy = (diff: number) => ({
  type: INCREASE_BY,
  payload: diff
});

그리고, 리듀서를 구현하기 전에 모든 액션들에 대한 타입을 하나의 type alias 에 담아주어야 되는데요 이는 이렇게 작성합니다.

type CounterAction =
  | ReturnType<typeof increase>
  | ReturnType<typeof decrease>
  | ReturnType<typeof increaseBy>;

다음, 리듀서에서 관리 할 상태와 초깃값은 다음과 같이 선언합니다.

// 이 리덕스 모듈에서 관리 할 상태의 타입을 선언합니다
type CounterState = {
  count: number
};

// 초기상태를 선언합니다.
const initialState: CounterState = {
  count: 0
};

리듀서는 다음과 같이 구현합니다.

function counter(state: CounterState = initialState, action: CounterAction): CounterState {
  switch (action.type) {
    case INCREASE: // case 라고 입력하고 Ctrl + Space 를 누르면 어떤 종류의 action.type들이 있는지 확인 할 수 있습니다.
      return { count: state.count + 1 };
    case DECREASE:
      return { count: state.count - 1 };
    case INCREASE_BY:
      return { count: state.count + action.payload };
    default:
      return state;
  }
}

그리고 루트 리듀서와 루트 상태는 다음과 같이 만듭니다.

import { combineReducers } from 'redux';
import counter from './counter';

const rootReducer = combineReducers({
  counter
});

// 루트 리듀서를 내보내주세요.
export default rootReducer;

// 루트 리듀서의 반환값를 유추해줍니다
// 추후 이 타입을 컨테이너 컴포넌트에서 불러와서 사용해야 하므로 내보내줍니다.
export type RootState = ReturnType<typeof rootReducer>;

컨테이너는 다음과 같이 만듭니다.

import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { RootState } from '../modules';
import { increase, decrease, increaseBy } from '../modules/counter';
import Counter from '../components/Counter';

export interface CounterContainerProps {}

const CounterContainer = (props: CounterContainerProps) => {
  // 상태를 조회합니다. 상태를 조회 할 때에는 state 의 타입을 RootState 로 지정해야합니다.
  const count = useSelector((state: RootState) => state.counter.count);
  const dispatch = useDispatch(); // 디스패치 함수를 가져옵니다

  // 각 액션들을 디스패치하는 함수들을 만들어줍니다
  const onIncrease = () => {
    dispatch(increase());
  };

  const onDecrease = () => {
    dispatch(decrease());
  };

  const onIncreaseBy = (diff: number) => {
    dispatch(increaseBy(diff));
  };

  return <Counter count={count} onIncrease={onIncrease} onDecrease={onDecrease} onIncreaseBy={onIncreaseBy} />;
};

export default CounterContainer;

여기 까지의 코드들은 basics/counter 브랜치에서 볼 수 있습니다.

typesafe-actions 사용하기

typesafe-actions](https://github.com/piotrwitek/typesafe-actions) 를 사용 할 때는 액션 타입액션 생성 함수를 다음과 같이 작성합니다.

// 액션 type 선언
const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';
const INCREASE_BY = 'counter/INCREASE_BY';

// 액션 생성함수를 선언합니다
export const increase = createStandardAction(INCREASE)();
export const decrease = createStandardAction(DECREASE)();
export const increaseBy = createStandardAction(INCREASE_BY)<number>(); // payload 타입을 Generics 로 설정해주세요.

액션 타입을 선언 할 때 as const 를 빼셔도 됩니다.

모든 액션들에 대한 타입은 이렇게 구현 할 수 있습니다.

// 액션 객체 타입 준비
const actions = { increase, decrease, increaseBy }; // 모든 액션 생성함수들을 actions 객체에 넣습니다
type CounterAction = ActionType<typeof actions>; // ActionType 를 사용하여 모든 액션 객체들의 타입을 준비해줄 수 있습니다

리듀서는 이렇게 작성 할 수 있습니다.

const counter = createReducer<CounterState, CounterAction>(initialState, {
  [INCREASE]: state => ({ count: state.count + 1 }), // 액션을 참조 할 필요 없으면 파라미터로 state 만 받아와도 됩니다
  [DECREASE]: state => ({ count: state.count - 1 }),
  [INCREASE_BY]: (state, action) => ({ count: state.count + action.payload }) // 액션의 타입을 유추 할 수 있습니다.
});

여기까지의 코드는 refactor/counter 브랜치에서 볼 수 있습니다.

리듀서를 메서드 체이닝 방식을 사용한다면 다음과 같이 구현 할 수 있습니다.

const counter = createReducer(initialState)
  .handleAction(increase, state => ({ count: state.count + 1 }))
  .handleAction(decrease, state => ({ count: state.count - 1 }))
  .handleAction(increaseBy, (state, action) => ({
    count: state.count + action.payload
  }));

이 코드는 refactor/counter2 에서 확인 할 수 있습니다.

파일 분리하기

리덕스 관련 코드를 Ducks 패턴으로 작성하다가 액션의 갯수가 너무 많아져서 코드가 길어지면 한 디렉터리에 actions, reducer, types 를 분리해서 작성하시는 것도 좋은 방법입니다.

modules/
  todos/
    index.ts
    actions.ts
    reducer.ts
    types.ts

이에 대한 코드는 refactor/todos2 에서 확인 할 수 있습니다.

index.ts 는 다음과 같이 작성합니다.

export { default } from './reducer'; // reducer 를 불러와서 default로 내보내겠다는 의미
export * from './actions'; // 모든 액션 생성함수들을 불러와서 같은 이름들로 내보내겠다는 의미
export * from './types'; // 모든 타입들을 불러와서 같은 이름들로 내보내겠다는 의미

redux-thunk 사용하기

비동기 작업 관련 액션 생성 함수는 typesafe-actions의 createAsyncAction를 사용하면 편하게 구현 할 수 있습니다.

import { createAsyncAction } from 'typesafe-actions';
import { GithubProfile } from '../../api/github';
import { AxiosError } from 'axios';

export const GET_USER_PROFILE = 'github/GET_USER_PROFILE';
export const GET_USER_PROFILE_SUCCESS = 'github/GET_USER_PROFILE_SUCCESS';
export const GET_USER_PROFILE_ERROR = 'github/GET_USER_PROFILE_ERROR';

export const getUserProfileAsync = createAsyncAction(
  GET_USER_PROFILE,
  GET_USER_PROFILE_SUCCESS,
  GET_USER_PROFILE_ERROR
)<undefined, GithubProfile, AxiosError>();

Thunk 함수를 만들 때는 다음과 같이 작성합니다.

import { ThunkAction } from 'redux-thunk';
import { RootState } from '..';
import { GithubAction } from './types';
import { getUserProfile } from '../../api/github';
import { getUserProfileAsync } from './actions';

// ThunkAction 의 Generics 에는 다음 값들을 순서대로 넣어줍니다.
/*
  1. thunk 함수에서 반환하는 값의 타입
  2. 리덕스 스토어의 상태 타입
  3. Extra Argument (https://github.com/reduxjs/redux-thunk#injecting-a-custom-argument)
  4. thunk 함수 내부에서 디스패치 할 수 있는 액션들의 타입
*/
export function getUserProfileThunk(username: string): ThunkAction<void, RootState, null, GithubAction> {
  return async dispatch => {
    const { request, success, failure } = getUserProfileAsync;
    dispatch(request());
    try {
      const userProfile = await getUserProfile(username);
      dispatch(success(userProfile));
    } catch (e) {
      dispatch(failure(e));
    }
  };
}

thunk 관련 코드는 middleware/thunk 브랜치에서 확인 할 수 있습니다.

thunk 와 비동기 작업을 위한 상태 관리 리팩토링

createAsyncThunkreducerUtils 유틸 함수들을 만들어서 사용하게 된다면,

thunk 함수는 다음과 같이 작성 할 수 있고

export const getUserProfileThunk = createAsyncThunk(getUserProfileAsync, getUserProfile);

리듀서는 다음과 같이 작성 할 수 있습니다.

const github = createReducer<GithubState, GithubAction>(initialState).handleAction(
  transformToArray(getUserProfileAsync),
  createAsyncReducer(getUserProfileAsync, 'userProfile')
);

이에 대한 코드는 middleware/thunk-refactor 에서 확인 하실 수 있습니다.

redux-saga 사용하기

saga 는 다음과 같이 작성합니다.

import { getUserProfileAsync, GET_USER_PROFILE } from './actions';
import { getUserProfile, GithubProfile } from '../../api/github';
import { call, put, takeEvery } from 'redux-saga/effects';

function* getUserProfileSaga(action: ReturnType<typeof getUserProfileAsync.request>) {
  try {
    const userProfile: GithubProfile = yield call(getUserProfile, action.payload);
    yield put(getUserProfileAsync.success(userProfile));
  } catch (e) {
    yield put(getUserProfileAsync.failure(e));
  }
}

export function* githubSaga() {
  yield takeEvery(GET_USER_PROFILE, getUserProfileSaga);
}

saga 를 적용한 코드는 middleware/saga 브랜치에서 확인 할 수 있습니다.

saga 리팩토링

프로미스를 다루는 saga 를 쉽게 만들어주는 함수 createAsyncSaga를 만들면

API 요청에 대한 saga 를 만들 때 다음과 같이 한 줄로 작성 할 수 있습니다.

const getUserProfileSaga = createAsyncSaga(getUserProfileAsync, getUserProfile);

이에 대한 코드는 saga-refactor 브랜치에서 보실 수 있습니다.

More Repositories

1

velog

JavaScript
754
star
2

velog-client

TypeScript
679
star
3

learning-react

[길벗] 리액트를 다루는 기술 서적에서 사용되는 코드
JavaScript
570
star
4

velog-server

TypeScript
362
star
5

react-tutorial

벨로퍼트와 함께하는 모던 리액트 튜토리얼 문서
JavaScript
348
star
6

velofolio

velofolio is yet another U.S. Stock backtest simulator tool. You can easily share your backtest or explore other's backtest within service. This service is currently in beta stage.
TypeScript
330
star
7

bitimulate

Simulated cryptocurrency trading system
JavaScript
262
star
8

sangte

Sangte is a fancy React state management library.
TypeScript
199
star
9

gin-rest-api-sample

Golang REST API sample with MariaDB integration using Gin and GORM
Go
189
star
10

veltrends

Veltrends is a website website where users can explore trending tech news.
TypeScript
112
star
11

redux-pender

redux middleware that helps to manages async actions based on promise
JavaScript
101
star
12

whotalk.us

Chat based SNS implemented using React.js on frontend-side and Node.js on backend-side - https://whotalk.us/
JavaScript
91
star
13

dealing-with-react-native

리액트 네이티브를 다루는 기술
Java
88
star
14

learnjs

벨로퍼트와 함께하는 모던 자바스크립트
JavaScript
85
star
15

learn-react-testing

벨로퍼트와 함께하는 리액트 테스팅
JavaScript
70
star
16

nodejs-jwt-example

sample implementation of an authentication system that uses JSON Web Token to manage users' login data in Node.js web server.
JavaScript
69
star
17

react-codelab-project

Single-page infinite-scrolling public memo app implemented using React.js, Redux, Express.js and MongoDB
JavaScript
58
star
18

react-webpack2-skeleton

Get started with React with Webpack2, React-Router, Redux, Code Splitting and Server Rendering
JavaScript
57
star
19

storybook-tutorial-code

Source code for storybook-tutorial
TypeScript
53
star
20

react-skeleton-2018

리액트 프로젝트 설정을 0부터 해보자.
JavaScript
50
star
21

react-tutorials

Sample projects from https://velopert.com/ (KOREAN)
JavaScript
43
star
22

develoxy

개발자들의 글쓰기 플랫폼, develoxy
JavaScript
37
star
23

velo-ui

TypeScript
31
star
24

saysomething

Realtime Chat Application using long polling technique; implemented using Express.js, MongoDB and React.js
JavaScript
30
star
25

nodejs-github-webhook

Github Webhook server built with Node.js
JavaScript
29
star
26

react-codelab-fundamentals

Initial project used in React Codelab
JavaScript
29
star
27

typescript-react-sample

Sample project that uses Typescript, React, Redux, Immutable.js
TypeScript
24
star
28

mongoose_tutorial

RESTful API using MongoDB & Mongoose & Express
JavaScript
18
star
29

do-chat

Sample React ChatApp project that uses redux-pender
JavaScript
15
star
30

react-express-hmr-example

An example project of using React.js on Express.js server together with webpack-dev-server that has Hot Module Replacement enabled using react-hot-loader.
JavaScript
13
star
31

mooyaho-meet

Google Meet Clone implemented with Mooyaho
JavaScript
12
star
32

express-tutorial

express tutorial - covering ejs, resful api and session
JavaScript
12
star
33

react-ajax-tutorial

Ajax example in React
CSS
12
star
34

ArticlesApp

Sample App in 리액트 네이티브를 다루는 기술
TypeScript
11
star
35

articles-server

리액트 네이티브를 다루는 기술의 15장 React-Query에서 사용할 Strapi로 만든 샘플 게시글 서버
JavaScript
11
star
36

figma-icons-library-automation

JavaScript
9
star
37

gulp-es6-webpack

Use gulp to automatize ES6 transpilation in both client-side and server-side. (Express.js, babel, webpack, browser-sync, nodemon)
JavaScript
8
star
38

react-skeleton

React workspace that you can simply start coding right away
JavaScript
8
star
39

react-ssr-and-splitting

JavaScript
7
star
40

react-router-4-demo

Example project of using Pre-release version of React-Router v4. [Outdated]
JavaScript
7
star
41

learning-nodejs

JavaScript
6
star
42

react-query-ssr-react-router-sample

Sample Server Side Rendering project that uses React Router and React Query
JavaScript
6
star
43

typescript-nodejs-boilerplate

TypeScript / Node.js boilerplate with TSLint, Prettier enabled
JavaScript
6
star
44

velogthumb

On demand image resizing using Serverless / TypeScript
TypeScript
6
star
45

PublicGallery

JavaScript
5
star
46

react-codelab-memopad

JavaScript
4
star
47

react-memo-app

REST API 기반 리액트 메모 앱 (강의용 프로젝트)
JavaScript
4
star
48

fc-seminar-react-todo

패스트캠퍼스 좋았을걸 세미나 리액트 입문 세션에서 사용된 프로젝트
JavaScript
3
star
49

react-remote-counter

An example project of using redux and express.js to demonstrate a simple counter that uses AJAX.
JavaScript
3
star
50

react-ssr-sample

React SSR Sample using redux, redux-thunk, react-router v4
JavaScript
3
star
51

DayLog

JavaScript
3
star
52

figma-svg-export-automation-sample

(WIP) Sample project that exports svg using Figma API
JavaScript
3
star
53

react-express-hot

React project generated using create-react-app; using with Express sever; react-hot-loader applied
JavaScript
1
star
54

react-sass-root-example

react-sass-root-example
JavaScript
1
star
55

react-router-tutorial

https://velopert.com/2937
JavaScript
1
star
56

react-contact-project

JavaScript
1
star
57

react-async-example

JavaScript
1
star
58

react-todo-list

CSS Module + Sass 를 사용하여 스타일링된 리액트 컴포넌트로 이뤄진 일정관리 예제 어플리케이션입니다
JavaScript
1
star