React Component

Step 1: Break the UI into a component hierarchy

Step 2: Build a static version in React

๋ฆฌ์•กํŠธ๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค๊ธฐ ์ „์— ๊ฐ€์ง€๊ณ  ์žˆ์–ด์•ผ ํ•˜๋Š” ๊ฒƒ โ†’ JSON API & mockup

๋ฐ์ดํ„ฐ โ†’ ๋ฐฑ์—”๋“œ์—์„œ JSON ํ˜•ํƒœ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋Œ๋ ค์ฃผ๋Š” API๋ฅผ ์ œ๊ณตํ•œ๋‹ค๊ณ  ๊ฐ€์ •

  • ๋Œ€๋ถ€๋ถ„์€ REST API ๋˜๋Š” GraphQL

REST API

  • fetch API๋ฅผ ์ด์šฉํ•ด์„œ GET, POST, PUT/PATCH, DELETE ์š”์ฒญ (CRUD ๊ตฌํ˜„) โ‡’ Resource ์ค‘์‹ฌ

GraphQL

  • Graph ์ž๋ฃŒ๊ตฌ์กฐ๋ฅผ ์ด์šฉ
  • Query์—์„œ ์–ป๊ณ ์ž ํ•˜๋Š” ๊ฒƒ์„ ์ง€์ •
    • ํฌ์ŠคํŠธ๋งŒ ๊ฐ€์ ธ์˜ฌ๊ฑด์ง€, ํฌ์ŠคํŠธ์— ๋”ธ๋ ค์žˆ๋Š” ๋Œ“๊ธ€๊นŒ์ง€ ๊ฐ€์ ธ์˜ฌ๊ฑด์ง€
  • Query(Read), Mutation(Command: Create, Update, Delete), Subscription(์ด๋ฒคํŠธ๋ฅผ ์ธ์ง€ํ•˜๋Š” ์šฉ๋„)

REST API์™€ GraphQL ๋‘˜ ๋‹ค JSON ํ˜•ํƒœ๋กœ ๋Œ๋ ค์ฃผ๊ณ  ํ˜•ํƒœ๋Š” ์ด๋Ÿฐ ์‹

1
2
3
4
5
6
7
8
[
{ category: 'Fruits', price: '$1', stocked: true, name: 'Apple' },
{ category: 'Fruits', price: '$1', stocked: true, name: 'Dragonfruit' },
{ category: 'Fruits', price: '$2', stocked: false, name: 'Passionfruit' },
{ category: 'Vegetables', price: '$2', stocked: true, name: 'Spinach' },
{ category: 'Vegetables', price: '$4', stocked: false, name: 'Pumpkin' },
{ category: 'Vegetables', price: '$1', stocked: true, name: 'Peas' },
];
  • ์‹ค์ œ JSON์—๋Š” key๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ๋“ค์„ ๋„ฃ์–ด์ค€๋‹ค.
  • F/E๋Š” ์ด๋Ÿฐ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉ์ž๊ฐ€ ๋ณผ ์ˆ˜ ์žˆ๋„๋ก UI๋ฅผ ๊ตฌ์„ฑ.
  • React๋Š” ์„ ์–ธํ˜•์œผ๋กœ UI๋ฅผ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค. (DSL์„ ์‚ฌ์šฉ, HTML๊ณผ ์œ ์‚ฌํ•œ ๋ชจ์–‘)

์ปดํฌ๋„ŒํŠธ ๊ณ„์ธต ๊ตฌ์กฐ

  • React์˜ ๊ฐ•๋ ฅํ•œ ํŠน์ง• ์ค‘ ํ•˜๋‚˜
  • Component-Based
  • ๊ฐ„๋‹จํ•œ ์ปดํฌ๋„ŒํŠธ์˜ ์กฐ๋ฆฝ์œผ๋กœ ๋ณต์žกํ•œ UI๋ฅผ ๊ตฌ์„ฑํ•œ๋‹ค.
    • ๋ฐ˜๋Œ€๋กœ ๋งํ•˜๋ฉด ์ปดํฌ๋„ŒํŠธ ํ•˜๋‚˜ํ•˜๋‚˜๋Š” ๋ณต์žกํ•˜์ง€ ์•Š์•„์•ผ ํ•œ๋‹ค.

๊ฐ„๋‹จํ•œ ์ปดํฌ๋„ŒํŠธ์˜ ๊ธฐ์ค€์€?

  • SRP(๋‹จ์ผ ์ฑ…์ž„ ์›์น™)
  • CSS โ†’ ์ด๋ฏธ ์•Œ๊ณ  ์žˆ๋Š” ๊ธฐ์ค€์„ ์žฌํ™œ์šฉ.
  • Designโ€™s Layer
  • Information Architecture (JSON Schema์˜ ์˜ํ–ฅ) โ†’ ์‹ค์ œ๋กœ ์—„์ฒญ ๋งŽ์ด ์“ฐ๊ฒŒ ๋จ. ์ž์—ฐ์Šค๋Ÿฌ์šด SRP๋ฅผ ์œ„ํ•ด์„œ ์‚ฌ์‹ค์ƒ ๊ฐ•์ œ๋จ.

์ž‘์€ ์ปดํฌ๋„ŒํŠธ = ๋ถ€ํ’ˆ์„ ๋งŒ๋“ค์–ด์„œ ์กฐ๋ฆฝ. ์กฐํ•ฉ์€ ๊ฐ€์ง€์ˆ˜๋ฅผ ํญ๋ฐœ์ ์œผ๋กœ ๋Š˜๋ฆด ์ˆ˜ ์žˆ๋Š” ๊ฐ€์žฅ ์ „ํ˜•์ ์ธ ๋ฐฉ๋ฒ•.

Atomic Design์€ ์šฐ๋ฆฌ๊ฐ€ ์ž˜ ์•Œ๊ณ  ์žˆ๋Š” ๊ณ„์ธตํ˜• ๊ตฌ์กฐ๋ฅผ ๋ช‡ ๊ฐ€์ง€ ์นดํ…Œ๊ณ ๋ฆฌ๋กœ ๋ฌถ์€ ๋ฐฉ๋ฒ•.

https://beta.reactjs.org/images/docs/s_thinking-in-react_ui_outline.png

Build a static version in React (์ •์ ์ธ ๊ฒƒ ๋จผ์ € ๋งŒ๋“ค๊ธฐ)

  • ๊ฒ€์ƒ‰, ์ฒดํฌ๋ฐ•์Šค
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <div className="search-bar">
    <div>
    <input type="text" placeholder="Search..." />
    </div>
    <div>
    <input type="checkbox" id="only-stock" />
    <label htmlFor="only-stock">Only Show products in stock</label>
    </div>
    </div>
  • ํ…Œ์ด๋ธ”
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <table className="products-tables">
    <thead>
    <tr>
    <th>Name</th>
    <th>Price</th>
    </tr>
    </thead>
    <tbody>
    <tr>
    <td colSpan={2}>์นดํ…Œ๊ณ ๋ฆฌ</td>
    </tr>
    <tr>
    <td>Apple</td>
    <td>$1</td>
    </tr>
    <tr>
    <td>Dragonfruit</td>
    <td>$2</td>
    </tr>
    </tbody>
    </table>

๐Ÿš€ ์‹ค์ œ ๋ฐ์ดํ„ฐ๋กœ ํ™”๋ฉด์— ๋ Œ๋”๋งํ•˜๊ธฐ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<table className="products-tables">
<thead>
<tr>
<th>Name</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<tr>
<td colSpan={2}>{products[0].category}</td>
</tr>
{products.map((product) => (
<tr>
<td>{product.name}</td>
<td>{product.price}</td>
</tr>
))}
</tbody>
</table>

.

โ€˜keyโ€™ prop ์ด ํ•„์š”ํ•œ ์ด์œ 
๋ฌด์–ธ๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์„ ๋•Œ ๊ฐ๊ฐ์„ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด key์˜ ์—ญํ• . ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— key๋Š” uniqueํ•ด์•ผ ํ•œ๋‹ค.
1
2
3
4
5
6
7
8
{
products.map((product) => (
<tr key={product.name}>
<td>{product.name}</td>
<td>{product.price}</td>
</tr>
));
}

์นดํ…Œ๊ณ ๋ฆฌ๊ฐ€ 2๊ฐœ์ด๊ธฐ ๋•Œ๋ฌธ์— ์นดํ…Œ๊ณ ๋ฆฌ์— ๋งž๊ฒŒ ์ƒํ’ˆ ๋ฆฌ์ŠคํŠธ๊ฐ€ ๋ณด์—ฌ์•ผ ํ•œ๋‹ค.

  • ์นดํ…Œ๊ณ ๋ฆฌ๋งŒ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๋ฐฐ์—ด์„ ํ•˜๋‚˜ ๋งŒ๋“ค๊ธฐ (reduce ํ•จ์ˆ˜ ์‚ฌ์šฉ)

    • acc๊ฐ€ category๋ฅผ ๊ฐ–๊ณ  ์žˆ์œผ๋ฉด ์ถ”๊ฐ€X
    • category๋ฅผ ๊ฐ–๊ณ  ์žˆ์ง€ ์•Š๋‹ค๋ฉด ์ถ”๊ฐ€ํ•˜๊ธฐ

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
33
34
35
36
37
38
39
40
41
42
export default function App() {
const categories = products.reduce(
(acc, product) =>
acc.includes(product.category) ? acc : [...acc, product.category],
[]
);

// ์ƒ๋žต...

<table className="products-tables">
<thead>
<tr>
<th>Name</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<tr>
<td colSpan={2}>{categories[0]}</td>
</tr>
{products
.filter((product) => product.category === categories[0])
.map((product) => (
<tr key={product.name}>
<td>{product.name}</td>
<td>{product.price}</td>
</tr>
))}
<tr>
<td colSpan={2}>{categories[1]}</td>
</tr>
{products
.filter((product) => product.category === categories[1])
.map((product) => (
<tr key={product.name}>
<td>{product.name}</td>
<td>{product.price}</td>
</tr>
))}
</tbody>
</table>;
}

์ฝ”๋“œ์— ๋นจ๊ฐ„ ์ค„์ด ๊ฐ€์žˆ๋Š” ์ด์œ 

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2023-03-22 แ„‹แ…ฉแ„Œแ…ฅแ†ซ 12.12.08.png

Type โ€˜string[]โ€™ is missing the following properties from type โ€˜{ category: string; price: string; stocked: boolean; name: string; }โ€™: category, price, stocked, name

๐Ÿ‘‰๐Ÿปย ํƒ€์ž…์ด ์—†๋‹ค๋Š” ์–˜๊ธฐ


1๏ธโƒฃ ์ฒซ ๋ฒˆ์งธ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

: ๊ธฐ๋ณธ ๊ฐ’์„ ์„ค์ •ํ•  ๋•Œ ํƒ€์ž… ์ง€์ •

1
2
3
4
const categories = products.reduce(
(acc, product) => (acc.includes(product.category) ? acc : [...acc, product.category]),
[] as string[]
);

2๏ธโƒฃ ๋‘ ๋ฒˆ์งธ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

: ๋งค๊ฐœ๋ณ€์ˆ˜์—์„œ ํƒ€์ž… ์ง€์ •

1
2
3
4
5
const categories = products.reduce(
(acc: string[], product) =>
acc.includes(product.category) ? acc : [...acc, product.category],
[]
);

3๏ธโƒฃ ์„ธ ๋ฒˆ์งธ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

: Type or Interface

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
interface Product {
category: string;
price: string;
stocked: boolean;
name: string;
}

const products: Product[] = [
{
category: 'Fruits',
price: '$1',
stocked: true,
name: 'Apple',
},
{
category: 'Fruits',
price: '$1',
stocked: true,
name: 'Dragonfruit',
},
...
];

export default function App() {
const categories = products.reduce(
(acc: string[], product: Product) => (acc.includes(product.category) ? acc : [...acc, product.category]),
[]
);
...
}

๊ฐ€์žฅ ์ข‹์€ ๋ฐฉ๋ฒ•์€ interface.

products์—์„œ name ํ•˜๋‚˜๊ฐ€ ๋น ์ ธ์žˆ๋Š” ์ƒํ™ฉ์ผ ๋•Œ ์˜ค๋ฅ˜๋ฅผ ๋ฐœ์ƒ์‹œํ‚จ๋‹ค.


Extract Fuction

Extract Function

Inline Function

์•„์ฃผ ํ”ํžˆ ์“ฐ์ด๋Š” SRP๋ฅผ ์œ„ํ•œ ์ˆ˜๋‹จ. ๋ณ€ํ™”์˜ ํฌ๊ธฐ(์˜ํ–ฅ ๋ฒ”์œ„)๋ฅผ ์ œ์•ฝํ•œ๋‹ค.

์ผ๋‹จ ๊ธธ๊ฒŒ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ณ , ์ ์ ˆํžˆ ์ž๋ฅผ ์ˆ˜ ์žˆ๋Š” ๋ถ€๋ถ„์ด ๋ณด์ผ ๋•Œ โ€œํ•จ์ˆ˜๋กœ ์ถ”์ถœโ€ํ•œ๋‹ค. ๋˜๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์–ด๋ ค์šด ์ƒํ™ฉ์— ์ง๋ฉดํ–ˆ์„ ๋•Œ ํ•จ์ˆ˜๋กœ ์ถ”์ถœ. ๋ฐ”๋กœ ๋‹ค๋ฅธ ํŒŒ์ผ์„ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•˜์ง€ ์•Š์•„๋„ ๋จ. ์ปดํฌ๋„ŒํŠธ ๋‚˜๋ˆ„๋Š” ๊ธฐ์ค€์ด ์• ๋งคํ•˜๋ฉด ๋‹ค์‹œ ํ•˜๋‚˜์˜ ์ปดํฌ๋„ŒํŠธ๋กœ ํ•ฉ์ณค๋‹ค๊ฐ€(Inline Method) ๋‹ค์‹œ ๋‚˜๋ˆ ์ค˜๋„ ๋œ๋‹ค.

ํ˜„์žฌ App.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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
interface Product {
category: string;
price: string;
stocked: boolean;
name: string;
}

const products: Product[] = [
{
category: 'Fruits',
price: '$1',
stocked: true,
name: 'Apple',
},
{
category: 'Fruits',
price: '$1',
stocked: true,
name: 'Dragonfruit',
},
{
category: 'Fruits',
price: '$2',
stocked: false,
name: 'Passionfruit',
},
{
category: 'Vegetables',
price: '$2',
stocked: true,
name: 'Spinach',
},
{
category: 'Vegetables',
price: '$4',
stocked: false,
name: 'Pumpkin',
},
{
category: 'Vegetables',
price: '$1',
stocked: true,
name: 'Peas',
},
];

export default function App() {
const categories = products.reduce(
(acc: string[], product: Product) =>
acc.includes(product.category) ? acc : [...acc, product.category],
[]
);
console.log(categories);

return (
<div className="filterable-product-table">
<div className="seach-bar">
<div>
<input type="text" placeholder="Search..." />
</div>
<div>
<input type="checkbox" id="only-stock" />
<label htmlFor="only-stock">Only Show products in stock</label>
</div>
</div>
<table className="product-table">
<thead>
<tr>
<th>Name</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<tr>
<td colSpan={2}>{products[0].category}</td>
</tr>
{products
.filter((product) => product.category === categories[0])
.map((product) => (
<tr key={product.name}>
<td>{product.name}</td>
<td>{product.price}</td>
</tr>
))}
<tr>
<td colSpan={2}>{categories[1]}</td>
</tr>
{products
.filter((product) => product.category === categories[1])
.map((product) => (
<tr key={product.name}>
<td>{product.name}</td>
<td>{product.price}</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
  • SRP ์›์น™์„ ์œ„๋ฐ˜ํ•˜๊ณ  ์žˆ๋Š” ์ƒํƒœ.
  • ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์ปดํฌ๋„ŒํŠธ ๋ถ„๋ฆฌํ•˜๊ธฐ

๐Ÿš€ย ๋ถ„๋ฆฌํ•  ์ปดํฌ๋„ŒํŠธ ๐Ÿ‘‰๐Ÿปย ์ƒํ’ˆ์ด๋ฆ„, ๊ฐ€๊ฒฉ์„ ๋‚˜ํƒ€๋‚ด๋Š” ๋ถ€๋ถ„

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
33
34
35
<tr>
<td colSpan={2}>{categories[1]}</td>
</tr>;
{
products
.filter((product) => product.category === categories[1])
.map((product) => (
<tr key={product.name}>
<td>{product.name}</td>
<td>{product.price}</td>
</tr>
));
}

/* ------------------------------------------------------------------------ */

function ProductInCategory({ category, products }: ProductsInCategoryProps) {
const productsInCategory = products.filter(
(product) => product.category === category
);

return (
<>
<tr>
<td colSpan={2}>{products[0].category}</td>
</tr>
{productsInCategory.map((product) => (
<tr key={product.name}>
<td>{product.name}</td>
<td>{product.price}</td>
</tr>
))}
</>
);
}

์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด?

1
<ProductInCategory category={categories[0]} products={products} />
1
2
3
4
5
6
7
8
9
10
11
12
<table className="product-table">
<thead>
<tr>
<th>Name</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<ProductInCategory category={categories[0]} products={products} />
<ProductInCategory category={categories[1]} products={products} />
</tbody>
</table>

ProductInCategory ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฐ˜๋ณต๋˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ map ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

1
2
3
4
5
{
categories.map((category) => (
<ProductInCategory key={category} category={category} products={products} />
));
}

ProductInCategory ์ปดํฌ๋„ŒํŠธ ์™ธ๋ถ€๋กœ ์ถ”์ถœํ•˜๊ธฐ

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2023-03-23 แ„‹แ…ฉแ„Œแ…ฅแ†ซ 10.08.32.png

๐Ÿ‘‰๐Ÿปย type์ด ์—†๋‹ค๋Š” ์˜ค๋ฅ˜

์ด์ „์— ์ผ๋˜ interface Product๋Š” App์—์„œ๋„ ์“ฐ๊ณ  ์žˆ๊ณ , ProductInCategory ์ปดํฌ๋„ŒํŠธ์—์„œ๋„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

์ด๋•Œ ์—ฌ๋Ÿฌ ํƒ€์ž…์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก types๋ผ๋Š” ํด๋”์— ๋„ฃ์–ด๋†“๋Š”๋‹ค.

1
2
3
4
5
6
7
8
interface Product {
category: string;
price: string;
stocked: boolean;
name: string;
}

export default Product;

ProductInCategory์— ์žˆ๋Š” product.name๊ณผ product.price๋„ ์ปดํฌ๋„ŒํŠธ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2023-03-23 แ„‹แ…ฉแ„Œแ…ฅแ†ซ 10.26.06.png

1
2
3
4
5
6
7
8
9
10
import Product from '../types/Product';

export default function ProductRow({ product }: { product: Product }) {
return (
<tr>
<td>{product.name}</td>
<td>{product.price}</td>
</tr>
);
}

{ product: Product } ์ด ๋ถ€๋ถ„๋„ ๋”ฐ๋กœ ๋นผ์ฃผ๊ธฐ

1
2
3
4
5
6
7
8
9
10
11
12
type ProductRowProps = {
product: Product,
};

export default function ProductRow({ product }: ProductRowProps) {
return (
<tr>
<td>{product.name}</td>
<td>{product.price}</td>
</tr>
);
}

Category ์ปดํฌ๋„ŒํŠธ ๋งŒ๋“ค๊ธฐ ๐Ÿ‘‰๐Ÿปย ์ƒํ’ˆ ์นดํ…Œ๊ณ ๋ฆฌ ์ •๋ณด๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ

1
2
3
4
5
6
7
8
9
10
11
<Category category={category} />;

/* ------------------------------------------------------------------------ */

export default function Category({ category }: { category: string }) {
return (
<tr>
<td colSpan={2}>{category}</td>
</tr>
);
}

+) ์šฐ๋ฆฌ๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ ๋ฒˆ ์ •๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด ๋‹ค๋ฅธ ๋ฐฉ์‹์œผ๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜จ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š”๋ฐ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€๊ณตํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

  • category๊ฐ€ products๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๊ฒŒ ๋˜๋ฉด?

ProductTable ์ปดํฌ๋„ŒํŠธ ๋งŒ๋“ค๊ธฐ

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2023-03-23 แ„‹แ…ฉแ„Œแ…ฅแ†ซ 10.59.45.png

.filterable-product-table ๋ถ€๋ถ„์—์„  category๋ฅผ ์“ฐ๊ณ  ์žˆ์ง€ ์•Š์œผ๋‹ˆ categories๋ฅผ ProductTable ์ปดํฌ๋„ŒํŠธ ์•ˆ์œผ๋กœ ๋„ฃ๋Š”๋‹ค.

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
function ProductTable({ products }: { products: Product[] }) {
const categories = products.reduce(
(acc: string[], product: Product) =>
acc.includes(product.category) ? acc : [...acc, product.category],
[]
);
return (
<table className="product-table">
<thead>
<tr>
<th>Name</th>
<th>Price</th>
</tr>
</thead>
<tbody>
{categories.map((category) => (
<ProductInCategory
key={category}
category={category}
products={products}
/>
))}
</tbody>
</table>
);
}

products.filter() ํ•จ์ˆ˜ ๋ถ€๋ถ„์„ ๋”ฐ๋กœ ์ถ”์ถœํ•˜๋Š” ์˜ˆ์ œ:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export default function ProductInCategory({
category,
products,
}: ProductsInCategoryProps) {
const productsInCategory = products.filter(
(product) => product.category === category
); // -> ์ด ๋ถ€๋ถ„์„ ๋”ฐ๋กœ ์ถ”์ถœํ•˜๊ธฐ

return (
<>
<Category category={category} />
{productsInCategory.map((product) => (
<ProductCategoryRow key={product.name} product={product} />
))}
</>
);
}

utils > select.ts ์ƒ์„ฑ

1
2
3
export default function select(items: any[], field: string, value: any) {
return items.filter((item) => item[field] === value);
}
1
2
3
4
5
6
7
const productsInCategory = products.filter(
(product) => product.category === category
);

โฌ‡๏ธ

const productsInCategory = selectProducts(products, 'category', category);

SearchBar ์ปดํฌ๋„ŒํŠธ ์ถ”์ถœ

1
2
3
4
5
6
7
8
9
10
11
12
13
export default function SearchBar() {
return (
<div className="seach-bar">
<div>
<input type="text" placeholder="Search..." />
</div>
<div>
<input type="checkbox" id="only-stock" />
<label htmlFor="only-stock">Only Show products in stock</label>
</div>
</div>
);
}

FilterableProductTable ์ปดํฌ๋„ŒํŠธ ์ถ”์ถœ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import ProductTable from './ProductTable';
import SearchBar from './SearchBar';

import Product from '../types/Product';

type FilterableProductTableProps = {
products: Product[],
};

export default function FilterableProductTable({
products,
}: FilterableProductTableProps) {
return (
<div>
<SearchBar />
<ProductTable products={products} />
</div>
);
}

App ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๊ต‰์žฅํžˆ ๊น”๋”ํ•ด์ง„ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2023-03-23 แ„‹แ…ฉแ„’แ…ฎ 9.54.44.png


๐Ÿ”ฅ Props๋ฅผ ๊ฐœ๋ณ„์ ์œผ๋กœ ๋„˜๊ฒจ์ค„ ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, ProductInCategory ์ปดํฌ๋„ŒํŠธ์—์„œ product={product} ๋กœ ๋„˜๊ฒจ์ฃผ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ๊ฐœ๋ณ„์ ์œผ๋กœ ๋„˜๊ฒจ์ค„ ์ˆ˜๋„ ์žˆ๋‹ค.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
return (
<>
<Category category={category} />
{productsInCategory.map((product) => (
<ProductCategoryRow key={product.name} product={product} />
))}
</>
);

/* ------------------------------------------------------------------------ */

return (
<>
<Category category={category} />
{productsInCategory.map((product) => (
<ProductCategoryRow
key={product.name}
name={product.name}
price={product.price}
/>
))}
</>
);

ํ•˜์ง€๋งŒ typeScript๋ฅผ ์‚ฌ์šฉํ•  ๋• ์ „์ฒด๋ฅผ ๋„˜๊ฒจ์ฃผ๋Š” ๊ฒƒ์ด ๋” ๊ฐ•๋ ฅํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ถ”์ฒœํ•œ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด products ์š”์†Œ์— isStocked๋ผ๋Š” ์†์„ฑ์ด ํ•˜๋‚˜ ๋” ์ถ”๊ฐ€๋œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด์ž. ์ด๋•Œ props๋ฅผ ๊ฐœ๋ณ„์ ์œผ๋กœ ๋„˜๊ฒจ์คฌ๋‹ค๋ฉด interface ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ props๋„ ์ˆ˜์ •์„ ํ•ด์•ผํ•˜๋Š” ๋ฒˆ๊ฑฐ๋กœ์›€์ด ์ƒ๊ธด๋‹ค.

CheckBoxField ์ปดํฌ๋„ŒํŠธ ์ถ”์ถœํ•˜๊ธฐ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function CheckBoxField({ label }: { label: string }) {
const id = `checkbox-${label}`.replace(/ /g, '-').toLowerCase();
return (
<div>
<input type="checkbox" id={id} />
<label htmlFor={id}>{label}</label>
</div>
);
}

export default function SearchBar() {
return (
<div className="seach-bar">
<div>
<input type="text" placeholder="Search..." />
</div>
<CheckBoxField label="Only Show products in stock" />
</div>
);
}

const id = checkbox-${label}.replace(/ /g, '-').toLowerCase(); ์™€ ๊ฐ™์€ ์ฝ”๋“œ๋Š” React์—์„  useRef๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

1
2
3
4
5
6
7
const id = useRef(`checkbox-${label}`.replace(/ /g, '-').toLowerCase());
return (
<div>
<input type="checkbox" id={id.current} />
<label htmlFor={id.current}>{label}</label>
</div>
);