TypeScript 예시로 알아보는 자료구조와 알고리즘
React Native #2 Redux 후려치기
2018-11-13
Explanation
오늘은 React Native로 가는 두번째 시간 Redux입니다. 전 특별한 이유 없이 처음부터 그냥 Redux만 사용해서 MobX에 대해 잘 모르는데요, 최근에 글들을 보면 MobX도 많이 사용하는 것 같아 보여요. 관심 있으신 분들은 같이 알아보시면 좋을 것 같아요. 저도 기회가 되면 관련해서 글을 적어 보도록 하겠습니다.
역시 이번 글도 예제 소스를 만들어 봤습니다.
예제 소스: https://github.com/falsy/blog-post-example/tree/master/react-redux-key-theorem
지난 글에서 사용한 예제와 거의 비슷한 예제인데요.
대신 부모 컴포넌트가 자식 컴포넌트에게 값을 전달해주지 않고 각각 필요로 하는 컴포넌트에서 Redux를 연결해여 전역의? 상태 변수를 가져오고 반영되도록 하였습니다.
조금 풀어서 이야기하면, 지난 글에서는 Person 에서 값이 변하면 변하는 값을 Arm 으로 전달하고 Arm 에서 Finger 로 전달해서 Finger 컴포넌트에서 수정된 값을 출력했는데요.
지금의 소스는 Person과 Finger 컴포넌트가 Redux로 연결되있고 Person 컴포넌트에서 값이 수정되면 Arm을 거치지 않고 Finger에서 수정된 값이 출력 된답니다.
이전 글을 보니 제가 너무 Package부분을 이야기하지 않고 지나간 것 같아서 이번엔 짧게 적어보려 합니다.
이번 예제에서는 아래와 같은 라이브러리가 사용되었습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// package.json ... "dependencies": { "prop-types": "^15.6.2", // prop 속성의 기본값이나 타입을 검증할 수 있는 라이브러리입니다. "react": "^16.6.1", // React 이고요 "react-dom": "^16.6.1", // 최상위 API에서 render()를 사용할 수 있습니다. "react-redux": "^5.1.1", // Redux는 React만을 위한 라이브러리는 아니기 때문에 React용으로 사용할 수 있도록 해주는 라이브러리입니다. "redux": "^4.0.1", // Redux 입니다. }, "devDependencies": { "@babel/core": "^7.1.5", // ECMAScript를 트랜스파일 해주는 라이브러리 babel 입니다. "@babel/plugin-proposal-export-default-from": "^7.0.0", // babel 7버전을 최근에 사용하게 되었는데요, export .. from .. 을 사용하려면 이 플러그인이 필요하네요. "@babel/preset-env": "^7.1.5", // 흔히 사용되는 preset 이고요 (ES6+ 를 모던 브라우저에서도 사용할 수 있도록) "@babel/preset-react": "^7.0.0", // react 문법도 필요하고요 "babel-loader": "^8.0.4", // Webpack에서 babel을 사용할 수 있는 로더이고, "html-webpack-plugin": "^3.2.0", // webpack으로 만든 번들을 html에 넣어주는 플러그인 이고요 "react-hot-loader": "^4.3.12", // 수정사항을 바로바로 반영해주는 핫로더 "webpack": "^4.25.1", // 우리의 친구 웹팩 "webpack-cli": "^3.1.2", // 웹팩 커맨드라인을 쓰기 쉽게 해주는 cli "webpack-dev-server": "^3.1.10" // 간편하게 사용할 수 있는 웹팩 개발서버입니다. } } |
굉장히 미흡하지만, 대충 이러합니다…
Redux는 크게 Action과 Reducer와 Store가 중요한거 같아요.
아주아주 간단하게 이야기하자면, Action에서 나는 ‘공의 색을 바꿀꺼야.’ ‘빨간색’으로 라고 지시하는? 거고 Reducer는 실질적으로 ‘공의 색을 바꿀꺼야.’ 라고 요청을 받았을때 직접 공의 색을 ‘빨간색’으로 바꾸는 일을합니다. 그리고 그런 Reducer들은 Store에 모여있고요 Store는 Action의 요청을 받고 Reducer로 수정된 것을 dispatch()를 통해 반영해 준답니다.
절대 위 설명이 온전히 바른 설명은 아닙니다. 그냥 그렇게 대충 가볍게만 읽어주세요.
이제 코드를 보며 가볍게 알아봅시다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// src/App.js import React, { Component } from 'react'; import { Provider } from 'react-redux' import store from './store'; import Person from './components/Person'; export default class App extends Component { render() { return ( // React-redux 컴포넌트로 감싸고 속성으로 store가 선언됩니다. // 이렇게하여 내부의 모든 컴포넌트는 react-redux의 손아귀에 들어옵니다. <Provider store={store}> <div> <Person /> </div> </Provider> ); } } |
1 2 3 4 5 6 7 |
// src/store.js import { createStore, combineReducers } from 'redux'; // Redux에서 Store를 만들고 reducer를 합치는 함수를 불러오고 import * as reducers from './reducers'; // reducers 라는 이름으로 reducers 폴더 안에 있는 리듀서들을 가져옵니다 const reducer = combineReducers({ ...reducers }); // reducers 의 모든 리듀서를 합치고 export default createStore(reducer) // 그 리듀서들을 가지고 스토어를 생성합니다. |
이렇게 하여 대충 redux를 사용할 준비가 끝났습니다. 다음으로 action과 reduce를 만들어 볼까요
1 2 3 4 5 6 7 8 9 10 11 |
// src/actions/finger.js // 일반적으로 상수들은 한곳에 모아서 정의합니다. const SET_FINGER = 'SET_FINGER'; import { SET_FINGER } from '../constants'; // setFinger 라는 함수를 export 합니다 type은 SET_FINGER, 인자로 받은 값은 number로 리턴합니다. export function setFinger(no) { return { type: SET_FINGER, number: no } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// src/reducers/finger.js import { SET_FINGER } from '../constants'; // 기본값을 설정해주고 const initialState = { number: 1, }; export default (state = initialState, action) => { switch (action.type) { // 위에서 작성한것처럼 type이 'SET_FINGER'로 요청이 오면 case SET_FINGER: // 그밖의 state 값들을 포함하고 number 라는 값에 actions에서 받은 number 값을 Set 합니다. return { ...state, number: action.number }; default: return state; } } |
1 2 3 |
// src/reducers/index.js // 위 finger 리듀서를 finger 라는 이름으로 export 합니다. export finger from './finger'; |
action과 reducer를 마치고, 이제 직접 컴포넌트에서 사용해봅니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
// src/components/Person.js import React, { Component } from 'react'; import { connect } from 'react-redux'; // connect 함수를 통해서 redux와 연결할 수 있습니다. import Arm from './Arm'; import Leg from './Leg'; // 아까 만들었던 setFinger() 라는 액션을 가져옵니다. import { setFinger } from '../actions/finger'; class Person extends Component { constructor(props) { super(props); } ... changeFinger() { // 손가락 바꾸기를 누르면 const { dispatch } = this.props; // 조금 전 리듀서 finger의 number의 초기값은 1로 설정하였으니 // this.props.finger.number 의 초기값은 1 입니다. // 여기에 + 1을 더한 값으로 Action을 실행하고 dispatch(setFinger(this.props.finger.number + 1)); // dispatch() 를 통해서 수정된 속성이 반영이 됩니다. } render() { return ( <div id={'person'}> <h1>사람</h1> <p>피부색은 {this.state.skin}</p> <Arm /> <Leg /> <button onClick={this.changeSkin.bind(this)}>피부색 바꾸기</button> <button onClick={this.changeFinger.bind(this)}>손가락 바꾸기</button> </div> ); } } // 아까 finger라는 이름으로 reducer를 만들었었죠. const mstp = (state) => { return { finger: state.finger }; }; // Person이라는 컴포넌트와 finger 라는 reducer가 연결 합니다. export default connect(mstp)(Person) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
// src/components/Finger.js import React, { Component } from 'react'; import { connect } from 'react-redux'; // 역시 connect 함수를 불러오고 import PropTypes from 'prop-types'; class Finger extends Component { constructor(props) { super(props); } render() { return ( <div id={'finger'}> // Person 컴포넌트에서 finger.number의 값이 dispatch() 될때마다 변경된 값을 출력합니다. {this.props.finger.number}번째 손가락 </div> ); } } Finger.propTypes = { fingerNo: PropTypes.number }; const mstp = (state) => { return { finger: state.finger }; }; // finger 와 연결을 하면 export default connect(mstp)(Finger) |
뒤 늦게 생각해보니 Redux에서는 미들웨어에 대해서도 알아야 하는데, 깜박 했네요. 저는 주로 미들웨어로 ‘redux-thunk’ 하나를 사용하는데요. 글로 적진 않았지만, Redux 미들웨어도 함께 알아두시면 좋을 거 같습니다~
이전 글과 마찬가지로 아래의 두 블로그가 Redux에 대해서도 참고하기 좋은 곳 인거 같아요~
https://velopert.com/reactjs-tutorials
https://www.zerocho.com/category/React
글에는 잘못 설명된 부분이 많이 있을 수 있습니다. 가볍게 읽어주시고 잘못된 부분은 알려주세요~