Post

[Day53] ๋ฐ˜์‘ํ˜•

[Day53] ๋ฐ˜์‘ํ˜•

๐Ÿ“ ๊ตฌ์กฐ๋ณ„ ํŠน์ง•

1. ๊ธฐ๋ณธ React ๊ตฌ์กฐ

1
2
3
4
5
src/
โ”œโ”€โ”€ components/
โ”œโ”€โ”€ pages/
โ”œโ”€โ”€ hooks/
โ”œโ”€โ”€ services/
  • ์ž…๋ฌธ์ž์—๊ฒŒ ์นœ์ˆ™
  • ํด๋”๊ฐ€ ๊ธฐ๋Šฅ๋ณ„๋กœ ๋‚˜๋‰˜์–ด ๊ด€๋ฆฌํ•˜๊ธฐ ์‰ฝ๊ณ  ๋‹จ์ˆœ

2. ๊ธฐ๋Šฅ ์ค‘์‹ฌ(Folder by Feature) ๊ตฌ์กฐ

1
2
3
4
5
6
7
8
9
10
11
12
src/
โ”œโ”€โ”€ features/
โ”‚   โ”œโ”€โ”€ auth/
โ”‚       โ”œโ”€โ”€ components/
โ”‚       โ”œโ”€โ”€ hooks/
โ”‚       โ”œโ”€โ”€ services/
โ”‚       โ”œโ”€โ”€ store/
โ”‚   โ”œโ”€โ”€ products/
โ”‚       โ”œโ”€โ”€ components/
โ”‚       โ”œโ”€โ”€ hooks/
โ”‚       โ”œโ”€โ”€ services/
โ”‚       โ”œโ”€โ”€ store/
  • ๊ธฐ๋Šฅ ๋‹จ์œ„๋กœ ๋ชจ๋“  ํŒŒ์ผ(์ปดํฌ๋„ŒํŠธ, ํ›…, ์„œ๋น„์Šค ๋“ฑ)์„ ๋ฌถ์Œ
  • ํ”„๋กœ์ ํŠธ๊ฐ€ ์ปค์ ธ๋„ ๊ธฐ๋Šฅ ๊ฐ„ ๋…๋ฆฝ์„ฑ ์œ ์ง€๊ฐ€ ์šฉ์ด

3. Atomic Design ํŒจํ„ด

1
2
3
4
5
src/
โ”œโ”€โ”€ components/
โ”‚   โ”œโ”€โ”€ atoms/
โ”‚   โ”œโ”€โ”€ molecules/
โ”‚   โ”œโ”€โ”€ organisms/
  • UI ์ปดํฌ๋„ŒํŠธ ์„ค๊ณ„ ์ฒ ํ•™ ๊ธฐ๋ฐ˜
  • ์žฌ์‚ฌ์šฉ์„ฑ ๋†’์€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ณ„์ธต์ ์œผ๋กœ ๋ถ„๋ฆฌ

4. ์„œ๋ฒ„/ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ ๋ถ„๋ฆฌ ๊ตฌ์กฐ (Next.js App Router ๊ธฐ๋ฐ˜)

1
2
3
4
5
6
7
8
src/
โ”œโ”€โ”€ components/
โ”‚   โ”‚   โ”œโ”€โ”€ server/    
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ DataTable.jsx
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ ServerNav.jsx
โ”‚   โ”‚   โ””โ”€โ”€ client/ 
โ”‚   โ”‚       โ”œโ”€โ”€ Button.jsx  // 'use client' ์ง€์‹œ์–ด ํฌํ•จ
โ”‚   โ”‚       โ””โ”€โ”€ Form.jsx    // 'use client' ์ง€์‹œ์–ด ํฌํ•จ
  • ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ vs ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ ๊ตฌ๋ถ„ ํ•„์ˆ˜
  • โ€˜use clientโ€™ ์ง€์‹œ์–ด๋กœ ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ ๋ช…์‹œ

๐Ÿ”ธ ์ฝ”๋“œ ๋ถ„์„

Header.jsx

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
import React, { useEffect, useState } from 'react'
import { Link, NavLink, useLocation } from 'react-router-dom'
import css from './Header.module.css'
import Logo from '../components/Logo'

const Header = () => {
  const [isOn, setIsOn] = useState(false) // ๋ฉ”๋‰ด๊ฐ€ ์—ด๋ ค์žˆ๋Š” ์ƒํƒœ(true) or ๋‹ซํžŒ ์ƒํƒœ(false)
  const location = useLocation() // ํ˜„์žฌ URL ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ด
  console.log(location)
  console.log(location.pathname)

  const addClassOn = () => {
    setIsOn(!isOn)  // ๋ฉ”๋‰ด ์—ด๊ณ  ๋‹ซ๊ธฐ
  }

  useEffect(() => {
    setIsOn(false)
  }, [location.pathname]) // URL์ด ๋ฐ”๋€Œ๋ฉด ๋ฉ”๋‰ด๋ฅผ ์ž๋™์œผ๋กœ ๋‹ซ์Œ

  const handleResize = () => {
    // ํ™”๋ฉด์ด 1100px๋ณด๋‹ค ๋„“์–ด์ง€๋ฉด ๋ฉ”๋‰ด๋ฅผ ๊ฐ•์ œ๋กœ ๋‹ซ์Œ (๋ชจ๋ฐ”์ผ์—์„œ ๋ฐ์Šคํฌํƒ‘ ์ „ํ™˜ ์‹œ)
    if (window.innerWidth > 1100) {  
      setIsOn(false)
    }
  }

  useEffect(() => {
    window.addEventListener('resize', handleResize) // ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งˆ์šดํŠธ๋˜๋ฉด resize ์ด๋ฒคํŠธ ๋“ฑ๋ก
    return () => {
      window.removeEventListener('resize', handleResize) 
      // ์–ธ๋งˆ์šดํŠธ ์‹œ ์ด๋ฒคํŠธ ์ œ๊ฑฐ (clean-up)
    }
  }, [])
  // ์ œ๊ฑฐ ์•ˆ ํ•˜๋ฉด ๋ฆฌ๋ Œ๋”๋ง๋งˆ๋‹ค ์ด๋ฒคํŠธ๊ฐ€ ๊ณ„์† ์Œ“์—ฌ์„œ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๋ฐœ์ƒ

  return (
    <header className={css.hd}>
      <div className={css.con}>
        <h1 className={css.logo}>
          <Link to={'/'}>
            <Logo />
          </Link>
        </h1>
        {/* ๋ฉ”๋‰ด๊ฐ€ ์—ด๋ ค ์žˆ์„ ๋•Œ๋งŒ .on ํด๋ž˜์Šค๋ฅผ ์ถ”๊ฐ€ */}
        <div className={isOn ? `${css.gnb} ${css.on}` : css.gnb}> 
          <nav>
            <CustomNavLink to={'/shop'} label={'shop'} />
            <CustomNavLink to={'/about'} label={'about'} />
            <CustomNavLink to={'/blog'} label={'blog'} />
          </nav>
          <div className={css.icon}>
            <CustomIconLink to={'/search'} icon={'bi-search'} />
            <CustomIconLink to={'/mypage'} icon={'bi-person-circle'} />
            <CustomIconLink to={'/cart'} icon={'bi-basket'} />
          </div>
        </div>
        <i className={`${css.ham} bi bi-list`} title="์ „์ฒด๋ฉ”๋‰ด ๋ณด๊ธฐ๋ฒ„ํŠผ" onClick={addClassOn}></i>
      </div>
    </header>
  )
}

const CustomNavLink = ({ to, label }) => (
  <NavLink className={({ isActive }) => (isActive ? `${css.active}` : '')} to={to}>
    {label}
  </NavLink>
)

const CustomIconLink = ({ to, icon }) => (
  <NavLink className={({ isActive }) => (isActive ? `${css.active}` : '')} to={to}>
    <i className={`bi ${icon}`}></i>
  </NavLink>
)

export default Header

1
2
3
4
5
const CustomNavLink = ({ to, label }) => (
<NavLink className={({ isActive }) => (isActive ? `${css.active}` : '')} to={to}>
    {label}
</NavLink>
)
  • to: ์ด๋™ํ•  ๊ฒฝ๋กœ (์˜ˆ: /shop)
  • label: ํ‘œ์‹œํ•  ํ…์ŠคํŠธ (์˜ˆ: โ€˜shopโ€™)
  • ํ˜„์žฌ URL๊ณผ to๊ฐ€ ์ผ์น˜ํ•˜๋ฉด ์ž๋™์œผ๋กœ isActive๋ฅผ true๋กœ ๋„˜๊ฒจ์คŒ

โœ… ๋ฉ”๋‰ด ํ•ญ๋ชฉ ์•„๋ž˜์— ๋ฐ‘์ค„ ์• ๋‹ˆ๋ฉ”์ด์…˜ ํšจ๊ณผ

1
2
3
4
5
6
7
8
9
10
11
.gnb nav a::before {
  content: '';
  position: absolute;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 0%;
  height: 2px;
  background-color: var(--dark-colors-accent-dark);
  transition: 0.3s;
}
์†์„ฑ์˜๋ฏธ
content: ''::before ๊ฐ€์ƒ ์š”์†Œ๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•œ ํ•„์ˆ˜ ๊ฐ’ (๋นˆ ๋‚ด์šฉ)
position: absolute๋ฉ”๋‰ด ํ•ญ๋ชฉ(a)์„ ๊ธฐ์ค€์œผ๋กœ ์ ˆ๋Œ€ ์œ„์น˜ ์ง€์ •
bottom: 0์•„๋ž˜์ชฝ์— ์œ„์น˜์‹œํ‚ด (์ฆ‰, ๋ฐ‘์ค„์ฒ˜๋Ÿผ ๋ณด์ด๊ฒŒ)
left: 50%, transform: translateX(-50%)๊ฐ€์šด๋ฐ ์ •๋ ฌ
width: 0%์ดˆ๊ธฐ์—๋Š” ๋ณด์ด์ง€ ์•Š์Œ
height: 2px๋ฐ‘์ค„ ๋‘๊ป˜
background-color: var(--dark-colors-accent-dark)๋ฐ‘์ค„ ์ƒ‰์ƒ
transition: 0.3s์• ๋‹ˆ๋ฉ”์ด์…˜ ํšจ๊ณผ (์„œ์„œํžˆ ๋ณ€ํ™”)
1
2
3
4
.gnb nav a.active::before,
.gnb nav a:hover::before {
  width: 100%;
}
  • ๋งˆ์šฐ์Šค๋ฅผ ์˜ฌ๋ฆฌ๊ฑฐ๋‚˜(hover), ํ˜„์žฌ ํŽ˜์ด์ง€์™€ ๋งํฌ๊ฐ€ ๊ฐ™์•„์„œ active ํด๋ž˜์Šค๊ฐ€ ๋ถ™์œผ๋ฉด
  • width: 100%์ด ๋˜๋ฉด์„œ ๋ฐ‘์ค„์ด ์• ๋‹ˆ๋ฉ”์ด์…˜์œผ๋กœ ํŽผ์ณ์ง.

โœ… useLocation

  • React Router ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ ์ œ๊ณตํ•˜๋Š” ํ›…(hook)
  • ํ˜„์žฌ ๋ธŒ๋ผ์šฐ์ €์˜ URL ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐ ์‚ฌ์šฉ
  • ํ˜„์žฌ ํŽ˜์ด์ง€์˜ ๊ฒฝ๋กœ, ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ, ํ•ด์‹œ ๋“ฑ URL์— ๊ด€๋ จ๋œ ์ •๋ณด์— ์‰ฝ๊ฒŒ ์•ก์„ธ์Šค

์ฃผ์š” ์†์„ฑ

์†์„ฑ๋ช…์„ค๋ช…
pathnameํ˜„์žฌ URL์˜ ๊ฒฝ๋กœ (์˜ˆ: /about)
searchURL์˜ ์ฟผ๋ฆฌ ๋ฌธ์ž์—ด (์˜ˆ: ?id=123)
hashURL์˜ ํ•ด์‹œ๊ฐ’ (์˜ˆ: #section1)
stateํŽ˜์ด์ง€ ์ด๋™ ์‹œ ์ „๋‹ฌ๋œ ์ƒํƒœ ๊ฐ์ฒด
keyํ˜„์žฌ location์„ ๊ณ ์œ ํ•˜๊ฒŒ ์‹๋ณ„ํ•˜๋Š” ๊ฐ’ (๋ผ์šฐํ„ฐ๊ฐ€ ๋‚ด๋ถ€์ ์œผ๋กœ ์‚ฌ์šฉ)

๐Ÿ› ๏ธ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { useLocation } from 'react-router-dom';

const MyComponent = () => {
  const location = useLocation();

  console.log(location.pathname); // ํ˜„์žฌ ๊ฒฝ๋กœ
  console.log(location.search);   // ์ฟผ๋ฆฌ ๋ฌธ์ž์—ด
  console.log(location.hash);     // ํ•ด์‹œ
  console.log(location.state);    // ์ „๋‹ฌ๋œ ์ƒํƒœ
  console.log(location.key);      // ๊ณ ์œ  ์‹๋ณ„์ž

  return (
    <div>
      <p>ํ˜„์žฌ ๊ฒฝ๋กœ: {location.pathname}</p>
    </div>
  );
};

day53


END

This post is licensed under CC BY 4.0 by the author.