๐งฉ 03. ํ์ atom โ ์ํ๋ฅผ ์กฐ๋ฆฝํ๋ ๊ธฐ์
๐ ๊ฐ์
read-only, write-only, read-write ํ์ atom ์ธ ๊ฐ์ง ํจํด์ ์์ ํ ์ตํ๊ณ , ์์กด์ฑ ์ถ์ ์๋ฆฌ์ ์ ๋ ํฐ ํจํด์ ๋ฐฐ์๋๋ค.
๐ฏ ์ด ์น์ ์ ์ฝ๊ณ ๋๋ฉด:
- read-only, write-only, read-write ์ธ ๊ฐ์ง ํ์ atom ํจํด์ ๋ง๋ค๊ณ ํ์ฉํ ์ ์๋ค.
get์ ์์กด์ฑ ์ถ์ ์ด ์ด๋ป๊ฒ ๋์ํ๋์ง ์ค๋ช ํ ์ ์๋ค.- ํ์ atom ์ผ๋ก ๋ณต์กํ ์ํ ์กฐํฉ ๋ก์ง์ ์ปดํฌ๋ํธ ๋ฐ์ผ๋ก ์ฎ๊ธธ ์ ์๋ค.
๐ ๋ชฉ์ฐจ
- ๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
- ๐ค ์ ํ์ atom ์ด ํ์ํ๊ฐ?
- ๐๏ธ ๋น์ ๋ก ๋จผ์ ์ดํดํ๊ธฐ
- ๐ Read-only ํ์ atom โ ์ ๋ ํฐ ํจํด
- โ๏ธ Write-only ํ์ atom โ ์ก์ ํจํด
- ๐ Read-Write ํ์ atom โ ์๋ฐฉํฅ ๋ณํ
- ๐งฌ ์์กด์ฑ ์ถ์ ์๋ฆฌ
- ๐ฅ ์๋ฌ ํด๊ฒฐ ์นดํ๋ก๊ทธ
- ๐ ์ด๋ฒ์ ๋ฐฐ์ด ๋ด์ฉ ์ด์ ๋ฆฌ
- ๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
- ๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
- ๐ ๋ ์์๋ณด๊ธฐ
๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
โฑ๏ธ ์์ ์ฝ๊ธฐ ์๊ฐ: 20๋ถ (์ ์ฒด) / ํต์ฌ ํํธ๋ง: 12๋ถ
๐บ๏ธ ์ด ๋ฌธ์์ ํ๋ฆ
[ํ์ atom ์ ํ์์ฑ] โ [read-only (์ ๋ ํฐ)] โ [write-only (์ก์ )] โ [read-write (์๋ฐฉํฅ)] โ [์์กด์ฑ ์ถ์ ์๋ฆฌ]
๐บ๏ธ ์ด ๋ฌธ์์ ๋ฐฐ๊ฒฝ ์ธ๊ณ๊ด: '์์๋ค ์ปค๋ฎค๋ํฐ'
์์๋ค ์ฑ์ ํํฐ + ๊ฒ์ + ์ ๋ ฌ ๊ธฐ๋ฅ์ ํจ๊ป ์ฐ๋ ์คํฐ๋ ๋ชฉ๋ก ํ์ด์ง๊ฐ ์๊ฒผ์ด.
๐ฃ ์์ฒ : (์ปดํฌ๋ํธ ์์ 50์ค์ง๋ฆฌ ๋ก์ง์ ์ง๋ฉฐ) "ํํฐ๋ ์คํฐ๋ ๋ชฉ๋ก... ๊ฒ์์ด ์ ์ฉ... ์ ๋ ฌ... ์ด๊ฑธ ๋ค ์ปดํฌ๋ํธ ์์์ ๊ณ์ฐํ๋ฉด ๋๊ฒ ๋ค!"
๐ฆ ์ํธ ๋: "์์ฒ ๋, ๊ทธ ๊ณ์ฐ ๋ก์ง ์ปดํฌ๋ํธ์ ๋ค ๋ฃ์ผ๋ฉด ๋์ค์ ๋ค๋ฅธ ํ์ด์ง์์ ๊ฐ์ ๋ก์ง ์ธ ๋ ์ด๋ป๊ฒ ํ๋ ค๊ณ ์? ํ์ atom ์ผ๋ก ๋นผ๋๋ฉด ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ๊ณ , ์ด๋์๋ ๊ตฌ๋ ํ ์ ์์ด์."
๐ฃ ์์ฒ : "ํ์ atom์ด ๋ญ๊ฐ์...?"
๐ค ์ ํ์ atom ์ด ํ์ํ๊ฐ? ๐ข
๐ฏ ์ด ์น์ ์ ์ฝ๊ณ ๋๋ฉด:
- ์ปดํฌ๋ํธ ์์ ์ํ ๊ณ์ฐ ๋ก์ง์ ๋๋ ๊ฒ ์ ๋ฌธ์ ์ธ์ง ์ค๋ช ํ ์ ์๋ค
- ํ์ atom ์ด ์ด๋ป๊ฒ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋์ง ์ดํดํ๋ค
์ปดํฌ๋ํธ ์์ ๋ชจ๋ ๊ณ์ฐ์ ๋ฃ์ผ๋ฉด?
// โ ๐ฃ ์์ฒ ์ ์ฝ๋ โ ์ปดํฌ๋ํธ๊ฐ ๋๋ฌด ๋๋ํด
const StudyListPage = () => {
const studyList = useAtomValue(studyListAtom)
const keyword = useAtomValue(searchKeywordAtom)
const filter = useAtomValue(studyFiltersAtom)
// ๊ณ์ฐ ๋ก์ง์ด ์ปดํฌ๋ํธ ์์ ์์
const filtered = studyList
.filter((s) => s.category === filter.category || filter.category === 'all')
.filter((s) => s.title.includes(keyword))
.sort((a, b) => filter.sortBy === 'popular' ? b.likeCount - a.likeCount : 0)
return <div>{filtered.map((s) => <StudyCard key={s.id} study={s} />)}</div>
}๋ฌธ์ ์ :
- ์ฌ์ฌ์ฉ ๋ถ๊ฐ โ ๋ค๋ฅธ ํ์ด์ง์์ ๊ฐ์ ํํฐ ๋ก์ง์ ์ฐ๋ ค๋ฉด ๋ณต๋ถ
- ํ ์คํธ ์ด๋ ค์ โ ์ปดํฌ๋ํธ๋ฅผ ๋ง์ดํธํด์ผ๋ง ๋ก์ง์ ํ ์คํธํ ์ ์์
- ๋ถํ์ํ ๋ณต์ก๋ โ ์ปดํฌ๋ํธ๊ฐ "ํ์" ์ "๊ณ์ฐ" ๋ ๊ฐ์ง ์ฑ ์์ ๊ฐ์ง
ํ์ atom ์ผ๋ก ๊ฐ์
// โ
๐ฆ ์ํธ์ ์ฝ๋ โ ๊ณ์ฐ ๋ก์ง์ ํ์ atom ์ผ๋ก ๋ถ๋ฆฌ
const filteredStudyListAtom = atom((get) => {
const studyList = get(studyListAtom)
const keyword = get(searchKeywordAtom)
const filter = get(studyFiltersAtom)
return studyList
.filter((s) => filter.category === 'all' || s.category === filter.category)
.filter((s) => s.title.toLowerCase().includes(keyword.toLowerCase()))
.sort((a, b) => filter.sortBy === 'popular' ? b.likeCount - a.likeCount : 0)
})
// ์ปดํฌ๋ํธ๋ ๋จ์ํด์ง
const StudyListPage = () => {
const filtered = useAtomValue(filteredStudyListAtom) // ๊ณ์ฐ ๊ฒฐ๊ณผ๋ง ๊ตฌ๋
return <div>{filtered.map((s) => <StudyCard key={s.id} study={s} />)}</div>
}๐ก ํ ์ค๋ก ๊ธฐ์ตํ๊ธฐ
ํ์ atom ์ "์์ ์์ ์ " ์ด์ผ. ์๋ณธ ๋ฐ์ดํฐ๊ฐ ๋ฐ๋๋ฉด ์์ ์ ๋ ์๋์ผ๋ก ๊ฐฑ์ ๋ผ.
์ง์ ๊ฐ์ ์ ์ฅํ๋ ๊ฒ ์๋๋ผ, ๋ค๋ฅธ atom ์ ์กฐํฉํด์ ์ ๊ฐ์ ๋ง๋ค์ด๋ด๋ ๊ฑฐ์ผ.
๐๏ธ ๋น์ ๋ก ๋จผ์ ์ดํดํ๊ธฐ ๐ข
๐ง 5์ด์๊ฒ ์ค๋ช ํ๋ค๋ฉด?
๋งํธ์ ์ฌ๊ณผ(์๋ณธ atom)๊ฐ ์์ด. ํ์ atom ์ "์ฌ๊ณผ๋ฅผ ์๋ผ์ ์ค" ๋ผ๋ ๋ฐฉ๋ฒ ์ ๊ธฐ์ตํด๋๋ ๊ฑฐ์ผ.
์ฌ๊ณผ๊ฐ ์ ๊ฒ์ผ๋ก ๋ฐ๋๋ฉด ๋ค์์ ๋ฌ๋ผ๊ณ ํ ๋ ์์์ ์ ์ฌ๊ณผ๋ก ์๋ผ์ค. ์กฐ๊ฐ์ ๋ฏธ๋ฆฌ ์ ์ฅํด๋๋ ๊ฒ ์๋๋ผ, ์๋ฅด๋ ๋ฐฉ๋ฒ์ ๊ธฐ์ตํด๋๋ ๊ฑฐ์ง.
์ธ ๊ฐ์ง ํ์ ํจํด ๋ฏธ๋ฆฌ ๋ณด๊ธฐ
// 1. Read-only โ ์ฝ๊ธฐ๋ง ๊ฐ๋ฅํ ๊ณ์ฐ ๊ฒฐ๊ณผ
const fullNameAtom = atom((get) =>
`${get(firstNameAtom)} ${get(lastNameAtom)}`
)
// 2. Write-only โ ์ฐ๊ธฐ๋ง ๊ฐ๋ฅํ ์ก์
const resetFiltersAtom = atom(null, (get, set) => {
set(searchKeywordAtom, '')
set(studyFiltersAtom, { category: 'all', tags: [], sortBy: 'latest' })
})
// 3. Read-Write โ ์ฝ๊ธฐ + ์ฐ๊ธฐ ์๋ฐฉํฅ
const celsiusAtom = atom(
(get) => (get(fahrenheitAtom) - 32) * 5 / 9, // ์ฝ๊ธฐ: ํ์จ โ ์ญ์จ
(get, set, celsius: number) => { // ์ฐ๊ธฐ: ์ญ์จ โ ํ์จ
set(fahrenheitAtom, celsius * 9 / 5 + 32)
}
)๐ Read-only ํ์ atom โ ์ ๋ ํฐ ํจํด ๐ข
๐ฏ ์ด ์น์ ์ ์ฝ๊ณ ๋๋ฉด:
- read-only ํ์ atom ์ ๋ง๋ค๊ณ , ์ฌ๋ฌ atom ์ ์กฐํฉํ๋ ์ ๋ ํฐ ํจํด์ ์ธ ์ ์๋ค
๊ธฐ๋ณธ ๊ตฌ์กฐ
// read ํจ์(์ฒซ ๋ฒ์งธ ์ธ์)๋ง ์์ผ๋ฉด read-only atom
const derivedAtom = atom((get) => {
const value = get(anotherAtom) // ๋ค๋ฅธ atom ์ ๊ฐ์ ์ฝ๊ธฐ
return value * 2 // ์๋ก์ด ๊ฐ ๋ฐํ
})์ค๋ฌด ์์ โ ์์๋ค ์คํฐ๋ ๊ฒ์/ํํฐ
// atoms/study.ts
// ์๋ณธ atom
export const allStudyListAtom = atom<Study[]>([])
export const searchKeywordAtom = atom('')
export const studyFiltersAtom = atom<StudyFilter>({
category: 'all',
tags: [],
sortBy: 'latest',
})
// ํ์ atom โ ํํฐ + ๊ฒ์ + ์ ๋ ฌ ์กฐํฉ
export const filteredStudyListAtom = atom((get) => {
const all = get(allStudyListAtom)
const keyword = get(searchKeywordAtom)
const { category, tags, sortBy } = get(studyFiltersAtom)
return all
.filter((s) => category === 'all' || s.category === category)
.filter((s) => tags.length === 0 || tags.every((t) => s.tags.includes(t)))
.filter((s) => s.title.toLowerCase().includes(keyword.toLowerCase()))
.sort((a, b) => {
if (sortBy === 'popular') return b.likeCount - a.likeCount
return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
})
})
// ํ์ atom โ ์นดํ
๊ณ ๋ฆฌ๋ณ ๊ฐ์
export const studyCountByCategoryAtom = atom((get) => {
const all = get(allStudyListAtom)
return all.reduce<Record<string, number>>((acc, study) => {
acc[study.category] = (acc[study.category] ?? 0) + 1
return acc
}, {})
})์ฌ๋ฌ atom ์ ์กฐํฉํ๋ ํจํด
// ๋ก๊ทธ์ธํ ์ ์ ๊ฐ ์ข์์ํ ์คํฐ๋๋ง ํํฐ๋ง
export const myLikedStudiesAtom = atom((get) => {
const allStudies = get(allStudyListAtom)
const user = get(userAtom)
if (!user) return []
return allStudies.filter((s) => user.likedStudyIds.includes(s.id))
})
// ์ปดํฌ๋ํธ์์ ์ฌ์ฉ โ ๋ฑ ํ ์ค
const MyLikedStudies = () => {
const myStudies = useAtomValue(myLikedStudiesAtom)
return <StudyList studies={myStudies} />
}๐ก ํ ์ค๋ก ๊ธฐ์ตํ๊ธฐ
Read-only ํ์ atom ์get์ "์ด๋ค atom ๋ค์ ์กฐํฉ์ผ๋ก ์ด ๊ฐ์ด ๋ง๋ค์ด์ง๋๊ฐ" ๋ฅผ ์ ์ธํ๋ ๊ฑฐ์ผ.
์์กดํ๋ atom ์ด ๋ณํ๋ฉด ํ์ atom ๋ ์๋ ์ฌ๊ณ์ฐ๋ผ.
โ๏ธ Write-only ํ์ atom โ ์ก์ ํจํด ๐ก
๐ฏ ์ด ์น์ ์ ์ฝ๊ณ ๋๋ฉด:
- write-only ํ์ atom ์ผ๋ก ์ฌ๋ฌ atom ์ ํ ๋ฒ์ ์์ ํ๋ ์ก์ ์ ๋ง๋ค ์ ์๋ค
๊ธฐ๋ณธ ๊ตฌ์กฐ
// read ์๋ฆฌ์ null, write ํจ์๋ง ์์ผ๋ฉด write-only atom
const actionAtom = atom(
null, // read: null (๊ฐ ์์)
(get, set, payload: SomeType) => { // write: ์ค์ ์ก์
๋ก์ง
set(atomA, payload.a)
set(atomB, (prev) => prev + payload.b)
}
)์ค๋ฌด ์์ โ ํํฐ ์ด๊ธฐํ ์ก์
// ๐ฆ ์ํธ: "์ฌ๋ฌ atom ์ ํ ๋ฒ์ ์ด๊ธฐํํด์ผ ํ ๋ write-only atom ์ด ๋ฑ์ด์์"
export const resetAllFiltersAtom = atom(
null,
(_get, set) => {
set(searchKeywordAtom, '')
set(studyFiltersAtom, { category: 'all', tags: [], sortBy: 'latest' })
}
)
// ์ปดํฌ๋ํธ์์ ์ฌ์ฉ
const FilterResetButton = () => {
const resetFilters = useSetAtom(resetAllFiltersAtom)
return <button onClick={() => resetFilters()}>ํํฐ ์ด๊ธฐํ</button>
}๋น๋๊ธฐ ์ก์ โ ์คํฐ๋ ์ข์์ ํ ๊ธ
// API ํธ์ถ + ์ํ ์
๋ฐ์ดํธ๋ฅผ ํ๋์ ์ก์
์ผ๋ก
export const toggleLikeAtom = atom(
null,
async (_get, set, studyId: string) => {
// ๋๊ด์ ์
๋ฐ์ดํธ (API ์๋ต ์ ์ UI ๋จผ์ ๋ณ๊ฒฝ)
set(allStudyListAtom, (prev) =>
prev.map((s) =>
s.id === studyId
? { ...s, likeCount: s.likeCount + 1, isLiked: true }
: s
)
)
try {
await api.toggleLike(studyId)
} catch {
// ์คํจ ์ ๋กค๋ฐฑ
set(allStudyListAtom, (prev) =>
prev.map((s) =>
s.id === studyId
? { ...s, likeCount: s.likeCount - 1, isLiked: false }
: s
)
)
}
}
)
const LikeButton = ({ studyId }: { studyId: string }) => {
const toggleLike = useSetAtom(toggleLikeAtom)
return (
<button onClick={() => toggleLike(studyId)}>โค๏ธ</button>
)
}๐ก ํ ์ค๋ก ๊ธฐ์ตํ๊ธฐ
Write-only atom ์ "Redux ์ ์ก์ + ๋ฆฌ๋์๋ฅผ ํ๋๋ก ํฉ์น ๊ฒ" ์ด์ผ.
์ฌ๋ฌ atom ์ ํ ๋ฒ์ ์กฐ์ํด์ผ ํ ๋ ๊น๋ํ๊ฒ ์บก์ํํด์ค.
๐ Read-Write ํ์ atom โ ์๋ฐฉํฅ ๋ณํ ๐ก
๐ฏ ์ด ์น์ ์ ์ฝ๊ณ ๋๋ฉด:
- read-write ํ์ atom ์ผ๋ก ์๋ฐฉํฅ ๋ณํ ๋ก์ง์ ๋ง๋ค ์ ์๋ค
- ์ด ํจํด์ด ์ธ์ ์ ์ฉํ์ง ์ค๋ฌด ์ผ์ด์ค๋ฅผ ํตํด ์ดํดํ๋ค
๊ธฐ๋ณธ ๊ตฌ์กฐ
const derivedAtom = atom(
(get) => transformRead(get(sourceAtom)), // ์ฝ๊ธฐ: ๋ณํ ํ ๋ฐํ
(get, set, newValue: OutputType) => { // ์ฐ๊ธฐ: ์ญ๋ณํ ํ ์๋ณธ ์
๋ฐ์ดํธ
set(sourceAtom, transformWrite(newValue))
}
)์ค๋ฌด ์์ 1 โ URL ํ๋ผ๋ฏธํฐ์ ๋๊ธฐํ๋๋ ํํฐ
// URL ์ฟผ๋ฆฌ์คํธ๋ง โ ํํฐ atom ์๋ฐฉํฅ ๋๊ธฐํ
export const urlSyncedCategoryAtom = atom(
(get) => get(studyFiltersAtom).category, // ์ฝ๊ธฐ: ํํฐ์์ ์นดํ
๊ณ ๋ฆฌ ์ถ์ถ
(get, set, newCategory: string) => { // ์ฐ๊ธฐ: ํํฐ ์
๋ฐ์ดํธ
set(studyFiltersAtom, (prev) => ({
...prev,
category: newCategory,
}))
// URL ๋ ๋์ ์
๋ฐ์ดํธ (Next.js router)
const params = new URLSearchParams(window.location.search)
params.set('category', newCategory)
window.history.replaceState(null, '', `?${params.toString()}`)
}
)
// ์ฌ์ฉ โ ๋ง์น ์ผ๋ฐ atom ์ฒ๋ผ
const CategoryFilter = () => {
const [category, setCategory] = useAtom(urlSyncedCategoryAtom)
return (
<select value={category} onChange={(e) => setCategory(e.target.value)}>
<option value="all">์ ์ฒด</option>
<option value="frontend">ํ๋ก ํธ์๋</option>
<option value="backend">๋ฐฑ์๋</option>
</select>
)
}์ค๋ฌด ์์ 2 โ ๋ค์ค ์ ํ ์ฒดํฌ๋ฐ์ค
// ํน์ ํ๊ทธ๊ฐ ์ ํ๋ ์ํ โ ์ ์ฒด ํ๊ทธ ๋ชฉ๋ก
const makeTagSelectedAtom = (tag: string) =>
atom(
(get) => get(studyFiltersAtom).tags.includes(tag), // ์ฝ๊ธฐ: ํฌํจ ์ฌ๋ถ
(get, set, isSelected: boolean) => { // ์ฐ๊ธฐ: ์ถ๊ฐ/์ ๊ฑฐ
set(studyFiltersAtom, (prev) => ({
...prev,
tags: isSelected
? [...prev.tags, tag]
: prev.tags.filter((t) => t !== tag),
}))
}
)
// ์ฒดํฌ๋ฐ์ค ์ปดํฌ๋ํธ
const TagCheckbox = ({ tag }: { tag: string }) => {
const tagAtom = useMemo(() => makeTagSelectedAtom(tag), [tag])
const [isSelected, setIsSelected] = useAtom(tagAtom)
return (
<label>
<input
type="checkbox"
checked={isSelected}
onChange={(e) => setIsSelected(e.target.checked)}
/>
{tag}
</label>
)
}๐ก ํ ์ค๋ก ๊ธฐ์ตํ๊ธฐ
Read-write ํ์ atom ์ "์ด๋ํฐ(Adapter)" ์ผ. ์๋ณธ atom ์ ๊ฑด๋๋ฆฌ์ง ์๊ณ ,
๋ค๋ฅธ ํํ๋ก ์ฝ๊ณ ์ธ ์ ์๋ ์ธํฐํ์ด์ค๋ฅผ ๋ง๋ค์ด์ค.
๐งฌ ์์กด์ฑ ์ถ์ ์๋ฆฌ ๐ก
๐ฏ ์ด ์น์ ์ ์ฝ๊ณ ๋๋ฉด:
get์ด ์์กด์ฑ์ ์ด๋ป๊ฒ ์ถ์ ํ๋์ง ์ค๋ช ํ ์ ์๋ค- ์กฐ๊ฑด๋ถ ์์กด์ฑ์ด ์ ์ฃผ์๊ฐ ํ์ํ์ง ์ดํดํ๋ค
์์กด์ฑ์ ๋์ ์ผ๋ก ์ถ์ ๋๋ค
// ์์กด์ฑ์ด ๋งค ์คํ๋ง๋ค ๋ฌ๋ผ์ง๋ ๊ฒฝ์ฐ
const dynamicAtom = atom((get) => {
const isLoggedIn = get(isLoggedInAtom)
if (isLoggedIn) {
// ๋ก๊ทธ์ธ ์ํ์ผ ๋๋ง userAtom ์ ๊ตฌ๋
return get(userAtom)?.name ?? '๋ก๊ทธ์ธ ์ ์ '
}
// ๋น๋ก๊ทธ์ธ ์ํ์ผ ๋๋ userAtom ์ ๊ตฌ๋
ํ์ง ์์
return '๊ฒ์คํธ'
})
// ๋์:
// - isLoggedIn = false โ userAtom ๋น๊ตฌ๋
, isLoggedInAtom ๋ง ๊ตฌ๋
// - isLoggedIn = true โ userAtom + isLoggedInAtom ๋ ๋ค ๊ตฌ๋
์์กด์ฑ ์ถ์ ํ๋ฆ
filteredStudyListAtom ์ด ๊ตฌ๋
๋จ
โ
read ํจ์ ์คํ
โ
์คํ ์ค get(allStudyListAtom), get(searchKeywordAtom), get(studyFiltersAtom) ํธ์ถ
โ
Jotai store: "filteredStudyListAtom ์ ์ธ atom ์ ์์กดํจ" ์ผ๋ก ๋ฑ๋ก
โ
์ธ atom ์ค ํ๋๋ผ๋ ๋ณ๊ฒฝ โ filteredStudyListAtom ์ฌ๊ณ์ฐ โ ๊ตฌ๋
์ปดํฌ๋ํธ ๋ฆฌ๋ ๋
โ ๏ธ ์ฃผ์: get ์ ์ฐ์ง ์์ผ๋ฉด ์์กด์ฑ ์์
// โ ๋ฌธ์ : get ์์ด ์ง์ ๋ชจ๋ ๋ณ์๋ฅผ ์ฐธ์กฐํ๋ฉด ๋ฐ์์ฑ ์์
let externalValue = 0
const brokenAtom = atom(() => externalValue * 2)
// externalValue ๊ฐ ๋ฐ๋์ด๋ brokenAtom ์ ์ฌ๊ณ์ฐ ์ ๋จ!
// โ
๋ฐ๋์ get ์ผ๋ก ์ฝ์ด์ผ ์์กด์ฑ ๋ฑ๋ก
const correctAtom = atom((get) => get(sourceAtom) * 2)๐ก ํ ์ค๋ก ๊ธฐ์ตํ๊ธฐ
ํ์ atom ์get์ผ๋ก ์ฝ์ atom ๋ง ์์กด์ฑ์ด ์๊ฒจ.get์ ์ ์ฐ๋ฉด ๋ฐ์์ฑ ์์ด.
๐ฅ ์๋ฌ ํด๊ฒฐ ์นดํ๋ก๊ทธ
โ ํ์ atom ์ด ์ ๋ฐ์ดํธ๋์ง ์์
์ฆ์: ์๋ณธ atom ์ ์ ๋ฐ์ดํธํ๋๋ฐ ํ์ atom ์ด ์๋ ๊ฐ์ ๋ฐํํจ
์์ธ: ํ์ atom ์ read ํจ์์์ get ์ ์ฌ์ฉํ์ง ์๊ณ ์ง์ ๊ฐ์ ์ฐธ์กฐ
// โ ์๋ชป๋ ์
const baseAtom = atom(0)
let externalRef = 0
const derivedAtom = atom(() => externalRef * 2) // get ์์ โ ์์กด์ฑ ์์!
// โ
์ฌ๋ฐ๋ฅธ ์
const derivedAtom = atom((get) => get(baseAtom) * 2) // get ์ผ๋ก ์์กด์ฑ ๋ฑ๋กโ write-only atom ์์ TypeScript ์ค๋ฅ: Argument of type 'null' is not assignable
์์ธ: write-only atom ์ ์ฒซ ๋ฒ์งธ ์ธ์(read) ์ null ์ ์ธ ๋ ํ์
์ค๋ฅ
// โ ํ์
์ค๋ฅ
const actionAtom = atom(null, (get, set) => { ... })
// โ
ํ์
๋ช
์
const actionAtom = atom<null, [payload: string], void>(
null,
(_get, set, payload) => {
set(someAtom, payload)
}
)๐ ์ด๋ฒ์ ๋ฐฐ์ด ๋ด์ฉ ์ด์ ๋ฆฌ
๐ ์ธ ๊ฐ์ง ํ์ atom ํจํด ์์ฝ
| ํจํด | ๊ตฌ์กฐ | ์ฉ๋ |
|---|---|---|
| Read-only | atom((get) => ...) | ๊ณ์ฐ๋ ๊ฐ, ์ ๋ ํฐ, ํํฐ/์ ๋ ฌ ๊ฒฐ๊ณผ |
| Write-only | atom(null, (get, set, payload) => ...) | ๋ณต์ atom ๋์ ์ ๋ฐ์ดํธ, ๋น๋๊ธฐ ์ก์ |
| Read-Write | atom((get) => ..., (get, set, val) => ...) | ์๋ฐฉํฅ ๋ณํ, URL ๋๊ธฐํ, ์ฒดํฌ๋ฐ์ค |
โ ๏ธ ์ ๋ ํ์ง ๋ง ๊ฒ
| ์ํฉ | โ ๋์ ์ | โ ์ข์ ์ |
|---|---|---|
| ํ์ atom ์์ ์ธ๋ถ ๋ณ์ ์ฐธ์กฐ | atom(() => externalVar) | atom((get) => get(sourceAtom)) |
| ์ปดํฌ๋ํธ์ ๊ณ์ฐ ๋ก์ง ์ง์ด๋ฃ๊ธฐ | ์ปดํฌ๋ํธ ๋ด๋ถ์์ filter/sort | ํ์ atom ์ผ๋ก ๋ถ๋ฆฌ |
| write-only atom ์ read ๊ฐ | atom(someValue, ...) | atom(null, ...) |
๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
Q1. ๐ ๏ธ ์ค๋ฌด ๋๋ ๋ง (์์ฒ ์ ์ ํ)
์์ ๋์ด ์์ฒญํ์ด: "์คํฐ๋ ๋ชฉ๋ก์์ ํ์ฌ ๋ก๊ทธ์ธํ ์ ์ ๊ฐ ์ ์ฒญํ ์คํฐ๋๋ง ํํฐ๋งํด์ ๋ณด์ฌ์ค."
์ด๋ค ํ์ atom ํจํด์ ์ฐ๋ ๊ฒ ๊ฐ์ฅ ์ ์ ํ ๊น?
- A) write-only atom โ
atom(null, (get, set) => ...) - B) read-only atom โ
atom((get) => get(studyListAtom).filter(...)) - C) primitive atom ์
useEffect๋ก ๊ฐ ์ธํ - D) ์ปดํฌ๋ํธ ๋ด๋ถ์์
useMemo๋ก ๊ณ์ฐ
โ ์ ๋ต: B
๐ก ์์ธ ํด์ค:
- ์๋ฆฌ ์ค๋ช
: ๋ก๊ทธ์ธ ์ ์ ๊ฐ ์ ์ฒญํ ์คํฐ๋ ๋ชฉ๋ก์ "์๋ณธ ๋ฐ์ดํฐ์์ ๊ณ์ฐ๋ ์ฝ๊ธฐ ์ ์ฉ ๊ฐ" ์ด์ผ. ์ฝ๊ธฐ๋ง ํ์ํ๊ณ , ์ด ๊ฐ ์์ฒด๋ฅผ ์ง์ ์์ ํ ์ผ์ด ์์ผ๋ read-only ํ์ atom ์ด ๋ฑ ๋ง์.
studyListAtom๊ณผuserAtom๋ ๋ค ์์กดํ๋ฉด ๋๊ณ , ์ด๋ ํ๋๊ฐ ๋ฐ๋๋ฉด ์๋์ผ๋ก ์ฌ๊ณ์ฐ๋ผ. - ์ค๋ต ํผ๋๋ฐฑ: C ์ D ๋ ์ฌ์ฌ์ฉ์ด ์ด๋ ค์. ๋ค๋ฅธ ์ปดํฌ๋ํธ์์ ๊ฐ์ ๋ฐ์ดํฐ๊ฐ ํ์ํ ๋ ์ค๋ณต ์ฝ๋๊ฐ ์๊ฒจ. A ๋ ์ฐ๊ธฐ ์ ์ฉ์ด๋ผ ๊ฐ์ ์ฝ๋ ์ฉ๋์ ๋ง์ง ์์.
- ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: "๊ณ์ฐ๋ ๊ฐ = read-only, ์ฌ๋ฌ atom ์์ = write-only, ์๋ฐฉํฅ = read-write."
Q2. ๋น์นธ ์ฑ์ฐ๊ธฐ
์๋ write-only atom ์ ๋น์นธ์ ์ฑ์๋ด. ์ข์์๋ฅผ ์ทจ์ํ๋ ์ก์ ์ด์ผ:
const unlikeStudyAtom = atom(
______, // (1)
(_get, set, studyId: string) => {
set(allStudyListAtom, (prev) =>
prev.______( // (2)
(s) => s.id === studyId
? { ...s, likeCount: s.likeCount - 1, isLiked: false }
: s
)
)
}
)โ
์ ๋ต: (1) null, (2) map
๐ก ์์ธ ํด์ค:
(1)โ write-only atom ์ read ํจ์ ์๋ฆฌ์null์ ๋ฃ์ด. ์ด atom ์ ๊ฐ์ ์ฝ์ ์ผ์ด ์๋ค๋ ๋ป์ด์ผ.useSetAtom์ผ๋ก๋ง ํธ์ถํ๊ฒ ๋ผ.(2)โ ๊ธฐ์กด ๋ฐฐ์ด์ ๋ฐ๊พธ์ง ์๊ณ ์ ๋ฐฐ์ด์ ๋ฐํํด์ผ ํ๋ฏ๋กmap์ ์จ.filter๋ ํน์ ํญ๋ชฉ์ ์ ๊ฑฐํ ๋ ์ฐ๊ณ ,map์ ํญ๋ชฉ์ ๋ณํํ ๋ ์จ.
Q3. ์น๊ตฌ์๊ฒ ์ค๋ช ํ๋ค๋ฉด?
read-only ํ์ atom ๊ณผ ์ปดํฌ๋ํธ ๋ด๋ถ
useMemo์ ์ฐจ์ด๋ฅผ ์ค๋ช ํด๋ด.
์์ ๋ต๋ณ:
"๋ ๋ค ๊ณ์ฐ๋ ๊ฐ์ ์บ์ฑํด์ ์ฑ๋ฅ์ ์ฌ๋ฆฌ๋ ๊ฑด ๋น์ทํ๋ฐ, ํต์ฌ ์ฐจ์ด๋ '๋๊ฐ ์์กดํ๋๊ฐ'์ผ.
useMemo๋ ๊ทธ ์ปดํฌ๋ํธ ํ๋๋ง ์จ๋จน์ ์ ์์ง๋ง, ํ์ atom ์ ์ฑ ์ด๋์๋useAtomValue๋ก ๊ตฌ๋ ํ ์ ์์ด. ์ฌ๋ฌ ํ์ด์ง์์ ๊ฐ์ ํํฐ ๋ก์ง์ด ํ์ํ๋ฉด ํ์ atom ์ด ํจ์ฌ ์ ๋ฆฌํด."
๐ก ์ง์ ์ค๋ช ํด๋ดค๋ค๋ฉด: ํ์ atom ์ ํต์ฌ์ ์ดํดํ ๊ฑฐ์ผ! ๐
๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
์ค๋ ์ง์ง ํฐ ๊นจ๋ฌ์์ ์ป์ ๋ ์ด์๋ค.
์ง๊ธ๊น์ง ์ปดํฌ๋ํธ ์์์ filter, sort, reduce ๋ฅผ ๋ค ํ๋๋ฐ... ๊ทธ๊ฒ ์ ๋ฌธ์ ์ธ์ง ๋ชฐ๋์ด. ๊ทผ๋ฐ ์ํธ ๋์ด "์ด ๋ก์ง ๋ค๋ฅธ ํ์ด์ง์์ ์ฐ๋ ค๋ฉด ์ด๋ป๊ฒ ํ ๊ฑฐ์์?" ๋ผ๊ณ ๋ฌผ์ด๋ณด๋ ์๊ฐ ๋จธ๋ฆฌ๊ฐ ํ์์ก๋ค.
๐ก ์ค๋์ ๊ตํ: "์ปดํฌ๋ํธ๋ ํ๋ฉด์ ๋ฌด์์ ๋ณด์ฌ์ค์ง๋ง ์๋ฉด ๋ผ. ๋ฐ์ดํฐ๋ฅผ ์ด๋ป๊ฒ ๊ฐ๊ณตํ๋์ง๋ ํ์ atom ์ด ์์์ ํด."
write-only atom ์ผ๋ก ์ฌ๋ฌ atom ์ ํ ๋ฒ์ ์ด๊ธฐํํ๋ ๊ฒ๋ ์ฒ์ ์์๋ค. "Redux ์ก์ ์ด๋ ๋ฆฌ๋์๋ฅผ ํ๋๋ก ํฉ์น ๊ฒ" ์ด๋ผ๋ ์ํธ ๋ ์ค๋ช ์ด ๋ฑ ์๋ฟ์์ด.
์์กด์ฑ ์ถ์ ๋ ์ ๊ธฐํ๋๋ฐ, get ์ผ๋ก ์ฝ์ ๊ฒ๋ง ์์กด์ฑ์ผ๋ก ์กํ๋ค๋ ๊ฑฐ... ๋น์ฐํ ๊ฒ ๊ฐ์ผ๋ฉด์๋ ๋ชฐ๋๋ ๋ด์ฉ์ด๋ค. ์ค๋ ํ๋ฃจ ๋ฟ๋ฏํ๋ค. ์ง ๊ฐ๋ฉด์ ๋ญ๊ผฌ์น ํ๋ ์ฌ๋จน๊ณ ์ถ๋ค. ๐ข