이전 포스팅에 이어서


실제로 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/

,

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/

,

이번 포스팅은 redux에 대한 포스팅입니다.


시작하겠습니다.


Redux란?


redex는 단일 스토어를 사용하는,  javascript application 에서 state를 관리해주는 도구로써, SPA에서 매우 유용하게 사용됩니다. react 뿐만 아니라, 다른 프레임워크를 사용하는 Application에도 사용이 가능합니다.


간단하게 말해서, state를 관리하는 아키텍쳐 방법 이론중 하나입니다.


Redux 사용 이유


혹시 이전 포스팅에서 공부했던 상위 컴포넌트에서 하위 컴포넌트를 바꾸는 것,

혹은 하위 컴포넌트에서 상위 컴포넌트를 바꾸는것에 대해 기억이 나시나요?

링크 : http://jaroinside.tistory.com/20


당시 redux를 사용하지 않고 데이터의 이동을 하기 위해서는 계속해서 props를 통해 하위 컴포넌트로 계속해서 데이터를 넘겨주는 방식으로 구현하였습니다.


즉 하나의 data를 변경하기 위해 많은 컴포넌트들을 거쳐가야만 했습니다.

이러한 방법은 비효율적일뿐만 아니라, 프로젝트가 커질수록 개발자로 하여금 복잡성을 증가시키고,

에러를 유발할 가능성도 커지게 됩니다.


아래 보이는것은 redux를 사용하지 않았을때의 데이터 흐름 예제입니다.

왼쪽의 붉은색 원을 클릭하여 확인해주세요.

( 이것은 제가 포스팅하는 세미나의 주최자이며 현 Studio XID에서 재직중인 이웅재님이

자사에서 서비스중인 protopie를 이용하여 만들었습니다. )



Redux의 데이터 교류


그렇다면 Redux는 어떠한 방식으로 데이터를 교류하는것일까요?


Redux는 기본적으로 Flux 아이디어를 발전시킨 형태라고 생각할수 있습니다.


그렇다면 Flux란 무엇일까요?


Flux는 디자인 패턴중 하나입니다.

FaceBook에서  Client Side Web Application을 만들기 위해 사용하는 Application 아키텍쳐로, 단방향 데이터 흐름을 활용해 뷰 컴포넌트를 구성하는 react를 보완하는 역할을 합니다.

Flux는 MVC 모델과는 다르게 단방향으로만 데이터가 흐르게 됩니다.


그림 출처 : https://haruair.github.io/flux/docs/overview.html


Flux는 그림과 같이 시스템이 어떠한 action을 받았을 경우, Dispatcher는 action들을 관리, 통제하여 store에 있는 데이터들을 업데이트하게 됩니다. 그리고, store에 업데이트된 ( 변동된 ) 데이터가 있다면 view에 렌더링을 하게 되는 방식입니다.


그림 출처 : https://haruair.github.io/flux/docs/overview.html


또한 view에서 Dispatcher로 action을 보낼수도 있습니다.


이러한 방식은 react의 선언형 프로그래밍 스타일. 즉, view가 어떤 방식으로 갱신해야 되는지 일일이 작성하지 않고서도 데이터를 변경할 수 있는 형태라는 점에서 매우 편리합니다.


자 그럼 Flux에 대해서도 기본적인 지식은 습득을 하였으니 다시 Redux로 돌아오겠습니다.


Redux는 위에서 서술하였듯, Flux 아이디어를 발전시킨 형태입니다.


단방향 데이터 흐름을 지닌다는 의미입니다.


아래 보이는것은 redux를 사용하였을때의 데이터 흐름 예제입니다.

왼쪽의 붉은색 원을 클릭하여 확인해주세요.

( 이것은 제가 포스팅하는 세미나의 주최자이며 현 Studio XID에서 재직중인 이웅재님이

자사에서 서비스중인 protopie를 이용하여 만들었습니다. )


예제에서 살펴보듯 각 각 컴포넌트간에는 데이터의 교류가 일어나지 않습니다.


외부의 다른 데이터 저장소를 통하게 됩니다. ( Store )



Redux의 작동 순서


action발생 -> Dispatch -> reducer -> store update - > rendering



Redux의 3가지 원칙


진실은 하나의 소스로부터


- redux는 하나의 어플리케이션에서 state를 관리하기 위하여 오직 하나의 store만 사용합니다 ( 단일 스토어 ) 그렇기 때문에 store에서의 state를 어떻게 잘 관리하는지가 중요합니다.

 

상태는 읽기 전용이다.


- 어플리케이션에서 store에 있는 state를 변경하기 위해서는 반드시 action 객체를 통해서만 가능합니다. 다른 방법으로는 할수가 없습니다.


변화는 순수 함수로 작성되어야 한다.


- 위에서 Redux의 작동 순서에서 reducer라는것을 통한다고 하였습니다. action객체는 어떠한 변화가 일어나야 하는지 알려주는 객체일뿐, 변화를 일으키지는 못합니다. 이전 상태와 action의 정보를 받고 state를 변화시키는것은 reducer가 하는것으로, 여기서 말하는 변화는 reducer를 의미합니다.


즉 reducer는 순수함수로만 작성되어야 하는데, 

1. 같은 입력에 항상 같은 결과를 반환하여야 합니다.

2. 사이드 이펙트가 없어야 합니다.

3. 외부상태와 무관해야 합니다.



정리


Redux는 Flux와 같이 단방향 데이터 흐름을 갖고 있으며, 단일스토어를 사용합니다.

action을 dispatch 하여 reducer로 store의 state를 변화시킨 후, 변화한 값을 view에 적용시키게 됩니다. 



다음 포스팅에서부터는 실제로 redux를 typescript로 사용하는것을 예제로 하여 해보겠습니다.

중간중간 일반 javascript를 이용한 redux 예제도 추가적으로 포스팅할수 있도록 하겠습니다.


감사합니다.



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

참고 동영상 - https://www.youtube.com/playlist?list=PLV6pYUAZ-ZoHx0OjUduzaFSZ4_cUqXLm0
참고 사이트 - https://deminoth.github.io/redux/




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

13. redux - part3  (0) 2017.07.25
13. redux - part2  (0) 2017.07.24
12. REACT ROUTER V4 - part3  (0) 2017.07.21
12. REACT ROUTER V4 - part2  (0) 2017.07.20
12. React Router V4 - part1  (0) 2017.07.19
블로그 이미지

Jaro

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

,