React Redux 리액트 리덕스, 기본 사용법
시작하기
React Redux 구글검색이 혼란스러운 이유
리액트 사용을 위한 리덕스 학습시 혼란스러운 점은 사용하는 라이브러리가 다음과 같이 두 개라는 것이다. 그 사용법도 다르다.
- redux 라이브러리
- react-redux 라이브러리
혼란을 가중시키는 것은 구글의 예제 검색 결과인데,
리덕스 예제를 검색해보면 세 종류 샘플코드가 혼재되어 나온다….
- JavaScript에 접목한 redux 샘플 코드
- 리액트에 redux 라이브러리를 사용한 샘플 코드
- 리액트에 react-redux 라이브러리를 사용한 샘플 코드
이렇게.
하지만 일단 이 사실만 알고 나면 혼란은 잠잠해지고, 내용을 걸러낼 수 있게 된다.(아마도)
Redux 개념 빠르게 깨닫는 방법
전반적인 학습을 원한다면 코드의 바다에서 몸부림치는 중생들에게 항상 생명수를 내려주는 생활코딩의 2개 수업을 추천한다. 그 수업을 연속해서 들음으로써 다음과 같은 순차적인 가르침을 얻을 수 있다.
> 리덕스의 개념
> JavaScript + 리덕스 예제
> 리액트와 react 라이브러리 예제
> 리액트와 react-redux 예제
이 글에서 다룰 범위
한편, 본인의 학습의 주요 목적은 리액트에 react-redux를 사용하는 것이므로 확실한 갈무리와 암기를 위해 react-redux 사용법만을 본 글에 기록하고자 한다. 코드 예제는 본인이 수강했던 생활코딩의 수업을 참조하였다. 아직 리덕스에 대한 기본 개념을 전혀 못잡은 상태라면 이전 포스팅을 보고 다시 돌아오길 권장한다.
Redux 리덕스 개념과 예제(JavaScript)
리덕스(Redux)의 개념 리덕스(Redux)란? 리덕스(Redux) 공식홈페이지에서는 리덕스를 자바스크립트 앱을 위한 예측 가능한 state 보관함("A predictable state container for JS App")으로 정의하고 있다. 여기서 sta
contentstoaster.tistory.com
react-redux 사용법
1 . 설치하기
안타까운 것은, react-redux 라이브러리만 사용하려고 해도 redux 라이브러리도 설치해야한다는 점.
#yarn으로 두 라이브러리 동시 설치. npm으로 설치 가능
yarn add redux react-redux
2. 스토어와 리듀서
리덕스를 코드에 쓰겠다고 마음먹는 순간 제일 먼저 할일은… 스토어를 세우는 것!
- 스토어만큼은 순수 redux 라이브러리에서 가져와야 한다.
- 그리고 스토어 안에서 실제로 일 할 놈인 ‘reducer’ 함수를 무조건 만들어 넣어야 한다.
- 그래서 일단 reducer 틀만 만들어 넣어준다.
1) Create 스토어 기본코드
import {createStore} from ‘redux’
const store = createStore(reducer);
2) Create 스토어 + 리듀서 ‘틀’
// src/store.js
import {createStore} from 'redux';
// reducer 틀
function reducer(state, action){
return state;
}
// *** 스토어 건립 ***
// 첫번째 매개변수는 무조건 reducer 함수!!
// 두번째 매개변수는 devtool 실행해주려고 쓴 놈. 무시해도 됨.
export default createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
3) 리듀서 내용 작성
// src/store.js
import {createStore} from 'redux';
function reducer(state, action){
// reducer는 아무도 안불러도 초기에 무조건 1번 실행되므로… 이것은 초기값
if(state === undefined){
return {number:0}
}
// 들어온 action놈이 ‘증가increment’를 외칠 때
if(action.type === 'INCREMENT'){
// 기존에 리덕스가 가지고 있던 state를 복사하되, number라는 놈 값만 바꿔라
return {...state, number:state.number + action.size}
}
// 리턴을 누구한테 하냐고? 리덕스 젤 안쪽에 있는 state에게.. 그놈을 새놈으로 교체시킴
return state;
}
export default createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())
* 생활코딩 수업에서 작성된 코드를 변형함
4) 최상위 컴포넌트에 store 씌우기
: react-redux 라이브러리에서 Provider 컴포넌트를 불러온 뒤, index.js 안에 들어 있는 <App /> 컴포넌트를 감싸준다. 그리고 Provider의 props로 store를 제공하면 하위 컴포넌트들은 store를 다시 호출하지 않아도 store의 내장함수를 사용할 수 있다. 이로써 세팅 끝.
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import {Provider} from 'react-redux';
import store from './store';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
3. 그 다음은 CONNECT 가 알아서 할게요
순수 redux라면 store.getState(), store.dispatch(), store.subscribe()를 써서 값 받고, 바꾸고, 렌더링 신호보내는 일을 하겠지만, react-redux 라이브러리에서는 connect만 있으면 된다. 리덕스의 3대 함수를 사용할 컴포넌트는 모두 connect만 외치면 된다.
Redux 라이브러리 | React-Redux 라이브러리 |
getState() dispatch() subscribe() |
connect()() |
1) CONNECT()() 기본 사용법
: 아래 코드 참조. 잘못 쓴 게 아니고 괄호가 두 개다. connect 함수는 return을 두번하는 구조인데, 첫 return에서 함수를 반환하고, 그걸을 실행시키기 위해 () 를 한 번 더 쓰기 때문이다.
import {connect} from 'react-redux';
connect()();
2) 매개변수 1
: connect의 두 번째 괄호부터 말하자면, 인자로 컴포넌트 이름이 들어간다. getState, dispatch 혹은 subscribe호출이 필요한 바로 그 컴포넌트 (아래 예제 코드에선 AddNumber가 그놈)
// 이 컴포넌트가 있다는 가정 하에 이 컴포넌트에 리덕스 함수를 적용할 것임
import AddNumber from "../components/AddNumber";
import {connect} from 'react-redux';
export default connect()(AddNumber); // <AddNumber /> 컴포넌트
2) 매개변수 2
: connect의 첫 번째 괄호에는 들어가는 인자는 두 개다.
① mapReduxStateToReactProps 함수: 기존의 getState, subscribe 대체
② mapDispatchToProps 함수: 기존의 dispatch를 대체
Redux 라이브러리 | React-Redux 라이브러리 |
connect( | |
getState, subscribe | mapReduxStateToReactProps, |
dispatch | mapDispatchToProps |
) (...) |
- 기본형태
import AddNumber from "../components/AddNumber";
import {connect} from 'react-redux';
// 일단 껍데기 만들어줌
function mapReduxStateToReactProps(){ return { myProp: "" } }
function mapDispatchToProps(){ return { myProp: "" } }
// 매개변수가 모두 다 들어간 connect
export default connect(mapReduxStateToReactProps, mapDispatchToProps)(AddNumber);
- mapReduxStateToReactProps
: getState의 역할을 하는 놈. 리덕스에 저장된 state의 데이터를 React의 Props값으로 넘겨주는 세팅을 하는 매개함수. connect 함수 내부에서 리덕스의 state를 전달받으므로, 밑도 끝도 없이 사용할 수 있다. 이 인자의 공식적인 이름은 mapStateToProps. 생활코딩에서 강사님께서 헷갈리지 않게 수정해주셨다.
function mapReduxStateToReactProps(reduxState){
return {
number:reduxState.number
}
}
// 첫 인자만 필요하면 그것만 쓰면 됨
export default connect(mapReduxStateToReactProps)(AddNumber);
- mapDispatchToProps
: dispatch의 역할을 하는 놈. subscribe도 된다. 리덕스의 state 객체에 저장된 데이터를 수정하고자 할 때 사용하는 매개함수. connect함수 내에서 store.dispatch를 전달받으므로, 밑도 끝도 없이 dispatch를 부를 수 있다.
function mapDispatchToProps(dispatch){
return {
onClick:function(size){
dispatch({type:'INCREMENT', size:size});
}
}
}
// 두번째 인자면 필요하면 첫 인지에 null
export default connect(null, mapDispatchToProps)(AddNumber);
redux를 react-redux로 변경 예제
1. dispatch 대신 mapDispatchToProps
변경전
import AddNumber from "../components/AddNumber";
import React, { Component } from "react";
import store from '../store';
export default class extends Component{
render(){
return <AddNumber onClick={function(size){
store.dispatch({type:'INCREMENT', size:size});
}.bind(this)}></AddNumber>
}
}
변경후
import AddNumber from "../components/AddNumber";
import {connect} from 'react-redux';
function mapDispatchToProps(dispatch){
return {
onClick:function(size){
dispatch({type:'INCREMENT', size:size});
}
}
}
export default connect(null, mapDispatchToProps)(AddNumber);
/*
import React, { Component } from "react";
import store from '../store';
export default class extends Component{
render(){
return <AddNumber onClick={function(size){
store.dispatch({type:'INCREMENT', size:size});
}.bind(this)}></AddNumber>
}
}
*/
2. getState 대신 mapStateToProps
변경전
import DisplayNumber from '../components/DisplayNumber';
import React, { Component } from 'react';
import store from "../store";
export default class extends Component{
state = {number:store.getState().number}
constructor(props){
super(props);
store.subscribe(function(){
this.setState({number:store.getState().number});
}.bind(this));
}
render(){
return <DisplayNumber number={this.state.number} unit={this.props.unit}></DisplayNumber>
}
}
변경후
import DisplayNumber from '../components/DisplayNumber';
import {connect} from 'react-redux';
function mapReduxStateToReactProps(state){
return {
number:state.number
}
}
export default connect(mapReduxStateToReactProps)(DisplayNumber);
/*
import React, { Component } from 'react';
import store from "../store";
export default class extends Component{
state = {number:store.getState().number}
constructor(props){
super(props);
store.subscribe(function(){
this.setState({number:store.getState().number});
}.bind(this));
}
render(){
return <DisplayNumber number={this.state.number} unit={this.props.unit}></DisplayNumber>
}
}
*/