Shopping App 회고 refactoring
CODE REFACTORING
1. Router 수정하기
- Routes를 중첩으로 사용하여 생긴 위험 경고 였습니다. 그래서 createBrowserRouter, BrowserRouter중 하나를 선택해서 적용을 해야 했습니다.
- 이를 위한 해결 방법으로 Outlet을 활용하는 것이었습니다.
- 코드를 다음과 같이 수정하였으며, 공통된 데이터는 redux로 관리하도록 수정하였습니다.
import { Outlet } from 'react-router-dom';
import Header from './components/Header';
import Footer from './components/Footer';
import ToastContainer from './components/ToastContainer';
import Modal from './components/Modal';
function App() {
//....
return (
<>
<Header />
<Outlet />
{toast.length > 0 && <ToastContainer />}
{modal.isModalOpened && (
<Modal
title={modal.title}
picture={modal.image}
bookmark={modal.bookmark}
id={modal.id}
/>
)}
<Footer />
</>
);
}2 CSS 수정하기
- Theme의 형태를 변경했습니다.
- GlobalStyle의 #root에 base font-size를 지정한 후 모드 상대 수치인 rem으로 변경했습니다.
- margin 관리를 spacing06, spacing11로 변경했습니다. 이는 해당 사이트를 참고하여 변경했습니다. 링크
3 Util Directory의 getRandomForSlice 수정하기
- 처음의 수정 방향은 random으로 뽑은 숫자가 배열 데이터의 길이 - 4보다 클시 주어진 배열에서
[99,0,1,2]식으로 뽑아낼 방식으로 수정하려 했다. 구현한 코드는 다음과 같다.
export default getRandomForSlice(data){
const random = Math.floor(Math.random() * 100);
const { length } = data;
const sliceData = [];
for (let i = random; i < random + 4; i++) {
if (i > length - 1) {
sliceData.push({...data[i - length]});
} else {
sliceData.push({...data[i]});
}
}
return sliceData;
}- 이렇게 해서 새로운 배열을 만들고 이를 state로 관리 할때 문제가 발생하였다. bookmark가 될시 별 마크의 색이 변화해야 하는데, 기존의 state형태를 고정으로 가지고 있기에 redux와 연동된 새로운 데이터를 받아오지 못하고 setState를 자식 컴포넌트에게 넘겨 수정해주어야 했다. 이는 불필요한 작업이라고 느껴서 기존의 방식처럼 number 데이터를 넘겨주는 형식을 사용하는 것으로 채택하였다.
- 만약 이렇게 뽑아온 배열을 state로 관리하지 않을시 북마크 클릭시 모든 상품 리스트가 재렌더링 되기 때문에, 사용 측면에서 불편함을 경험했다.
- 그래서 기존의 RandomNum을 뽑아주는 형식을 사용하였고 코드를 최종적으로 다음과 같이 수정하였다.
`export default function getRandomForSlice(data, chooseNum) {
return Math.floor(Math.random() * data.length - chooseNum);
}`;- 뿌려줄 데이터의 개수가 변경, 혹은 전체 데이터의 개수가 변경될 것을 고려했습니다.
- 렌더링 할 데이터의 개수가 변화할 경우 Home에서 상수로 관리하고 있는 변수만 조절할 경우 자동으로 렌더링 되도록 수정했습니다.
4. 헤더 컴포넌트의 햄버거 아이콘 클릭 전역에서 관리하기.
- 이전에는 APP에 state를 설정해서 각 페이지에 뿌려주는 형식으로 진행했습니다. 하지만 router를 수정 하는 과정에서 각 기능을 분리하는 게 좋을 것 같아 이를 전역 관리 변수로 변경했습니다.
- hambegerReducer를 만들어 관리를 하였으며, 각 페이지에서 Dropdown 컴포넌트가 열려 있을시 화면 어디를 클릭해도 닫히도록 하기 위해서 별개의 훅 useClick을 만들어서 코드를 수정했습니다.
5. Toast UI 작동 방식 refactioring.
- 최상위 컴포넌트인 APP에서 전역으로 toast관련 state를 설정하여 각각의 페이지의 카드 컴포넌트에 뿌려주는 방식으로 tost UI를 만들었습니다. 이때 마주친 문제는 총 두 가지 였습니다.
- state 관리가 어려웠습니다. 그렇게 깊은 구조도 아니지만, state가 잘못 되었을 시 수정 하는 방법이 쉽지 않았습니다.
- 하나의 toast ui를 관리하는 것은 충분했지만, 배열 형식으로 state를 관리하고자 할때 하드 코딩이 되고 하나의 state에 너무 많은 값을 관리하게 되었습니다.
- Vite.+.React.1.mp4
- useReducer를 활용하면 데이터가 복잡해지는 구조는 해결할 수 있지만, 여전히 state 관리가 어렵다는 문제가 있었습니다. 따라서 contextAPI와 함께 사용을 해야 했습니다. 하지만 현재 전역 상태관리로 redux를 사용하고 있기 때문에 redux를 활용하기로 하였습니다.
- 비동기로 동작하여 데이터를 처리해야 하기 때문에 관련 middleware인 redux-thunk를 설치하여 새로운 reducer를 만들었습니다. (toastReducer)
- toastReducer는 기본적으로 데이터를 비워둔 상태로 관리하는 것을 목표로 했습니다. 빈 배열을 초기 데이터로 설정하여 새로운 데이터가 들어오면 하나씩 쌓이고 처음 들어온 데이터 부터 제거되는 queue를 차용해서 reducer의 구조를 작성했습니다.
- Vite.+.React.4.mp4
6. MODAL 버그 픽스
- 기존에는 Modal을 Card 컴포넌트 안에 위치하게 하였습니다. 이때 정상적으로 작동하지만, 북마크 페이지에서 북마크를 해제할시 Card component가 unmount되면서 Modal 또한 강제로 종료되는 현상이 있었습니다. (위의 영상에서 확인할 수 있습니다.)
- 따라서 Modal 컴포넌트를 전역에서 설정해서 redux를 통해 상태 관리를 하도록 수정하였습니다. 이를 통해 버그를 해결할 수 있었습니다.
- Vite.+.React.5.mp4
7. 컴포넌트 수정
1. Card 컴포넌트 수정
// ./pages/home.js
<section>
<Title>상품 리스트</Title>
<List>
{state.slice(randomNumber, randomNumber + CHOOSENUMBER).map(item => (
<li key={item.id}>
<Card data={item} />
</li>
))}
</List>
</section>- 위의 코드를 다음과 같이 Component로 분리하여 수정하였습니다.
<CardList
title={'상품 리스트'}
state={state.slice(randomNumber, randomNumber + CHOOSENUMBER)}
/>2. 문자열을 객체로 관리
- 이전에는 직접 하드 타이핑 하는 식으로 문자열을 사용했습니다.
- 반복적으로 사용되는 문자열을 객체로 관리하여 실수가 나오는 부분을 줄였습니다.
// ./src/components/Description.jsx
const cardType = {
product: 'Product',
category: 'Category',
brand: 'Brand',
exhibition: 'Exhibition',
};3. 북마크페이지, 상품리스트 페이지 통합관리
- 기존에는 뿌려주는 데이터만 다르고 동일한 페이지를 하드코딩해서 분리하여 사용하고 있었습니다.
- 따라서 컴포넌트와 페이지의 역할을 분리했습니다. 두 페이지의 공통된 부분을 컴포넌트로 뽑아냈습니다. 각각의 페이지는 컴포넌트에 필요한 데이터를 정제하여 뿌려주는 역할을 하도록 수정했습니다.
4. 숫자 타입을 통한 페이지 관리의 불명확성
- 숫자 타입으로 필터 컴포넌트를 관리하여 뿌려줄 데이터를 정했습니다.
const [numState, setNumState] = useState(0);
{
numState === 0 &&
state
.filter(item => item.bookmarked)
.map(item => (
<li key={item.id}>
<Card data={item} />
</li>
));
}- 이때의 문제점은 어떤 데이터를 적용하는지가 명확하지 않았으며, 데이터를 분류하는 방법도 명확히 알기 힘들다는 점을 알 수 있었습니다. 그래서 숫자가 아닌 상품 타입을 문자열로 작성하여 관리하도록 하였습니다.
const filterSet = {
all: "전체",
product: "상품",
categoryl: "카테고리",
exhibition: "기획전",
brand: "브랜드",
};
{filterState === filterSet.all && (
<CardList state={state.slice(0, dataNum)} />
)}- 다음과 같이 뿌려줄 데이터를 관리하는 필터를 문자열로 하여 명확히 코드를 보고 의도를 파악할 수 있도록 하였습니다.
타입스크립트 적용
- 타입스크립트 설정을 위해 다음과 같은 라이브러리를 설치했습니다.
{
"devDependencies": {
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@types/redux": "^3.6.0",
"@types/styled-components": "^5.1.26",
"@typescript-eslint/eslint-plugin": "^5.59.8",
"@typescript-eslint/parser": "^5.59.8",
"typescript": "^5.0.4"
}
}- tsconfig, tsconfig.node 파일을 생성하여 타입스크립트를 설정했습니다.
- eslint를 다음과 같이 설정했습니다.
//eslintrc.json
module.exports = {
env: {
browser: true,
es2020: true,
node: true,
},
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react/jsx-runtime',
'plugin:react-hooks/recommended',
'plugin:prettier/recommended',
'plugin:storybook/recommended',
'plugin:@typescript-eslint/recommended',
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
settings: {
react: {
version: '18.2',
},
},
ignorePatterns: ['!.storybook'],
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': 'warn',
'prettier/prettier': [
'error',
{
endOfLine: 'auto',
parser: 0,
},
],
'no-unused-vars': 0, //사용하지 않는 변수
'no-fallthrough': 0, //case문 default
'@typescript-eslint/no-non-null-assertion': 0,
},
};- 이후 redux에 우선적으로 TS를 적용시켰습니다. TS를 적용시키는 과정에서 TS의 자동 추론은 string과 string | null 을 별개의 타입으로 취급한다는 것을 명확하게 이해할 수 있었습니다. 이로 인해 redux에서 적용한 type을 이해하지 못해 type을 nerver로 취급한다는 것을 확인할 수 있었습니다.
- redux이후 API와 customHook에 TS를 적용하였습니다. 이후 다른 컴포넌트파일에 TS를 적용하였습니다. redux이 외에는 각 prop의 type 명시와 type 좁히기와 type 가드만 명시해 주면 문제없이 동작한다는 것을 확인할 수 있었습니다.
- 더불어 type 적용이 코드 작성에 큰 스트레스로 다가오지는 않았습니다. 오히려 정확한 타입 추론 덕분에 각 객체의 파라미터 타입과 종류를 자동으로 추론해주는 것이 매우 편리하다는 것을 알 수 있었습니다. 또한 타입 혼동 없이 받아오는 데이터의 형식을 알 수 있기에 적절한 코드를 작성하는 것에 큰 어려움이 없었습니다.
Content Table
- CODE REFACTORING
- - 1. Router 수정하기
- 2 CSS 수정하기
- 3 Util Directory의 getRandomForSlice 수정하기
- 4. 헤더 컴포넌트의 햄버거 아이콘 클릭 전역에서 관리하기.
- 5. Toast UI 작동 방식 refactioring.
- 6. MODAL 버그 픽스
- 7. 컴포넌트 수정
- - 1. Card 컴포넌트 수정
- - 2. 문자열을 객체로 관리
- - 3. 북마크페이지, 상품리스트 페이지 통합관리
- - 4. 숫자 타입을 통한 페이지 관리의 불명확성
- - 타입스크립트 적용
Solo-project
2023.05.18
2 / 3