이전 포스팅에 이어서 redux의 심화내용에 대해 간단히 포스팅 하겠습니다.


combineReducers


이전 포스팅에서 했던 예제 프로젝트에서는, action 1개 reducer 1개로 매우매우 관리하기 쉬운 프로젝트였습니다.


하지만, 프로젝트의 크기가 커지고, 여러 개발자분들이 동시에 개발을 하게 되어 각각 맡은 부분의 reducer를 만들게 되었다면 어떻게 될까요?

A라는 사람은 age를 관리하는 reducer를, B라는 사람은 imageShow에 관한 reducer를 작성한다고 가정해 보도록 하겠습니다.


이전 포스팅에서 사용한 프로젝트를 열어주세요.


그리고 우선 image를 띄워주는 컴포넌트를 생성해보도록 하겠습니다.

( 그냥 img 태그를 쓰셔도 되지만 복습할겸 하겠습니다. )


이미지는 아무거나 쓰셔도 됩니다. components 폴더에 넣어주세요.


src/components/Image.tsx

import * as React from 'react';

const image = require('./test.jpeg');

interface ImageProps {

}

const Image: React.SFC<ImageProps> = (props) => {
return (
<div className="Image">
<img src={image} className="Test-image" alt="jaro" />
</div>
);
};

export default Image;


src/components/index.tsx

import Image from './Image';

export { Image };


그리고 App.tsx에 적용하여 확인해 보겠습니다.  ( 헤더를 지워버렸습니다! )

import { Image } from '../components';
const App: React.SFC<AppProps> = (props) => {
return (
<div className="App">
<Image />
<h1>{props.age}</h1>
<button onClick={props.onAddClick}>증가합니다.</button>
</div>
);
};


확인해보겠습니다.



이미지가 제대로 나오는것을 확인할수 있습니다.


이제, 이 이미지를 보이거나 혹은 안보이게 하는 action을 만들고 reducer를 작성해 보겠습니다.


src/action/actionType.tsx

// age 관련
export const ADD_AGE = 'ADD_AGE';

// image 관련
export const SHOW_HIDE_IMAGE = 'SHOW_HIDE_IMAGE';

그리고 image.tsx 파일을 생성하여 작성하겠습니다.


src/action/image.tsx

import * as types from './actionType';

export function imageShowHide(): { type: string; } {
return {
type: types.SHOW_HIDE_IMAGE
};
}

이제 리듀서 차례입니다.


src/reducer/image.tsx 파일을 생성하고 리듀서를 작성해주세요.

import * as types from '../action/actionType';

type State = boolean;

const initialState: State = true;

type Action = {
type: string;
};

export function image(state: State = initialState, action: Action): State {
switch (action.type) {
case types.SHOW_HIDE_IMAGE:
return !state;
default:
return state;
}
}


이전에 ageApp 리듀서를 만들었을때와는 조금 다른 방식입니다.


typescript를 사용하면서 좀더 type을 명확하게 사용하기 위해 type을 사용해 보았습니다. ( 안하셔도 됩니다. )


그런데 ageApp 리듀서와 다른 또한가지가 눈에 보입니다.


이전에 ageApp 리듀서 코드를 보겠습니다.


export function ageApp(state: { age: number; } = { age: 30 }, action: { type: string; }): { age: number } {
switch (action.type) {
case types.ADD_AGE:
return { age: state.age + 1 };
default:
return state;
}
}


이전에 리듀서에서는 state에 age라는 프로퍼티를 명시하여 사용하였는데, image에서는 그냥 state값을 변화시키고 있습니다.


사실 리듀서를 쪼개서 작업하는것은 처음부터 하나의 리듀서를 생각하고 쪼개서 작업을 해야 합니다. 따라서 합쳐진 하나의 리듀서를 쪼갯을때 각각의 리듀서들의 state들이 곧 프로퍼티가 된다고 생각하셔야 합니다. 


예를들어 위에서 image 라는 리듀서의 state는 각각의 리듀서들이 합쳐졌을때,

state.image로써 역할을 한다는 의미입니다.


그렇다면, ageApp.tsx 파일도 바꿔야겠네요.

import * as types from '../action/actionType';

type State = number;

const initialState: State = 30;

type Action = {
type: string;
};

export function age(state: State = initialState, action: Action): State {
switch (action.type) {
case types.ADD_AGE:
return state + 1;
default:
return state;
}
}


이렇게 바꿔주세요. 이렇게 바꾸면 나중에 바꾸었을때, state.age로 쓸수 있을것입니다.


그럼 이제 이 두개의 리듀서를 합쳐보겠습니다. 우선 combineReducer를 쓰지 않고 해보겠습니다.


ageApp.tsx의 내용과 image.tsx의 내용을 combine.tsx 파일로 합쳐보겠습니다.


src/reducer/index.tsx 파일을 생성해주세요.

import { age } from './age';
import { image } from './image';

type CombinedState = {
age: number;
image: boolean;
};

const initialCombinedState: CombinedState = {
age: 30,
image: true
};

type Action = {
type: string;
};

export function combine(state: CombinedState = initialCombinedState, action: Action): CombinedState {
return {
age: age(state.age, action),
image: image(state.image, action)
};
}


2개의 리듀서를 가져와서 combine 하는 작업입니다.

함수를 생성하고, state와 action을 넣어주면서 return 값으로 각 리듀서들을 넣어주면 됩니다.

여기서 이제 state의 프로퍼티가 생성됩니다.


이제 src/index.tsx에 가서 createStore에 reducer를 변경해주세요.


import { combine } from './reducer';


const store = createStore<{ age: number; image: boolean; }>(combine);


이전 코드와 좀 바뀐것은 좌측에 Store와 제네릭이 빠졌는데요, 우측의 createStore의 제네릭과 동일하기때문에 빼도 상관이 없습니다.


이제 image component에 redux를 연결하여 사용해 보겠습니다.

( 복습입니다 )


src/components/image.tsx


컨테이너를 만들고


import { connect } from 'react-redux';


const ImageContainer = connect(
mapStateToProps,
mapDispatchToProps
)(Image);

export default ImageContainer;


함수를 구현하고

const mapStateToProps = (state: { image: boolean; }) => {
return {
image: state.image,
};
};

const mapDispatchToProps = (dispatch: Function) => {
return {
onShowClick: (): void => {
dispatch(imageShowHide());
}
};
};


인터페이스를 작성하고

interface ImageProps {
image: boolean;
onShowClick(): void;
}


적용합니다.

const Image: React.SFC<ImageProps> = (props) => {
return (
<div className="Image">
{ props.image ?
<img src={image} className="Test-image" alt="jaro" /> :
null }
<br/>
<button onClick={props.onShowClick}>이미지 버튼</button>
</div>
);
};


그리고 실행하여 이미지 버튼을 눌러주세요.


버튼을 누를때마다 props.image 값이 true 혹은 false로 바뀌면서 이미지가 생겼다가 사라졌다가를 할것입니다.


그렇다면 이번에는 combineReducers 를 사용하여 해보겠습니다.


src/reducer/index.tsx

import { combineReducers } from 'redux';
import { age } from './age';
import { image } from './image';

type CombinedState = {
age: number;
image: boolean;
};

export const combine = combineReducers<CombinedState>({
age: age,
image: image
});


생성할 state의 type을 지정하는것 말고는 그냥 쓰면 바로 작동이 됩니다.

아주 편리하네요.


실행시켜 확인하면, 제대로 동작할것입니다.


이번 포스팅에서는 redux의 심화과정(?)중 하나인 combineReducer를 해보았습니다.


원래 목표는 async Action과 middleware, redux-thunk도 같이 포스팅 하려 했으나


아직 좀더 공부가 필요한것 같아 빠르게 이해한뒤 바로 포스팅 하겠습니다.


소스코드입니다.


https://github.com/JaroInside/tistory-react-typescript-study/tree/13.redux-part4



참고 슬라이드 - http://slides.com/woongjae/react-with-typescript-3#/

참고 동영상 - https://www.youtube.com/playlist?list=PLV6pYUAZ-ZoHx0OjUduzaFSZ4_cUqXLm0

'React > React&typeScript' 카테고리의 다른 글

14.mobx-part1  (5) 2017.07.28
13. redux - part5  (0) 2017.07.26
13. redux - part3  (0) 2017.07.25
13. redux - part2  (0) 2017.07.24
13. redux - part1  (0) 2017.07.23
블로그 이미지

Jaro

대한민국 , 인천 , 남자 , 기혼 , 개발자 jaro0116@gmail.com , https://github.com/JaroInside https://www.linkedin.com/in/seong-eon-park-16a97b113/

,