Redux 사용 이유
- 컴포넌트 간 state 공유가 편해진다 -> props 전달할 필요가 없다.
Q. 장바구니 state가 App, Detail, Cart 컴포넌트에 필요하다면 어디에 만들어야할까?
A. 최상단 컴포넌트에. 그런데 props를 일일히 전달해주기 귀찮다. Redux를 사용하면 컴포넌트들이 props 없이 state를 공유할 수 있다.
redux를 설치하고 store.js 파일을 생성해서 그 안에 모든 state를 보관할 수 있고, 모든 state를 어느 컴포넌트에서든 가져다 쓸 수 있다.
사이즈가 큰 프로젝트는 Redux가 필수이므로, 리액트 구인 시 대부분 Redux를 요구하니 알아두자!
Redux Toolkit 설치
설치 전 주의사항
package.json에서 'react'와 'react-dom'이 18.1v 이상인지 확인하자.
Redux 설치하기
npm install @reduxjs/toolkit react-redux
Redux 세팅
세팅1. store.js 파일 생성
🕹️store.js
import { configureStore } from '@reduxjs/toolkit'
export default configureStore({
reducer: { }
});
세팅2. index.js 가서 <Provider store={store}> 쓰기
store.js에서 export한 configureStore를 store명으로 불러오고, index.js에서 렌더링되는 컴포넌트 전체를 <Provider store={store}>로 감싼다.
🕹️index.js
import {Provider} from 'react-redux';
import store from './store.js';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<BrowerRouter>
<App />
</BrowerRouter>
</Provider>
);
그러면 이제 App와 그의 모든 자식들은 store.js에 있던 state를 전부 사용할 수 있게 된다.
Redux store에 state 보관하는 법
🕹️store.js
import { configureStore } from '@reduxjs/toolkit'
const user = createSlice({
name: 'state이름',
initialState: '값'
})
export default configureStore({
reducer: {
user: user.reducer
}
});
- **createSlice** : useState의 역할이다
- state 하나를 slice라고 한다.
- slice를 만들면 변수로 저장하고, configureStore에 등록한다.
값을 지정해보자.
import { configureStore } from '@reduxjs/toolkit'
const user = createSlice({
name: 'user',
initialState: 'kim'
})
export default configureStore({
reducer: {
user: user.reducer
}
});
Redux store에 있던 state 가져다 쓰는 법: useSelector
Cart.js에서 store에 있는 값을 가져다 써보자.
🕹️Cart.js
import { useSelector } from 'react-redux';
function Cart(){
const a = useSelector((state) => state)
console.log(a); // {user: 'kim'}
}
user만 가져오고 싶으면
const {user} = useSelector((state) => state);
console.log(user); // kim
store.js에서 state를 하나 더 만들어 보자. 재고 stock를 나타내는 state를 만든다.
🕹️store.js
import { configureStore } from '@reduxjs/toolkit'
const user = createSlice({
name: 'user',
initialState: 'kim'
})
const stock = createSlice({
name: 'stock',
initialState: [10, 11, 12]
})
export default configureStore({
reducer: {
user: user.reducer,
stock: stock.reducer
}
});
이제 다시 Cart.js의 출력 결과를 보면, 추가한 state도 같이 나온다.
🕹️Cart.js
import { useSelector } from 'react-redux';
function Cart(){
const a = useSelector((state) => state)
console.log(a); // {user: 'kim', stock: Array(3)}
}
stock 부분만 쓰고 싶으면 state.stock로 가져다 쓰면 된다.
(참고) useSelector 편하게 쓰려면
import { useSelector } from 'react-redux';
function Cart(){
const a = useSelector((state) => state) // ⬅️
}
화살표 부분에서 state는 store안에 있던 모든 state가 된다.
그래서 .을 찍어서 원하는 것만 쉽게 가져다 쓰도록 하자.
import { useSelector } from 'react-redux';
function Cart(){
const a = useSelector((state) => state.stock) // ⬅️
}
Redux 쓰면 되지 props는 왜 가르친대?
Redux vs props
왜냐하면 Redux를 사용하려면 라이브러리 설치도 해야하고,
세팅하는 문법도 필요하고,
state 만들었으면 등록하는 과정도 필요하고,
...
이처럼 코드가 더 길어질 수 있다.
그래서 간단한 프로젝트의 경우에는 그냥 props를 사용하는 게 코드가 더 짧고 간단하고, 컴포넌트가 많은 경우에는 Redux를 쓰는 게 좋다.
Redux store 안에 모든 걸 넣지 말자
Redux 쓴다고 해서 모든 state를 store에 보관할 필요는 없다.
만약 생성된 state가 한 컴포넌트 내에서만 사용되고 공유될 필요가 없는 경우에는 Redux store에 등록하지 않으면 된다.
Redux store의 state를 변경하는 법: useDispatch
- store.js에서 state를 수정해주는 함수를 만들고,
- export한다
- 원할 때 그 함수를 실행해달라고 store.js에 요청한다. (dispatch(state변경함수()))
1. state 수정해주는 함수 만들기
user의 initialState인 'kim'을 'john'으로 변경해주도록 해보자.
먼저 store.js에서 state를 수정해주는 함수를 만든다.
🕹️store.js
import { configureStore } from '@reduxjs/toolkit'
const user = createSlice({
name: 'user',
initialState: 'kim',
reducers: {
changeName(){
return 'john'
}
}
})
export default configureStore({
reducer: {
user: user.reducer,
}
});
근데 만약 state를 변경할 때 기존 state가 필요하다면?
const user = createSlice({
name: 'user',
initialState: 'kim',
reducers: {
changeName(state){ // ⬅️ 함수에 state를 추가한다.
return 'john' + state;
}
}
})
여러 함수를 만들고 싶다면, reducers 객체 내에 함수를 더 추가한다.
const user = createSlice({
name: 'user',
initialState: 'kim',
reducers: {
changeName(state){
return 'john' + state;
},
다른함수(){
},
...
}
})
2. 만든 함수 export 하기
만든 함수를 다른 컴포넌트에서 쓸 수 있도록 export 해준다.
user.actions를 작성하면 slice 안에 있는 모든 함수를 가져온다. destructuring을 사용해서 더 간편하게 모든 함수를 가져올 수 있다.
🕹️store.js
export let { changeName, 다른함수, ... } = user.actions
3. 만든 함수 import해서 사용하기
버튼을 누르면 state를 john kim으로 변경하려면?
🕹️Cart.js
import { useDispatch } from 'react-redux';
import { changeName } from './../store.js';
function Cart(){
const dispatch = useDispatch();
const onClick = () => {
dispatch(changeName());
}
return(
<>
<button onClick={onClick}>+</button>
</>)
}
- **useDispatch**: store.js로 요청을 보내주는 함수
- 사용 시엔 **dispatch(state변경함수())** 이렇게 한다.
- changeName()을 이 자리에서 실행해 달라는게 아니라, store.js에게 changeName()을 실행해달라고 부탁하는 메시지를 보낸다.
이렇게 쓰는 게 처음엔 이해가 되지 않고 번거롭겠지만, 프로젝트 사이즈가 커지면 실은 좋은 방식이다. 버그를 잘 방지할 수 있기 때문이다. 생각해보자.
만약 user state 값인 'kim'을 가져다 쓰는 컴포넌트가 10개가 있다고 가정하자. 변경도 자유자제로 할거다. 그러면 갑자기 버그가 나서 'kim'이 갑자기 숫자 123이 됐다고 하자. 그럼 이 버그를 일으킨 범인을 찾아야한다. 하지만 모든 컴포넌트가 사용하고 있으니까 범인을 찾으려면 모든 컴포넌트를 뒤져야한다.
store.js에 state를 수정하는 함수를 미리 만들어놓고 컴포넌트들이 이 수정하는 함수를 부르고 수정해달라고 부탁하는 식으로 state를 변경하게 되면 버그를 추적할 때 훨씬 쉬워진다. 왜냐하면 범인은 무조건 state를 수정하는 store.js이기 때문이다.
state가 object/array일 경우 변경하는 법
state가 object/array인 경우 변경하는게 특이하니까 알아보자.
object/array의 경우, 직접 수정해도 state가 변경된다
initialState를 object로 변경한다.
🕹️store.js
const user = createSlice({
name: 'user',
initialState: {
name: 'kim',
age: 20
},
reducers: { }
}
})
{name: 'park'}으로 바꾸려면?
const user = createSlice({
name: 'user',
initialState: {
name: 'kim',
age: 20
},
reducers: {
changeName(state){
return {name: 'park', age: 20}
}
}
})
이렇게도 되겠지만 더 쉬운 방법이 있다.
state가 object/array인 경우에는 returnn문을 사용하지 않아도 변경이 된다. Immer.js라는 라이브러리가 자동으로 설치되기 때문에 이 도움을 받기 때문이다.
reducers: {
changeName(state){
state.name = 'park'; // ⬅️
}
}
Q. 버튼 누르면 age가 +1 되는 기능?
reducers: {
changeName(state){
state.name = 'park';
},
increate(state){
state.age += 1;
}
}
...
export let { changeName, increase } = user.actions;
Q. 원하는 수만큼 age +n 하는 기능? -> state 변경 함수에 파라미터 뚫기
reducers: {
changeName(state){
state.name = 'park';
},
increate(state, action){
state.age += action.payload; // ⬅️
}
}
...
export let { changeName, increase } = user.actions;
왜 payload인가?
increase라는 함수를 실행해달라고 store.js에 요청을 하는데, 메시지에 실어 나르는 화물(payload)라 하여 payload라고 작성한다.
왜 action인가?
**state 변경 함수를 action이라고 한다**. payload 뿐만 아니라 action에 관련된 다양한 정보들을 가지고 있기 때문에 action이라 부른다.
파일 분할하기
코드가 길어지면 import, export 하여 쓰도록 파일을 분할하자.
user가 길어서 다른 파일로 저장해서 가져다 쓰도록 하자.
🕹️store/userSlice.js
import { createSlice } from '@reduxjs/toolkit';
const user = createSlice({
name: 'user',
initialState: {
name: 'kim',
age: 20
},
reducers: {
changeName(state){
return {name: 'park', age: 20}
},
increate(state, action){
state.age += action.payload;
}
}
});
export const { changeName, increase } = user.actions;
export default user;
🕹️store.js
import user form './store/userSlice.js';
그리고 changeName과 increase의 export하는 파일이 바뀌었으니까 가져다 쓰는 곳에서도 from을 변경한다.
🕹️Cart.js
import { changeName, increase } from './../store/userSlice.js';
'Front-End: Web > React.js' 카테고리의 다른 글
[코딩애플] 성능개선 1: 개발자 도구 & lazy import (0) | 2022.11.08 |
---|---|
[코딩애플] react-query 사용하여 실시간 데이터 가져오기 (0) | 2022.11.08 |
[제로초] ReactJS Chpater 1. 리액트 시작하기 (0) | 2022.10.22 |
memo, useMemo, useCallback, useRef (0) | 2022.10.19 |
React & Redux (0) | 2020.08.03 |