이 글은 KUIT Web 부원분들을 대상으로 쓴 글입니다.
6주차 영상을 올리고 나서, OOO 파트장님이 제가 올린 코드들을 리팩토링해주셨습니다 XD
그리고 파트장님이 어떤 부분이 리팩토링해주셨는지 전부 비교 분석해서 알려주셨습니다!!
그러면 코드가 어떻게 바뀌었는지, 전후 코드들을 비교해보면서 같이 살펴볼까요?
1. InputBar 컴포넌트에 product props를 내려주는 이유?
비어있는 product 객체를 생성하고 이를 사용해서 값을 채워주려고 만든 것 같은데, 이렇게 하면 확장에 유연하지 못하다.
구현체가 아니라 인터페이스를 기반으로 구현하는 것이 바람직하다(객체지향 기본 원칙)
그리고 이 구현체를 FilterableProductTable에서 생성해서 내려줄 이유가 없어보인다. 응집도 측면에서 InputBar에서 생성하는 것이 맞다.(FilterableProductTable에서는 emptyProduct를 사용하지 않기 때문에)
전(InputBar.tsx)
(FilterableProductTable.tsx에서 emptyProduct 선언하고 props로 전달)
interface Props {
product: Products;
addProduct: (product: Products) => void;
}
후(InputBar.tsx)
(FilterableProductTable.tsx에서 emptyProduct 선언x)
interface Props {
addProduct: (product: Products) => void;
}
const InputBar: React.FC<Props> = ({ addProduct }) => {
// 원래는 FilterableProductTable에서 emptyProduct를 생성해서
// InputBar에 props로 전달해줬었는데, 이렇게 하면 확장에 유연하지 못하다는 단점이 있다.
// 구현체가 아니라 인터페이스를 기반으로 구현하는 것이 바람직하다(객체지향 기본 원칙)
// 이 구현체를 FilterableProductTable에서 생성해서 내려줄 이유가 없음
// 응집도 측면에서 InputBar에서 생성하는 것이 맞음
// (FilterableProductTable에서는 emptyProduct를 사용하지 않기 때문에)
// <Omit<Products>, 'id'>> -> Products 타입에서 "id" 속성을 제거한 새로운 타입을 생성
const [newProduct, setNewProduct] = useState<Omit<Products, 'id'>>({
category: '',
price: 0,
stocked: true,
name: '',
});
2. Products -> Product로, price는 number로, id 속성 추가
Products[] -> 제품들들?
Products가 아닌 Product로 선언하는 것이 맞다!
price는 string이 아니라 number로 선언하는 것이 맞다.
→ $를 표기하고 싶으면 아래와 같은 코드를 추가하자
const ProductRow: React.FC<Props> = ({ product }) => {
return (
<tr>
<td style={{ color: product.stocked ? "color" : "red" }}>
{product.name}
</td>
<td>{product.price}$</td>
</tr>
);
};
전(App.tsx)
export interface Products {
category: string;
price: string;
stocked: boolean;
name: string;
}
function App() {
const initialProducts: Products[] = [
{ category: 'Fruits', price: '$1', stocked: true, name: 'Apple' },
{ category: 'Fruits', price: '$1', stocked: true, name: 'Dragonfruit' },
{ category: 'Fruits', price: '$2', stocked: false, name: 'Passionfruit' },
후(App.tsx)
// Products -> Product로 바꾸는 것이 맞다.
// 이미 선언을 하고 많이 쓰여서 일단은 놔두는 걸로...
export interface Products {
// id 속성 추가
id: string;
category: string;
// price는 number로 선언하는 것이 맞다.
price: number;
stocked: boolean;
name: string;
}
// id 속성 추가
// price가 string->number로 바뀌었으므로 price들의 type을 number로 변경
const initialProducts: Products[] = [
{ id: '1', category: 'Fruits', price: 1, stocked: true, name: 'Apple' },
{
id: '2',
category: 'Fruits',
price: 1,
stocked: true,
name: 'Dragonfruit',
},
{
3. InputBar의 HandleChange 함수 수정 → 확장에 유연하지 않음
AS-IS(현재 코드)
const handleChange = (value: string | boolean, label: keyof Products) => {
setNewProduct({ ...newProduct, [label]: value });
};
value의 타입에 뭐가 추가될지 모르기 때문에 string | boolean으로 제한하면 안됨 → 상품수가 추가될 수도 있고 object가 추가될 수도 있음
TO-BE(개선 코드)
const handleChange = (
// value의 타입에 뭐가 추가될지 모르기 때문에
// string | boolean으로 제한하면 안됨
// typeof newProduct: newProduct의 타입을 가져옴
// keyof Omit<Products, 'id'>: Products 타입에서 'id' 속성을 제외한 속성의 키
// value -> newProduct의 속성 값의 타입
// label -> newProduct의 속성 키의 타입(category, price, stocked...)
value: (typeof newProduct)[keyof Omit<Products, 'id'>],
label: keyof Omit<Products, 'id'>
) => {
setNewProduct({ ...newProduct, [label]: value });
};
value의 타입에 뭐가 추가될지 모르기 때문에 string | boolean으로 제한하면 안됨
typeof newProduct: newProduct의 타입을 가져옴
keyof Omit<Products, 'id'>: Products 타입에서 'id' 속성을 제외한 속성의 키
value -> newProduct의 속성 값의 타입
label -> newProduct의 속성 키의 타입(category, price, stocked...)
4. ProductTable.tsx 수정
1. 함수 분리의 필요성
filteredProducts → filterdProducts와 groupedProductsByCategory로 분리
filteredProducts: 필터링만 하는 함수
- inStockOnly → 재고 여부
- filterText → 검색어
groupedProductsByCategory: 위의 filteredProducts에서 필터링된 제품들을 카테고리를 기준으로 그룹화 해주는 함수
- Object.values → 특정 객체를 대상으로 value 값들만 뽑아서 배열로 반환하는 메서드
전(ProductTable.tsx)
const filteredProducts = productsCopy
.sort((a, b) => (a.category > b.category ? 1 : -1))
.filter((product) => {
const filterTextMatch = product.name
.toLowerCase()
.includes(filterText.toLowerCase());
const inStockCheck = !inStockOnly || product.stocked;
return filterTextMatch && inStockCheck;
});
후(ProductTable.tsx)
- GroupedProducts interface 추가
// GroupedProducts interface 추가
interface GroupedProducts {
category: string;
products: Products[];
}
- filteredProducts
// rows와 lastCategory는 사용하지 않으므로 선언x
// 기존에는 filteredProducts 하나만 있었는데
// filterdProducts -> filteredProducts와 groupedProductsByCategory로 분리
// filteredProducts는 필터링만 하는 함수
// inStockOnly -> 재고 여부 | filterText -> 검색어
const filteredProducts = products.filter(
(product) =>
(!inStockOnly || product.stocked) &&
product.name.toLowerCase().includes(filterText.toLowerCase())
);
- groupedProductsByCategory
// groupedProductsByCategory는 위의 filteredProducts에서 필터링된 제품들을
// 카테고리를 기준으로 그룹화해주는 함수
// Object.values -> 특정 객체를 대상으로 value값들만 뽑아서 배열로 반환하는 메서드
const groupedProductsByCategory = Object.values(
// acc: 누적값, product: 현재 처리 중인 상품
filteredProducts.reduce((acc, product) => {
const { category } = product;
// acc 객체에 category 속성이 이미 있는지 확인
// category 속성이 없다면, 새로운 { category, products: [] } 객체를 생성하여 acc[category]에 할당
acc[category] = acc[category] || { category, products: [] };
// 현재 처리 중인 상품 product를 해당 카테고리의 products 배열에 추가
acc[category].products.push(product);
return acc;
// reduce 함수의 두 번째 인자는 acc의 초기값
// -> 빈 객체 {}를 acc의 초기값으로 설정하고
// 이 객체의 타입을 { [category: string]: GroupedProducts }로 지정
// 각 카테고리 이름을 키로 가지고, 그 값으로 GroupedProducts 타입의 객체를 가지는 객체를 의미
}, {} as { [category: string]: GroupedProducts })
);
2. rows는 무슨 역할을 하는 지 알아보기 어렵다.
AS-IS(기존 코드)
<tbody>{rows}</tbody>
rows 라는 변수에 ReactElement가 배열로 들어가있는데,
view와 logic이 혼재되어 있어서 어떤 역할을 하는지 예측하기 어렵습니다.
TO-BE(개선 코드)
<tbody>
{groupedProductsByCategory.map((productCategory) => {
return (
<Fragment key={productCategory.category}>
<ProductCategoryRow category={productCategory.category} />
{productCategory.products.map((product) => (
<ProductRow key={product.id} product={product} />
))}
</Fragment>
);
})}
</tbody>
이와 같이 각 렌더 함수(return문) 위에서는 컴포넌트에 전달해줄 데이터를 가공하고
렌더 함수 안에서는 오로지 뷰와 관련된 코드를 작성하면 됩니다.
'KUIT' 카테고리의 다른 글
[KUIT] 7주차 워크북 보충 (0) | 2024.05.16 |
---|---|
[KUIT] 6주차 워크북 보충 (0) | 2024.05.10 |
[KUIT] 5주차 워크북 보충(React) (0) | 2024.05.03 |
[KUIT] 4주차 워크북 보충(TypeScript) (1) | 2024.04.27 |
[KUIT] 3주차 워크북 보충(JavaScript) (0) | 2024.04.05 |