[Redux] React-Redux
이전 포스팅에서 React+Redux 조합을 했듯이, Container 와 Presentational 기능분리의 번거로움이 있었다.
아마, 이를 Context API의 Provider, Consumer 개념처럼 Global State를 공급하는 역할을 이 react-redux가 해줄 것 같다.
💜 React? Redux? React-Redux?
React는 자바스크립트 기반 SPA 라이브러리, Redux는 React 뿐만 아니라 JS환경의 상태관리를 개선하기 위해 등장한 라이브러리다.
그렇다면, React 프로젝트 시 설치하는 Redux 말고, React-Redux 라이브러리의 의의는 무엇일까?
- 의의
Redux 자체는 React, Angular, Vue 및 vanila JS 를 포함한 모든 UI 레이어 또는 프레임워크와 함께 사용 가능한 독립형 라이브러리이다.
모든 UI 프레임워크는 Redux를 사용하는 경우, UI 코드에서 'UI 바인딩 라이브러리' 를 사용하여 프레임워크와 Redux를 연결한다.
React-Redux 는 React 공식 Redux 바인딩 라이브러리이다. React + Redux를 사용하는 경우, 두 라이브러리를 바인딩해야 한다.
(출처: https://react-redux.js.org/introduction/why-use-react-redux )
한마디로, react-redux는 react와 redux를 바인딩(연결) 해주는 라이브러리 인 것이다.
- 설치
npm install react-redux
💜 React-Redux 주요 키워드
react-redux 에서 추가적으로 다루는 개념(키워드)들이 있다. 이에 대해 간단하게 정리해보았다.
위에서 언급했듯, React-Redux 가 제공하는 함수들은 대부분 React ~ Redux 간 연결기능을 주로 담당한다.
1. Provider
- Provider는 connect() 함수에 포장된 모든 컴포넌트가 Redux 스토어를 사용할 수 있게 해준다. (props로 스토어를 받음)
- 이같은 특징으로, 앱 최상위 레벨에서 Provider를 렌더링한다.
import React from "react";
import ReactDOM from "react-dom";
import { createStore } from "redux";
import reducers from "./reducers";
import { Provider } from "react-redux"; // Provider는 react-redux 라이브러리에서 가져옴
import App from "./components/App";
ReactDOM.render(
// Provider 사용예시
<Provider store={createStore(reducers)}>
<App />
</Provider>,
document.querySelector("#root")
);
2. connect()
- connect() 함수는 React 컴포넌트를 Redux 스토어와 연결한다.
- connect() 된 컴포넌트는 스토어에서 상태를 받아오거나, 디스패치 전달을 할 수 있다.
- connect() 문법: connect([mapStateToProps], [mapDispatchToProps])([전달할 컴포넌트명])
import React from 'react'
import { connect } from 'react-redux; // connect 는 'react-redux' 라이브러리에서 가져옴
class Example extends Component {
render(){
return(
<>
예시 컴포넌트의 내용
</>
)
}
export default connect(null)(Example)
3. mapStateToProps
- connect() 함수의 첫 번째 인자로 전달된다.
- 스토어 내부의 모든 state 값을 인자로 받으며, 컴포넌트가 필요로 하는 데이터 객체를 반환한다.
- 연결된 컴포넌트에서 필요한 상태(state)를 선택하는데 사용된다. (= 상태 불러오기, getState)
- store 상태가 바뀔 때마다 호출된다. (= 구독, subscribe)
import React from 'react'
import { connect } from 'react-redux;
class Example extends Component {
render(){
return(
<>
예시 컴포넌트의 내용
</>
)
}
const mapStateToProps = (state) => {
return {
//받아올때 쓸 state의 이름' : state.'reducer에서 정한 데이터 이름'
needData : state.needData
}
}
export default connect(mapStateToProps)(Example)
mapStateToProps() 함수는 해당 컴포넌트에서 스토어의 상태를 받고자 할 때 사용된다. (useContext Hooks 적용과 유사)
스토어의 state를 인자로 받아, 우리가 사용하고자 하는 객체형태로 재구성한다. 이 객체는, Example 컴포넌트의 props로 전달된다.
4. mapDispatchToProps
- connect() 함수의 두 번째 인자로 전달된다.
- 스토어의 dispatch 함수를 인자로 받으며, 컴포넌트에 적용할 함수를 객체에 담아 전달한다.
import React from 'react'
import { connect } from 'react-redux;
class Example extends Component {
render(){
return(
<Example onClick={this.props.onClick}>
예시 컴포넌트의 내용
</Example>
)
}
const mapDispatchToProps = (dispatch) => {
return {
//받아올때 쓸 props(함수명)의 이름' : dispatch(action) 함수
onClick : () => {
dispatch({
type: 'INCREMENT',
size: 2,
})
}
}
}
export default connect(null, mapDispatchToProps)(Example)
mapDispatchToProps 역시 객체를 반환하는데, 특징은 키가 props명, 키값은 우리가 활용할 디스패치 함수가 된다.
💜 React-Redux 적용하기
기존에 제작한 Container 컴포넌트들을 각각 React-Redux 를 통해 간소화하는 작업을 해보겠다.
// AddNumberCon.js
import React from 'react'
import AddNumber from '../components/AddNumber';
import store from '../Store';
const AddNumberCon = () => {
return (
<AddNumber onClick={(size) => {
store.dispatch({
type: 'INCREMENT',
size,
})
}}/>
)
}
// ShowNumberCon.js
import React, { useState } from 'react';
import store from '../Store';
import ShowNumber from '../components/ShowNumber';
const ShowNumberCon = () => {
const [number, setNumber] = useState(0);
store.subscribe(() => {
setNumber(store.getState().number)
})
return (
<ShowNumber number={number} />
)
}
- React-Redux 적용
1. Provider 설정
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { Provider } from 'react-redux';
import store from './Store';
import './index.css';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.getElementById('root')
);
reportWebVitals();
앱 최상위에서 다음과 같이 <Provider>로 감싸준다. props는 스토어를 지정해주면 된다.
2. Container 컴포넌트의 connect() 적용
1) mapStateToProps : map + Redux-State + to + React-Props
// ShowNumberCon.js
import { connect } from 'react-redux';
import ShowNumber from '../components/ShowNumber';
const mapStateToProps = (state) => {
return {
number: state.number,
}
}
export default connect(mapStateToProps)(ShowNumber);
상태값을 활용하는 <ShowNumber> 컴포넌트의 경우, mapStateToProps 를 통해 스토어의 상태를 전달한다.
스토어 상태 중, 우리가 필요한 number에 대해서만 재가공한 객체가 props로 전달된다.
2) mapDispatchToProps : map + Redux-Dispatch + to + React-Props
// AddNumberCon.js
import { connect } from 'react-redux';
import AddNumber from '../components/AddNumber';
const mapDispatchToProps = (dispatch) => {
return {
onClick: (size) => {
dispatch({
type: 'INCREMENT',
size,
})
}
}
}
export default connect(null, mapDispatchToProps)(AddNumber);
이벤트 핸들러로 디스패치를 활용하는 <AddNumber> 컴포넌트의 경우, mapDispatchToProps 를 통해 디스패치 함수를 전달한다.
객체 형태로 props가 전달되며, 키는 props명, 키값은 dispatch(action) 함수가 들어간다.
확실히 Context API 에 비해 러닝커브가 높다는 것이 느껴졌다. (useReducer 를 제외한 비교이기는 하나)
무엇보다, Container 컴포넌트가 아직 존재한다는 것에 대해 의문이 들기도 한다. (mapStateToProps, mapDispatchToProps)
또한, 생활코딩 강의는 클래스형 컴포넌트 위주로 이루어졌기에, 함수형에 관한 공부도 추가하면서 좀 더 원활하고 효율적으로 Redux를 활용할 수 있는 방법에 대해 고민해봐야겠다.
[출처]
- Redux 공식문서: react-redux.js.org/introduction/quick-start
- mokyungg 님의 블로그: velog.io/@mokyoungg/Redux-react-redux-react-redux