Redux 포스팅 2번째입니다.


이번 포스팅에서는 part1에서 설명한 Redux를 구현하고 실행하여 확인해보는 과정을 포스팅하겠습니다.


첫번째는 react-redux를 사용하지 않고 구현하는 예제입니다.


굳이 이 예제를 먼저 하는 이유는, 기초적인것을 구현해봄으로써 좀더 redux를 이해하기 위함입니다.




시작하겠습니다.


우선 CRA를 이용하여 typescript로 된 프로젝트를 생성해주세요.


create-react-app redux-study --scripts-version=react-scripts-ts


그리고 redux를 설치해주세요.


yarn add redux

혹은

npm install --s redux


* redux는 index.d.ts 파일을 지원해주므로 @types/redux를 설치하지 않아도 됩니다.


containers 폴더 생성후 App.tsx, App.css, logo.svg 파일을 옮겨주시고, index.tsx 파일 생성후 내용 작성해주세요.

src/index.tsx 의 App import 변경도 해주시기 바랍니다.



src/containers/index.tsx

import App from './App';

export { App };


src/index.tsx

import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { App } from './containers';
import registerServiceWorker from './registerServiceWorker';
import './index.css';

ReactDOM.render(
<App />,
document.getElementById('root') as HTMLElement
);
registerServiceWorker();


이제 action 을 추가해보도록 하겠습니다.

src폴더에 action 폴더를 추가해 주세요.


그리고 actionType.tsx 파일과 addAge.tsx 파일을 생성해주세요.


우선 addAge.tsx 파일입니다.


export function addAge(): {type: string;} {
return {
type: "ADD_AGE"
};
};


사실 이것만 구현을 해도 상관은 없습니다.


하지만 action.tsx 파일을 생성한 이유는, 개발시 의도하지 않은 오타로 인해 발생하는 문제를 줄이기 위함입니다.


addAge를 보면, type에 string으로 "ADD_AGE" 라고 써있습니다.


이 string값은 나중에 리듀서에서도 사용을 해야 하는데, 만일 오타로 인해 에러가 발생한다면 찾기가 어려워집니다. string 값이니까요..


따라서 문제 발생을 막기 위해 상수값으로 선언하여 사용한다면, 설사 처음 string에 오타가 있어도, 프로그램에는 문제가 없고, 나중에 상수를 사용함에 있어 문제가 생긴다면 오류를 찾기 쉬워집니다.


그럼 actionType.tsx에 ADD_AGE를 상수로 선언하여 export 해주세요.

export const ADD_AGE = 'ADD_AGE';


그리고 addAge.tsx 파일을 변경해주세요.

import * as types from './actionType';

export function addAge(): { type: string; } {
return {
type: types.ADD_AGE
};
};


이번에는 리듀서를 만들어보겠습니다.


src 폴더에 reducer 폴더를 생성해주세요.


그리고 ageApp.tsx 파일을 생성하고, 함수를 만들어주세요.


리듀서 함수의 기본은

function todoApp(state, action) {
return state;
}​


형태입니다.


여기서 state는 리듀서로 들어오는 이전 state입니다.


src/reducer/ageApp.tsx


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

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;
}
}


이 리듀서에서는 age를 관리하므로 state에는 age가 들어가고, 초기값으로 30을 넣었습니다.

초기값을 넣는 이유는, 만약 undefined가 호출될수도 있기 때문입니다.


* 대부분의 리듀서를 구성하는데 switch 문을 사용하는데, if 문을 사용해도 무방합니다.


이제 액션과 리듀서를 정의하였으니 createStore를 통해 store를 만들어 사용해 보겠습니다.


1. Store를 Props로 전달 & index에서 Subscribe


src/index.tsx에 작성해보도록 하겠습니다.


우선, store를 만들어서 props로 하위 컴포넌트로 전달하는 방법으로 구현해보겠습니다.


redux를 import 하고, store를 작성해주세요.


src/index.tsx


import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { App } from './containers';
import registerServiceWorker from './registerServiceWorker';
import './index.css';

import { Store, createStore } from 'redux';
import { ageApp } from './reducer/ageApp';

const store: Store<{ age: number; }> = createStore<{ age: number; }>(ageApp);

ReactDOM.render(
<App />,
document.getElementById('root') as HTMLElement
);

registerServiceWorker();


여기서 또다시 몇가지 방법이 있습니다.


Store 객체를 App에 전달하여  subscribe를 App에서 하는 방법과,

index에서 하는 방법입니다,

만약 index에서 하게 되면 전체 렌더링을 다시 하게 됩니다.


저는 index에서 하는것을 먼저 해보도록 하겠습니다.


import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { App } from './containers';
import registerServiceWorker from './registerServiceWorker';
import './index.css';

import { Store, createStore } from 'redux';
import { ageApp } from './reducer/ageApp';

const store: Store<{ age: number; }> = createStore<{ age: number; }>(ageApp);

store.subscribe(render);

function render() {
ReactDOM.render(
<App />,
document.getElementById('root') as HTMLElement
);
}
render();

registerServiceWorker();


즉 초기에 render를 하고,  subscribe가 일어나면 또다시 전체 렌더링을 다시 하게 됩니다.

( 좋은 방법이 아닙니다!! 공부하기 위해 해보는 예제입니다! )


자 그럼 이제 App 컴포넌트로 만들어진 store를 보내주어야 합니다.


가장 기본적인 방법인 props로 보내보겠습니다.


<App store={store}/>


그리고 src/containers/App.tsx에서 받는것을 구현하겠습니다.


먼저 interface를 작성해주세요.


src/containers/App.tsx


import { Store } from 'redux';

interface AppProps {
store: Store<{ age: number; }>;
}


그리고 컴포넌트 클래스에 추가해줍니다.


class App extends React.Component<AppProps, {}> {
render() {
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2>Welcome to React</h2>
</div>
</div>
);
}
}


이제 store를 일반 props처럼 받아서 사용해보겠습니다.


render() {
const store = this.props.store;
const state = store.getState();
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2>Welcome to React</h2>
</div>
<h1>{state.age}</h1>
</div>
);
}


store를 props로 받았으므로, this.props.store를 통해 받은뒤,

getState를 통해 내용을 받아옵니다.

* getState는 store에 있는 state들을 가져오는 함수입니다.

그리고 화면에 출력합니다.


실행시켜보겠습니다.



초기값 30이 렌더링 되는것을 확인할수 있습니다.


그럼 이번에는 이 숫자를 증가, 즉 store의 age값을 증가 시키는 버튼을 추가해보겠습니다.


class안에 store를 dispatch하는 함수를 추가하겠습니다.


private _addAge() {
this.props.store.dispatch({type: 'ADD_AGE'});
}


이렇게 사용해도 되지만, 저희는 이전에 action을 만들면서 오타로 인한 위험을 줄이고 좀더 명확하게 하기 위해 타입생성 함수를 작성해 놓았습니다.


타입생성 함수를 적용하여 바꾸겠습니다.


import { addAge } from '../action/addAge';


private _addAge() {
this.props.store.dispatch(addAge());
}


그리고 생성자함수에 _addAge를 바인딩 해주세요.


constructor(props: AppProps) {
super(props);
this._addAge = this._addAge.bind(this);
}


버튼을 추가하겠습니다.


<button onClick={this._addAge}>증가합니다.</button>


실행하고 버튼을 눌러서 숫자가 증가하는지 확인해주세요.



정상적으로 값이 증가하는것을 보니 store가 제대로 업데이트 되는것이라고 생각할수 있습니다.


하지만 위와 같은 방법은 subscribe를 index에서 하기때문에 전체 리렌더링이 되므로 좋지 않은 방법입니다.


따라서 이번에는 특정 컴포넌트에서만 subscribe를 하여 좀더 효율있는 프로그램을 작성해보도록 하겠습니다.


2. Store를 Props로 전달 & 하위 컴포넌트에서 Subscribe


src/index.tsx의 render 함수와 subscribe를 지워주세요.


import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { App } from './containers';
import registerServiceWorker from './registerServiceWorker';
import './index.css';

import { Store, createStore } from 'redux';
import { ageApp } from './reducer/ageApp';

const store: Store<{ age: number; }> = createStore<{ age: number; }>(ageApp);

ReactDOM.render(
<App store={store}/>,
document.getElementById('root') as HTMLElement
);

registerServiceWorker();


그리고 App.tsx 에서 subscribe를 하겠습니다.


그런데 여기서 생각해야 할 점은, 과연 컴포넌트의 라이프사이클중, 어디에서 subscribe를 해야 하는가 입니다.


이전에 포스팅한 LifeCycle의 내용을 기억해보겠습니다.

http://jaroinside.tistory.com/18 )


컴포넌트가 생성될때는 총 4개의 LifeCycle이 동작하는데, 이중 ComponentDidMount는 컴포넌트가 mount된후 동작하는것으로, 다른 프레임워크의 연동이나 ajax등과같은 작업을 하는것을 권장한다고 하였습니다.


subscribe또한 이곳에서 하는것을 권장합니다.


하나 더 추가할 것은, 컴포넌트가 mount 될때 subscribe를 해주었으니,

unmount될때는 Unsubscribe를 해줘야 합니다.

그래야 store가 이 컴포넌트에 관여를 하지 않을것입니다.


Unsubscribe 는 컴포넌트가 unmount되기 직전, 즉 componentWillUnmount에서 해주면 됩니다.


src/containers/App.tsx


import * as React from 'react';
import './App.css';

import { Store, Unsubscribe } from 'redux';

import { addAge } from '../action/addAge';

const logo = require('./logo.svg');

interface AppProps {
store: Store<{ age: number; }>;
}

class App extends React.Component<AppProps, {}> {
private _unsubscribe: Unsubscribe;
constructor(props: AppProps) {
super(props);
this._addAge = this._addAge.bind(this);
}

componentDidMount() {
const store = this.props.store;
this._unsubscribe = store.subscribe(() => {
this.forceUpdate();
});
}
componentWillUnmount() {
if (this._unsubscribe !== null) {
this._unsubscribe();
}
}

render() {
const state = this.props.store.getState();
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2>Welcome to React</h2>
</div>
<h1>{state.age}</h1>
<button onClick={this._addAge}>증가합니다.</button>
</div>
);
}

private _addAge() {
this.props.store.dispatch(addAge());
}
}

export default App;


그런데 여기서 

store.subscribe(() => {
this.forceUpdate();
});

부분이 있습니다.

이전에 index에서는 render를 함수로 만들어 subscribe가 일어나면, 다시 render를 호출하여 새롭게 렌더링을 하였는데, 여기서는 render를 다시 부를수가 없습니다.

따라서 subscribe가 일어나면, callback 으로 forceUpdate 즉 강제적으로 렌더링으르 다시 해주어야 합니다.


실행하면, 이전과 같은 결과를 보여줄것입니다.




지금까지 예제를 하셨으면, 의문이 들수 있습니다.

그렇다면 App 컴포넌트의 아래에 있는 컴포넌트에서도 store를 사용하면, 또 props로 넘겨줘야 하는거 아닌가? 라는 의문입니다.


위와 같은 방법을 사용한다면, 답은 yes입니다.

즉 실제로는 위와같은 방법은 잘 사용하지 않는다는 이야기 입니다.

따라서 이번에는 위와 같은 방법이 아닌 다른 방법을 통해 redux를 사용하는 예제를 만들어보겠습니다.


포스팅이 길어져서 다음 포스팅으로 넘겨서 포스팅 하겠습니다.


소스코드 주소입니다.


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



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

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

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

13. redux - part4  (0) 2017.07.26
13. redux - part3  (0) 2017.07.25
13. redux - part1  (0) 2017.07.23
12. REACT ROUTER V4 - part3  (0) 2017.07.21
12. REACT ROUTER V4 - part2  (0) 2017.07.20
블로그 이미지

Jaro

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

,