둘다 Store를 컴포넌트가 Subscribe 하여, Store의 데이터가 변하면 , 구독하는 컴포넌트들이 리렌더링 되는데 의외로 퍼포먼스 측면에서 차이가 있다고한다.
ContextApi를 사용하면 root 컴포넌트에서, Provider컴포넌트가 네스팅 되는데, 마치 콜백지옥을 보는 것 같이 복잡해질 수 있다.
결론은 깔끔하게 리덕스로 구현하자!
패키지 설치 :npm install redux react-redux
Store.js
import {createStore} from 'redux';
// import {createSlice} from '@reduxjs/toolkit';
const initialState = {
counter:0,
showCounter:true
};
const counterReducer = (state=initialState,action) => {
if(action.type ==='increment'){
return {
...state,
counter:state.counter+1,
}
}
if(action.type ==='decrement'){
return {
...state,
counter: state.counter-1,
}
}
if(action.type === 'toggle'){
return {
...state,
showCounter:!(state.showCounter)
}
}
return state
}
const store = createStore(counterReducer);
export default store;
핵심이 되는 Store 객체이다.
커멘트 해놓은 리덕스 툴킷은, 이후에 여러가지 리듀서를 포함할 경우에 사용할 것이다.
맨처음 리듀서가 초기화될 때를 위해 initialState를 설정하고, 리듀서함수에서 return state 를 해주어야한다.
store 객체를 생성할때는 createStore() 매개변수로 , 만든 리듀서함수를 전달한다.
구독하는 컴포넌트에서 dispatch함수를 호출하면, 리듀서 함수가 실행될 것이다.
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import './index.css';
import App from './App';
import store from './store/index';
ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('root'));
root 폴더의 index.js 에서 , App컴포넌트 전체를 Provider로 감싼다. App 컴포넌트, App컴포넌트 하위의 컴포넌트들이 store 데이터에 접근할 수 있다.
위에서 만든 store 객체를 Provider 컴포넌트에 연결시켜주자.
App.js
import React,{Fragment} from 'react';
import Counter from './components/Counter';
function App() {
return (
<Fragment>
<Counter/>
</Fragment>
);
}
export default App;
구현할 기능은, Counter 컴포넌트에서 store에 접근하여 count값을 받아오고, dispatch함수를 통해 store를 업데이트 할것이다.
Counter.js
import {useSelector, useDispatch} from 'react-redux';
import classes from './Counter.module.css';
const Counter = () => {
const counter = useSelector(state => state.counter); // redux store의 state의 데이터들중에 일부를 가져오게해줌, 해당 hook을씀으로써 자동 구독이됨.
const toggle = useSelector(state => state.showCounter);
const dispatch = useDispatch();
const toggleCounterHandler = () => {
dispatch({type:'toggle'});
};
const incrementHandler = () => {
dispatch({type:'increment'});
};
const decrementHandler = () => {
dispatch({type:'decrement'});
};
return (
<main className={classes.counter}>
<h1>Redux Counter</h1>
{!toggle && <div className={classes.value}>{counter}</div>}
<div>
<button onClick={incrementHandler}>Increment</button>
<button onClick={decrementHandler}>Decrement</button>
</div>
<button onClick={toggleCounterHandler}>Toggle Counter</button>
</main>
);
};
export default Counter;
useSelector 훅 을 통해 store 값을 받아올 수 있다.
주의할 점은,
store 의 state 중에, 구독하고싶은 데이터들을 하나씩 뽑아서 사용한다. useSelector 훅을 사용하면 해당 컴포넌트가 store를 구독할 수 있도록, 리덕스 라이브러리가 처리해준다.
if(action.type ==='decrement'){
return {
...state,
counter: state.counter-1,
}
}
위 부분을 아래와 같이 바꿔도 기능상 동작은 한다.
if(action.type ==='decrement'){
state.counter--;
return state;
}
반드시 새로운 state값을 리턴할때는 불변성을 유지해주어야한다.
리액트에서 비교알고리즘을 얕은비교(shallow)를 택했기 때문이다.
따라서 store의 나머지 데이터들도 함께 넣어서 리턴해주어야한다.
이런 작업이 귀찮아서 결국 리덕스 툴킷을 사용하게된다 (immer)
예를들어, store 의 state 값으로, counter,toggle 값이 있다고 가정하자.
A컴포넌트는 counter를 구독하고 (useSelector를 통해), B컴포넌트에는 toggle값만 구독하고 있고.
C컴포넌트는 counter,toggle 모두 구독하고있다고 가정하자.
toggle값만 변함 -> B컴포넌트,C컴포넌트 리렌더링
counter값만 변함 -> A컴포넌트,C컴포넌트 리렌더링
훨씬 포스팅할 내용들은 많은데... 천천히 더 추가해야겠다
Git 👇
https://github.com/sae1013/react-redux-demo/tree/redux-basic/src