본 포스트는 Udemy 의 리액트 강좌를 정리한 글입니다.
우선 Firebase 에 업데이트하는 기능을 제외하고 완성했다.
( Firebase 구현은 다음 포스트에서 )
완성된 모습
먼저 계속 변하는 상품 목록 정보를 관리할 상태가 필요했다.
나는 이것을 Redux로 구현했다.
product-slice.js
import { createSlice } from '@reduxjs/toolkit';
import randomString from '../util/randomString';
const initialState = { items: [] }; // 처음에는 빈 배열로 시작
const productSlice = createSlice({
name: 'product',
initialState, // 처음의 상태를 정의
// reducers -> 상태를 업데이트 해줄 함수 모음집
reducers: {
// 상품 목록에 상품을 추가하는 함수
addProduct(state, action) {
const newItem = action.payload;
const randomId = randomString(10); // 새로운 상품의 ID는 랜덤 스트링으로 생성한다.
state.items.push({
id: randomId + newItem.title,
price: newItem.price,
title: newItem.title,
description: newItem.description,
});
},
// 상품 목록에서 상품을 삭제하는 함수 (삭제할 상품의 ID를 매개변수로 받아옴)
removeProduct(state, action) {
const id = action.payload;
state.items = state.items.filter((item) => item.id !== id);
},
},
});
// 이걸 이용해서 함수명 자동완성을 할 수 있다.
export const productActions = productSlice.actions;
export default productSlice;
redux-toolkit 의 createSlice
함수를 사용했다.
Redux 의 상태는 reducers
에서 정의된 함수를 사용해서 변경할 수 있다.
위의 함수들은 state(상태)를 직접 변경하는 것처럼 보이지만 실제로는 createSlice
함수가 새로운 state 로 덮어씌우는 과정을 은밀히 수행한다.
위의 removeProduct
함수는 매개변수로 상품의 아이디를 받아온다.
매개변수는 action.payload 에서 딕셔너리 형태로 받아올 수 있다.
이제 생성한 reducer 함수를 configureStore
함수에 추가한다.
( 여기서 Redux 의 장점이 나오는데 useContext 와 다르게 여러 곳에서 생성한 state 들을 한번에 모아서 정리할 수 있다.)
store/index.js
import { configureStore } from '@reduxjs/toolkit';
import cartSlice from './cart-slice';
import editProductSlice from './editProduct-slice';
import productSlice from './product-slice';
import uiSlice from './ui-slice';
const store = configureStore({
reducer: {
ui: uiSlice.reducer,
cart: cartSlice.reducer,
product: productSlice.reducer, // 방금 작성한 상품 목록 reducer 함수이다.
editProduct: editProductSlice.reducer,
},
});
export default store;
마지막으로 Provider 태그로 App 컴포넌트를 감싸고 위에서 생성한 store 변수를 props로 보내주면 이제 App에서 사용할 수 있게 된다.
index.js
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { Provider } from 'react-redux';
import store from './store/index';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
이제 위의 상품 목록을 Redux 에서 관리하므로 위에서 생성한 product-slice
의 state 와 목록 상태를 연동해야 한다. 이 작업을 useSelector
함수로 구현한다.
products.js
import { useSelector } from 'react-redux';
import ProductItem from './ProductItem';
import classes from './Products.module.css';
const Products = (props) => {
// useSelector 함수로 상품 목록 redux의 state를 받아온다.
const productItems = useSelector((state) => state.product.items);
return (
<section className={classes.products}>
<h2>Buy your favorite products</h2>
<ul>
{productItems.map((product) => (
<ProductItem
key={product.id}
id={product.id}
title={product.title}
price={product.price}
description={product.description}
/>
))}
</ul>
</section>
);
};
export default Products;
useSelector
함수는 상태를 받아오는 역할만 수행한다.
( reducer 함수를 실행하는 역할은 useDispatch
가 수행한다. )
위의 useSelector
의 state.product.items는
// store/index.js
const store = configureStore({
reducer: {
product: productSlice.reducer
},
});
configureStore에서 이름지은 product 의
// product-slice.js
const initialState = { items: [] };
createSlice 에서 처음의 상태를 정의한 items 를 가리킨 것이다.
useSelector
함수가 상품의 state 를 받아오는 역할을 수행했다면 useDispatch
함수는 아까 만들어둔 reducer 함수를 호출해서 상품의 state 를 변경하는 역할을 수행한다.
위의 - 버튼을 누르면 상품이 목록에서 삭제되어야 한다.
EditProductItem.js
import { useDispatch } from 'react-redux';
import { productActions } from '../../store/product-slice';
import classes from './EditProductItem.module.css';
const EditProductItem = (props) => {
const dispatch = useDispatch(); // useDispatch() 함수 사용
const { id, title } = props.item;
const removeItemHandler = () => {
// productActions의 자동완성을 이용해서 상품 목록을 삭제하는 함수를 호출 & 실행한다.
dispatch(productActions.removeProduct(id));
};
return (
<li className={classes.item}>
<header>
<h3>{title}</h3>
<button onClick={removeItemHandler}>-</button>
</header>
</li>
);
};
export default EditProductItem;
상품을 삭제하려면 useDispatch
함수에서 매개변수로 상품의 ID 를 reducer 에 넘겨주어야 한다.
// product-slice.js
// 상품 목록에서 상품을 삭제하는 함수 (삭제할 상품의 ID를 매개변수로 받아옴)
removeProduct(state, action) {
const id = action.payload;
state.items = state.items.filter((item) => item.id !== id);
},
위에서 봤던 것처럼 reducer에서 매개변수를 action.payload 로 받아올 수 있다. ( 값이 여러개면 딕셔너리 형태로 넘긴다. )
이렇게 useDispatch
함수를 이용해서 Redux 의 state 를 변경하는 것까지 구현해보았다. 상품 목록을 추가하는 기능도 구현했지만 Form 으로 상품의 정보를 입력받기 때문에 코드가 길어져서 생략하겠다. ( 로직은 동일하다. )
다음 포스트에선 업데이트 된 상품 목록 정보를 Firebase 에도 업데이트 하는 기능을 다뤄본다.