๐ง ๋ฆฌ์กํธ ์ฝ๋๋ฅผ ์์ฑํ ๋ ์๊ฐํ ๊ฒ๋ค ๐ง mithi/react-philosophies ์ ํ๊ตญ์ด ๋ฒ์ญ๋ณธ (๋ง์ง๋ง ์ ๋ฐ์ดํธ: 2023.04.08)
- ์๊ฐ๊ธ
- ์ต์ํ์ ์์น
- ํ๋ณต์ ์ํ ๋์์ธ
- ์ฑ๋ฅ ํ
- ํ ์คํ ์์น
- ๋ค๋ฅธ ์ฌ๋๋ค์ด ๊ณต์ ํ ์ธ์ฌ์ดํธ
react-philosophies
๋:
React
์ฝ๋๋ฅผ ์์ฑํ ๋ ์ ๊ฐ ์๊ฐํ๋ ๊ฒ๋ค์ ๋๋ค.- ๋ค๋ฅธ ์ฌ๋์ด๋ ์ ์ฝ๋๋ฅผ ๊ฒํ ํ ๋ ํญ์ ์ผ๋์ ๋๊ณ ์์ต๋๋ค.
- ๊ฐ์ด๋๋ผ์ธ์ผ ๋ฟ์ด๋ฉฐ, ๋ฐ๋์ ๋ฐ๋ฅผ ํ์๋ ์์ต๋๋ค.
- ์ง์์ ์ผ๋ก ๊ฐ์ ๋ ์ ์๋ ๋ฌธ์๋ก, ์ ๊ฒฝํ์ด ์์ผ์๋ก ๋ฐ์ ํ ์์ ์ ๋๋ค.
- ๋๋ถ๋ถ ๊ธฐ๋ณธ์ ์ธ ๋ฆฌํฉํ ๋ง ๋ฐฉ๋ฒ, SOLID ์์น, ๊ทธ๋ฆฌ๊ณ ์ต์คํธ๋ฆผ ํ๋ก๊ทธ๋๋ฐ ์์ด๋์ด์ ๋ณํ ๊ธฐ์ ์
๋๋ค... ๊ทธ์
React
์ ๋ง์ถฐ ์ ์ฉํ ๊ฒ๋ฟ์ด์ฃ ๐
react-philosophies
๋ ์ ์ฝ๋ฉ ์ฌ์ ์ค ๋ฐ๊ฒฌํ ์ฌ๋ฌ ๊ณณ์์ ์๊ฐ์ ๋ฐ์์ต๋๋ค.
๋ค์์ ๊ทธ์ค ๋ช ๊ฐ์ง์ ๋๋ค:
- Sandi Metz
- Kent C Dodds
- Zen of Python (PEP 20), Zen of Go
- trekhleb/state-of-the-art-shitcode, droogans/unmaintainable-code, sapegin/washingcode-book
ESLint
๋ก ์ฝ๋๋ฅผ ์ ์ ๋ถ์ํ์.rule-of-hooks
๋ฐexhaustive-deps
๊ท์น์ ํ์ฑํํ์ฌReact
ํน์ ์ค๋ฅ๋ฅผ ์ก์ ์ ์๋ค.- "strict" ๋ชจ๋๋ฅผ ํ์ฑํํ์. 2023๋ ์ด๋๊น.
- ๋ชจ๋ ์์กด์ฑ์ ์์งํ๊ฒ ๋ช
์ํ์.
useMemo
,useCallback
๋ฐuseEffect
์์ ๋จ๋exhaustive-deps
๊ฒฝ๊ณ /์ค๋ฅ๋ฅผ ๊ณ ์น์. ๋ถํ์ํ ๋ฆฌ๋ ๋๋ง ์์ด ์ฝ๋ฐฑ์ ํญ์ ์ต์ ์ผ๋ก ์ ์งํ๊ธฐ ์ํด "์ต์ ref ํจํด" ์ ์ฌ์ฉํด๋ณผ ์ ์๋ค. - ์ปดํฌ๋ํธ๋ฅผ ํ์ํ๊ธฐ ์ํด
map
์ ์ฌ์ฉํ ๋ ๋ฐ๋์ ํค๋ฅผ ์ถ๊ฐํ์. - ํ ์ ํญ์ ์ต์์ ๋ ๋ฒจ์์๋ง ํธ์ถํ์. ๋ฐ๋ณต๋ฌธ์ด๋ ์กฐ๊ฑด๋ฌธ, ์ค์ฒฉ ํจ์ ๋ด๋ถ์์ ํ ์ ํธ์ถํ์ง ๋ง์.
- "Can't perform state update on unmounted component" ๊ฒฝ๊ณ ๋ฅผ ์ดํดํ์. facebook/react/pull/22114
- ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฌ๋ฌ ๋ ๋ฒจ์ error boundaries ๋ฅผ ์ถ๊ฐํด "์ฃฝ์์ ํ์ ํ๋ฉด(white screen of death)" ๋ฅผ ๋ฐฉ์งํ์. ๋ํ ์ํ๋ค๋ฉด ์ด๋ฅผ ์ฌ์ฉํด Sentry ๊ฐ์ ์ค๋ฅ ๋ชจ๋ํฐ๋ง ์๋น์ค์ ์๋ฆผ์ ๋ณด๋ผ ์๋ ์๋ค. (React ์์ ์๋ฌ ์ฒ๋ฆฌํ๊ธฐ)
- ์ฝ์์ ์ค๋ฅ๋ ๊ฒฝ๊ณ ๊ฐ ๋จ๋ ๋ฐ๋ ๋ค ์ด์ ๊ฐ ์๋ค.
tree-shaking
์ ๊ธฐ์ตํ์!- Prettier (๋๋ ๊ทธ ๋์) ์ ์ฝ๋ ํ์์ ์๋์ผ๋ก ์์ ํด ๋งค๋ฒ ์ผ๊ด๋ ์คํ์ผ์ ์ ์งํด์ค๋ค. ๋ ์ด์ ์ด์ ๋ํด ์๊ฐํ ํ์๊ฐ ์๋ค!
Typescript
๋NextJS
๊ฐ์ ํ๋ ์์ํฌ๋ ์ถ์ ๋ ์ฝ๊ฒ ๋ง๋ค์ด์ค๋ค.- ์คํ ์์ค ๋ฆฌํฌ์งํ ๋ฆฌ์ด๊ฑฐ๋, ์ฌ์ ๊ฐ ์๋ค๋ฉด Code Climate (๋๋ ๋น์ทํ ํด) ์ ๊ฐ๋ ฅ ์ถ์ฒํ๋ค. ์๋ ๊ฐ์ง๋ ์ฝ๋ ์ค๋ฉ(code smell) ์ ํ์ฌ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ธฐ์ ๋ถ์ฑ๋ฅผ ์ค์ด๋ ๋ฐ ํฐ ๋๊ธฐ๋ถ์ฌ๊ฐ ๋๋ค!
"์ต์ ์ ์ฝ๋๋ ์๋ฌด ์ฝ๋๋ ์ ์ง ์๋ ๊ฒ์ด๋ค. ๋น์ ์ด ๋ง๋ ์๋ก์ด ํ ์ค์ ์ฝ๋๋ ๊ณง ๋๋ฒ๊น ์ด ํ์ํ ์ฝ๋, ์ฝ๊ณ ์ดํดํด์ผ ํ๋ ์ฝ๋, ์ง์ํด์ผ ํ๋ ์ฝ๋๊ฐ ๋๋ค." โ Jeff Atwood
"๋์ ๊ฐ์ฅ ์์ฐ์ ์ธ ๋ ์ค ํ๋๋ ์ฒ ์ค์ ์ฝ๋๋ฅผ ์ญ์ ํ ๋ ์ด์๋ค." โ Eric S. Raymond
๋๋ณด๊ธฐ: Write Less Code - Rich Harris, Code is evil - Artem Sapegin
์์กด์ฑ์ ์ถ๊ฐํ ์๋ก, ๋ ๋ง์ ์ฝ๋๋ฅผ ๋ธ๋ผ์ฐ์ ๋ก ๋ถ๋ฌ์์ผ ํ๋ค. ํด๋น ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํ๋ฅญํ๊ฒ ๋ง๋๋ ๊ธฐ๋ฅ์ ์ค์ ๋ก ์ฌ์ฉํ๊ณ ์๋์ง ์๋ฌธํ์.
๐ ์ ๋ง ํ์ํ๊ฐ? ๋ถํ์ํ ์๋ ์๋ ์์กด์ฑ/์ฝ๋ ์์
-
Redux
๊ฐ ์ ๋ง ํ์ํ ๊น? ๊ทธ๋ด ์๋ ์์ง๋ง, React ์์ฒด๊ฐ ์ด๋ฏธ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ ์์ง ๋ง์. -
Apollo client
๊ฐ ์ ๋ง ํ์ํ ๊น? Apollo client ์๋ ์๋ ์ ๊ทํ์ ๊ฐ์ ๋ฉ์ง ๊ธฐ๋ฅ์ด ๋ง์ง๋ง, ๋ฒ๋ค ํฌ๊ธฐ๋ฅผ ๋ง์ด ์ฆ๊ฐ์ํจ๋ค. Apollo client ์๋ง ์กด์ฌํ๋ ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ ๊ฒ ์๋๋ผ๋ฉด,react-query
๋SWR
๊ฐ์ด ๋ ์์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๊ณ ๋ คํด๋ณด์ (ํน์ ์์ ์ฌ์ฉํ์ง ์๋ ๊ฒ๋ ๋ฐฉ๋ฒ์ด๋ค). -
Axios
๋ ์ด๋จ๊น? Axios ๋fetch
๋ด์ฅํจ์๋ก๋ ์ฝ๊ฒ ๋์ฒดํ ์ ์๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ ๋ฉ์ง ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๋ค. ํ์ง๋ง ๋ง์ผ Axios ๋ฅผ ์ฌ์ฉํ๋ ์ ์ผํ ์ด์ ๊ฐ ๋จ์ง API ๊ฐ ๋ ๊น๋ํ๊ฒ ์๊ฒผ๊ธฐ ๋๋ฌธ์ด๋ผ๋ฉด, fetch ๋ฅผ ๋ํผ๋ก ๊ฐ์ธ ์ฌ์ฉํ๋ ๊ฒ์ ๊ณ ๋ คํด๋ณด์ (redaxios
๋ ์ง์ ๊ตฌํํด์). ์ ํ๋ฆฌ์ผ์ด์ ์ด Axios ์ ์ต๊ณ ์ ๊ธฐ๋ฅ๋ค์ ์ ๋ง๋ก ์ฌ์ฉํ๊ณ ์๋์ง ์๊ฐํด๋ณด์. -
Decimal.js
๋? Big.js ๋ ๋ค๋ฅธ ์์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ก ์ถฉ๋ถํ ์๋ ์๋ค. -
Lodash
/underscoreJS
๋? you-dont-need/You-Dont-Need-Lodash-Underscore -
ํ ๋ง(๋ผ์ดํธ/๋คํฌ ๋ชจ๋)๋ฅผ ์ํด
Context
๊ฐ ํ์ํ์ง ์์ ์๋ ์๋ค. ๋์css ๋ณ์
์ฌ์ฉ์ ๊ณ ๋ คํด๋ณด์. -
์ฌ์ง์ด
Javascript
๋ง์ ํ์ํ์ง ์์ ์๋ ์๋ค. CSS ๋ ๊ฐ๋ ฅํ๋ค. you-dont-need/You-Dont-Need-JavaScript
"๋ฏธ๋์ ๋ด ์ํํธ์จ์ด์ ์ด๋ค ์ผ์ด ์๊ธธ๊น? ์, ์ด๋ฐ ์ผ์ด๋ ์ ๋ฐ ์ผ์ด ์๊ธธ ์๋ ์๊ฒ ๊ตฐ. ์ด์ฐจํผ ์ด ๋ถ๋ถ์ ์์ ํ๊ณ ์์ผ๋ ๋ฏธ๋ฆฌ ๋ค ๊ตฌํํด๋ฒ๋ฆฌ์. ๋ฏธ๋๋ฅผ ๋๋นํด์."
ํ์ ์์ ๊ฑฐ๋ค (You Aren't Gonna Need It)! ์ธ์ ๋ ์ค์ ๋ก ํ์ํ ๋ ๊ตฌํํ๊ณ , ๋ฏธ๋ฆฌ ์์ํด์ ๊ตฌํํ์ง ๋ง์. ์ฝ๋๋ ์ ์ ์๋ก ์ข๋ค! (Martin Fowler: YAGNI, C2 Wiki: You Arent Gonna Need It!)
๊ด๋ จ ์น์ : 2.4 ์ค๋ณต์ ์๋ชป๋ ์ถ์ํ๋ณด๋ค ํจ์ฌ ์ ๋ ดํ๋ค
1.3.1 ์ฝ๋ ์ค๋ฉ์ ๊ฐ์งํ๊ณ ํ์ํ๋ฉด ์กฐ์น๋ฅผ ์ทจํ์
๋ญ๊ฐ ์๋ชป๋ ๋ถ๋ถ์ ๋ฐ๊ฒฌํ๋ค๋ฉด, ์ฆ์ ๊ณ ์น์. ๋ง์ฝ ์ฝ๊ฒ ๊ณ ์น ์ ์๊ฑฐ๋ ๋น์ฅ ๊ณ ์น ์๊ฐ์ด ์๋ค๋ฉด, ์ ์ด๋ ๋ฌธ์ ์ ๋ํ ๊ฐ๊ฒฐํ ์ค๋ช
์ ๋ด์ ์ฃผ์์ด๋ผ๋ ๋ฌ์ (FIXME
๋๋ TODO
). ๋ค๋ฅธ ๋ชจ๋๊ฐ ๋ฌธ์ ๊ฐ ์๋ค๋ ๊ฑธ ์ ์ ์๋๋ก ํ์. ๋น์ ์ด ์ ๊ฒฝ์ ์ฐ๊ณ ์๋ค๋ ๊ฑธ ์๋ฆด ๋ฟ ์๋๋ผ, ๋ค๋ฅธ ์ฌ๋๋ค๋ ์ด์ ๊ฐ์ ๋ฌธ์ ๋ฅผ ๋ฐ๊ฒฌํ์ ๋ ๋๊ฐ์ด ํ๋๋ก ๋
๋ คํ ์ ์๋ค.
๐ ์ฝ๊ฒ ์ก์ ์ ์๋ ์ฝ๋ ์ค๋ฉ ์์
- โ ๋งค์ฐ ๋ง์ ์ธ์๋ฅผ ๋ฐ๋ ๋ฉ์๋ ๋๋ ํจ์
- โ ์ดํดํ๊ธฐ ์ด๋ ค์ด boolean ๋ก์ง
- โ ํ ํ์ผ ๋ด์ ๋๋ฌด ๋ง์ ์ค์ ์ฝ๋๊ฐ ์๋ ๊ฒ
- โ (์์์ด ๋ค๋ฅด๋๋ผ๋) ๊ตฌ๋ฌธ์(syntactically) ๋์ผํ ์ค๋ณต๋๋ ์ฝ๋
- โ ์ดํดํ๊ธฐ ์ด๋ ค์ด ํจ์ ๋๋ ๋ฉ์๋
- โ ๋ง์ ํจ์๋ ๋ฉ์๋๋ฅผ ๊ฐ์ง ํด๋์ค/์ปดํฌ๋ํธ
- โ ๋จ์ผ ํจ์๋ ๋ฉ์๋ ๋ด์ ๋๋ฌด ๋ง์ ์ค์ ์ฝ๋๊ฐ ์๋ ๊ฒ
- โ ๋ฐํ๋ฌธ์ด ๋ง์ ํจ์ ๋๋ ๋ฉ์๋
- โ ์ผ์นํ์ง ์์ง๋ง ๊ตฌ์กฐ๊ฐ ๋์ผํ ์ค๋ณต๋๋ ์ฝ๋ (์: ๋ณ์๋ช ์ด ๋ค๋ฆ)
์ฝ๋ ์ค๋ฉ์ ๋ฐ๋์ ์ฝ๋๋ฅผ ์์ ํด์ผ ํจ์ ์๋ฏธํ์ง ์๋๋ค. ๊ทธ์ ๊ฐ์ ๊ธฐ๋ฅ์ ๋ ๋์ ๋ฐฉ๋ฒ์ผ๋ก ๊ตฌํํ ์๋ ์์์ ์๋ ค์ค ๋ฟ์ด๋ค
1.3.2 ๊ฐ์ฐจ์์ด ๋ฆฌํฉํฐ๋งํ๋ผ. ๋จ์ํจ์ด ๋ณต์กํจ๋ณด๋ค ๋ซ๋ค
CL (์ญ: ๊ตฌ๊ธ์์ PR์ ๊ฐ๋ฆฌํค๋ ์ฉ์ด) ์ด ํ์ ์ด์์ผ๋ก ๋ณต์กํ๊ฐ? CL ์ ๋ชจ๋ ๋ ๋ฒจ์์ ๋ค์์ ํ์ธํ๋ผ โ ๊ฐ ์ค์ด ๋๋ฌด ๋ณต์กํ๊ฐ? ํจ์๊ฐ ๋๋ฌด ๋ณต์กํ๊ฐ? ํด๋์ค๊ฐ ๋๋ฌด ๋ณต์กํ๊ฐ? "๋๋ฌด ๋ณต์กํ๋ค"๋ผ๋ ๊ฑด ์ผ๋ฐ์ ์ผ๋ก "์ฝ๋๋ฅผ ์ฝ๋ ์ฌ๋์ด ๋นจ๋ฆฌ ์ดํดํ ์ ์๋ค"๋ ๊ฒ์ ์๋ฏธํ๋ค. ๋๋ "๊ฐ๋ฐ์๊ฐ ์ด ์ฝ๋๋ฅผ ํธ์ถํ๊ฑฐ๋ ์์ ํ๋ ค ํ ๋ ๋ฒ๊ทธ๋ฅผ ๋ฐ์์ํฌ ํ๋ฅ ์ด ๋๋ค"๋ ๊ฒ์ ์๋ฏธํ๊ธฐ๋ ํ๋ค. โ Google Engineering Practices: What to look for in a code review
๐โโ๏ธ TIP: ๋ณต์กํ ์กฐ๊ฑด๋ฌธ ์ ๋จ์ํํ๊ณ , ๊ฐ๋ฅํ๋ค๋ฉด ๋นจ๋ฆฌ ๋ฆฌํด(early return)ํ์
๐ ๋น ๋ฅธ ๋ฆฌํด ์์
# โ Not-so-good
if (loading) {
return <LoadingScreen />
} else if (error) {
return <ErrorScreen />
} else if (data) {
return <DataScreen />
} else {
throw new Error('This should be impossible')
}
# โ
BETTER
if (loading) {
return <LoadingScreen />
}
if (error) {
return <ErrorScreen />
}
if (data) {
return <DataScreen />
}
throw new Error('This should be impossible')
๐โโ๏ธ TIP: ๋ฐ๋ณต๋ฌธ๋ณด๋ค ์ฐ์์ ์ธ ๊ณ ์ฐจ ํจ์(chained HOF)๋ฅผ ์ ํธํ์
๋๋ ทํ ์ฑ๋ฅ ์ฐจ์ด๊ฐ ์๊ณ ๊ฐ๋ฅํ๋ค๋ฉด, ์ ํต์ ์ธ ๋ฐ๋ณต๋ฌธ์ ์ฐ์๋ ๊ณ ์ฐจ ํจ์(map
, filter
, find
, findIndex
, some
๋ฑ)๋ก ๋์ฒดํ์. Stackoverflow: ๋ฐ๋ณต๋ฌธ ๋์ ํจ์๋ฅผ ์ฌ์ฉํ์ ๋ ์ฅ์ ์ ๋ฌด์์ธ๊ฐ์?
๐โโ๏ธ TIP: ์ํ(state)๋ฅผ ์์กด์ฑ์ผ๋ก ๋ฃ์ด์ค ํ์๊ฐ ์์ ์๋ ์๋ค. ๋์ ์ฝ๋ฐฑ ํจ์๋ฅผ ์ ๋ฌํ ์ ์๋ค
useEffect
๋ useCallback
ํ
์ ์์กด์ฑ ๋ฐฐ์ด์ (useState
์) setState
๋ (useReducer
์) dispatch
๋ ๋ฃ์ง ์์๋ ๋๋ค. React ๊ฐ ์ด๋ค์ ์์ ์ฑ์ ๋ณด์ฅํ๊ธฐ ๋๋ฌธ์ ESLint ๋ ๋ถํํ์ง ์์ ๊ฒ์ด๋ค.
๐ ์์
โ Not-so-good
const decrement = useCallback(() => setCount(count - 1), [setCount, count])
const decrement = useCallback(() => setCount(count - 1), [count])
โ
BETTER
const decrement = useCallback(() => setCount(count => (count - 1)), [])
๐โโ๏ธ TIP: ๋ง์ฝ useMemo
๋ useCallback
์ ์์กด์ฑ์ด ์๋ค๋ฉด, ๋ญ๊ฐ ์๋ชป ์ฌ์ฉํ๊ณ ์๋ ๊ฒ์ผ ์ ์๋ค
๐ ์์
โ Not-so-good
const MyComponent = () => {
const functionToCall = useCallback(x: string => `Hello ${x}!`,[])
const iAmAConstant = useMemo(() => { return {x: 5, y: 2} }, [])
/* ์ฌ๊ธฐ์ functionToCall ์ iAmAConstant ์ฌ์ฉ */
}
โ
BETTER
const I_AM_A_CONSTANT = { x: 5, y: 2 }
const functionToCall = (x: string) => `Hello ${x}!`
const MyComponent = () => {
/* ์ฌ๊ธฐ์ functionToCall ์ iAmAConstant ์ฌ์ฉ */
}
๐โโ๏ธ TIP: ์ปค์คํ ์ปจํ ์คํธ๋ฅผ ํ ์ผ๋ก ๊ฐ์ธ๋ฉด ๋ ๋ณด๊ธฐ ์ข์ API ๋ฅผ ๋ง๋ค ์ ์๋ค
๋ ๋ณด๊ธฐ ์ข์ ๋ฟ ์๋๋ผ, ์ด์ ๋ ๊ฐ๊ฐ ์๋ ํ๋๋ง import ํ๋ฉด ๋๋ค.
๐ ์์
โ Not-so-good
// ๋งค๋ฒ ๋ ๊ฐ๋ฅผ ๋ชจ๋ import ํด์ผ ํ๋ค
import { useContext } from "react"
import { SomethingContext } from "some-context-package"
function App() {
const something = useContext(SomethingContext) // ๋์์ง ์์ง๋ง, ๊ฐ์ ํ ์ ์๋ค
}
โ
Better
// ํ์ผ ํ๋์ ๋ค์ ํ
์ ์ ์ธํ๋ค
function useSomething() {
const context = useContext(SomethingContext)
if (context === undefined) {
throw new Error('useSomething must be used within a SomethingProvider')
}
return context
}
// ๋งค๋ฒ ํ๋๋ง import ํ๋ฉด ๋๋ค
import { useSomething } from "some-context-package"
function App() {
const something = useSomething() // ๋ ๋ณด๊ธฐ ์ข๋ค
}
๐โโ๏ธ TIP: ์ปดํฌ๋ํธ๋ฅผ ์ฝ๋ฉํ๊ธฐ ์ ์ ์ด๋ป๊ฒ ์ฌ์ฉ๋ ์ง ์๊ฐํ์
API ์์ฑ์ ์ด๋ ต๋ค. ๋ ๋์ API ๋ฅผ ๋์์ธํ๋ ๋ฐ ๋์์ด ๋๋ ์ ์ฉํ ๊ธฐ๋ฒ ์ค ํ๋๋ README Driven Development
์ด๋ค. RDD ๋ฅผ ๋งน๋ชฉ์ ์ผ๋ก ๋ฐ๋ฅด์๋ ์๋ฏธ๋ ์๋์ง๋ง, ์์ด๋์ด๋ ํ๋ฅญํ๋ค๊ณ ์๊ฐํ๋ค. ๊ฒฝํ์ ๊ตฌํํ๊ธฐ ์ ์ API (์ปดํฌ๋ํธ๊ฐ ์ด๋ป๊ฒ ์ฌ์ฉ๋ ์ง) ๋ฅผ ๋จผ์ ์์ฑํ๋ฉด, ๋๋ถ๋ถ ๊ทธ๋ ์ง ์์์ ๋๋ณด๋ค ๋ ์ ๋์์ธ๋ ์ปดํฌ๋ํธ๊ฐ ๋ง๋ค์ด์ง๊ณค ํ๋ค.
"์ปดํจํฐ๊ฐ ์ดํดํ ์ ์๋ ์ฝ๋๋ ๋ฐ๋ณด๋ผ๋ ์์ฑํ ์ ์๋ค. ์ข์ ๊ฐ๋ฐ์๋ ์ฌ๋์ด ์ดํดํ ์ ์๋ ์ฝ๋๋ฅผ ์์ฑํ๋ค." โ Martin Fowler
"์ฝ๋ ๋ฐ ๊ฑธ๋ฆฌ๋ ์๊ฐ ๋ ์ฐ๋ ๋ฐ ๊ฑธ๋ฆฌ๋ ์๊ฐ์ ๋น์จ์ 10 ๋ 1 ์ด์์ด๋ค. ์ฐ๋ฆฐ ์๋ก์ด ์ฝ๋๋ฅผ ์ ๊ธฐ ์ํด ๋ ์๋ ์ฝ๋๋ฅผ ์ฝ๋๋ค. ๋ฐ๋ผ์ ๋นจ๋ฆฌ ์ผํ๊ณ ์ถ๋ค๋ฉด, ์ฝ๋๋ฅผ ์ฝ๊ฒ ์์ฑํ๊ณ ์ถ๋ค๋ฉด, ์ฝ๊ธฐ ์ฝ๊ฒ ๋ง๋ค์ด๋ผ." โ Robert C. Martin (๊ทธ์ ์ ์น์ ๊ฒฌํด์ ๋์ํ๋ค๋ ๊ฒ์ ์๋)
TL;DR
- ๐ ์ค๋ณต๋๋ ์ํ๋ฅผ ์ ๊ฑฐํด ๋ณต์กํ ์ํ ๊ด๋ฆฌ๋ฅผ ํผํ์.
- ๐ ๋ฐ๋๋๋ง ์ ๋ฌํ์. ๋ฐ๋๋๋ฅผ ๋ค๊ณ ์๋ ๊ณ ๋ฆด๋ผ์ ์ ์ฒด ์ ๊ธ์ ๋ค ๊ฐ์ด ์ ๋ฌํ์ง ๋ง๊ณ . (props ๋ก ์์ ๊ฐ์ ์ ๋ฌํ์)
- ๐ ์ปดํฌ๋ํธ๋ฅผ ์๊ณ ๋จ์ํ๊ฒ ์ ์งํ์ - ๋จ์ผ ์ฑ ์ ์์น!
- ๐ ์ค๋ณต์ ์๋ชป๋ ์ถ์ํ๋ณด๋ค ํจ์ฌ ์ ๋ ดํ๋ค (๋๋ฌด ์ด๋ฅธ / ๋ถ์ ์ ํ ์ผ๋ฐํ๋ฅผ ์ง์ํ์)
- ํฉ์ฑ (composition) ์ ์ฌ์ฉํด ํ๋กํผํฐ ๋๋ฆด๋ง (prop drilling) ์ ํผํ์ (Michael Jackson).
Context
๊ฐ ๋ชจ๋ ์ํ ๊ณต์ ๋ฌธ์ ์ ๋ํ ๋ต์ ์๋๋ค.
(์ญ: ๋ฆฌ์กํธ context ์ ๋ฃ๋ ๊ฒ์ prop ์ด์ด์ผ ํ๋ฉฐ, Context ๋ฅผ ์ ์ฌ์ฉํ ๊ฒฝ์ฐ๋ ์ ๋ณด์ง ๋ชปํ๊ณ , ๋๋ถ๋ถ์ ๊ฒฝ์ฐ ๋ด ์ ํ๋ฆฌ์ผ์ด์ ์ด ์๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฝ๋์ ๋ํด ์ฌ์ฉํ๋ ๊ฒ์ด ์ณ์๋ค๊ณ ๋งํ๋ค) - ๊ฑฐ๋ํ
useEffect
๋ฅผ ์์ ๋ ๋ฆฝ์ ์ธuseEffect
๋ค๋ก ๋๋์. (KCD: Myths about useEffect) - ๋ก์ง์ ํ ๊ณผ ํฌํผ ํจ์๋ก ์ถ์ถํ์.
useCallback
,useMemo
,useEffect
์ ์์กด์ฑ์ผ๋ก ๊ฐ๋ฅํ ํ ์์ ๊ฐ๋ง ์ฌ์ฉํ์.useCallback
,useMemo
,useEffect
์ ๋๋ฌด ๋ง์ ์์กด์ฑ์ ๋์ง ๋ง์.- ์ํ์ ์ผ๋ถ ๊ฐ์ด ๋ค๋ฅธ ์ํ ๊ฐ ๋๋ ์ด์ ์ํ ๊ฐ์ ์์กดํ๋ ๊ฒฝ์ฐ, ๋ค์์
useState
๋ณด๋คuseReducer
๋ฅผ ์ฌ์ฉํ๋ฉด ๋ ๊ฐ์ํด์ง๋ค. Context
๋ฅผ ์ ์ฒด ์ ํ๋ฆฌ์ผ์ด์ ์ ์ ์ญ์ผ๋ก ์ฌ์ฉํ ํ์๋ ์๋ค.Context
๋ ์ปดํฌ๋ํธ ํธ๋ฆฌ์์ ์ต๋ํ ๋ฎ์ ์์น์ ๋ฐฐ์นํด๋ผ. ๋ณ์, ์ฃผ์, ์ํ (๊ทธ๋ฆฌ๊ณ ๊ทธ๋ฅ ์ ๋ฐ์ ์ผ๋ก ์ฝ๋) ๋ฅผ ์ฐ๊ด๋/์ฌ์ฉ๋๋ ๊ณณ์ ์ต๋ํ ๊ฐ๊น์ด ์์น์ํค๋ ๊ฒ๊ณผ ๋์ผํ๋ค.
์ค๋ณต๋ ์ํ๊ฐ ์์ผ๋ฉด ์ผ๋ถ ์ํ๊ฐ ๋๊ธฐํ๋์ง ์์ ์ ์๋ค. ๋ณต์กํ ์ํธ์์ฉ ์ํ์ค๋ฅผ ๋ฐ๋ผ๊ฐ๋ค ๋ณด๋ฉด ์ํ ๊ฐ ๊ฐฑ์ ์ ์์ ์ ์๊ธฐ ๋๋ฌธ์ด๋ค. ๋๊ธฐํ ๋ฒ๊ทธ๋ฅผ ํผํ๋ ๊ฒ ์ธ์๋, ์ดํดํ๊ธฐ๋ ๋ ์ฝ๊ณ ์ฝ๋ ์๋ ์ค์ผ ์ ์๋ค. ๋๋ณด๊ธฐ: KCD: Don't Sync State. Derive It!, Tic-Tac-Toe
๐๐๏ธ ๋น์ฆ๋์ค ์๊ตฌ์ฌํญ / ๋ฌธ์ ์ค๋ช ๋ณด๊ธฐ
๋น์ ์ ์ง๊ฐ ์ผ๊ฐํ์ ๋ค์ ์์ฑ์ ํ์ํด์ผ ํ๋ค.
- ์ธ ๋ณ์ ๊ธธ์ด
- ๋๋
- ๋์ด
์ผ๊ฐํ์ API ๋ก ๊ฐ์ ธ์จ ๋ ์ซ์ {a: number, b: number}
๋ก ๊ตฌ์ฑ๋ ๊ฐ์ฒด์ด๋ค. ๋ ์ซ์๋ ์ง๊ฐ ์ผ๊ฐํ์ ์งง์ ๋ ๋ณ์ ๋ํ๋ธ๋ค.
โ ์ข์ง ์์ ์๋ฃจ์
const TriangleInfo = () => {
const [triangleInfo, setTriangleInfo] = useState<{a: number, b: number} | null>(null)
const [hypotenuse, setHypotenuse] = useState<number | null>(null)
const [perimeter, setPerimeter] = useState<number | null>(null)
const [areas, setArea] = useState<number | null>(null)
useEffect(() => {
fetchTriangle().then(t => setTriangleInfo(t))
}, [])
useEffect(() => {
if(!triangleInfo) {
return
}
const { a, b } = triangleInfo
const h = computeHypotenuse(a, b)
setHypotenuse(h)
const newArea = computeArea(a, b)
setArea(newArea)
const p = computePerimeter(a, b, h)
setPerimeter(p)
}, [triangleInfo])
if (!triangleInfo) {
return null
}
/*** show info here ****/
}
โ ๋ "๋์" ์๋ฃจ์
const TriangleInfo = () => {
const [triangleInfo, setTriangleInfo] = useState<{
a: number;
b: number;
} | null>(null)
useEffect(() => {
fetchTriangle().then((r) => setTriangleInfo(r))
}, []);
if (!triangleInfo) {
return
}
const { a, b } = triangeInfo
const area = computeArea(a, b)
const hypotenuse = computeHypotenuse(a, b)
const perimeter = computePerimeter(a, b, hypotenuse)
/*** show info here ****/
};
๐๐๏ธ ๋น์ฆ๋์ค ์๊ตฌ ์ฌํญ / ๋ฌธ์ ์ค๋ช ๋ณด๊ธฐ
๋น์ ์ ๋ค์ ์กฐ๊ฑด์ ๋ง์กฑํ๋ ์ปดํฌ๋ํธ๋ฅผ ๋์์ธํด์ผ ํ๋ค:
- ๊ณ ์ ํ ์ ๋ค์ ๋ชฉ๋ก์ API ๋ก ๊ฐ์ ธ์จ๋ค.
x
๋๋y
๋ก ์ ๋ ฌํ ์ ์๋ ๋ฒํผ์ด ์๋ค (์ค๋ฆ์ฐจ์)maxDistance
๋ฅผ ๋ณ๊ฒฝํ ์ ์๋ ๋ฒํผ์ด ์๋ค (10
์ฉ ์ฆ๊ฐํ๋ฉฐ, ์ด๊ธฐ๊ฐ์100
์ด๋ค)- ์์
(0, 0)
์์ ํ์ฌmaxDistance
๋ณด๋ค ๋ ๋จ์ด์ ธ ์์ง ์์ ์ ๋ค๋ง ํ์ํ๋ค - ๋ชฉ๋ก์ 100๊ฐ์ ํญ๋ชฉ๋ง ์๋ค๊ณ ๊ฐ์ ํ๋ค (๋ฐ๋ผ์ ์ต์ ํ๋ ๊ฑฑ์ ํ ํ์๊ฐ ์๋ค). ๋ง์ ์์ ํญ๋ชฉ์ ์ฒ๋ฆฌํ๋ ๊ฒฝ์ฐ์๋
useMemo
๋ก ์ผ๋ถ ๊ณ์ฐ์ ๋ฉ๋ชจ์ด์ ์ด์ ํ ์ ์๋ค.
โ ์ข์ง ์์ ์๋ฃจ์
type SortBy = 'x' | 'y'
const toggle = (current: SortBy): SortBy => current === 'x' ? : 'y' : 'x'
const Points = () => {
const [points, setPoints] = useState<{x: number, y: number}[]>([])
const [filteredPoints, setFilteredPoints] = useState<{x: number, y: number}[]>([])
const [sortedPoints, setSortedPoints] = useState<{x: number, y: number}[]>([])
const [maxDistance, setMaxDistance] = useState<number>(100)
const [sortBy, setSortBy] = useState<SortBy>('x')
useEffect(() => {
fetchPoints().then(r => setPoints(r))
}, [])
useEffect(() => {
const sorted = sortPoints(points, sortBy)
setSortedPoints(sorted)
}, [sortBy, points])
useEffect(() => {
const filtered = sortedPoints.filter(p => getDistance(p.x, p.y) < maxDistance)
setFilteredPoints(filtered)
}, [sortedPoints, maxDistance])
const otherSortBy = toggle(sortBy)
const pointToDisplay = filteredPoints.map(
p => <li key={`${p.x}|{p.y}`}>({p.x}, {p.y})</li>
)
return (
<>
<button onClick={() => setSortBy(otherSortBy)}>
Sort by: {otherSortBy}
<button>
<button onClick={() => setMaxDistance(maxDistance + 10)}>
Increase max distance
<button>
Showing only points that are less than {maxDistance} units away from origin (0, 0)
Currently sorted by: '{sortBy}' (ascending)
<ol>{pointToDisplay}</ol>
</>
)
}
โ ๋ "๋์" ์๋ฃจ์
// NOTE: You can also use useReducer instead
type SortBy = 'x' | 'y'
const toggle = (current: SortBy): SortBy => current === 'x' ? : 'y' : 'x'
const Points = () => {
const [points, setPoints] = useState<{x: number, y: number}[]>([])
const [maxDistance, setMaxDistance] = useState<number>(100)
const [sortBy, setSortBy] = useState<SortBy>('x')
useEffect(() => {
fetchPoints().then(r => setPoints(r))
}, [])
const otherSortBy = toggle(sortBy)
const filtedPoints = points.filter(p => getDistance(p.x, p.y) < maxDistance)
const pointToDisplay = sortPoints(filteredPoints, sortBy).map(
p => <li key={`${p.x}|{p.y}`}>({p.x}, {p.y})</li>
)
return (
<>
<button onClick={() => setSortBy(otherSortBy)}>
Sort by: {otherSortBy} <button>
<button onClick={() => setMaxDistance(maxDistance + 10)}>
Increase max distance
<button>
Showing only points that are less than {maxDistance} units away from origin (0, 0)
Currently sorted by: '{sortBy}' (ascending)
<ol>{pointToDisplay}</ol>
</>
)
}
๐ 2.2 ๋ฐ๋๋๋ง ์ ๋ฌํ์. ๋ฐ๋๋๋ฅผ ๋ค๊ณ ์๋ ๊ณ ๋ฆด๋ผ์ ์ ์ฒด ์ ๊ธ์ ๋ค ๊ฐ์ด ์ ๋ฌํ์ง ๋ง๊ณ
"๋ฐ๋๋๋ฅผ ์ํ๋๋ฐ ๋ฐ๋๋๋ฅผ ๋ค๊ณ ์๋ ๊ณ ๋ฆด๋ผ์ ์ ๊ธ ์ ์ฒด๋ฅผ ์ป์๋ค." โ Joe Armstrong
์ด๋ฌํ ํจ์ ์ ๋น ์ง์ง ์์ผ๋ ค๋ฉด, ๊ฐ๋ฅํ ํ ์์ ๊ฐ (boolean
, string
, number
๋ฑ) ์ props ๋ก ์ ๋ฌํ๋ ๊ฒ์ด ์ข๋ค. (React.memo
๋ก ์ต์ ํํ๊ณ ์ถ์ ๋๋ ์์ ๊ฐ์ ๋๊ธฐ๋ ๊ฒ ์ข๋ค)
์ปดํฌ๋ํธ๋ ๋ฑ ์๊ธฐ ์ผ์ ์ํํ ์ ์์ ๋งํผ๋ง ์๊ณ , ๊ทธ ์ด์์ ์์ง ๋ชปํด์ผ ํ๋ค. ์ปดํฌ๋ํธ๋ ๋๋๋ก ๋ค๋ฅธ ์ปดํฌ๋ํธ๊ฐ ๋ฌด์์ด๊ณ ๋ฌด์์ ํ๋์ง ๋ชจ๋ฅด๋ ์ฑ ํ๋ ฅํ ์ ์์ด์ผ ํ๋ค.
์ด๋ ๊ฒ ํ๋ฉด ์ปดํฌ๋ํธ๊ฐ ๋์จํ๊ฒ ๊ฒฐํฉํ์ฌ ๋ ์ปดํฌ๋ํธ ๊ฐ์ ์์กด๋๊ฐ ๋ฎ์์ง๋ค. ๋์จํ๊ฒ ๊ฒฐํฉํ ์ปดํฌ๋ํธ๋ ๋ค๋ฅธ ์ปดํฌ๋ํธ์ ์ํฅ์ ๋ฏธ์น์ง ์๊ณ ๋ณ๊ฒฝ, ๊ต์ฒด ๋๋ ์ ๊ฑฐํ๊ธฐ ์ฌ์์ง๋ค. stackoverflow:2832017
๐๐๏ธ ๋น์ฆ๋์ค ์๊ตฌ์ฌํญ / ๋ฌธ์ ์ค๋ช ๋ณด๊ธฐ
๋น์ ์ ๋ ์ปดํฌ๋ํธ Summary
์ SeeMore
๋ฅผ ํ์ํ๋ MemberCard
์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค์ด์ผ ํ๋ค.
MemberCard
๋ id
๋ฅผ prop ์ผ๋ก ๋ฐ๋๋ค. MemberCard
๋ id
๋ฅผ ๋ฐ์ ํด๋น Member
์ ๋ณด๋ฅผ ๋ฐํํ๋ useMember
ํ
์ ์ฌ์ฉํ๋ค.
type Member = {
id: string
firstName: string
lastName: string
title: string
imgUrl: string
webUrl: string
age: number
bio: string
/****** 100 more fields ******/
}
SeeMore
์ปดํฌ๋ํธ๋ member
์ age
์ bio
๋ฅผ ํ์ํด์ผ ํ๋ค. member
์ age
์ bio
๋ฅผ ํ์ํ๊ฑฐ๋ ์จ๊ธฐ๋ ํ ๊ธ ๋ฒํผ์ ํฌํจํ๋ค.
Summary
์ปดํฌ๋ํธ๋ member
์ ์ฌ์ง์ ํ์ํ๋ค. ๋ํ title
, firstName
๋ฐ lastName
์ ํ์ํ๋ค (์: Mr. Vincenzo Cassano
). member
์ ์ด๋ฆ์ ํด๋ฆญํ๋ฉด member
์ ๊ฐ์ธ ์ฌ์ดํธ๋ก ์ด๋ํ๋ค. Summary
์ปดํฌ๋ํธ์๋ ๋ค๋ฅธ ๊ธฐ๋ฅ๋ ์์ ์ ์๋ค. (์: ์ปดํฌ๋ํธ๋ฅผ ํด๋ฆญํ ๋๋ง๋ค ํฐํธ, ์ด๋ฏธ์ง ํฌ๊ธฐ ๋ฐ ๋ฐฐ๊ฒฝ์์ด ๋ฌด์์๋ก ๋ณ๊ฒฝ๋๋ค. ๊ฐ๋จํ "๋๋ค ์คํ์ผ๋ง ๊ธฐ๋ฅ" ์ด๋ผ๊ณ ๋ถ๋ฅด์)
โ ์ข์ง ์์ ์๋ฃจ์
const Summary = ({ member } : { member: Member }) => {
/*** include "the random styling feature" ***/
return (
<>
<img src={member.imgUrl} />
<a href={member.webUrl} >
{member.title}. {member.firstName} {member.lastName}
</a>
</>
)
}
const SeeMore = ({ member }: { member: Member }) => {
const [seeMore, setSeeMore] = useState<boolean>(false)
return (
<>
<button onClick={() => setSeeMore(!seeMore)}>
See {seeMore ? "less" : "more"}
</button>
{seeMore && <>AGE: {member.age} | BIO: {member.bio}</>}
</>
)
}
const MemberCard = ({ id }: { id: string })) => {
const member = useMember(id)
return <><Summary member={member} /><SeeMore member={member} /></>
}
โ ๋ "๋์" ์๋ฃจ์
const Summary = ({ imgUrl, webUrl, header }: { imgUrl: string, webUrl: string, header: string }) => {
/*** include "the random styling feature" ***/
return (
<>
<img src={imgUrl} />
<a href={webUrl}>{header}</a>
</>
)
}
const SeeMore = ({ componentToShow }: { componentToShow: ReactNode }) => {
const [seeMore, setSeeMore] = useState<boolean>(false)
return (
<>
<button onClick={() => setSeeMore(!seeMore)}>
See {seeMore ? "less" : "more"}
</button>
{seeMore && <>{componentToShow}</>}
</>
)
}
const MemberCard = ({ id }: { id: string }) => {
const { title, firstName, lastName, webUrl, imgUrl, age, bio } = useMember(id)
const header = `${title}. ${firstName} ${lastName}`
return (
<>
<Summary {...{ imgUrl, webUrl, header }} />
<SeeMore componentToShow={<>AGE: {age} | BIO: {bio}</>} />
</>
)
}
โ
๋ "๋์" ์๋ฃจ์
์์, SeeMore
์ Summary
๋ฅผ Member
์์๋ฟ๋ง ์๋๋ผ ๋ค๋ฅธ ๋ฐ์๋ ์ฌ์ฉํ ์ ์๋ค๋ ๊ฒ์ ์ฃผ๋ชฉํ์. CurrentUser
, Pet
, Post
๋ฑ ํด๋น ๊ธฐ๋ฅ์ด ํ์ํ ๋ชจ๋ ๊ฐ์ฒด์์ ์ฌ์ฉํ ์ ์๋ค.
๋จ์ผ ์ฑ ์ ์์น์ด๋?
์ปดํฌ๋ํธ๋ ๋จ ํ๋์ ์ผ๋ง ํด์ผ ํ๋ค. ๋ํ ๊ฐ๋ฅํ ๊ฐ์ฅ ์์ ๋จ์์ ์ ์๋ฏธํ(useful) ์ผ์ ํด์ผ ํ๋ค. ์ปดํฌ๋ํธ๋ ๊ทธ ๋ชฉ์ ์ ์ถฉ์กฑํ๋ ์ฑ ์๋ง์ ์ ธ์ผ ํ๋ค.
์ฌ๋ฌ ์ฑ ์์ ์ง๋ ์ปดํฌ๋ํธ๋ ์ฌ์ฌ์ฉํ๊ธฐ ์ด๋ ต๋ค. ์ผ๋ถ ๋์๋ง ์ฌ์ฌ์ฉํ๋ ค๋ ๊ฒฝ์ฐ์๋, ํ์ํ ๊ฒ๋ง ๊ฐ์ ธ์ค๋ ๊ฒ์ด ๊ฑฐ์ ๋ถ๊ฐ๋ฅํ๋ค. ๋ํ, ๋ค๋ฅธ ์ฝ๋์ ์ฎ์ฌ์๋ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค. ๋๋จธ์ง ์ ํ๋ฆฌ์ผ์ด์ ๊ณผ ๋ถ๋ฆฌ๋ ํ๋์ ์ผ๋ง ํ๋ ์ปดํฌ๋ํธ๋ ๋ถ์์ฉ ์์ด ๋ณ๊ฒฝํ ์ ์์ผ๋ฉฐ ์ค๋ณต ์์ด ์ฌ์ฌ์ฉํ ์ ์๋ค.
์ปดํฌ๋ํธ๊ฐ ๋จ์ผ ์ฑ ์์ ์ง๊ณ ์๋์ง ์ด๋ป๊ฒ ์ ์ ์์๊น?
์ปดํฌ๋ํธ๋ฅผ ํ ๋ฌธ์ฅ์ผ๋ก ์ค๋ช ํด๋ณด๋ผ. ํ ๊ฐ์ง ์ฑ ์๋ง ๊ฐ์ง๊ณ ์๋ค๋ฉด ์ฝ๊ฒ ์ค๋ช ํ ์ ์์ ๊ฒ์ด๋ค. '๊ทธ๋ฆฌ๊ณ ' ๋ '๋๋' ๊ฐ์ ๋จ์ด๋ฅผ ์ฌ์ฉํ๋ค๋ฉด ํด๋น ์ปดํฌ๋ํธ๋ ์ด ํ ์คํธ๋ฅผ ํต๊ณผํ์ง ๋ชปํ ๊ฐ๋ฅ์ฑ์ด ๋๋ค.
์ปดํฌ๋ํธ์ ์ํ, props, ํ , ๊ทธ๋ฆฌ๊ณ ์ปดํฌ๋ํธ ๋ด๋ถ์ ์ ์ธ๋ ๋ณ์ ๋ฐ ๋ฉ์๋๋ฅผ ๊ฒ์ฌํ๊ณ (๋๋ฌด ๋ง์ง ์์์ผ ํ๋ค) ์๋ฌธํ์: ์ด๋ค์ด ์ค์ ๋ก ์ปดํฌ๋ํธ์ ๋ชฉ์ ์ ๋ฌ์ฑํ๊ธฐ ์ํด ํจ๊ป ์๋ํ๋๊ฐ? ์ผ๋ถ๊ฐ ๊ทธ๋ ์ง ์๋ค๋ฉด, ์ด๋ค์ ๋ค๋ฅธ ๊ณณ์ผ๋ก ์ฎ๊ธฐ๊ฑฐ๋ ํฐ ์ปดํฌ๋ํธ๋ฅผ ๋ ์์ ๋จ์๋ก ๋ถํดํ๋ ๋ฐฉ์์ ๊ณ ๋ คํด๋ณด์.
(์ ๋จ๋ฝ์ 2015๋ ์ ์์ฑํ ํ์์ ๊ธ์ ๋ฐํ์ผ๋ก ํ๋ค: Three things I learned from Sandi Metzโs book as a non-Ruby programmer)
๐๐๏ธ ๋น์ฆ๋์ค ์๊ตฌ์ฌํญ / ๋ฌธ์ ์ค๋ช ๋ณด๊ธฐ
๋น์ ์ ํน์ ์นดํ ๊ณ ๋ฆฌ์ ์์ดํ ์ ๊ตฌ๋งคํ ์ ์๋ ํน์ํ ์ข ๋ฅ์ ๋ฒํผ์ ํ์ํด์ผ ํ๋ค. ์๋ฅผ ๋ค์ด, ์ฌ์ฉ์๋ ๊ฐ๋ฐฉ, ์์ ๋ฐ ์์์ ์ ํํ ์ ์๋ค.
- ๊ฐ ๋ฒํผ์ ์์ดํ ์ ์ ํํ๊ณ "์ ์ฅ"ํ ์ ์๋ ๋ชจ๋ฌ์ ์ฐ๋ค.
- ์ฌ์ฉ์๊ฐ ํน์ ์นดํ ๊ณ ๋ฆฌ์์ ์ ํํ ํญ๋ชฉ์ "์ ์ฅ"ํ ๊ฒฝ์ฐ, ํด๋น ์นดํ ๊ณ ๋ฆฌ๋ "์์ฝ"๋๋ค.
- ์์ฝ๋ ๊ฒฝ์ฐ, ํด๋น ๋ฒํผ์๋ ์ฒดํฌ๋งํฌ(โจ)๊ฐ ํ์๋๋ค.
- ํด๋น ์นดํ ๊ณ ๋ฆฌ๊ฐ ์์ฝ๋ ์ํ๋ผ๋ ์์ฝ์ ์์ (์์ดํ ์ ์ถ๊ฐํ๊ฑฐ๋ ์ญ์ ํจ)ํ ์ ์์ด์ผ ํ๋ค.
- ์ฌ์ฉ์๊ฐ ๋ฒํผ ์๋ฅผ hoverํ๋ฉด
WavingHand
์ปดํฌ๋ํธ๋ฅผ ํจ๊ป ํ์ํด์ผ ํ๋ค. - ํน์ ์นดํ ๊ณ ๋ฆฌ์ ์์ดํ ์ด ์์ ๋ ๋ฒํผ์ ๋นํ์ฑํํด์ผ ํ๋ค.
- ์ฌ์ฉ์๊ฐ ๋นํ์ฑํ๋ ๋ฒํผ ์๋ฅผ hoverํ๋ฉด ํดํ์ "์ฌ์ฉ ๋ถ๊ฐ๋ฅ"์ด๋ผ๋ ๋ฉ์์ง๊ฐ ํ์๋ผ์ผ ํ๋ค.
- ์นดํ ๊ณ ๋ฆฌ์ ์์ดํ ์ด ์์ผ๋ฉด ๋ฒํผ์ ๋ฐฐ๊ฒฝ์ ํ์์ด์ด์ผ ํ๋ค.
- ํด๋น ์นดํ ๊ณ ๋ฆฌ๊ฐ ์์ฝ๋ ์ํ๋ผ๋ฉด ๋ฒํผ์ ๋ฐฐ๊ฒฝ์ ์ด๋ก์์ด์ด์ผ ํ๋ค.
- ํด๋น ์นดํ ๊ณ ๋ฆฌ์ ์์ดํ ์ด ์๊ณ ์์ฝ๋์ง ์์ ์ํ๋ผ๋ฉด ๋ฒํผ์ ๋ฐฐ๊ฒฝ์ ๋นจ๊ฐ์์ด์ด์ผ ํ๋ค.
- ์นดํ ๊ณ ๋ฆฌ์ ํด๋นํ๋ ๋ฒํผ์๋ ๊ณ ์ ํ ๋ผ๋ฒจ ๋ฐ ์์ด์ฝ์ด ์์ด์ผ ํ๋ค.
โ ์ข์ง ์์ ์๋ฃจ์
type ShopCategoryTileProps = {
isBooked: boolean
icon: ReactNode
label: string
componentInsideModal?: ReactNode
items?: {name: string, quantity: number}[]
}
const ShopCategoryTile = ({
icon,
label,
items
componentInsideModal,
}: ShopCategoryTileProps ) => {
const [openDialog, setOpenDialog] = useState(false)
const [hover, setHover] = useState(false)
const disabled = !items || items.length === 0
return (
<>
<Tooltip title="Not Available" show={disabled}>
<StyledButton
className={disabled ? "grey" : isBooked ? "green" : "red" }
disabled={disabled}
onClick={() => disabled ? null : setOpenDialog(true) }
onMouseEnter={() => disabled ? null : setHover(true)}
onMouseLeave={() => disabled ? null : setHover(false)}
>
{icon}
<StyledLabel>{label}<StyledLabel/>
{!disabled && isBooked && <FaCheckCircle/>}
{!disabled && hover && <WavingHand />}
</StyledButton>
</Tooltip>
{componentInsideModal &&
<Dialog open={openDialog} onClose={() => setOpenDialog(false)}>
{componentInsideModal}
</Dialog>
}
</>
)
}
โ ๋ "๋์" ์๋ฃจ์
// split into two smaller components!
const DisabledShopCategoryTile = ({ icon, label }: { icon: ReactNode, label: string }) => {
return (
<Tooltip title="Not available">
<StyledButton disabled={true} className="grey">
{icon}
<StyledLabel>{label}<StyledLabel/>
</Button>
</Tooltip>
)
}
type ShopCategoryTileProps = {
icon: ReactNode
label: string
isBooked: boolean
componentInsideModal: ReactNode
}
const ShopCategoryTile = ({
icon,
label,
isBooked,
componentInsideModal,
}: ShopCategoryTileProps ) => {
const [openDialog, setOpenDialog] = useState(false)
const [hover, setHover] = useState(false)
return (
<>
<StyledButton
disabled={false}
className={isBooked ? "green" : "red"}
onClick={() => setOpenDialog(true) }
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
>
{icon}
<StyledLabel>{label}<StyledLabel/>
{isBooked && <FaCheckCircle/>}
{hover && <WavingHand />}
</StyledButton>
{openDialog &&
<Dialog onClose={() => setOpenDialog(false)}>
{componentInsideModal}
</Dialog>
}}
</>
)
}
์ฐธ๊ณ : ์ ์์๋ ์ค์ ํ๋ก๋์ ์์ ๋ณธ ์ปดํฌ๋ํธ๋ฅผ ๋จ์ํํ ๋ฒ์ ์ด๋ค.
โ ์ข์ง ์์ ์๋ฃจ์
const ShopCategoryTile = ({
item,
offers,
}: {
item: ItemMap;
offers?: Offer;
}) => {
const dispatch = useDispatch();
const location = useLocation();
const history = useHistory();
const { items } = useContext(OrderingFormContext)
const [openDialog, setOpenDialog] = useState(false)
const [hover, setHover] = useState(false)
const isBooked =
!item.disabled && !!items?.some((a: Item) => a.itemGroup === item.group)
const isDisabled = item.disabled || !offers
const RenderComponent = item.component
useEffect(() => {
if (openDialog && !location.pathname.includes("item")) {
setOpenDialog(false)
}
}, [location.pathname]);
const handleClose = useCallback(() => {
setOpenDialog(false)
history.goBack()
}, [])
return (
<GridStyled
xs={6}
sm={3}
md={2}
item
booked={isBooked}
disabled={isDisabled}
>
<Tooltip
title="Not available"
placement="top"
disableFocusListener={!isDisabled}
disableHoverListener={!isDisabled}
disableTouchListener={!isDisabled}
>
<PaperStyled
disabled={isDisabled}
elevation={isDisabled ? 0 : hover ? 6 : 2}
>
<Wrapper
onClick={() => {
if (isDisabled) {
return;
}
dispatch(push(ORDER__PATH));
setOpenDialog(true);
}}
disabled={isDisabled}
onMouseEnter={() => !isDisabled && setHover(true)}
onMouseLeave={() => !isDisabled && setHover(false)}
>
{item.icon}
<Typography variant="button">{item.label}</Typography>
<CheckIconWrapper>
{isBooked && <FaCheckCircle size="26" />}
</CheckIconWrapper>
</Wrapper>
</PaperStyled>
</Tooltip>
<Dialog fullScreen open={openDialog} onClose={handleClose}>
{RenderComponent && (
<RenderComponent item={item} offer={offers} onClose={handleClose} />
)}
</Dialog>
</GridStyled>
)
}
๋๋ฌด ์ด๋ฅธ/๋ถ์ ์ ํ ์ผ๋ฐํ๋ฅผ ์ง์ํ์. ๊ฐ๋จํ ๊ธฐ๋ฅ ๊ตฌํ์ ์ํด ํฐ ์ค๋ฒํค๋๊ฐ ํ์ํ๋ค๋ฉด, ๋ค๋ฅธ ์ต์ ์ ๊ณ ๋ คํ์. ๋ค์ ๊ธ์ ๊ฐ๋ ฅํ ์ถ์ฒํ๋ค: Sandi Metz: The Wrong Abstraction.
๋ณต์กํจ์ ํ ์ ํ์ ์ค๋ฒ ์์ง๋์ด๋ง(over-engineering)์ด๋ค. ๊ฐ๋ฐ์๋ค์ด ์ฝ๋๋ฅผ ํ์ ์ด์์ผ๋ก ์ผ๋ฐํ(generic)ํ๊ฑฐ๋ ํ์ฌ ์์คํ ์์ ํ์ํ์ง ์์ ๊ธฐ๋ฅ์ ์ถ๊ฐํ ๊ฒฝ์ฐ๋ค. ๊ฐ๋ฐ์๋ค์ด ๋ฏธ๋์ ํด๊ฒฐํด์ผํ ์ง๋ ๋ชจ๋ฅด๋ ๋ฌธ์ ๊ฐ ์๋๋ผ, ํ์ฌ ํด๊ฒฐํด์ผ ํ๋ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋๋ก ๊ถ์ฅํ์. ๋ฏธ๋์ ๋ฌธ์ ๋ ์ค์ ๋ก ๋น๋ฉดํด ๋ฌธ์ ์ ํํ์ ์๊ตฌ์ฌํญ์ ๊ตฌ์ฒด์ ์ผ๋ก ๋ณผ ์ ์์ ๋ ํด๊ฒฐํ๋ฉด ๋๋ค. โ Google Engineering Practices: What to look for in a code review
๋๋ณด๊ธฐ: KCD: AHA Programming, C2 Wiki: Contrived Interfaces/The Expensive Setup Smell/Premature Generalization
"๋๋ฌด ์ด๋ฅธ ์ต์ ํ๋ ๋ง์ ์ ๊ทผ์์ด๋ค." โ Tony Hoare
"ํ ๋ฒ์ ์ ํํ ์ธก์ ์ด ์ฒ ๊ฐ์ ์ ๋ฌธ๊ฐ ์๊ฒฌ๋ณด๋ค ๋ซ๋ค." โ Grace Hopper
TL;DR
- ๋๋ฆฌ๋ค๊ณ ์๊ฐํ๋ค๋ฉด, ๋ฒค์น๋งํฌ(benchmark)๋ก ์ฆ๋ช ํ์. "๋ถํ์คํ ์ํฉ์์ ์ถ์ธกํ๋ ค๋ ์ ํน์ ๊ฒฌ๋๋ผ." React Developer Tools (Chrome extension) ์ ํ๋กํ์ผ๋ฌ๊ฐ ๋น์ ์ ์น๊ตฌ๋ค!
useMemo
๋ ์ฃผ๋ก ๋น์ผ ๊ณ์ฐ์ ์ํด์๋ง ์ฌ์ฉํ์.- ๋ฆฌ๋ ๋๋ง์ ์ค์ด๊ธฐ ์ํด
React.memo
,useMemo
,useCallback
๋ฅผ ์ฌ์ฉํ ๊ฑฐ๋ผ๋ฉด ์์กด์ฑ์ด ๋ง์ง ์์์ผ ํ๋ฉฐ, ๋๋ถ๋ถ ์์ ๊ฐ์ด์ด์ผ ํ๋ค. React.memo
,useMemo
,useCallback
๊ฐ ๋น์ ์ด ์๋ํ๋ ๋๋ก ๋์ํ๋์ง ํ์ธํ์. (์ ๋ง ๋ฆฌ๋ ๋๋ง์ ๋ฐฉ์งํ๊ณ ์๋๊ฐ? ์ฌ์ฉํ๋ค๋ฉด ์๋ฏธ ์๋ ์ฑ๋ฅ ํฅ์์ด ์ด๋ค์ง ๊ฒ์ ์ค์ฆ์ ์ผ๋ก ์ ์ฆํ ์ ์๋๊ฐ? ๋ฉ๋ชจ์ด์ ์ด์ ์ด ๋๋ก๋ ์ฑ ์ฑ๋ฅ์ ๋ ์ ํ์ํฌ ์ ์์ผ๋ ์ฃผ์ํ์!)- ๋์ ๊น๋นก์ผ ๋๋ง๋ค ์ค์ค๋ก ๋๋ฆฌ๋ ์ง์ ๋ฉ์ถ์. (fix slow renders before fixing rerenders)
(์ญ: ๋๊ตฐ๊ฐ ๋์ ๊น๋นก์ผ ๋๋ง๋ค ์ค์ค๋ก๋ฅผ ๋๋ฆฌ๋ผ๊ณ ํ๋ค๋ฉด ์ด๋ป๊ฒ ํ ๊ฒ์ธ๊ฐ? ๋๋ฆฌ์ง(slow render) ์๊ธฐ ์ํด ๋์ ๋ ๊น๋นก์ด๋ ค(less re-render) ๋ ธ๋ ฅํ์ง ๋ง๊ณ , ๋๋ฆฌ๋ ๊ฑธ ๋ฉ์ถ๊ณ ๋ง์๊ป ๊น๋นก์ด์. ์ฆ, ๋ฆฌ๋ ๋๋ง์ ์ค์ด๋ ๊ฒ๋ณด๋ค ๋๋ฆฐ ๋ ๋๋ง์ ์์ ๋ ๋ฐ ์ง์คํ์๋ ๋ด์ฉ์ด๋ค) - ์ํ๋ฅผ ์ฌ์ฉํ๋ ๊ณณ์ ์ต๋ํ ๊ฐ๊น๊ฒ ๋๋ฉด ์ฝ๋๋ฅผ ์ฝ๊ธฐ ํจ์ฌ ์ฌ์์ง ๋ฟ ์๋๋ผ ์ฑ์ด ๋ ๋นจ๋ผ์ง๋ค. (state colocation) (์ญ: React ์ต์ ํํ๊ธฐ ์ฝ์ด๋ณด๊ธฐ)
Context
๋ ๋ ผ๋ฆฌ์ ์ผ๋ก ๋๋ ์ผ ํ๋ฉฐ, ํ๋์ context provider ์ ๋๋ฌด ๋ง์ ๊ฐ์ ๋ฃ์ง ๋ง์์ผ ํ๋ค. ์ปจํ ์คํธ์ ์ด๋ค ํ ๊ฐ์ด๋ผ๋ ๋ฐ๋๋ค๋ฉด, ์ค์ ๋ก ๋ฐ๋ ๊ฐ์ ์ฌ์ฉํ์ง ์๋๋ผ๋ ํด๋น ์ปจํ ์คํธ๋ฅผ ์๋นํ๋ ๋ชจ๋ ์ปดํฌ๋ํธ๊ฐ ๋ฆฌ๋ ๋๋ง ๋๊ธฐ ๋๋ฌธ์ด๋ค.state
์dispatch
ํจ์๋ฅผ ๋ถ๋ฆฌํดcontext
๋ฅผ ์ต์ ํํ ์ ์๋ค.lazy loading
๊ณผbundle/code splitting
์ ์ดํดํ์.- ๊ฑฐ๋ํ ๋ฆฌ์คํธ๋ ์๋์(window) ํ์. (
tannerlinsley/react-virtual
๋ ๋น์ทํ ๋๊ตฌ๋ฅผ ์ฌ์ฉํ์) - ๋๊ฐ ๋ฒ๋ค ํฌ๊ธฐ๊ฐ ์์์๋ก ์ฑ์ด ๋นจ๋ผ์ง๋ค. ์์ฑํ ์ฝ๋ ๋ฒ๋ค์
source-map-explorer
๋๋@next/bundle-analyzer
(NextJS ์ฌ์ฉ์) ๊ฐ์ ๋๊ตฌ๋ฅผ ์ฌ์ฉํด ์๊ฐํํ ์ ์๋ค. - ํผ(form) ํจํค์ง๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ๊ฐ์ธ์ ์ผ๋ก
react-hook-forms
๋ฅผ ์ถ์ฒํ๋ค. ์ข์ ์ฑ๋ฅ๊ณผ ์ข์ ๊ฐ๋ฐ์ ๊ฒฝํ์ ๊ท ํ์ ์ ๋ง์ท๋ค๊ณ ์๊ฐํ๋ค.
์ฑ๋ฅ์ ๋ํด ์ฝ์ด๋ณด๋ฉด ์ข์ KCD ๊ธ๋ค
- KCD: State Colocation will make your React app faster
- KCD: When to
useMemo
anduseCallback
- KCD: Fix the slow render before you fix the re-render
- KCD: Profile a React App for Performance
- KCD: How to optimize your context value
- KCD: How to use React Context effectively
- KCD: One React Mistake that is slowing you down
- KCD: One simple trick to optimize React re-renders
"ํ ์คํธ๋ฅผ ์์ฑํด๋ผ. ๋๋ฌด ๋ง์ด๋ ๋ง๊ณ . ๋๋ถ๋ถ ํตํฉ(integration)์ผ๋ก." โ Guillermo Rauch
TL;DR
- ํ ์คํธ๋ ํญ์ ์ํํธ์จ์ด๊ฐ ์ค์ ์ฌ์ฉ๋๋ ๋ชจ์ต์ ๋ฎ์์ผ ํ๋ค.
- ๊ตฌํ์ ์ธ ์ธ๋ถ ์ฌํญ์ ํ ์คํธํ์ง ์๋๋ก ์ ์ํ์ - ์ฌ์ฉ์๋ ์ฌ์ฉํ์ง๋, ๋ณด์ง๋, ์์ง๋ ๋ชปํ ๊ฒ๋ค์ด๋ค
- ์๋ฌด๊ฒ๋ ๋ง๊ฐ๋จ๋ฆฌ์ง ์์๋ค๋ ํ์ ์ด ํ ์คํธ๋ฅผ ํด๋ ๋ค์ง ์๋๋ค๋ฉด, ํ ์คํธ์ (๋จ ํ๋๋ฟ์ธ) ์์์ ๋คํ์ง ๋ชปํ ๊ฒ์ด๋ค.
- ์ฝ๋๋ฅผ ๋ฆฌํฉํ ๋งํ ํ ๋์ผํ ์ฌ์ฉ์ ๋์์ ๋ํด ํ ์คํธ๋ฅผ ๊ฑฐ์ ๋ณ๊ฒฝํ ํ์๊ฐ ์๋ค๋ฉด ์ฌ๋ฐ๋ฅธ ํ ์คํธ๋ฅผ ๊ตฌํํ ๊ฒ์ด๋ค.
- ํ๋ก ํธ์๋๋ 100% ํ ์คํธ ์ปค๋ฒ๋ฆฌ์ง๋ฅผ ๋ฌ์ฑํ ํ์๋ ์๋ค. 70% ์ ๋๋ฉด ์ถฉ๋ถํ ๊ฒ์ด๋ค. ํ ์คํธ๋ ์์ฐ์ฑ์ ๋์ฌ์ผ์ง, ์์ ์ ๋ฆ์ถฐ์ ์ ๋๋ค. ํ ์คํธ ์ ์ง ๊ด๋ฆฌ๋ ์์ ์๋๋ฅผ ๋ฆ์ถ ์ ์๋ค. ํน์ ์์ ์ดํ์ ํ ์คํธ๋ฅผ ์ถ๊ฐํ์ฌ ์ป๋ ์ด์ ์ด ๊ธ์๋๋ก ์ค์ด๋ค ๊ฒ์ด๋ค.
- ๊ฐ์ธ์ ์ผ๋ก Jest, React testing library, Cypress, ๊ทธ๋ฆฌ๊ณ Mock service worker ๋ฅผ ์ข์ํ๋ค.
ํ ์คํธ์ ๋ํด ์ฝ์ด๋ณด๋ฉด ์ข์ KCD ๊ธ๋ค
- KCD: Testing Implementation Details
- KCD: Stop mocking fetch
- KCD: Common mistakes with React Testing Library
- KCD: Making your UI tests resilient to change
- KCD: Write fewer, longer tests
- KCD: Write tests. Not too many. Mostly integration.
- KCD: How to know what to test
- KCD: Common Testing Mistakes
- KCD: Why you've been bad about testing
- KCD: Demystifying Testing
- KCD: UI Testing Myths
- KCD: Effective Snapshot Testing
If you'd like to share some of the things you think about when you write React code that I didn't touch upon, you can submit a PR and add them to this section. Thanks for taking the time to share your ideas!
๋น์ทํ์ง๋ง ๊ฐ์ธ์ ์ผ๋ก ๋ ๊ฐ๋ ฅํ๋ค๊ณ ๋๋ผ๋ ์์น์ ์ง๋์น๊ฒ ๊ฐ๋ ฅํ ๊ถํ์ ๊ฐ์ง setters ๋ฅผ ์ ๋ฌํ์ง ์๋ ๊ฒ์ด๋ค. ์๋ฅผ ๋ค์ด,
setUser
ํจ์ ๋์updateEmail
ํจ์๋ฅผ ์ ๋ฌํ๋ ๊ฒ์ด ์ฌ๋ฐ๋ฅด๋ค๊ณ ์๊ฐํ๋ค. ์ปดํฌ๋ํธ๊ฐ ๋ณ๊ฒฝํ๋ฉด ์ ๋๋ ๊ฒ์ ๋ณ๊ฒฝํ๋ ๊ฑธ ๋ฐฉ์งํ๊ธฐ ์ํด์๋ค.
์ปดํฌ๋ํธ๋ ๋จ์ผ ์ฑ ์์ ์ ธ์ผ ํ ๋ฟ๋ง ์๋๋ผ, ์ถ์ํ ์คํํธ๋ผ์์ ์๊ธฐ ์์น๊ฐ ์ด๋์ธ์ง ํ์คํ ํด์ผ ํ๋ค. ํ์ชฝ ๋์
<Button>
,<Heading>
,<Modal>
๊ฐ์ ์ผ๋ฐ์ ์ธ ๊ตฌ์ฑ ์์๊ฐ ์๊ณ , ๋ค๋ฅธ ์ชฝ ๋์๋<Homepage>
๋<ContactForm>
๊ฐ์ ์ผํ์ฑ ํ ํ๋ฆฟ์ด ์๋ค. ๋ชจ๋ ์ปดํฌ๋ํธ๋ ์ด ์คํํธ๋ผ์์ ๋ช ํํ ์์น์ ์์ด์ผ ํ๋ค.<Button>
์ปดํฌ๋ํธ๋ user prop ์ ๊ฐ์ง๋ฉด ์ ๋๋ค. ์ฌ์ฉ์๋ ๋ ๋์ ์ถ์ํ ๊ฐ๋ ์ด๊ณ ,Button
์ ๋ฎ์ ์ถ์ํ ์ปดํฌ๋ํธ์ด๊ธฐ ๋๋ฌธ์ด๋ค.
(Note, the above is taken from his response to my email... I really appreciate that he took the time to share his ideas ๐)