장바구니 페이지를 만들려고 하는데
장바구니에 들어갈 데이터들을 먼저 만들어보려고 한다.
근데 이 데이터들은 Cart.js에서만 쓰이는 것이 아니라 그 상위페이지에서도 쓰일 가능성이 있는데
이번에는 props 말고 redux를 이용해서 state 관리를 해보려고 한다.
Redux란?
Redux는 props없이 state를 공유할 수 있게 도와주는 라이브러리이다.
이걸 설치하면 js 파일 하나에 state 를 보관할 수 있는데
모든 컴포넌트에서 그 state 를 꺼내쓸 수 있다.
큰 사이트 일 수록 이 redux를 쓸 수 밖에 없어서 구인하는 거 보면 redux 숙련도를 요구한다.
Redux 쓰기
일단 설치부터 한다.
npm install @reduxjs/toolkit react-redux
터미널에 이걸 입력하고
state 를 관리할 store.js 라는 파일을 하나 만들어주고
그 파일에 다음과 같이 작성을 해준다.
import { configureStore } from '@reduxjs/toolkit'
export default configureStore({
reducer: { }
})
그리고 index.js 에 가서 Provider를 import 해오고
전체를 감싼다.
이렇게 하면 <App> 과 그 하위에 있는 모든 자식 컴포넌트들은 store.js에 있던 state 를 쓸 수 있게 되는 거다.
state 보관하기
store.js 파일에 다음과 같이 state 를 만들어서 등록한다.
createSlice 를 이용한다.
import { configureStore, createSlice } from "@reduxjs/toolkit";
let user = createSlice({
name: "user",
initialState: "kim",
});
let stock = createSlice({
name: "stock",
initialState: [10, 11, 12],
});
export default configureStore({
reducer: {
user: user.reducer,
stock: stock.reducer,
},
});
그리고 이 state 들을 가져다 쓰려면 다음과 같이 usesSelector 를 이용한다.
...
import { useSelector } from "react-redux";
function Cart() {
let a = useSelector((state) => {
return state;
});
return (
<div>
...
a라는 변수에 저장했으니 이제
a
a.user
a.stock
이런식으로 데이터 바인딩이 가능하다.
let cartdata = createSlice({
name: "cartdata",
initialState: [
{ id: 0, name: "White and Black", count: 2 },
{ id: 2, name: "Grey Yordan", count: 1 },
],
});
cartdata 라는 state 를 만들어서
데이터 바인딩을 해본 모습이다.
import { useSelector } from "react-redux";
function Cart() {
let a = useSelector((state) => {
return state;
});
return (
<div>
<Table striped>
<thead>
<tr>
<th>id</th>
<th>Name</th>
<th>Count</th>
</tr>
</thead>
<tbody>
<tr>
<td>{a.cartdata[0].id}</td>
<td>{a.cartdata[0].name}</td>
<td>{a.cartdata[0].count}</td>
</tr>
<tr>
<td>{a.cartdata[1].id}</td>
<td>{a.cartdata[1].name}</td>
<td>{a.cartdata[1].count}</td>
</tr>
</tbody>
</Table>
</div>
);
}
map 을 이용해서 코드를 개선시켜봤다.
import { useSelector } from "react-redux";
function Cart() {
let state = useSelector((state) => {
return state;
});
return (
<div>
<Table striped>
<thead>
<tr>
<th>id</th>
<th>Name</th>
<th>Count</th>
</tr>
</thead>
<tbody>
{state.cartdata.map((c, i) => (
<tr>
<td>{state.cartdata[i].id}</td>
<td>{state.cartdata[i].name}</td>
<td>{state.cartdata[i].count}</td>
</tr>
))}
</tbody>
</Table>
</div>
);
}
state 변경 함수 만들기
일단 store.js에 state 를 수정하는 함수를 만든다.
slice 안에 reducers 라고 새로 만들어서 여기에 함수를 작성해주면 된다.
let user = createSlice({
name : 'user',
initialState : 'kim',
reducers : {
changeName(state){
return 'john ' + state
}
}
})
그다음에 export 해준다.
export let { changeName } = user.actions
이걸 Cart.js 라는 파일에서 쓰고 싶을때에는 일단 저 changeName 함수를 import 해와주고
useDispatch 라는 걸 라이브러리에서 가져와준 후
함수를 쓸때마다 dispatch로 감싸주면 된다.
import { useSelector, useDispatch } from "react-redux";
import { changeName } from "../store";
function Cart() {
let state = useSelector((state) => {
return state;
});
let dispatch = useDispatch();
return (
<div>
{state.user}의 Cart
<Table striped>
<thead>
<tr>
<th>id</th>
<th>Name</th>
<th>Count</th>
</tr>
</thead>
<tbody>
{state.cartdata.map((a, i) => (
<tr>
<td>{state.cartdata[i].id}</td>
<td>{state.cartdata[i].name}</td>
<td>{state.cartdata[i].count}</td>
<td>
<button
onClick={() => {
dispatch(changeName());
}}
>
...
이런 방식의 장점?
프로젝트가 커진다면 여러 컴포넌트에서 state 를 변경하다가
state 값 버그가 나면
그 문제인 함수를 찾으려고 많은 컴포넌트를 찾아볼 필요가 없다.
지금 이 방식은 미리 state 변경함수는 store.js에다가 만들어놓고
컴포넌트는 그 함수를 실행해달라고 부탁만 하는 식이기 때문에 디버깅에 용이하다.
state 가 object나 array 일 경우
let user = createSlice({
name : 'user',
initialState : {name : 'kim', age : 20},
reducers : {
changeName(state){
state.name = 'park'
}
}
})
이런식으로 직접 state 를 수정할 수 있다.
만약에 파라미터를 받는 함수를 만들고 싶다면( 5라는 파라미터라면 5만큼 증가..)
다음과 같이 payload를 써준다.
let user = createSlice({
name : 'user',
initialState : {name : 'kim', age : 20},
reducers : {
increase(state, a){
state.age += a.payload
}
}
})