이전 포스팅에 이어서


실제로 react에서 redux를 사용하는 방법에 대한 예제입니다.


props가 아닌 context를 사용하는 방법입니다.


시작하겠습니다.


우선 먼저 Provider라는 컴포넌트가 필요합니다.


src 폴더에 components 폴더를 생성하고 Provider.tsx 파일을 생성해주세요.


필요한 모듈을 설치해주세요.


yarn add prop-types @types/prop-types

혹은

npm install --s prop-types @types/prop-types


이전 포스팅을 보신 분이라면, 왜 prop-types를 설치하나? 하실거라 생각됩니다.


typescript에서 쓰지 않아도 되는거 아니었나? 라는 의문입니다.


우선 Provider 컴포넌트를 만들고 말씀드리겠습니다.


src/components/Provider.tsx


import * as React from 'react';
import * as PropTypes from 'prop-types';
import { Store } from 'redux';

export default class Provider extends React.Component<{ store: Store<{ age: number; }>; children: JSX.Element; }, {}> {

public static childContextTypes = {
store: PropTypes.object
};
getChildContext() {
return {
store: this.props.store
};
}
render() {
return this.props.children;
}
}


provider에서는 내 하위 컴포넌트들에게 context로 store를 주겠다를 명시하고 있습니다.

이때 contextTypes를 지정해주기 위해서 PropTypes를 사용해야 됩니다. 

강제로 지정되어있기 때문에 PropTypes를 사용하지 않으면 오류가 나서 어쩔수 없이 사용하게 됩니다. ( 좀더 알아보겠습니다 )


여기서 Provider 컴포넌트는 store와 child를 받고있는데, child의 경우 JSX Element라는 보장이 없으므로 명시해 주어야 합니다.


여기서의 Provider는 하나의 컴포넌트이므로 render가 필요하며, render하는 대상은 children 입니다.


이제 Provider를 만들었으니 적용해보겠습니다.


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';
import Provider from './components/Provider';

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

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

registerServiceWorker();


기존에 App 컴포넌트에 Prop로 주던 store를 Provider의 Props로 내려주게 됩니다.

이렇게 하면, 이제 Provider 하위에 있는 컴포넌트들은 context를 통하여 store에 접근이 가능해집니다.


src/contatiners/App.tsx 파일을 수정하겠습니다.

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

import { Unsubscribe } from 'redux';
import * as PropTypes from 'prop-types';

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

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

class App extends React.Component<{}, {}> {

public static contextTypes = {
store: PropTypes.object
};

private _unsubscribe: Unsubscribe;
constructor(props: {}) {
super(props);
this._addAge = this._addAge.bind(this);
}

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

render() {
const state = this.context.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(): void {
const store = this.context.store;
const action = addAge();
store.dispatch(action);
}
}

export default App;


역시 App에서도 context의 Type을 지정해주기 위해 Prop-types를 사용해야 합니다.


그리고 이전에는 store를 가져오기 위해 this.porps.store를 사용하였는데

this.context.store로 변경되었습니다.


즉 props로 받지 않아도 사용이 가능해졌습니다.


실행해 보겠습니다.



제대로 동작함을 확인할수 있습니다.


여기까지 한 예제는 react-redux를 설치하지 않고 구현한 redux 예제였습니다.


즉, 실무에서는 아마 사용할 경우가 없을것입니다.


하지만 이 과정을 아는것은 실제로 모듈을 사용함에 있어 더 잘 이해할수 있을것이라 생각되어 포스팅 하였습니다.


그럼 이제 실제로 react-redux를 사용한 예제를 시작하겠습니다.


여기까지 소스코드 주소입니다.


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


* react-redux 사용하기

react-redux를 설치해 주세요


yarn add react-redux @types/react-redux

혹은

npm install --s react-redux @types/react-redux


그리고 src/index.tsx에서 이전에 import한 Provider를 지우고, react-redux의 Provider를 import 해주세요.


import { Provider } from 'react-redux';


그리고 그냥 실행하겠습니다.



똑같이 작동합니다.


즉 이전에 만든 Provider와 같은 역할을 하고 있음을 알수 있습니다.


하지만 그것뿐이라면 굳이 사용할 이유는 없을거 같네요. 

다른 기능이 무엇이 있는지 사용해보겠습니다.


Connect


connect함수를 통해 컨테이너를 생성하여 컴포넌트와 연결 할 수 있습니다.

컨테이너는 store의 state와 dispatch(action)을 연결된 컴포넌트에 props로 넣어주는 역할을 하게 됩니다. 

connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])


connect를 사용하게 되면, 이전 소스의 App.tsx에서 사용했던 contextType의 지정이나,

subscribe등의 작업을 connect로 대체 가능합니다.


그럼 사용해 보도록 하겠습니다.


우선 redux를 사용할 컴포넌트를 컨테이너로 감싸서 export 해야 합니다.


src/contatiners/App.tsx 입니다.


const AppContainer = connect(
mapStateToProps,
mapDispatchToProps
)(App);

export default AppContainer;

class 아래 컨테이너 생성을 추가해주세요


그리고 export default App 을 export default AppContainer 로 변경해주세요.


1. mapStateToProps - 어떤 state를 어떤 props에 연결할 것인지 정의하는 함수입니다.

2. mapDispatchToProps - 어떤 dispatch(action) 을 어떤 props에 연결할 것인지에 대해 정의하는 함수입니다.

3. App - props를 보낼 컴포넌트입니다. 여기서는 App에 보내게 됩니다.


그럼 이제 mapStateToProps 과 mapDispatchToProps 를 구현해주도록 하겠습니다.


const mapStateToProps = (state: { age: number; }) => {
return {
age: state.age,
};
};

const mapDispatchToProps = (dispatch: Function) => {
return {
onAddClick: (): void => {
dispatch(addAge());
}
};
};


이 예제에서는 state 값으로 age를 사용하므로, mapStateToProps에서

 return 값으로 age를 넘기게 됩니다.


즉 store에 여러 state가 있더라도, 이 함수에서는 연결할 컴포넌트에서 사용할 state만 정의해주면 됩니다.


mapDispatchToProps 에서는 dispatch 를 정의하는데, 여기서는 버튼을 눌렀을때 작동할 함수로 작성하여 return 해주겠습니다.


위에서 connect는 props의 형태로 컴포넌트에 값을 전달한다고 했는데요,


따라서 이전에는 명시하지 않았던 컴포넌트에 props를 명시해주어야 합니다.


우선 interface를 작성해주세요.


interface AppProps {
age: number;
onAddClick(): void;
}


그리고 App 컴포넌트에 추가해주는데, 이제 connect로 연결을 하였으니 props를 명시하면서


이전에 추가하였던 subscribe등의 작업들을 삭제하겠습니다.


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>
<h1>{this.props.age}</h1>
<button onClick={this.props.onAddClick}>증가합니다.</button>
</div>
);
}
}


src/containers/index.tsx 수정해주세요.

import AppContainer from './App';

export { AppContainer };


src/index.tsx 에도 App 대신 AppContainer 컴포넌트를 사용합니다.

import { AppContainer } from './containers';


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


이제 실행시켜 보겠습니다.



제대로 연결이 되어 잘 작동하는것을 확인할수 있습니다.


* 추가


이전 포스팅에서 컴포넌트에는 여러 종류가 있는데 그중 state를 사용하지 않는 함수형 컴포넌트가 있었습니다. stateless component는 state를 사용하지 않는 특징과 동시에 테스트도 간단하며 추후 성능의 향상을 기대할수 있기 때문에 state를 사용하지 않는 컴포넌트의 경우에는 함수형 컴포넌트의 사용을 권장합니다.


따라서 위의 App 컴포넌트는

const App: React.SFC<AppProps> = (props) => {
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2>Welcome to React</h2>
</div>
<h1>{props.age}</h1>
<button onClick={props.onAddClick}>증가합니다.</button>
</div>
);
};


의 형태로 바꿀수 있습니다.



이전 포스팅에 이어 redux를 사용하는 몇가지 예제를 해보았습니다.


이전에 했던 props를 이용하는 방법을 사용한 컴포넌트를 Dumb컴포넌트( redux 사용 X ),

마지막에 했던 container를 이용하는 방법을 사용한 컴포넌트를 smart 컴포넌트 라고 하는데요,


표로 나타내면 이렇게 표현 가능합니다.

 

 Smart

Dumb 

 위치

 최상위, 라우트핸들러

 중간과 말단 컴포넌트

 Redux와 연결됨

 예

 아니오

 데이터를 읽기 위해

 Redux 상태를 구독

 props에서 데이터를 읽음

 데이터를 바꾸기 위해

 Redux에 액션을 보냄

 props에서 콜백을 부름




지금까지  Redux의 기본과정을 살펴보고, 예제를 만들어보았습니다.


다음 포스팅에서는 짧게 redux의 심화내용에 대해 포스팅을 할 예정입니다.


좀더 열심히 공부를 하여 이해가 쉬운 포스팅을 할수 있도록 노력하겠습니다.


감사합니다.


이번 포스팅의 소스코드 입니다.


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



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

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

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

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

Jaro

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

,