오늘은 리액트를 활용하여 가계부 프로젝트를 계속 진행했습니다. CRUD 기능을 완성하고, 상태 관리를 리팩토링하면서 주요 학습 내용과 작업 과정을 정리했습니다.
목표
- CRUD 기능 완성
- 상태 관리 props drilling에서 useContext로 리팩토링
- Redux를 활용해서 상태 관리 리팩토링
주요 학습 내용
- Update와 Delete 기능 구현
- useContext로 상태 관리 리팩토링
- Redux를 사용한 상태 관리 리팩토링
1. Update와 Delete 기능 구현
// expense 항목 Update 기능
const updateExpense = (targetExpense, modifiedExpense) => {
const updatedData = fetchedData.map((expense) =>
expense.id === targetExpense.id ? modifiedExpense : expense
);
localStorage.setItem("dataItem", JSON.stringify(updatedData));
setFetchedData(updatedData);
};
// expense 항목 Delete 기능
const removeExpense = (targetExpense) => {
const updatedData = fetchedData.filter(
(expense) => expense.id !== targetExpense.id
);
localStorage.setItem("dataItem", JSON.stringify(updatedData));
setFetchedData(updatedData);
};
2. useContext로 상태 관리 리팩토링
// Context 생성 및 Provider 설정: App.jsx
import { createContext, useEffect, useState } from "react";
export const ExpenseContext = createContext();
function App() {
const [fetchedData, setFetchedData] = useState([]);
useEffect(() => {
const loadData = async () => {
const fetchedData = await fetchData();
const localData = JSON.parse(localStorage.getItem("dataItem")) || [];
const combinedData = [
...fetchedData.filter(
(item) => !localData.some((localItem) => localItem.id === item.id)
),
...localData,
];
setFetchedData(combinedData);
localStorage.setItem("dataItem", JSON.stringify(combinedData));
};
loadData();
}, []);
const addExpense = (newExpense) => {
const updatedData = [...fetchedData, newExpense];
localStorage.setItem("dataItem", JSON.stringify(updatedData));
setFetchedData(updatedData);
};
return (
<ExpenseContext.Provider value={{ fetchedData, addExpense, updateExpense, removeExpense }}>
<RouterProvider router={router} />
</ExpenseContext.Provider>
);
}
export default App;
props drilling의 단점을 해결하기 위해 useContext를 사용하여 상태 관리를 리팩토링했습니다. 이를 통해 컴포넌트 간의 상태 공유가 더 간편해졌습니다.
3. Redux로 상태 관리 리팩토링
// configureStore.js
import { configureStore } from "@reduxjs/toolkit";
import fetchedDataReducer from "../fetchedDataSlice";
// Redux 스토어 구성
const store = configureStore({
reducer: {
fetchedData: fetchedDataReducer,
},
});
export default store;
Redux를 사용하여 중앙 집중식 상태 관리를 구현했습니다.
// fetchedDataSlice.js
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import fetchData from "../../fetchData";
// 비동기 데이터 로드 Thunk 생성
export const loadFetchedData = createAsyncThunk(
"fetchedData/loadFetchedData",
async () => {
const fetchedData = await fetchData();
const localData = JSON.parse(localStorage.getItem("dataItem")) || [];
const combinedData = [
...fetchedData.filter(
(item) => !localData.some((localItem) => localItem.id === item.id)
),
...localData,
];
localStorage.setItem("dataItem", JSON.stringify(combinedData));
return combinedData;
}
);
// Redux 슬라이스 생성
const fetchedDataSlice = createSlice({
name: "fetchedData",
initialState: [],
reducers: {
addExpense: (state, action) => {
state.push(action.payload);
localStorage.setItem("dataItem", JSON.stringify(state));
},
updateExpense: (state, action) => {
const index = state.findIndex(
(expense) => expense.id === action.payload.id
);
if (index !== -1) {
state[index] = action.payload;
localStorage.setItem("dataItem", JSON.stringify(state));
}
},
removeExpense: (state, action) => {
const index = state.findIndex(
(expense) => expense.id === action.payload.id
);
if (index !== -1) {
state.splice(index, 1);
localStorage.setItem("dataItem", JSON.stringify(state));
}
},
},
extraReducers: (builder) => {
builder.addCase(loadFetchedData.fulfilled, (state, action) => {
return action.payload;
});
},
});
export const { addExpense, updateExpense, removeExpense } =
fetchedDataSlice.actions;
export default fetchedDataSlice.reducer;
비동기 데이터 로드를 위해 createAsyncThunk를 사용하고, fetchedDataSlice에 리듀서와 액션을 설정했습니다.
리팩토링 하면서 느낀 점
Props Drilling 단점
- 복잡성 증가: 부모 컴포넌트에서 필요한 props를 여러 레벨(props가 필요하지 않는 컴포넌트)에 걸쳐 전달하면서 코드가 복잡해졌습니다.
- 유지보수 어려움: 상태를 변경할 때 중간 컴포넌트가 추가되거나 변경되면 디버깅이 어렵습니다 .
useContext 장단점
장점
- 간편한 상태 공유: 여러 레벨에 걸쳐 props를 전달할 필요 없이 전역에서 상태를 쉽게 공유할 수 있었습니다.
- 간결한 코드: 코드가 간결해지고 가독성이 좋아졌습니다.
단점
- 성능 문제: Context가 업데이트될 때마다 모든 하위 컴포넌트가 리렌더링 되었습니다..
- 테스트 어려움: Context를 사용하는 컴포넌트는 테스트 시 Provider를 필요하다고 합니다.
Redux 장단점
장점
- 중앙 집중식 상태 관리: 애플리케이션의 모든 상태를 Store(저장소)에서 관리할 수 있어 상태의 흐름을 쉽게 파악할 수 있습니다.
- 예측 가능한 상태 변경: 데이터의 변경이 예측 가능하고 유지 보수성이 증가해서 큰 규모의 애플리케이션에서도 상태 관리가 용이합니다.
- 확장성: 큰 규모의 애플리케이션에서도 상태 관리가 용이했습니다.
단점
- 복잡한 설정: 아주 간단한 상태 관리를 위해서도 처음 작성해야 하는 코드량이 많습니다.
- 러닝 커브: 알아야 하는 개념과 용어가 많아 학습하는 데 시간이 걸렸습니다
결론
오늘은 Update와 Delete 기능을 구현하고, props drilling의 단점을 보완하기 위해 useContext와 Redux를 각각 이용해서 상태 관리를 리팩토링해봤습니다. 이를 통해 코드의 복잡성을 줄이고 가독성과 유지보수성을 높일 수 있었습니다. useContext는 간편하게 전역 상태를 관리할 수 있지만 성능 문제가 있을 수 있고, Redux는 강력한 상태 관리 도구지만 초기 설정이 복잡할 수 있다는 것을 학습 했습니다.
'TIL' 카테고리의 다른 글
TIL - 리액트로 가계부 만들기 - 3 (0) | 2024.05.23 |
---|---|
Redux - 비동기 처리 createAsyncThunk (0) | 2024.05.23 |
TIL - 리액트로 가계부 만들기 - 1 (0) | 2024.05.21 |
TIL - [React] 리렌더링 동작을 제어하는 역할 useEffect, useRef (0) | 2024.05.20 |
TIL - 리액트 컴포넌트 파일 구조 및 상태 업데이트 방식 개선 (0) | 2024.05.17 |