๐ก 07. atom ์ค๊ณ ์ ๋ต โ ํ์ผ ๊ตฌ์กฐ์ ์ฑ ์ ๋ถ๋ฆฌ
๐ ๊ฐ์
๋๋ฉ์ธ๋ณ atom ํ์ผ ๋ถ๋ฆฌ, ๋ค์ด๋ฐ ์ปจ๋ฒค์ , ์ํ ์์กด์ฑ ๋ฐฉ์ง, atoms/hooks ๋ ์ด์ด ์ค๊ณ ์ ๋ต์ ๋ฐฐ์๋๋ค.
๐ฏ ์ด ์น์ ์ ์ฝ๊ณ ๋๋ฉด:
- ๋๋ฉ์ธ๋ณ atom ํ์ผ ๋ถ๋ฆฌ ์ ๋ต์ ์ค๋ฌด ํ๋ก์ ํธ์ ์ ์ฉํ ์ ์๋ค.
- ์ํ ์์กด์ฑ์ด ์๊ธฐ๋ ์์ธ๊ณผ ํด๊ฒฐ์ฑ ์ ์ค๋ช ํ ์ ์๋ค.
atoms/๋ ์ด์ด์hooks/๋ ์ด์ด์ ์ญํ ์ ๋ช ํํ ๊ตฌ๋ถํ ์ ์๋ค.
๐ ๋ชฉ์ฐจ
- ๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
- ๐ค ์ atom ๊ตฌ์กฐ ์ค๊ณ๊ฐ ์ค์ํ๊ฐ?
- ๐ท๏ธ ๋ค์ด๋ฐ ์ปจ๋ฒค์
- ๐๏ธ ๋๋ฉ์ธ๋ณ ํ์ผ ๋ถ๋ฆฌ ์ ๋ต
- ๐ ์ํ ์์กด์ฑ ๋ฐฉ์ง
- ๐ฏ atoms vs hooks ๋ ์ด์ด ์ค๊ณ
- ๐ข Feature ๋จ์ atom ์ฌ๋ผ์ด์ฑ (๋๊ท๋ชจ ์ฑ)
- ๐ฅ ์๋ฌ ํด๊ฒฐ ์นดํ๋ก๊ทธ
- ๐ ์ด๋ฒ์ ๋ฐฐ์ด ๋ด์ฉ ์ด์ ๋ฆฌ
- ๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
- ๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
- ๐ ๋ ์์๋ณด๊ธฐ
๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
โฑ๏ธ ์์ ์ฝ๊ธฐ ์๊ฐ: 20๋ถ (์ ์ฒด) / ํต์ฌ ํํธ๋ง: 12๋ถ
๐บ๏ธ ์ด ๋ฌธ์์ ํ๋ฆ
[atoms.ts ํ๋์ 100๊ฐ ์ง์ฅ] โ [๋ค์ด๋ฐ ์ปจ๋ฒค์ ] โ [๋๋ฉ์ธ๋ณ ๋ถ๋ฆฌ] โ [์ํ ์์กด์ฑ ๋ฐฉ์ง] โ [atoms/hooks ๋ ์ด์ด] โ [Feature ์ฌ๋ผ์ด์ฑ]
๐บ๏ธ ์ด ๋ฌธ์์ ๋ฐฐ๊ฒฝ ์ธ๊ณ๊ด: '์์๋ค ์ปค๋ฎค๋ํฐ'
์์๋ค ์ฑ์ด ์ฌ์ฌ ์ปค์ง๋ฉด์ atom ๊ด๋ฆฌ๊ฐ ๋ฌธ์ ๊ฐ ๋ ๋ ์ด์ผ.
๐ฆ ์ํธ ๋: (์คํ๋ฆฐํธ ํ์์์) "์ด๋ฒ ์คํ๋ฆฐํธ์ ๊ธฐ์ ๋ถ์ฑ ํ๋ ์ ๋ฆฌํด์ผ ํ ๊ฒ ๊ฐ์์.
atoms.tsํ์ผ ํ ๋ฒ ์ด์ด๋ณผ๊ฒ์."(ํ์ผ์ ์ด์ ์คํฌ๋กค์ด ๋์์ด ๋ด๋ ค๊ฐ๋ค)
๐ฃ ์์ฒ : (์กฐ์ฉํ) "...์ ๋ ์ง๊ธ ๋ญ๊ฐ ์ด๋ ์๋์ง ๋ชจ๋ฅด๊ฒ ์ด์."
๐ ์์ ๋: "atom ์ด 100๊ฐ๊ฐ ๋์ด๊ฐ๋๋ฐ ํ์ผ์ด ํ๋์ผ? ์ฐพ์ ์๊ฐ ์์ด?"
๐ฆ ์ํธ ๋: "๋ง์์. ์ง๊ธ
searchKeywordAtom์ด auth ๊ด๋ จ ์ฝ๋ ์ฌ์ด์ ๋ผ์ด์๊ณ ,isModalOpenAtom์ด ์คํฐ๋ fetch ๋ก์ง ๋ฐ์ ์์ด์. ๋ฆฌํฉํ ๋ง ํ์ํด์."๐ฃ ์์ฒ : (์์งํ๊ฒ) "์ ๊ฐ ์ฒ์์ atom ์ถ๊ฐํ ๋๋ง๋ค ๊ทธ๋ฅ ํ์ผ ๋งจ ๋ฐ์ ๋ถ์์ด์... ์ฃ์กํฉ๋๋ค."
๐ฆ ์ํธ ๋: "์ง๊ธ ์์์ผ๋ฉด ๋ ๊ฑฐ์์. ์ค๋ ๊ฐ์ด ์ ๋ฆฌํด๋ด ์๋ค. ๋๋ฉ์ธ๋ณ๋ก ๋๋๋ ์ ๋ต๋ถํฐ ์์ํ ๊ฒ์."
๐ค ์ atom ๊ตฌ์กฐ ์ค๊ณ๊ฐ ์ค์ํ๊ฐ? ๐ข
๐ฏ ์ด ์น์ ์ ์ฝ๊ณ ๋๋ฉด:
atoms.tsํ๋์ ๋ชจ๋ atom ์ ์๋ ๋ฐฉ์์ ๋ฌธ์ ์ ์ ๊ตฌ์ฒด์ ์ผ๋ก ์ค๋ช ํ ์ ์๋ค- ๊ตฌ์กฐ ์ค๊ณ๊ฐ ์ฝ๋ ์ ์ง๋ณด์์ฑ์ ๋ฏธ์น๋ ์ํฅ์ ์ดํดํ๋ค
๋ฌด์ง์ํ ๋จ์ผ ํ์ผ์ ๋ฌธ์ ๋ค
// โ ๐ฃ ์์ฒ ์ atoms.ts โ ์ค์ ๋ก ์ด๋ฌ์ (100๊ฐ ์ด์)
// src/atoms.ts
// ์ธ์ฆ ๊ด๋ จ
const userAtom = atom<User | null>(null)
const isLoggedInAtom = atom((get) => get(userAtom) !== null)
// ์คํฐ๋ ๋ชฉ๋ก
const studyListAtom = atom<Study[]>([])
// ๊ฒ์ (์ธ์ฆ ๊ด๋ จ ์ฝ๋๋ค ์ฌ์ด์ ๋ผ์ด์์)
const searchKeywordAtom = atom('')
// ๋ ์ธ์ฆ ๊ด๋ จ (๊ฒ์ ์ฝ๋ ๋ค์ ๋ค๋ฆ๊ฒ ์ถ๊ฐ๋จ)
const authTokenAtom = atom<string | null>(null)
// ๋ชจ๋ฌ (์คํฐ๋ ์ฝ๋ ์ค๊ฐ์)
const isCreateStudyModalOpenAtom = atom(false)
// ์คํฐ๋ ํํฐ (๋ชจ๋ฌ ์ฝ๋ ๋ค์์)
const selectedTagsAtom = atom<string[]>([])
const sortByAtom = atom<'latest' | 'popular'>('latest')
// ์ฑํ
(ํํฐ ์ฝ๋ ์ฌ์ด์ ๋ผ์ด์์)
const chatRoomIdAtom = atom<string | null>(null)
// ... ์ด๋ฐ ์์ผ๋ก 80๊ฐ ๋ ...์ํธ ๋์ด ์ฝ๋ ๋ฆฌ๋ทฐ์์ ์ง์ ํ ๋ฌธ์ ๋ค:
๐ฆ ์ํธ: "์ง๊ธ ์ด ํ์ผ์๋ ์ธ ๊ฐ์ง ์ฌ๊ฐํ ๋ฌธ์ ๊ฐ ์์ด์:
1. ๊ด์ฌ์ฌ ๋ถ๋ฆฌ ์คํจ: auth, study, search, chat ์ด ํ ํ์ผ์ ๋ค์์ฌ ์์ด์.
'searchKeywordAtom ์ด๋ ์์ด?' ๋ผ๊ณ ๋ฌผ์ผ๋ฉด ์ ์ฒด ๊ฒ์์ ํด์ผ ํด์.
2. ํ ํ์
์ถฉ๋: ์์ฒ ๋์ด ์คํฐ๋ atom ์ถ๊ฐํ๊ณ , ๋ด๊ฐ ์ธ์ฆ atom ์์ ํ๋ฉด
๋งค๋ฒ ๊ฐ์ ํ์ผ์ git conflict ๊ฐ ๋์.
3. ๋ฒ๋ค ๊ฒฝ๊ณ ๋ถ๋ช
ํ: ๊ฒ์ ๊ธฐ๋ฅ๋ง ์ฐ๋ ํ์ด์ง๋ chat, auth ๊ด๋ จ atom ์
์ ๋ถ import ํ๊ฒ ๋ผ์. ํธ๋ฆฌ ์์ดํน์ด ํ๋ค์ด์ ธ์."
๐ก ํ ์ค๋ก ๊ธฐ์ตํ๊ธฐ
atom ํ์ผ์ด ํ๋๋ผ๋ ๊ฑด "ํ ์ ์ฒด๊ฐ ํ๋์ ์๋์ ์ฐ๋ ๊ฒ" ์ด์ผ. ๋๊ฐ ๋ญ ๋ฃ์๋์ง, ์ด๋ ์๋์ง ์ฐพ๊ธฐ ์ด๋ ต๊ณ ์ถฉ๋๋ ์ฆ์์ ธ.
๐ท๏ธ ๋ค์ด๋ฐ ์ปจ๋ฒค์ ๐ข
๐ฏ ์ด ์น์ ์ ์ฝ๊ณ ๋๋ฉด:
*Atom์ ๋ฏธ์ฌ ์ปจ๋ฒค์ ์ด ์ ํ ํ์ ์ ๋์์ด ๋๋์ง ์ดํดํ๋ค- ์ข์ atom ์ด๋ฆ๊ณผ ๋์ atom ์ด๋ฆ์ ๊ตฌ๋ถํ ์ ์๋ค
*Atom ์ ๋ฏธ์ฌ โ ์๋ณ์ ๊ธฐ์ค
// โ
๊ถ์ฅ โ ์ ๋ฏธ์ฌ Atom ์ผ๋ก ๋๋๋๋ก
const searchKeywordAtom = atom('')
const userAtom = atom<User | null>(null)
const studyListAtom = atom<Study[]>([])
const isModalOpenAtom = atom(false)
const filteredStudyListAtom = atom((get) => ...)
const toggleStudyLikeAtom = atom(null, (_, set, id: string) => ...)
// โ ๋น๊ถ์ฅ โ atom ์ธ์ง ์๋ณ ์ด๋ ค์
const searchKeyword = atom('') // ์ผ๋ฐ ๋ณ์์ธ์ง atom ์ธ์ง ๋ถ๋ช
ํ
const user = atom<User | null>(null) // ๊ฐ์ ์ด๋ฆ์ User ๊ฐ์ฒด์ ํผ๋
const STUDY_LIST = atom<Study[]>([]) // ์์์ฒ๋ผ ๋ณด์์ด๋ฆ ์์ฒด๋ก ์ญํ ์ด ๋๋ฌ๋๋ ํจํด
// ์ํ ์ฑ๊ฒฉ์ด ์ด๋ฆ์์ ๋ช
ํํ ๋ณด์ฌ์ผ ํด
const isLoadingAtom = atom(false) // boolean ์ํ โ is/has ์ ๋์ฌ
const selectedStudyIdAtom = atom<string | null>(null) // ์ ํ ์ํ โ selected ์ ๋์ฌ
const filteredStudyListAtom = atom(...) // ํ์ ์ํ โ filtered/sorted ์ ๋์ฌ
const toggleLikeAtom = atom(null, ...) // action atom โ ๋์ฌ ์ ๋์ฌ
// ๐ฆ ์ํธ: "์ด๋ฆ๋ง ๋ด๋ '์ด๊ฑด boolean', '์ด๊ฑด action', '์ด๊ฑด derived' ๊ฐ ๋ณด์ฌ์ผ ํด์"๐๏ธ ๋๋ฉ์ธ๋ณ ํ์ผ ๋ถ๋ฆฌ ์ ๋ต ๐ก
๐ฏ ์ด ์น์ ์ ์ฝ๊ณ ๋๋ฉด:
- ์ค๋ฌด ํ๋ก์ ํธ์ ๋ฐ๋ก ์ ์ฉ ๊ฐ๋ฅํ atom ํ์ผ ๋ถ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ์ ์ ์๋ค
- ๊ฐ ํ์ผ์ ์ด๋ค atom ์ด ๋ค์ด๊ฐ์ผ ํ๋์ง ํ๋จํ ์ ์๋ค
์ํธ ๋์ด ์ ์ํ ์์๋ค ํ์ ํ์ผ ๊ตฌ์กฐ
src/atoms/
โโโ auth.ts โ ์ธ์ฆ ๊ด๋ จ atom
โโโ study.ts โ ์คํฐ๋ ๋๋ฉ์ธ atom
โโโ search.ts โ ๊ฒ์ ๊ด๋ จ atom
โโโ ui.ts โ UI ์ํ atom (๋ชจ๋ฌ, ํญ ๋ฑ)
โโโ chat.ts โ ์ฑํ
๊ด๋ จ atom
โโโ index.ts โ ๊ณต๊ฐ API (re-export)
// src/atoms/auth.ts
import { atom } from 'jotai'
import type { User } from '@/types'
// ๐ฆ ์ํธ: "auth ๋๋ฉ์ธ์ ์ํ๋ atom ๋ง ์ฌ๊ธฐ์"
export const userAtom = atom<User | null>(null)
export const authTokenAtom = atom<string | null>(null)
export const isLoggedInAtom = atom((get) => get(userAtom) !== null)
export const userRoleAtom = atom((get) => get(userAtom)?.role ?? 'guest')// src/atoms/study.ts
import { atom } from 'jotai'
import { searchKeywordAtom, selectedTagsAtom } from './search'
// โ search atom ์ ์์กด (auth ์๋ ์์กด ์ ํจ)
import type { Study } from '@/types'
// ๐ฆ ์ํธ: "์คํฐ๋ ๋๋ฉ์ธ atom. ํ์ atom ์ search atom ์ ์์กดํด๋ OK"
export const studyListAtom = atom<Study[]>([])
export const selectedStudyIdAtom = atom<string | null>(null)
// search atom ์ผ๋ก๋ถํฐ ํ์
export const filteredStudyListAtom = atom((get) => {
const studies = get(studyListAtom)
const keyword = get(searchKeywordAtom)
const tags = get(selectedTagsAtom)
return studies
.filter((s) => s.title.toLowerCase().includes(keyword.toLowerCase()))
.filter((s) => tags.length === 0 || tags.some((t) => s.tags.includes(t)))
})// src/atoms/search.ts
import { atom } from 'jotai'
// ๐ฆ ์ํธ: "๊ฒ์ ๊ด๋ จ ์์ ์ํ. ๋ค๋ฅธ ๋๋ฉ์ธ atom ์ ์์กดํ์ง ์์์"
export const searchKeywordAtom = atom('')
export const selectedTagsAtom = atom<string[]>([])
export const sortByAtom = atom<'latest' | 'popular'>('latest')
export const isSearchActiveAtom = atom((get) => get(searchKeywordAtom).length > 0)// src/atoms/ui.ts
import { atom } from 'jotai'
// ๐ฆ ์ํธ: "UI ์ํ๋ ๋น์ฆ๋์ค ๋ก์ง๊ณผ ๋ฌด๊ดํ๊ฒ ๋
๋ฆฝ์ ์ผ๋ก ์ ์งํด์"
export const isCreateStudyModalOpenAtom = atom(false)
export const isProfileMenuOpenAtom = atom(false)
export const activeTabAtom = atom<'explore' | 'my-studies' | 'chat'>('explore')
export const sidebarWidthAtom = atom(280)// src/atoms/index.ts โ ๊ณต๊ฐ API
// ๐ฆ ์ํธ: "์ธ๋ถ์์๋ ์ด index.ts ์์๋ง import ํ๋๋ก ๊ฐ์ ํ๋ฉด
// ๋ด๋ถ ๊ตฌ์กฐ๊ฐ ๋ฐ๋์ด๋ import ๊ฒฝ๋ก๋ฅผ ์์ ํ ํ์ ์์ด์"
export * from './auth'
export * from './study'
export * from './search'
export * from './ui'
export * from './chat'Before / After ๋น๊ต
// โ Before โ ๋ชจ๋ ํ์ผ์ด atoms.ts ๋ฅผ import
import { userAtom, searchKeywordAtom, isModalOpenAtom } from '@/atoms'
// atoms.ts ์ 100๊ฐ atom ์ด ์๊ณ , ์ด ํ์ผ์์ 3๊ฐ๋ง ์
// โ ๋ฒ๋ค๋ฌ๊ฐ 100๊ฐ๋ฅผ ์ ๋ถ ํฌํจํ ๊ฐ๋ฅ์ฑ
// โ
After โ ํ์ํ ๋๋ฉ์ธ ํ์ผ๋ง import (tree-shaking ์นํ์ )
import { userAtom } from '@/atoms/auth'
import { searchKeywordAtom } from '@/atoms/search'
import { isCreateStudyModalOpenAtom } from '@/atoms/ui'
// ๋๋ index.ts ์์ โ ๊ฒฝ๋ก ์ผ๊ด์ฑ ์ ์ง
import { userAtom, searchKeywordAtom, isCreateStudyModalOpenAtom } from '@/atoms'๐ ์ํ ์์กด์ฑ ๋ฐฉ์ง ๐ด
๐ฏ ์ด ์น์ ์ ์ฝ๊ณ ๋๋ฉด:
- ์ํ ์์กด์ฑ์ด ์ด๋ป๊ฒ ๋ฐ์ํ๋์ง ์ดํดํ๋ค
- ๊ณตํต atoms ๋ถ๋ฆฌ๋ก ์ํ ์์กด์ฑ์ ํด๊ฒฐํ๋ ๋ฒ์ ์ ์ ์๋ค
์ํ ์์กด์ฑ ๋ฐ์ ์๋๋ฆฌ์ค
auth.ts โ study.ts ๋ฅผ import (์คํฐ๋ atom ์ user ๊ด๋ จ ํํฐ ์ ์ฉ)
study.ts โ auth.ts ๋ฅผ import (๋ฉค๋ฒ ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ)
โ ์ํ ์์กด์ฑ ๋ฐ์! ๋ชจ๋ ๋ก๋ ์์ ๋ถํ์ค โ ๋ฐํ์ ์๋ฌ
// โ ์ํ ์์กด์ฑ์ด ์๊ธฐ๋ ํจํด
// src/atoms/auth.ts
import { selectedStudyIdAtom } from './study' // study.ts ๋ฅผ import
export const userStudyRoleAtom = atom((get) => {
const studyId = get(selectedStudyIdAtom) // study atom ์ ์์กด
const user = get(userAtom)
// ...
})
// src/atoms/study.ts
import { userAtom } from './auth' // auth.ts ๋ฅผ import โ ์ํ!
export const studyMembersAtom = atom((get) => {
const user = get(userAtom) // auth atom ์ ์์กด
// ...
})ํด๊ฒฐ์ฑ : ๊ณตํต atoms ๋ฅผ ๋ณ๋ ํ์ผ๋ก ๋ถ๋ฆฌ
src/atoms/
โโโ core.ts โ ๋ค๋ฅธ atom ์ ์์กดํ์ง ์๋ ๊ธฐ๋ฐ atom (์ํ ์์กด ์์)
โโโ auth.ts โ core.ts ์๋ง ์์กด
โโโ study.ts โ core.ts ์๋ง ์์กด
โโโ derived.ts โ auth.ts, study.ts ์ ์์กดํ๋ ํ์ atom
โโโ index.ts
// src/atoms/core.ts โ ๋ชจ๋ ํ์ผ์ ๊ธฐ๋ฐ
// ๐ฆ ์ํธ: "๋ค๋ฅธ ํ์ผ์ ์์กดํ์ง ์๋ ์์ ๊ธฐ๋ฐ atom ๋ง ์ฌ๊ธฐ์"
export const userAtom = atom<User | null>(null)
export const studyListAtom = atom<Study[]>([])
export const searchKeywordAtom = atom('')// src/atoms/auth.ts โ core.ts ์๋ง ์์กด
import { userAtom } from './core'
export const isLoggedInAtom = atom((get) => get(userAtom) !== null)
export const userRoleAtom = atom((get) => get(userAtom)?.role ?? 'guest')// src/atoms/study.ts โ core.ts ์๋ง ์์กด
import { studyListAtom, searchKeywordAtom } from './core'
export const filteredStudyListAtom = atom((get) => {
const studies = get(studyListAtom)
const keyword = get(searchKeywordAtom)
return studies.filter((s) => s.title.includes(keyword))
})// src/atoms/derived.ts โ auth.ts, study.ts ์์ชฝ์ ์์กด
// ๐ฆ ์ํธ: "๋ ๋๋ฉ์ธ์ ๊ฒฐํฉํ ํ์ ์ํ๋ ๋ณ๋ ํ์ผ์ ๊ฒฉ๋ฆฌํด์"
import { userAtom } from './auth'
import { studyListAtom } from './study'
export const myStudiesAtom = atom((get) => {
const user = get(userAtom)
const studies = get(studyListAtom)
if (!user) return []
return studies.filter((s) => s.members.some((m) => m.id === user.id))
})๐ก ํ ์ค๋ก ๊ธฐ์ตํ๊ธฐ
์ํ ์์กด์ฑ์ ํด๋ฒ์ ํญ์ ๊ฐ์. "๋ ๋ชจ๋์ด ์๋ก๋ฅผ ๋ฐ๋ผ๋ณด๋ฉด, ๋ ๋ชจ๋์ด ๊ณตํต์ผ๋ก ๋ฐ๋ผ๋ณผ ์ธ ๋ฒ์งธ ๋ชจ๋์ ๋ง๋ค์ด."
๐ฏ atoms vs hooks ๋ ์ด์ด ์ค๊ณ ๐ก
๐ฏ ์ด ์น์ ์ ์ฝ๊ณ ๋๋ฉด:
atoms/๋ ์ด์ด์hooks/๋ ์ด์ด์ ์ฑ ์ ์ฐจ์ด๋ฅผ ๋ช ํํ ์ค๋ช ํ ์ ์๋ค- ์ปค์คํ ํ ์ผ๋ก atom ์ ์บก์ํํ๋ ์ค๋ฌด ํจํด์ ์ ์ฉํ ์ ์๋ค
๋ ๋ ์ด์ด์ ์ญํ ๋ถ๋ฆฌ
src/
โโโ atoms/ โ "๋ฌด์์ ์ ์ฅํ ๊ฒ์ธ๊ฐ" โ ์์ํ ์ํ ์ ์ธ
โ โโโ auth.ts
โ โโโ study.ts
โ โโโ search.ts
โ
โโโ hooks/ โ "์ปดํฌ๋ํธ์๊ฒ ์ด๋ค ์ธํฐํ์ด์ค๋ฅผ ์ค ๊ฒ์ธ๊ฐ" โ ๋น์ฆ๋์ค ๋ก์ง ์กฐํฉ
โโโ useStudySearch.ts
โโโ useAuth.ts
โโโ useStudyActions.ts
// src/hooks/useStudySearch.ts
// ๐ฆ ์ํธ: "์ปดํฌ๋ํธ๋ atom ์ ์ง์ ์ฐ๋ ๊ฒ๋ณด๋ค ์ด ํ
์ ์ฐ๋ ๊ฒ ์ข์์.
// atom ์ด ๋ฐ๋์ด๋ ์ปดํฌ๋ํธ ์ฝ๋๋ฅผ ์์ ํ ํ์๊ฐ ์๊ฑฐ๋ ์."
import { useAtom, useAtomValue, useSetAtom } from 'jotai'
import { searchKeywordAtom, selectedTagsAtom, sortByAtom } from '@/atoms/search'
import { filteredStudyListAtom } from '@/atoms/study'
import { resetSearchAtom } from '@/atoms/search'
export function useStudySearch() {
const [keyword, setKeyword] = useAtom(searchKeywordAtom)
const [selectedTags, setSelectedTags] = useAtom(selectedTagsAtom)
const [sortBy, setSortBy] = useAtom(sortByAtom)
const results = useAtomValue(filteredStudyListAtom)
const resetSearch = useSetAtom(resetSearchAtom)
const addTag = (tag: string) => {
setSelectedTags((prev) => [...new Set([...prev, tag])])
}
const removeTag = (tag: string) => {
setSelectedTags((prev) => prev.filter((t) => t !== tag))
}
return {
keyword,
setKeyword,
selectedTags,
addTag,
removeTag,
sortBy,
setSortBy,
results,
resetSearch,
hasActiveFilter: keyword.length > 0 || selectedTags.length > 0,
}
}// ์ปดํฌ๋ํธ โ atom ์ ์ง์ ์ ํ์ ์์
const StudySearchPanel = () => {
// ๐ฃ ์์ฒ : "์ปดํฌ๋ํธ์์ atom import ๋ฅผ ํ๋๋ ์ ํด๋ ๋๋ ๊ฑฐ์์?"
// ๐ฆ ์ํธ: "๋ง์์. ํ
์ด atom ์ ๋ณต์ก์ฑ์ ๊ฐ์ถฐ์ค์.
// ๋์ค์ atom ์ด๋ฆ์ด ๋ฐ๋์ด๋ ์ด ์ปดํฌ๋ํธ๋ ์์ ์ ํด๋ ๋ผ์."
const { keyword, setKeyword, results, selectedTags, addTag, removeTag, hasActiveFilter } =
useStudySearch()
return (
<div>
<input value={keyword} onChange={(e) => setKeyword(e.target.value)} placeholder="๊ฒ์..." />
<TagFilter tags={selectedTags} onAdd={addTag} onRemove={removeTag} />
{hasActiveFilter && <span>{results.length}๊ฐ ๊ฒฐ๊ณผ</span>}
<StudyList studies={results} />
</div>
)
}// src/hooks/useAuth.ts
import { useAtom, useAtomValue, useSetAtom } from 'jotai'
import { userAtom, isLoggedInAtom, authTokenAtom } from '@/atoms/auth'
export function useAuth() {
const [user, setUser] = useAtom(userAtom)
const isLoggedIn = useAtomValue(isLoggedInAtom)
const setToken = useSetAtom(authTokenAtom)
const login = async (credentials: LoginCredentials) => {
const { user, token } = await authApi.login(credentials)
setUser(user)
setToken(token)
}
const logout = () => {
setUser(null)
setToken(null)
}
return { user, isLoggedIn, login, logout }
}atoms/ ๋ ์ด์ด์ ์์์ฑ ์ ์ง
// โ
atoms/ ์๋ ์ด๊ฒ๋ง โ ์ํ ์ ์ธ๊ณผ ํ์ ๊ณ์ฐ
// src/atoms/search.ts
export const searchKeywordAtom = atom('')
export const selectedTagsAtom = atom<string[]>([])
// write-only reset atom (action ์ ํด๋นํ์ง๋ง ์์ํ ์ํ ์กฐ์)
export const resetSearchAtom = atom(null, (_get, set) => {
set(searchKeywordAtom, '')
set(selectedTagsAtom, [])
})
// โ atoms/ ์๋ ์ด๋ฐ ๊ฒ์ด ๋ค์ด๊ฐ๋ฉด ์ ๋จ
// fetch, console.log, router.push ๊ฐ์ ์ฌ์ด๋ ์ดํํธ
// ์ปดํฌ๋ํธ ๋ผ์ดํ์ฌ์ดํด ์ฐ๋ (useEffect ๋ฑ)
// ์ด๋ฐ ๊ฑด hooks/ ๋ ์ด์ด์์ ์ฒ๋ฆฌ๐ข Feature ๋จ์ atom ์ฌ๋ผ์ด์ฑ (๋๊ท๋ชจ ์ฑ) ๐ด
๐ฏ ์ด ์น์ ์ ์ฝ๊ณ ๋๋ฉด:
- Feature ๊ธฐ๋ฐ atom ๊ตฌ์กฐ์ ๋๋ฉ์ธ ๊ธฐ๋ฐ ๊ตฌ์กฐ์ ์ฐจ์ด๋ฅผ ์ดํดํ๋ค
- ๋๊ท๋ชจ ์ฑ์์ Feature ์ฌ๋ผ์ด์ฑ์ด ์ ์ ๋ฆฌํ์ง ์ค๋ช ํ ์ ์๋ค
Feature ์ฌ๋ผ์ด์ฑ ๊ตฌ์กฐ
src/features/
โโโ auth/
โ โโโ atoms/
โ โ โโโ user.ts
โ โ โโโ session.ts
โ โโโ hooks/
โ โ โโโ useAuth.ts
โ โโโ components/
โ โโโ LoginForm.tsx
โ โโโ UserAvatar.tsx
โ
โโโ study/
โ โโโ atoms/
โ โ โโโ list.ts
โ โ โโโ detail.ts
โ โ โโโ filter.ts
โ โโโ hooks/
โ โ โโโ useStudyList.ts
โ โ โโโ useStudyActions.ts
โ โโโ components/
โ โโโ StudyCard.tsx
โ โโโ StudyList.tsx
โ
โโโ chat/
โโโ atoms/
โ โโโ chatRoom.ts
โโโ hooks/
โ โโโ useChatRoom.ts
โโโ components/
โโโ ChatWindow.tsx
// src/features/study/atoms/filter.ts
// ๐ฆ ์ํธ: "feature ๋ด๋ถ์์๋ง ์ฐ๋ atom ์ feature ์์ ๊ฒฉ๋ฆฌํด์"
import { atom } from 'jotai'
export const studyFilterAtom = atom({
keyword: '',
tags: [] as string[],
sortBy: 'latest' as 'latest' | 'popular',
category: null as string | null,
})
// feature ๋ด๋ถ์์๋ง ์ฐ๋ derived atom
export const hasActiveFilterAtom = atom((get) => {
const filter = get(studyFilterAtom)
return filter.keyword.length > 0 || filter.tags.length > 0 || filter.category !== null
})์ ์ญ ๊ณต์ vs Feature ๋ก์ปฌ โ ์ ํ ๊ธฐ์ค
์ด๋์ atom ์ ๋ฐฐ์นํ ์ง ํ๋จํ๋ ๊ธฐ์ค:
โ
src/atoms/ (์ ์ญ)
- ๋ ๊ฐ ์ด์์ feature ์์ ์ฌ์ฉ
- ์ฑ ์ ์ฒด ์๋ช
๋์ ์ ์ง
- ์: userAtom, authTokenAtom
โ
src/features/{feature}/atoms/ (๋ก์ปฌ)
- ์ด feature ์์๋ง ์ฌ์ฉ
- feature ์ ๋ผ์ดํ์ฌ์ดํด๊ณผ ๋์ผ
- ์: studyFilterAtom, chatRoomStateAtom
๐ก ํ ์ฒ์์๋ ์ ์ญ
src/atoms/์ผ๋ก ์์ํ๊ณ , feature ๊ฐ ๋ ๋ฆฝ์ ์ผ๋ก ๋ฐฐํฌ/๋ถ๋ฆฌ๋ ๊ฐ๋ฅ์ฑ์ด ๋ณด์ด๋ฉด feature ๋ด๋ถ๋ก ์ด๋ํ๋ ์ ๋ต์ด ์ข์. ๊ณผ๋ํ ์ฌ์ ์ค๊ณ๋ณด๋ค ์ ์ง์ ๋ฆฌํฉํ ๋ง์ด ํ์ค์ ์ด์ผ.
๐ฅ ์๋ฌ ํด๊ฒฐ ์นดํ๋ก๊ทธ
โ ReferenceError: Cannot access 'atomX' before initialization
์ฆ์: ์ฑ ์์ ์ "Cannot access before initialization" ์๋ฌ
์์ธ:
// src/atoms/study.ts
import { userFilteredStudiesAtom } from './derived'
// derived.ts ๊ฐ study.ts ๋ฅผ import ํ๊ณ , study.ts ๊ฐ derived.ts ๋ฅผ import โ ์ํ!ํด๊ฒฐ์ฑ :
// ๊ณตํต ๊ธฐ๋ฐ atom ์ core.ts ๋ก ๋ถ๋ฆฌ (์ ์ํ ์์กด์ฑ ์น์
์ฐธ๊ณ )
// ๋๋ derived atom ์ ์ฌ์ฉํ๋ ํ์ผ๋ก ์ด๋ (์ธ๋ผ์ธํ)โ ESLint import/no-cycle ๊ฒฝ๊ณ
์ฆ์: ESLint ๊ฐ ์ํ import ๋ฅผ ๊ฒฝ๊ณ
ํด๊ฒฐ์ฑ :
// .eslintrc.js ์ import/no-cycle ๊ท์น ์ถ๊ฐ (์ฌ์ ๋ฐฉ์ง)
rules: {
'import/no-cycle': 'error'
}
// ๊ฒฝ๊ณ ๋ฐ์ ์ ์์ "์ํ ์์กด์ฑ ๋ฐฉ์ง" ์น์
์ ํด๊ฒฐ์ฑ
์ ์ฉโ atom ์ ์ด๋์ import ํด์ผ ํ ์ง ๋ชจ๋ฅด๊ฒ ์
์ฆ์: ํ์์ด ๊ฐ์ ๋ค๋ฅธ ๊ฒฝ๋ก๋ก atom ์ import ํด์ ์ผ๊ด์ฑ ์์
ํด๊ฒฐ์ฑ :
// src/atoms/index.ts ๋ฅผ ๊ณต๊ฐ API ๋ก ์ค์ ํ
// eslint ์ no-restricted-imports ๋ก ์ง์ import ๊ธ์ง
// .eslintrc.js
rules: {
'no-restricted-imports': ['error', {
patterns: ['@/atoms/*'], // ํ์ ํ์ผ ์ง์ import ๊ธ์ง
}],
}
// โ
ํ์ฉ
import { userAtom } from '@/atoms'
// โ ๊ธ์ง (eslint ์๋ฌ)
import { userAtom } from '@/atoms/auth'๐ ์ด๋ฒ์ ๋ฐฐ์ด ๋ด์ฉ ์ด์ ๋ฆฌ
๐ atom ๊ตฌ์กฐ ์ค๊ณ ์์น
| ์์น | ์ค๋ช |
|---|---|
*Atom ์ ๋ฏธ์ฌ | ๋ชจ๋ atom ์ด๋ฆ์ Atom ์ ๋ฏธ์ฌ โ ์ฆ๊ฐ์ ์ธ ์๋ณ |
| ๋๋ฉ์ธ๋ณ ํ์ผ ๋ถ๋ฆฌ | auth.ts, study.ts, search.ts, ui.ts ๋ฑ |
| ์์กด์ฑ ๋ฐฉํฅ ๋จ์ผํ | A โ B (OK), A โ B (์ํ โ ๊ธ์ง) |
| ๊ณตํต ๋ถ๋ฆฌ | ์ํ ๋ฐ์ ์ ๊ณตํต ๋ถ๋ถ์ core.ts ๋ก ์ถ์ถ |
| atoms/hooks ๋ ์ด์ด | atoms ๋ ์์ ์ํ ์ ์ธ, hooks ๋ ๋น์ฆ๋์ค ๋ก์ง ์กฐํฉ |
โ ๏ธ ์ ๋ ํ์ง ๋ง ๊ฒ
| ์ํฉ | โ ๋์ ์ | โ ์ข์ ์ |
|---|---|---|
| ํ์ผ ๊ตฌ์กฐ | ๋ชจ๋ atom ์ atoms.ts ํ๋์ | ๋๋ฉ์ธ๋ณ๋ก ๋ถ๋ฆฌ |
| ์ํ ์์กด์ฑ | A ๊ฐ B ๋ฅผ import, B ๊ฐ A ๋ฅผ import | ๊ณตํต core.ts ๋ก ๋ถ๋ฆฌ |
| atoms ๋ ์ด์ด | fetch, sideEffect ๋ฅผ atoms ์์ ์คํ | ์ฌ์ด๋ ์ดํํธ๋ hooks ๋ ์ด์ด๋ก |
๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
Q1. ๐๏ธ ์ํคํ ์ฒ ์ค๊ณ (ํ ์ ์ฒด ํ์)
์์๋ค ํ์ด ๊ธฐ์ ๋ถ์ฑ ํ์๋ฅผ ํ์ด. ํ์ฌ
atoms.tsํ์ผ ํ๋์ 120๊ฐ์ atom ์ด ์์ด.
์ํธ ๋์ด ์ ์ํ ๋ฆฌํฉํ ๋ง ๋ฐฉํฅ์ผ๋ก ๊ฐ์ฅ ์ ์ ํ ๊ฒ์?
- A) atom ์ ์ซ์๋ฅผ ์ค์ด๊ธฐ ์ํด ์ฌ๋ฌ atom ์ ํ๋์ ํฐ ๊ฐ์ฒด atom ์ผ๋ก ํฉ์น๋ค
- B) ๋๋ฉ์ธ๋ณ(auth, study, search, ui)๋ก ํ์ผ์ ๋ถ๋ฆฌํ๊ณ ,
index.ts์์ re-export ํ์ฌ ๊ณต๊ฐ API ๋ฅผ ์ผ์ํํ๋ค - C) atom ์ ์ฐ๋ ์ปดํฌ๋ํธ ํ์ผ ์์ ๊ฐ๊ฐ ์ ์ธํ๋ค
- D) ๋ชจ๋ atom ์ ํ๋์ Redux ์คํ ์ด๋ก ๋ง์ด๊ทธ๋ ์ด์ ํ๋ค
โ ์ ๋ต: B
๐ก ์์ธ ํด์ค:
- ์๋ฆฌ ์ค๋ช
: ๋๋ฉ์ธ๋ณ ํ์ผ ๋ถ๋ฆฌ๋ ์ธ ๊ฐ์ง ๋ฌธ์ ๋ฅผ ๋์์ ํด๊ฒฐํด. (1) ๊ด์ฌ์ฌ ๋ถ๋ฆฌ: ๊ฐ์ ๋๋ฉ์ธ atom ์ด ํ ํ์ผ์ โ ์ฐพ๊ธฐ ์ฌ์. (2) ํ ํ์
: ๊ฐ์ ๋ค๋ฅธ ํ์ผ์์ ์์
โ git conflict ๊ฐ์. (3) ๋ฒ๋ค ์ต์ ํ: ํ์ํ ๋๋ฉ์ธ ํ์ผ๋ง import ๊ฐ๋ฅ.
index.tsre-export ๋ ๋ด๋ถ ๊ตฌ์กฐ ๋ณ๊ฒฝ ์ ์ธ๋ถ import ๊ฒฝ๋ก๋ฅผ ๋ฐ๊พธ์ง ์์๋ ๋๊ฒ ํด์ค. - ์ค๋ต ํผ๋๋ฐฑ: A ๋ ์คํ๋ ค ์ปดํฌ๋ํธ ๋ฆฌ๋ ๋๋ง ์ต์ ํ๊ฐ ๊นจ์ ธ โ Jotai ์ ํต์ฌ ์ฅ์ ์ธ ์์ ๋จ์ ๊ตฌ๋ ์ด ์ฌ๋ผ์ ธ. C ๋ atom ์ด ์ปดํฌ๋ํธ ์๋งํผ ๋ถ์ฐ โ ์ฌ์ฌ์ฉ ๋ถ๊ฐ. D ๋ ์์ ํ ๋ค๋ฅธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก์ ์ ํ โ ๊ธฐ์ ๋ถ์ฑ ํด๊ฒฐ์ด ์๋์ผ.
- ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: "atom ๋ ์ฝ๋์ผ. ์ฝ๋๋ ๊ด์ฌ์ฌ๋ณ๋ก ๋ชจ์์ผ ํด."
Q2. ๐ฅ ๊ธด๊ธ ๋๋ฒ๊น (์์์ ํธํต)
๋ฐฐํฌ ํ ์์ ๋์ด ์ฐ๋ฝ์์ด: "์ฑ ์ฒ์ ๋ก๋ํ ๋ ์ฝ์์ ReferenceError ๊ฐ ๊ฐํ์ ์ผ๋ก ๋์."
์์ฒ ์ด๊ฐ ์ฝ๋๋ฅผ ๋ณด๋:
// atoms/auth.ts
import { selectedStudyAtom } from './study'
export const userStudyRoleAtom = atom((get) => {
const studyId = get(selectedStudyAtom)?.id
const user = get(userAtom)
// ...
})
// atoms/study.ts
import { userAtom } from './auth'
export const studyMembersAtom = atom((get) => {
const user = get(userAtom)
// ...
})์์ธ๊ณผ ํด๊ฒฐ์ฑ ์?
- A) atom ์ ์ด๊ธฐ๊ฐ์ด ์๋ชป๋๋ค. ์ด๊ธฐ๊ฐ์
null์์undefined๋ก ๋ฐ๊พผ๋ค - B)
auth.ts์study.ts์ฌ์ด์ ์ํ ์์กด์ฑ์ด ์๋ค. ๊ณตํต ๊ธฐ๋ฐ atom ์core.ts๋ก ๋ถ๋ฆฌํ๋ค - C)
Provider๊ฐ ์์ด์ ์๊ธฐ๋ ๋ฌธ์ ๋ค.layout.tsx์Provider๋ฅผ ์ถ๊ฐํ๋ค - D) atom ์ด ๋๋ฌด ๋ง์์ ์๊ธฐ๋ ๋ฌธ์ ๋ค. atom ์๋ฅผ ์ค์ธ๋ค
โ ์ ๋ต: B
๐ก ์์ธ ํด์ค:
- ์๋ฆฌ ์ค๋ช
:
auth.ts๊ฐstudy.ts๋ฅผ import ํ๊ณ ,study.ts๊ฐauth.ts๋ฅผ import ํ๋ฉด ์ํ ์์กด์ฑ์ด ์๊ฒจ. JavaScript ๋ชจ๋ ๋ก๋๋ ์ํ ์ฐธ์กฐ๊ฐ ์์ ๋ ์ผ๋ถ ๋ชจ๋์ด ์์ ํ ์ด๊ธฐํ๋๊ธฐ ์ ์ ์ฌ์ฉ๋๋ ์ํฉ์ด ์๊ธฐ๊ณ , ์ด ๋ "Cannot access before initialization" ์๋ฌ๊ฐ ๋. ํด๊ฒฐ์ฑ ์userAtom์ฒ๋ผ ๋ ํ์ผ์ด ๊ณตํต์ผ๋ก ํ์ํ atom ์core.ts๊ฐ์ ๋ณ๋ ํ์ผ๋ก ์ด๋ํด์ A โ core, B โ core ์ ๋จ๋ฐฉํฅ ์์กด์ฑ์ ๋ง๋๋ ๊ฑฐ์ผ. - ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: "์ํ ์์กด์ฑ = ๋์ด ์๋ก ๋ฐ๋ผ๋ด. ํด๊ฒฐ์ฑ = ๊ณตํต์ผ๋ก ๋ฐ๋ผ๋ณผ ์ธ ๋ฒ์งธ๋ฅผ ๋ง๋ค๊ธฐ."
Q3. ์น๊ตฌ์๊ฒ ์ค๋ช ํ๋ค๋ฉด?
atoms/๋ ์ด์ด์hooks/๋ ์ด์ด๋ฅผ ๋ถ๋ฆฌํ๋ ์ด์ ๋ฅผ ๊ฐ๋ฐ์ ์น๊ตฌ์๊ฒ ์ค๋ช ํด๋ด.
์์ ๋ต๋ณ:
"
atoms/๋ '์ํ๋ฅผ ์ด๋ป๊ฒ ์ ์ฅํ ๊ฒ์ธ๊ฐ' ๋ง ๋ด๋นํ๊ณ ,hooks/๋ '์ปดํฌ๋ํธ์๊ฒ ์ด๋ค ์ธํฐํ์ด์ค๋ฅผ ์ค ๊ฒ์ธ๊ฐ' ๋ฅผ ๋ด๋นํด. ๋ถ๋ฆฌํ๋ฉด ๋ ๊ฐ์ง ์ด์ ์ด ์์ด. ์ฒซ์งธ, atom ์ด๋ฆ์ด ๋ฐ๋์ด๋ hooks ๋ง ๊ณ ์น๋ฉด ์ปดํฌ๋ํธ๋ ์์ ์ ํด๋ ๋ผ. ๋์งธ, ์ปดํฌ๋ํธ๊ฐ atom ์ ๋ณต์กํ ์กฐํฉ ๋ก์ง์ ๋ชฐ๋ผ๋ ๋๋๊น ๋จ์ํด์ ธ. hooks ๊ฐ atom ๋ค์ ๋ณต์ก์ฑ์ ์บก์ํํด์ฃผ๋ ๊ฑฐ์ผ."
๐ก ์ด ๊ฐ๋ ์ ์ดํดํ๋ค๋ฉด: ๋ ์ด์ด๋ ์ํคํ ์ฒ์ ํต์ฌ ๊ฐ์น์ธ "์บก์ํ์ ์์กด์ฑ ์ญ์ " ์ ์ดํดํ ๊ฑฐ์ผ. ๋ค์ ๊ฐ์ด๋๋ก ๋์ด๊ฐ๋ ์ถฉ๋ถํด!
๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
์ค๋ atoms.ts ๋ฅผ ์ด์์ ๋ ์ํธ ๋์ด ์ ๊น ์นจ๋ฌตํ์ ๊ฒ ์๊พธ ์๊ฐ๋๋ค. ๊ทธ ์นจ๋ฌต์ด ๋ํํ
"์์ฒ ์, ์ด๊ฒ ๋ง๋?" ์ฒ๋ผ ๋ค๋ ธ์ด.
์์งํ atom ์ถ๊ฐํ ๋๋ง๋ค ๊ทธ๋ฅ ํ์ผ ๋งจ ๋ฐ์ ๋ถ์ธ ๊ฒ ๋ง์. ๊ท์ฐฎ๊ธฐ๋ ํ๊ณ , ์ด๋ ๋ฃ์ด์ผ ํ๋์ง ๊ท์น์ด ์์ผ๋๊น. ๊ทผ๋ฐ ๊ทธ๊ฒ ์์ด๋๊น ์ด์ 100๊ฐ ๋๋ atom ์ด ํ ํ์ผ์์ ๋ค์ํจ ๊ฑฐ์์.
๋ฆฌํฉํ ๋งํ๋ฉด์ auth.ts, study.ts, search.ts, ui.ts ๋ก ๋๋๊ณ ๋๋๊น, ์ง์ง ์ ๊ธฐํ๊ฒ๋ ๋ญ๊ฐ ์ด๋ ์๋์ง ๋ฐ๋ก ๋ณด์ด๋๋ผ๊ณ . 2๋ถ์ด๋ฉด ์ฐพ์ ๊ฑฐ 10๋ถ ๋ค์ง๋ ๋๊ฐ ์ด๋ฏธ ์๋ ์ผ ๊ฐ์.
๐ก ์ค๋์ ๊ตํ: "์ฝ๋๊ฐ ์ด๋ ์๋์ง ์ฐพ๋ ๋ฐ ์๊ฐ์ด ๊ฑธ๋ฆฌ๊ธฐ ์์ํ๋ฉด, ๊ทธ๊ฒ ๊ตฌ์กฐ๋ฅผ ๋ฐ๊ฟ ์ ํธ๋ค."
์ํ ์์กด์ฑ ์๊ธฐ๋ ์ถฉ๊ฒฉ์ด์์ด. A ๊ฐ B ๋ฅผ ๋ณด๊ณ B ๊ฐ A ๋ฅผ ๋ณด๋ฉด ์๋ก ๋ฌดํ ๊ฑฐ์ธ์ด ๋๋ค ๋ ๋น์ ๊ฐ ๋๋ฌด ์ฐฐ์ก์ด์ ์ด์ ์ ๋ ๋ชป ์์ ๊ฒ ๊ฐ์.
์ค๋ ํ๋ฃจ ํด๋ ๊ตฌ์กฐ ๋ค๋ฌ๊ณ ๋๋๊น ์ ์ง ๋ญ๊ฐ ์ ๋๋ ๋๋. ์ง ๋์ฒญ์ํ ๊ฒ ๊ฐ์ ๊ธฐ๋ถ์ด๋๊น. ํด๊ทผํ๊ณ ํฌ์ค์ฅ์ด๋ ๊ฐ์ผ๊ฒ ๋ค. ๋ค์ ๋ฌ ๋ฑ๋กํ PT ์ธ์ ์ด๋ฒ ์ฃผ๋ ๋น ์ง๋ฉด ์ ๋๋๋ฐ.