Hook์ React 16.8 ๋ฒ์ ๋ถํฐ ์๋กญ๊ฒ ์ถ๊ฐ๋ ์ํ ๋ฐ ์๋ช ์ฃผ๊ธฐ ๊ด๋ฆฌ API์ด๋ค. Hook์ ์ฌ์ฉํ๋ฉด ํด๋์ค ์ปดํฌ๋ํธ์ ์ฌ์ฉ ์์ด ํจ์ํ ์ปดํฌ๋ํธ ๋ด๋ถ์์ ์ํ ์ ์ฅ์ด ๊ฐ๋ฅํ๋ฉฐ ์๋ช ์ฃผ๊ธฐ ๊ด๋ฆฌ๋ฅผ ํ ์ ์๋ค.
useState๋ ์ํ ๊ด๋ฆฌ๋ฅผ ์ํ hook์ด๋ค.
const [<์ํ ์ ์ฅ ๋ณ์>, <์ํ ๊ฐฑ์ ํจ์>] = useState(<์ํ ์ด๊ธฐ ๊ฐ>);
์ด๊ธฐ state ์์ฑ์ ์ํ ์ ์ฅ ๋ณ์์ ์ด๊ธฐ ๊ฐ์ array, number, boolean ๋ฑ ๋ค์ํ ํ์ ๋ฐ ๊ฐ์ผ๋ก ์ง์ ํ ์ ์๋ค. ๋ํ useState๋ ๊ฐ์ด ์๋ ํจ์๋ฅผ ์ธ์๋ก ๊ฐ์ง์ ์๋๋ฐ ์ด ๊ฒฝ์ฐ, ์ฒซ ๋ ๋๋ง๋ ๋๋ง ์คํ๋๋ค.
const [count, setCount] = useState(0)
setCount(prevCount => prevCount + 1)
์ด๊ธฐ state ์์ฑ์ ์ ์ธํ ์ํ ๊ฐฑ์ ํจ์๋ฅผ ํตํด ์ํ ์ ์ฅ ๋ณ์์ ๊ฐ์ ๊ฐฑ์ ํ ์ ์๋ค.
useState hook๊ณผ ์ํ ์ ์ฅ ๋ณ์ count๋ฅผ ์ฌ์ฉํ ์นด์ดํฐ ์์
function Counter() {
const [count, setCount] = useState(0) // creating initial state
function changeCount(amount) {
setCount(prevCount => prevCount + amount)
}
function resetCount() {
setCount(0)
}
return (
<>
<span>{count}</span>
<button onClick={() => changeCount(1)}>+</button>
<button onClick={() => changeCount(-1)}>-</button>
<button onClick={() => resetCount()}>Reset</button>
</>
)
}
useEffect(function, deps)
useEffect๋ ํด๋์ค ์ปดํฌ๋ํธ์ ๋ผ์ดํ์ฌ์ดํด ๋ฉ์๋๋ฅผ ๋์ฒดํ๋ฉฐ ์ปดํฌ๋ํธ๊ฐ ๋ ๋๋ง์ ํน์ ๋ ๋๋ง ์ดํ ํน์ ์์ ์ ์ํํ๋๋กํ๋ hook์ด๋ค.
๋คํธ์ํฌ ์์ฒญ, ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ, ๊ตฌ๋ ์ค์ , ์๋์ผ๋ก ์ปดํฌ๋ํธ์ DOM์ ์์ ํ๋ ๋ฑ ํ์ฌ ํจ์์ ๋ฒ์์ ๋ฒ์ด๋ ๊ฒ์ ์ํฅ์ ๋ผ์น๋ ๊ฒ์ side effect๋ผ๊ณ ํ๋ค. useEffect๋ ์ด๋ฌํ side effect ์ฒ๋ฆฌ๋ฅผ ์ํด ์ฌ์ฉ๋๋ค.
useEffect๋ฅผ ์ฌ์ฉํ๋ ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ๋ฐฉ๋ฒ์ ๋จ์ผ ํจ์(single function)๋ฅผ ์ ๋ฌํ๋ ๊ฒ์ด๋ค.
useEffect(() => {
console.log('This is a side effect')
})
์ด ๊ฒฝ์ฐ ์ปดํฌ๋ํธ๊ฐ ์ฒ์ ๋ง์ดํธ๋ ๋, props๊ฐ ๋ณ๊ฒฝ ๋ ๋, ์ํ๊ฐ ๋ณ๊ฒฝ ๋ ๋ ๋ฑ ๋ ๋๋ง ๋ ๋๋ง๋ค ์คํ๋๋ค. ๊ทธ๋ฌ๋ ๋ง์ดํธ ๋จ๊ณ์์๋ง side effect๊ฐ ํ์ํ๊ฑฐ๋ ํน์ props๋ ์ํ๊ฐ ๋ณ๊ฒฝ๋๋ ๊ฒฝ์ฐ์๋ ์ด์์ ์ด์ง ์๋ค.
๋๋ฒ์งธ ๋งค๊ฐ๋ณ์์ธ ์์กด์ฑ ๋ฐฐ์ด์ ์ฌ์ฉํ๋ฉด ์ด์ ๋ ๋๋ง์ ์์กด์ฑ๊ณผ ๋น๊ตํด ๋ง์ง๋ง ๋ ๋๋ง ์ดํ ์์กด์ฑ์ด ๋ณ๊ฒฝ๋ ๊ฒฝ์ฐ side effect๊ฐ ์คํ๋๋ค. ๋ง์ดํธ ๋จ๊ณ์์๋ง ์คํํ๊ณ ์ถ๋ค๋ฉด ๋น ๋ฐฐ์ด์ ๋๋ฒ์งธ ๋งค๊ฐ ๋ณ์๋ก ์ ๋ฌํ๋ฉด ๋๋ค.
useEffect(() => {
console.log('Only run on mount')
}, [])
useEffect(() => {
console.log('Only run on url change')
}, [url])
์ธ๋ง์ดํธ ์ด์ , ์ ๋ฐ์ดํธ ์ง์ ์ side effect๊ฐ ํ์ํ๋ค๋ฉด clean up ํจ์๋ฅผ ๋ฐํํด์ผํ๋ค.
useEffect(() => {
console.log('This is my side effect')
return () => {
console.log('This is my clean up')
}
})
์์ ๊ฐ์ useEffect๋ฅผ ํฌํจํ ์ปดํฌ๋ํธ๊ฐ ๋ง์ดํธ ํ ๋ฆฌ๋ ๋๋ง 2ํ ๊ทธ๋ฆฌ๊ณ ์ธ๋ง์ดํธ ๋ ๊ฒฝ์ฐ ์๋์ ๊ฐ์ด ์ฝ์ ์ถ๋ ฅํ๊ฒ ๋๋ค.
// MOUNTED
// This is my side effect
// RE-RENDER 1:
// This is my clean up
// This is my side effect
// RE-RENDER 2:
// This is my clean up
// This is my side effect
// UN-MOUNT:
// This is my clean up
useMemo์ useCallback hook์ ์ดํดํ๊ธฐ ์ํด์๋ memoization์ ๋ํด ์์์ผํ๋ค.
Memoization์ ๋ณธ์ง์ ์ผ๋ก ์บ์ฑ๊ณผ ๊ฐ์ ์๋ฏธ์ด๋ค. ๋์ผํ ๊ณ์ฐ์ ๋ฐ๋ณตํ๋ ํจ์๋ฅผ ์คํํ ๋ ๊ฐ์ ๋ค์ ๊ณ์ฐํ๋ ๋์ ์บ์๋ ๊ฐ์ ์ฌ์ฉํด ์ฒ๋ฆฌ ์๋๋ฅผ ๋น ๋ฅด๊ฒ ํฅ์์ํค๋ ๊ฒ์ด memoization์ด๋ค.
const cache = {}
function slow(a) {
if (cache[a]) return cache[a]
const result = /* Complex logic */
cache[a] = result
return result
}
๋ ๋๋ง ๋ ๋๋ง๋ค ๋ชจ๋ ์ปดํฌ๋ํธ ๋ก์ง์ด ์ฌ๊ณ์ฐ๋๊ณ ๋ก์ง์ ๊ณ์ฐ์๋๊ฐ ๋๋ฆฐ ๊ฒฝ์ฐ ๊ธ๊ฒฉํ ์๋์ ํ๊ฐ ์ผ์ด๋๋ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํ hook์ด ๋ฐ๋ก useMemo์ useCallback์ด๋ค.
useMemo๋ useEffect์ ๋น์ทํ ๋ฐฉ์์ผ๋ก ๋์ํ๋ฉฐ ๋๋ฒ์งธ ์ธ์์ธ ์์กด์ฑ์ด ๋ณ๊ฒฝ๋์์ ๋๋ง memoization๋ ๊ฐ๋ง ๋ค์ ๊ณ์ฐํ๋ค. ์ฆ, useMemo๋ memoization๋ ๊ฐ์ ๋ฐํํ๋ค.
const result = useMemo(() => {
return slowFunction(a)
}, [a])
useMemo๋ก ์ ๋ฌ๋ ํจ์๋ ๋ ๋๋ง ์ค์ ์คํ๋๋ค. ๋๋ฒ์งธ ์ธ์๊ฐ ์์ ๊ฒฝ์ฐ ๋ ๋๋ง ๋ ๋๋ง๋ค ์ ๊ฐ์ ๊ณ์ฐํ๋ค.
useCallback์ ์์กด์ฑ ๋ฐฐ์ด์ ๊ธฐ๋ฐ์ผ๋ก ๊ฒฐ๊ณผ๋ฅผ ์บ์ํ๊ธฐ ๋๋ฌธ์ useMemo์ ๋์ผํ๊ฒ ์๋ํ๋ค. ๊ทธ๋ฌ๋ useCallback์ ์บ์ฑ๋ ๊ฐ์ด ์๋ ์บ์ฑ๋ ํจ์๋ฅผ ์ํด ์ฌ์ฉ๋๋ค. ์ฆ, useCallback์ memoization๋ ์ฝ๋ฐฑ์ ๋ฐํํ๋ค.
const handleReset = useCallback(() => {
return doSomething(a, b)
}, [a, b])
๊ตฌ๋ฌธ์์ useMemo์ ๋์ผํ์ง๋ง useMemo๋ ์์กด์ฑ์ด ๋ณ๊ฒฝ๋ ๋๋ง๋ค ์ ๋ฌ๋ ํจ์๋ฅผ ํธ์ถํ ํ ํธ์ถ์ ๋ํ ๊ฐ์ ๋ฐํํ๋ค. ๋ฐ๋ฉด์ useCallback์ ์์กด์ฑ์ด ๋ณ๊ฒฝ ๋ ๋๋ง๋ค ํจ์๋ฅผ ํธ์ถํ์ง์๊ณ ํจ์์ ์๋ก์ด ๋ฒ์ ์ ๋ฐํํ๋ค.
useCallback(() => {
return a + b
}, [a, b])
useMemo(() => {
return () => a + b
}, [a, b])
์ ์ฝ๋์์ useCallback๊ณผ useMemo๋ ๋์ผํ ๊ฐ์ ๋ฐํํ์ง๋ง, useCallback์ ์ ๋ฌ๋ ํจ์๋ฅผ ๋ฐํํ๊ณ useMemo๋ ์ ๋ฌ๋ ํจ์์ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํ๋ค.
๋ํ useMemo์ useCallback์ referential equality๋ฅผ ์ ์งํ๊ธฐ์ํด ์ฌ์ฉ๋๋ค.
function Parent() {
const [items, setItems] = useState([])
const handleLoad = (res) => setItems(res)
return <Child onLoad={handleLoad} />
}
function Child({ onLoad }) {
useEffect(() => {
callApi(onLoad)
}, [onLoad])
}
์ ์์ ์์ handleLoad
ํจ์๋ Parent
์ปดํฌ๋ํธ๊ฐ ๋ฆฌ๋ ๋๋ง ๋ ๋๋ง๋ค re-create๋๋ค. ์ด๊ฒ์ onLoad
ํจ์๊ฐ ๋งค ๋ ๋๋ง๋ง๋ค ๋ค๋ฅธ referential equality๋ฅผ ๊ฐ์ง๊ณ ์๊ธฐ ๋๋ฌธ์ Child
์ปดํฌ๋ํธ์ useEffect๊ฐ ์ฌ์คํ๋๋ค๋ ๊ฒ์ ์๋ฏธํ๋ค.
function Parent() {
const [items, setItems] = useState([])
const handleLoad = useCallback((res) => setItems(res), [])
return <Child onLoad={handleLoad} />
}
function Child({ onLoad }) {
useEffect(() => {
callApi(onLoad)
}, [onLoad])
}
handleLoad
์ useCallback๋ฅผ ์ฌ์ฉํจ์ผ๋ก์จ handleLoad
ํจ์๋ ๋ณ๊ฒฝ๋์ง ์๊ณ , Child
์ปดํฌ๋ํธ์ useEffect๋ ๋ ๋๋ง ๋ ๋๋ง๋ค ํธ์ถ๋์ง ์๋๋ค.
useRef๋ DOM ์์์ ์ง์ ์ ๊ทผํ๊ณ ์กฐ์ํ๋๋ฐ ์ ์ฉํ hook์ด๋ค. ๋ํ ๋ ๋๋ง๊ฐ ๋ฐ์ดํฐ๋ฅผ ์ ์งํ ์ ์๋ค.
useRef๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์ ๋จผ์ ref๋ฅผ ์ด๊ธฐํํด์ผํ๋ค.
useRef(initialValue)
useRef๋ current๋ผ๋ ๋จ์ผ ํ๋กํผํฐ๋ฅผ ๊ฐ์ง๊ณ ์๋ ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋๋ฐ ์ด๊ธฐ๊ฐ์ current ํ๋กํผํฐ์ ํ ๋นํ๋ค.
const myRef = useRef(0);
console.log(myRef);
// { current: 0 }
useRef๋ ์ ์ธํ ๋ค์ ๋ ๋๋ง์ด ๋์ด๋ ๋์ผํ ์ฐธ์กฐ๊ฐ ์ง์๋๋ค. ๋ํ ์ฐธ์กฐ๊ฐ ๋ณ๊ฒฝ๋์ด๋ ์ปดํฌ๋ํธ๋ฅผ ๋ค์ ๋ ๋๋งํ์ง ์๋๋ค. ์ฆ, ref๋ ๋ ๋๋ง๊ฐ์ ์ง์๋๋ ๊ฐ์ ์ ์ฅํ๋ ๊ฐ์ฒด์ด๋ค.
function State() {
const [rerenderCount, setRerenderCount] = useState(0);
useEffect(() => {
setRerenderCount(prevCount => prevCount + 1);
});
return <div>{rerenderCount}</div>;
}
function Ref() {
const rerenderCount = useRef(0);
useEffect(() => {
rerenderCount.current = rerenderCount.current + 1;
});
return <div>{rerenderCount.current}</div>;
}
State
์ปดํฌ๋ํธ์์๋ state๊ฐ ์
๋ฐ์ดํธ๋๋ฉด ์ปดํฌ๋ํธ๊ฐ ๋ค์ ๋ ๋๋ง๋์ง๋ง Ref
์ปดํฌ๋ํธ์ ref๋ ๊ฐ์ด ๋ณ๊ฒฝ๋์ด๋ ๋ค์ ๋ ๋๋ง๋์ง์๋๋ค.
ref๋ ์ผ๋ฐ์ ์ผ๋ก DOM ์์๋ฅผ ์ฐธ์กฐํ ๋ ์ฌ์ฉ๋๋ค. ์๋ฅผ ๋ค์ด ๋ฒํผ์ ํด๋ฆญํ ๋๋ง๋ค input
์์์ ์ปค์๋ฅผ ์ด๋ํ๊ณ ์ถ๋ค๋ฉด ref๋ฅผ ์ฌ์ฉํด ์๋์ ๊ฐ์ ์ฝ๋๋ฅผ ์์ฑํ ์ ์๋ค.
function Component() {
const inputRef = useRef(null)
const focusInput = () => {
inputRef.current.focus()
}
return (
<>
<input ref={inputRef} />
<button onClick={focusInput}>Focus Input</button>
</>
)
}
ref๋ ๋ ๋๋ง๊ฐ ์ผ์ข ์ ์ ์ฅ๊ณต๊ฐ์ผ๋ก ์ฌ์ฉํ ์ ์๋ค.
unction Component() {
const [name, setName] = useState('Kyle')
const previousName = useRef(null)
useEffect(() => {
previousName.current = name
}, [name])
return (
<>
<input value={name} onChange={e => setName(e.target.value)} />
<div>{previousName.current} => {name}</div>
</>
)
}
์ย ์์ ๋ ์ํ ๋ณ์ name
์ดย ๋ณ๊ฒฝ๋ ๋ ๋ง๋ค ref๋ฅผ ์
๋ฐ์ดํธ ํด ์ํ ๋ณ์ name
์ย ์ด์ ๊ฐ์ ์ ์ฅํ๋๋ก ํ๋ ์ฝ๋์ด๋ค.
๋ฆฌ์กํธ์์ state๋ ๋ฐ์ดํฐ์ props๋ฅผ ์ ์ฅํ๊ณ ์ปดํฌ๋ํธ๋ค๊ฐ์ ๋ฐ์ดํฐ ์ ๋ฌํ๊ธฐ ์ํด ์ฌ์ฉ๋๋ค. ๊ทธ๋ฌ๋ ๋ง์ ์ปดํฌ๋ํธ๋ค์ด ์ค์ฒฉ๋ ๊ตฌ์กฐ์์ state๋ฅผ ์ ๋ฌํ๋ ค๋ฉด ๋ณต์กํ ๋จ๊ณ๋ฅผ ์ฌ๋ฌ๋ฒ ๊ฑฐ์ณ์ผํด ์ ์ง๋ณด์์ ์ด๋ ค์์ด ์๋ค.
Context API๋ฅผ ์ฌ์ฉํ๋ฉด ์ปดํฌ๋ํธ๋ฅผ ํตํด ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ ํ์์์ด Context ๋ด ์ค์ฒฉ๋ ๋ชจ๋ ์ปดํฌ๋ํธ์์ ์ฌ์ฉํ ์ ์๋ ๋ฐ์ดํฐ๋ฅผ ์ง์ ํ ์ ์๋ค. ์ด ๋ฐ์ดํฐ๋ Context ๋ด๋ถ ์ด๋์์๋ ์ฌ์ฉํ ์ ์๋ semi-global state์ธ ๊ฒ์ด๋ค.
const ThemeContext = React.createContext()
function App() {
const [theme, setTheme] = useState('dark')
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<ChildComponent />
</ThemeContext.Provider>
)
}
function ChildComponent() {
return <GrandChildComponent />
}
class GrandChildComponent {
render() {
return (
<ThemeContext.Consumer>
{({ theme, setTheme }) => {
return (
<>
<div>The theme is {theme}</div>
<button onClick={() => setTheme('light')}>
Change To Light Theme
</button>
</>
)
}}
</ThemeContext.Consumer>
)
}
}
์ ์ฝ๋์์ React.createContext
๋ฅผ ์ฌ์ฉํด ๋ ๋ถ๋ถ์ ๊ฐ์ง ๋ณ์๋ฅผ ์์ฑํ๋ค.
์ฒซ๋ฒ์งธ ๋ถ๋ถ์ ์ค์ฒฉ๋ ๋ชจ๋ ์ปดํฌ๋ํธ๋ค์ ๊ฐ์ ์ ๊ณตํ๋ provider์ด๋ค. ์ ์ฝ๋์ ๊ฒฝ์ฐ theme
๋ฐ setTheme
์ด ์๋ ๋จ์ผ ๊ฐ์ฒด์ด๋ค.
๋๋ฒ์งธ ๋ถ๋ถ์ consumer์ด๋ค. consumer์์ Context์ ๊ฐ์ ์ก์ธ์คํ๋ ค๋ฉด ์ฝ๋๋ฅผ ๋ํํด์ผํ๋ค. ๋ํ๋ ์ปดํฌ๋ํธ ์์์์์ธ ํจ์์ ์ธ์๋ก Context ๊ฐ์ ์ ๊ณตํ๋ค.
๊ทธ๋ฌ๋ ๋๋ฒ์งธ ๋ถ๋ถ์์ Context์ ๊ฐ์ ์ป๊ธฐ์ํด ํจ์๋ฅผ ํฌํจํ ์ปดํฌ๋ํธ์ JSX๋ฅผ ๋ํ์ํจ์ผ๋ก์จ ์ฝ๋ ์ค์ฒฉ ๋ฐ ๋ณต์กํ ๋ ์ด์ด๋ค์ด ์ถ๊ฐ๋๋ค๋ ๋ฌธ์ ์ ์ด ์๋ค.
useContext๋ฅผ ์ฌ์ฉํ๋ฉด ํจ์ํ ์ปดํฌ๋ํธ์์ Context๋ฅผ ์ฌ์ฉํ๊ธฐ์ํด consumer์์ JSX ์ฝ๋๋ฅผ ๋ํํ ํ์ ์์ด Context๋ฅผ useContext hook์ ์ ๋ฌํ๋ค.
function GrandChildComponent() {
const { theme, setTheme } = useContext(ThemeContext)
return (
<>
<div>The theme is {theme}</div>
<button onClick={() => setTheme('light')}>
Change To Light Theme
</button>
</>
)
}
useContext๋ฅผ ์ฌ์ฉํจ์ผ๋ก์จ ๊ธฐ์กด consumer ๋ถ๋ถ์ ๋ณต์กํ ์ค์ฒฉ์ ์ ๊ฑฐํ ์ ์๋ค. ์ด์ Context๋ Context๋ฅผ ํธ์ถํ๋ ์ผ๋ฐ ํจ์์ฒ๋ผ ๋์ํ๋ฉฐ Context ๋ด๋ถ์ ๊ฐ์ ์ ๊ณตํ๋ค. useContext์ provider๋ฅผ ์ค์ ํ๋ ๊ฒ์ ๊ธฐ์กด Context API์์์ ์์ ํ ๋์ผํ๋ค.
Reference