여기서는 state같은 동적인 상태를 제외하고 정적인 화면을 구현한다.
페이지 방식 개발의 퍼블리싱에 해당한다고 보면 된다.
Step1 – UI를 컴포넌트로 분해
먼저 화면을 재사용할 수 있는 컴포넌트 단위로 쪼갠다.
Single Responsibility Principle 원칙에 기반하여 컴포넌트는 하나의 책임을 가질때까지 작게 쪼갠다. 색깔별로 다음과 같이 5가지 종류의 컴포넌트로 나눈다.
FilterableProductTable
(orange): contains the entirety of the exampleSearchBar
(blue): receives all user inputProductTable
(green): displays and filters the data collection based on user inputProductCategoryRow
(turquoise): displays a heading for each categoryProductRow
(red): displays a row for each product
전체 계층 구조는 다음과 같다.
FilterableProductTable
SearchBar
ProductTable
ProductCategoryRow
ProductRow
ProductRow
ProductRow
ProductCategoryRow
ProductRow
ProductRow
ProductRow
먼저 components 폴더를 만들고 5가지 컴포넌트를 먼저 만든다.
Step2 – 정적인 화면 구성
컴포넌트를 먼저 만든후 각 컴포넌트별로 정적인 화면을 만든다.
App.tsx은 다음과 같다.
1 2 3 4 5 6 7 |
function App() { return ( <div> <FilterableProductTable></FilterableProductTable> </div> ); } |
FilterableProductTable.tsx
1 2 3 4 5 6 7 8 |
export const FilterableProductTable = () => { return ( <div> <SearchBar></SearchBar> <ProductTable></ProductTable> </div> ) } |
SearchBar.tsx
1 2 3 4 5 6 7 8 |
export const SearchBar = () => { return ( <div> <input placeholder="Search..."></input><br /> <input type="checkbox" ></input>Only show products in stock </div> ) } |
ProductTable.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
export const ProductTable = () => { return ( <> <table> <thead> <tr> <th>Name</th> <th>Price</th> </tr> </thead> <tbody> <ProductCategoryRow></ProductCategoryRow> <ProductRow></ProductRow> <ProductRow></ProductRow> <ProductRow></ProductRow> <ProductCategoryRow></ProductCategoryRow> <ProductRow></ProductRow> <ProductRow></ProductRow> <ProductRow></ProductRow> </tbody> </table> </> ) } |
ProductCategoryRow.tsx
1 2 3 4 5 6 7 |
export const ProductCategoryRow = () => { return ( <tr> <th colSpan={2}>Sporting Goods</th> </tr> ) } |
ProductRow.tsx
1 2 3 4 5 6 7 8 |
export const ProductRow = () => { return ( <tr> <td>Football</td> <td>$49.99</td> </tr> ) } |
만들어진화면을 확인해보자.
Step3 – props를 이용한 정적인 화면 구성
json 데이터를 최상위 컴포넌트에 주입하고 그 데이터를 자식 컴포넌트에 전달해서 화면을 구성한다.
Array 안의 json object를 먼저 타입스크립트로 정의한다.
dto폴더를 만들고 Product.ts 화일을 만든다.
1 2 3 4 5 6 |
export interface Product { category: string; price: string; stocked: boolean; name: string; } |
FilterableProductTable에 Json Array를 정의하고 타입을 명시한다.
그리고 products 라는 property로 데이터를 주입할려니 타입스크립트에서 에러가 발생한다. 타입스크립트로 개발시에는 property로 주입하기 위해서는 반드시 타입을 정의해야 한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
const products: Array<Product> = [ {category: "Sporting Goods", price: "$49.99", stocked: true, name: "Football"}, {category: "Sporting Goods", price: "$9.99", stocked: true, name: "Baseball"}, {category: "Sporting Goods", price: "$29.99", stocked: false, name: "Basketball"}, {category: "Electronics", price: "$99.99", stocked: true, name: "iPod Touch"}, {category: "Electronics", price: "$399.99", stocked: false, name: "iPhone 5"}, {category: "Electronics", price: "$199.99", stocked: true, name: "Nexus 7"} ] export const FilterableProductTable = () => { return ( <div> <SearchBar></SearchBar> <ProductTable products={products}></ProductTable> </div> ) } |
ProductTable.tsx에서 Props를 정의한다. 여기에 products 속성을 추가해야 한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
interface Props { products: Array<Product> } export const ProductTable: React.FC<Props> = () => { return ( <> <table> <thead> <tr> <th>Name</th> <th>Price</th> </tr> </thead> <tbody> <ProductCategoryRow></ProductCategoryRow> <ProductRow></ProductRow> <ProductRow></ProductRow> <ProductRow></ProductRow> <ProductCategoryRow></ProductCategoryRow> <ProductRow></ProductRow> <ProductRow></ProductRow> <ProductRow></ProductRow> </tbody> </table> </> ) } |
이제 ProductTable에서 products를 받아서 ProductCategoryRow와 ProductRow를 구현한다.
lodash 라이브러리가 필요하므로 먼저 인스톨한다. lodash와 타입스크립트를 정의한 @types/lodash 두개를 인스톨한다.
1 |
npm i -S lodash @types/lodash |
먼저 category로 분류를 한다. groupBy 함수를 사용하면 리턴되는 타입은 객체이다. 그리고 객체의 키에 분류된 객체들이 array로 들어가 있으므로 객체를 루프돌리면 해당하는 컴포넌트들을 생성한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
interface Props { products: Array<Product> } export const ProductTable: React.FC<Props> = (props) => { const category = _.groupBy(props.products, 'category'); console.log(category); const categoryList = [] for (let key in category) { categoryList.push(<ProductCategoryRow category={key} key={key}></ProductCategoryRow>); category[key].forEach((item: Product) => { categoryList.push(<ProductRow name={item.name} price={item.price} stocked={item.stocked} key={item.name}></ProductRow>); }) } return ( <> <table> <thead> <tr> <th>Name</th> <th>Price</th> </tr> </thead> <tbody> { categoryList } </tbody> </table> </> ) } |
ProductCategoryRow, ProductRow에 property를 각각정의한다.
1 2 3 4 5 6 7 8 9 10 11 |
interface Props { category: string; } export const ProductCategoryRow: React.FC<Props> = (props) => { return ( <tr> <th colSpan={2}>{props.category}</th> </tr> ) } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
interface Props { name: string; price: string; stocked: boolean; } export const ProductRow: React.FC<Props> = (props) => { return ( <tr> <td>{props.name}</td> <td>{props.price}</td> </tr> ) } |
화면에 json 데이터들이 나오는지 확인한다.
이제 재고가 없는 경우에 빨간색으로 표시하도록 한다.
먼저 필요한 모듈들을 설치한다.
classnames 는 클래스를 동적으로 바인딩하기 위해서 필요한다.
그리고, scss를 사용하기 위해서 node-sass도 인스톨한다.
1 2 |
npm i -S classnames @types/classnames npm i -D node-sass |
ProductRow.module.scss 화일을 추가한다.
1 2 3 |
.stock { color: red; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import React from 'react'; import styles from './ProductRow.module.scss'; import classNames from 'classnames' interface Props { name: string; price: string; stocked: boolean; } export const ProductRow: React.FC<Props> = (props) => { return ( <tr className={classNames({[styles.stock]: !props.stocked})}> <td>{props.name}</td> <td>{props.price}</td> </tr> ) } |
재고가 없는 경우 빨간색으로 표시되는지 확인한다.