[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
โ CustomNavLink ์ปดํฌ๋ํธ
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 ) |
search | URL์ ์ฟผ๋ฆฌ ๋ฌธ์์ด (์: ?id=123 ) |
hash | URL์ ํด์๊ฐ (์: #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>
);
};
END
This post is licensed under CC BY 4.0 by the author.