useState
로 관리를 하며 복잡한 로직이나 상태가 동시에 변경되는 위험성을 가지고 있는 경우 useReducer
를 사용할 수 있다.useState
, 또는 useReducer
로 관리가 가능하지만 props
를 이용해 props
체인을 구축해야합니다.useState
, 또는 useReducer
관리를 하며 props 전체 주변의 함수를 업데이트 하며 관리를 할 수 있습니다. (즉, usState
or useReducer
+ 상태변화시키는 - props함수)//ex)
return (
<AuthContextProvider>
<ThemeContextProvider>
<UIInteractionContextProvider>
<MultiStepFormContextProvider>
<UserRegistration />
</MultiStepFormContextProvider>
</UIInteractionContextProvider>
</ThemeContextProvider>
</AuthContextProvider>
)
※ 리액트 컨택스트 및 리덕스는 각각의 장점이 있기 때문에 어떤 것이 더 좋다 나쁘다라고 할 수 없습니다. 각각의 상황에 맞춰 내 프로젝트에 더 적합한 상태관리 시스템을 선택해서 사용을 하면 됩니다.(둘을 함께 사용할 수도 있습니다.)
설치
$ yarn add redux
리듀서
import redux = require('redux')
const counterReducer = (state = {count: 0}, action) => {
return {
counter: state.counter + 1
}
}
const store = redux.createStore(counterReducer)
구독(sbuscribe)
const couterSubscriber = () => {
const latestState = store.getState()
}
store.subscribe(couterSubscriber)
store.dsipatch({ type: 'increment' })
실제 예시
import redux = require('redux')
const counterReducer = (state = {count: 0}, action) => {
if(action.type === 'increment' {
return {
counter: state.counter + 1
}
})
if(action.type === 'decrement') {
return {
counter: state.counter - 1
}
}
return state;
}
const store = redux.createStore(counterReducer)
store.dsipatch({ type: 'increment' })
store.dsipatch({ type: 'decrement' })
$ yarn add redux react-redux
redux는 리액트에서만 사용되는 것이 아니기 때문에 리덕스 스토어에 컴포넌트를 구독하는 react-redux도 함께 설치를 해줍니다.
import { createStore } from 'redux'
const couterReducer = (state = { count: 0 }, action) => {
if (action.type === 'increment') {
return {
count: state.count + 1,
}
}
if (action.type === 'decrement') {
return {
count: state.count - 1,
}
}
return state
}
const store = createStore(couterReducer)
export default store
만든 스토어를 리액트를 제공합니다. 보통은 어플리케이션의 가장 높은 레벨인 index.js파일에 제공을 합니다.
index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import './index.css'
import App from './App'
import { Provider } from 'react-redux'
import store from './store/index'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<Provider store={store}>
<App />
</Provider>,
)
import {useSelector, connect} from 'react-redux'
두가지를 사용할 수 있습니다.counter.js
import { useSelector } from 'react-redux' ✅
import store from '../store'
import classes from './Counter.module.css'
const Counter = () => {
const counter = useSelector((state) => store.counter) ✅
const toggleCounterHandler = () => {}
return (
<main className={classes.counter}>
<h1>Redux Counter</h1>
<div className={classes.value}>{counter}</div> ✅
<button onClick={toggleCounterHandler}>Toggle Counter</button>
</main>
)
}
useDispatch
를 사용합니다.useDispatch
를 호출하면 호출된 값에서 dispatch
를 꺼내올 수 있습니다.import { useSelector, useDispatch } from 'react-redux'
import store from '../store'
const Counter = () => {
const dispatch = useDispatch()
const counter = useSelector((state) => store.cointer)
const incrementHandler = () => {
dispatch({ type: 'increment' })
}
const decrementHandler = () => {
dispatch({ type: 'decrement' })
}
return (
...
<button onClick={incrementHandler}>Increment</button>
<button onClick={decrementHandler}>Decrement</button>
)
import {Component} form 'react'
import {useSelector, useDispatch, connect} from 'react-redux'
class Counter extends Component {
const incrementHandler = () => {
this.props.increment()
}
const decrementHandler = () => {
this.props.decrement()
}
render() {
return (
<div>{this.props.counter}</div>
<button onClick={this.incrementHandler.bind(this)}>Increment</button>
<button onClick={this.decrementHandler.bind(this)}>Decrement</button>
)
}
}
const mapStateToProps = state => {
return {
counter: state.counter
}
}
const mapDispatchToProps =dsipatch => {
return {
increment: () => dispatch({type: 'increment'})
decrement: () => dispatch({type: 'decrement'})
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Counter) ✅
connect
가 클래스 기반 컴포넌트를 리덕스에 연결하는데 도움을 줍니다.connect
은 고차함수로 리턴을 할 때 새함수를 반환합니다. 그리고 리턴함수에서 카운터를 보냅니다.payload란 액션에 추가적인 속성을 전달 할 수 있습니다.
만약, 1이 아니라 숫자 5씩 증가하는 버튼을 만드는 경우는 어떻게할까?
물론, store에서 5씩 증가하는 reducer를 하나 더 작성할 수도 있지만,이는 sortable하지 않습니다. 따라서 action에 접근할 때 추가적인 로직을 함께 전달해야합니다.
reducer
import { createStore } from 'react-redux'
const couterReducer = (state = { count: 0 }, action) => {
...
//추가
if (action.type === 'increase') {
return {
counter: state.counter + action.amount,
}
}
...
return state
}
const store = createStore(couterReducer)
export default store
Counter.js
//추가
const increseHandler = () => {
dispatch({ type: 'increase', amount: 5 })
}
토글버튼을 눌렀을 때 모든 버튼이 사라지고 다시 토글 버튼을 누르면 나타나게 만들어보도록 하겠습니다.
버튼이 보이거나 사라지게 하는 토글동작의 경우 현재의 컴포넌트에만 관심을 있는 것이기 때문에 redux가 아니라 useState로 관리하는 것이 좋습니다.
단, 현재 counter역시 로컬 상태이기 때문에 액션에 디스페치를 해보도록 하겠습니다.
스토어-리듀서
import { createStore } from 'react-redux'
const initialState = { count: 0, showCounter: true }
const couterReducer = (state = initialState, action) => {
if (action.type === 'increment') {
return {
count: state.count + 1,
showCounter: state.showCounter,
}
}
if (action.type === 'increase') {
return {
count: state.count + action.amount,
showCounter: state.showCounter,
}
}
if (action.type === 'decrement') {
return {
count: state.count - 1,
showCounter: state.showCounter,
}
}
if (action.type === 'toggle') {
return {
showCounter: !state.showCounter,
counter: state.counter,
}
}
return state
}
const store = createStore(couterReducer)
export default store
Counter.js
const Counter = () => {
const dispatch = useDispatch()
const counter = userSelector((state) => state.counter)
const show = useSelector(state => state.showCounter)
return(
...
const toggleCounterHandler = () => {
dispatch({ type: 'toggle' })
}
)}
리덕스가 기존의 state를 대체하는 데 사용하는 완전히 새로운 객체인 새 스냅샷을 항상 반환해야합니다.
즉, reducer에서 반환하는 객체는 중요하지 않지만, 중요한 것은 기존 state와 병합되지 않고 기존 state를 덮어쓴다는 것입니다.
절대 기존 state를 변경해서는 안됩니다! 대신에 새로운 state 객체를 반환하여 항상 재정의합니다.