๐ ์ด๋ฏธ์ง : React hooks best practices in 2021
React๋ฅผ ์ฌ์ฉํ๋ค ๋ณด๋ฉด ํ ! ์ด๋ผ๋ ๊ฒ์ ์์ฃผ ์ ํ๊ฒ ๋๋ค.
๋ฆฌ์กํธ v16.8์ ์๋ก ๋์ ๋ ๊ธฐ๋ฅ์ผ๋ก ๊ธฐ์กด์ ํจ์ ์ปดํฌ๋ํธ์์ ํ ์ ์์๋ ๋ค์ํ ์์ ์ ํ ์ ์๊ฒ ํด ์ค๋ค.
๐ React๋ฅผ ๋ค๋ฃจ๋ ๊ธฐ์ ์ฑ ๋ด์ฉ
๋ฌธ์ฅ๋ง ๋ด์๋ ๋์ ํ ํ ์ ๋ํด ๊ฐ์ด ์์ค๋๋ฐ, ์ฌ์ค ๋ฆฌ์กํธ๋ฅผ ๋ง ์ฒ์ ๋ฐฐ์ฐ๋ฉด์ ๊ฐ์ ๋ง์ง๋ ์์ ์ ํ๋๋ฐ ๊ทธ๋ ์ด๋ฏธ ํ ์ ์ฌ์ฉํ๊ณ ์์๋ค๋ ์ฌ์ค...!
์๋์์๋ ๋ค๋ฃจ๊ฒ ์ง๋ง ๋ฐ๋ก useState๋ผ๋ ํ ์ด๋ค.
์ฐ์ ํ
์ ์ฌ์ฉํ๊ธฐ ์ํด์๋ ์๋จ์ ์ฌ์ฉํ ํ
์ importํด์ผํ๋ค.
์๋ฅผ ๋ค์ด useState๋ผ๋ ํ
์ ์ฌ์ฉํ๊ณ ์ถ๋ค! ๊ทธ๋ฌ๋ฉด ์๋์ ๊ฐ์ด ์์ฑํด ์ฃผ๋ฉด ๋๋ค.
import { useState } from 'react';
์ด๋ฐ์์ผ๋ก ์ฐ์ ์ฌ์ฉํ ํ
์ import ํ ์์ ๋กญ๊ฒ ์ฌ์ฉํ๋ฉด ๋!
์ด๋ฒ ๊ธ์์๋ ๊ฐ ํ
์ด ๋ฌด์์ธ์ง ์ด๋ค ๋์ ์ฌ์ฉ๋๋์ง ์ ๋ฆฌํด ๋ณด์๋ค.
import { useState } from 'react';
const Counter = () => {
const [value, setValue] = useState(0);
return (
<div>
<p>ํ์ฌ ์นด์ดํฐ ๊ฐ์ <b>{value}</b> ์
๋๋ค.</p>
<button onClick={()=> setValue(value + 1)}> +1 ํ๊ธฐ</button>
<button onClick={()=> setValue(value - 1)}> -1 ํ๊ธฐ</button>
</div>
)
}
๊ฐ๋จํ ์นด์ดํฐ๋ฅผ useState ํ ์ ์ฌ์ฉํด์ ๋ง๋ ์ฝ๋๋ค.
const [value, setValue] = useState(0);
value๋ ์ํ ๊ฐ์ด๊ณ setValue๋ ์ํ๋ฅผ ์ค์ ํ๋ค.
useState์ ()์์๋ ์ด๊ธฐ๊ฐ์ ์ง์ ํ ์ ์๋๋ฐ, ๋ณดํต ๋ฌธ์์ด๋ฉด ('')์ผ๋ก ํํํ๊ณ ์ซ์๋ฉด (0)์ ์ด๊ธฐ๊ฐ์ผ๋ก ์
ํ
ํด ๋๋ค.
์ด ํจ์์ ํ๋ผ๋ฏธํฐ๋ฅผ ๋ฃ์ด ํธ์ถํ๋ฉด ์ ๋ฌ๋ฐ์ ํ๋ผ๋ฏธํฐ๋ก ๊ฐ์ด ๋ฐ๋๊ณ ์ปดํฌ๋ํธ๊ฐ ๋ฆฌ๋ ๋๋ง ๋๋ค.
useState๋ฅผ ์ฌ์ฉํ๋ฉด ๊ฐ์ด ๋ฐ๋ ๋๋ง๋ค ์ปดํฌ๋ํธ๊ฐ ๋ฆฌ๋ ๋๋ง์ด ๋๋ค.
ํ์ง๋ง ๋ง์ฝ ๋ถํ์ํ ๋ฆฌ๋ ๋๋ง์ ๋ฐฉ์งํ๋ค๋ฉด ์กฐ๊ธ์ ์ฑ๋ฅ์ ํฅ์ ์ํฌ ์ ์์ง ์์๊น?
๊ทธ๋์ ๋ฐ๋ก useEffect ๋ผ๋ ํ
์ ์ฌ์ฉํ๋ฉด ๋ถํ์ํ ๋ฆฌ๋ ๋๋ง์ ์กฐ๊ธ์ ๋ฐฉ์งํ ์ ์๋ค!
import { useState, useEffect } from "react";
import ReactDOM from "react-dom/client";
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
setTimeout(() => {
setCount((count) => count + 1);
}, 1000);
});
return <h1>I've rendered {count} times!</h1>;
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Timer />);
์ฐ์ ์์ ๊ฐ์ ํ์ด๋จธ๊ฐ ์๋ค๊ณ ํ์.
์คํ์ ์์ผ๋ณด๋ฉด 1์ด๋ง๋ค count๊ฐ 1์ฉ ์ฆ๊ฐํ๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
์ฆ, ๊ณ์ ๋ฆฌ๋ ๋๋ง์ ํ๋ฉด์ count๋ฅผ ์ฆ๊ฐ ์ํค๊ณ ์๋ค๋ ๊ฒ์ด๋ค.
๊ทธ๋ ๋ค๋ฉด ์ต์ด ๋ ๋๋งํ ๋๋ง ์คํ๋๊ณ ์
๋ฐ์ดํธ๋ ๋๋ ์คํ์ํค์ง ์์ผ๋ ค๋ฉด?
์ด๋ด๋ ๋ฐ๋ก useEffect์ 2๋ฒ์งธ ํ๋ผ๋ฏธํฐ์ ๋น ๋ฐฐ์ด([])์ ์ถ๊ฐํด ์ฃผ๋ฉด ๋๋ค.
import { useState, useEffect } from "react";
import ReactDOM from "react-dom/client";
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
setTimeout(() => {
setCount((count) => count + 1);
}, 1000);
}, []); // <- useEffect์ 2๋ฒ์งธ ํ๋ผ๋ฏธํฐ์ ๋น ๋ฐฐ์ด ์ถ๊ฐ!
return <h1>I've rendered {count} times!</h1>;
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Timer />);
์์ ๊ฐ์ด ์ถ๊ฐ๋ฅผ ํ๊ณ ๋๋ฉด ํ์ด๋จธ๋ ์ต์ด ๋ ๋๋ง ์ ์คํ๋ง ํ๊ณ ์ฆ๊ฐํ์ง ์๋๋ค.
์ฆ ๋ง์ดํธ๋ ๋๋ง ์คํ๋๋ค.
์ ๊ทธ๋ฌ๋ฉด ํน์ ๊ฐ์ด ์
๋ฐ์ดํธ๋ ๋๋ง ์คํ์ํค๊ณ ์ถ์ ๋ ์ด๋ป๊ฒ ํ ๊น?
๋ฐ๋ก ๋น ๋ฐฐ์ด์ ๊ทธ ํน์ ๊ฐ์ ๋ฃ์ด์ฃผ๋ฉด ๋๋ค!
import { useState, useEffect } from "react";
import ReactDOM from "react-dom/client";
function Counter() {
const [count, setCount] = useState(0);
const [calculation, setCalculation] = useState(0);
useEffect(() => {
setCalculation(() => count * 2);
}, [count]); // <- count๊ฐ์ด ๋ณ๊ฒฝ๋๋ฉด ์
๋ฐ์ดํธ๊ฐ ๋๋ค!
return (
<>
<p>Count: {count}</p>
<button onClick={() => setCount((c) => c + 1)}>+</button>
<p>Calculation: {calculation}</p>
</>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Counter />);
์ด๋ ๊ฒ ์ํ๋ ๊ฐ์ ์ ๋ฐ์ดํธ๋ ๋ ์คํ์ํฌ ์ ์๋ค.
useReducer๋ useState์ ๊ต์ฅํ ์ ์ฌํ ํ
์ด๋ค.
์ฌ์ค ๋ฆฌ๋์๋ ๋ฆฌ๋์ค๋ฅผ ์ฌ์ฉํ๋ฉด์ ๋ฐฐ์ฐ๊ฒ ๋ ํ
์ด์๋๋ฐ ๊ฐ๋จํ ์ ๋ฆฌ๋ฅผ ํ๊ณ ๋์ด๊ฐ ๋ณด๊ฒ ๋ค.
import { useReducer } from 'react';
function reducer (state, action) {
switch (action.type) {
case 'INCREMENT' :
return { value : state.value + 1};
case 'DECREMENT' :
return { value : state.value - 1};
default :
return state;
}
}
const Counter = () => {
const [state, dispatch] = useReducer(reducer, {value : 0})
}
return (
<div>
<p>ํ์ฌ ์นด์ดํฐ ๊ฐ์ <b>{state.value}</b> ์
๋๋ค.</p>
<button onClick={()=> dispatch({type: 'INCREMENT'})}> +1 ์ฆ๊ฐ</button>
<button onClick={()=> dispatch({type: 'DECREMENT'})}> -1 ๊ฐ์</button>
</div>
)
๋ฆฌ๋์์ ์ฒซ๋ฒ์งธ ํ๋ผ๋ฏธํฐ๋ ๋ฆฌ๋์ ํจ์๋ฅผ, ๋๋ฒ์งธ ํ๋ผ๋ฏธํฐ์๋ ๊ธฐ์ด๊ฐ์ ๋ฃ์ด์ค๋ค.
dispatch()๋ ๋ฆฌ๋์์ ํ์
์ ๋ฐ๋ฅธ return๋๋ ๋ถ๋ถ์ ์คํ์ํจ๋ค.
๋ฆฌ๋์๋ฅผ ์ฌ์ฉํ๋ค๋ฉด dispatch๋ ํ์๋ก ๋ฐ๋ผ๋ค๋๋ ๋จ์ง ์น๊ตฌ ๊ฐ์ ์กด์ฌ!
useMomo๋ ์ข ๋ ํนํ ํ ์ด๋ค. ํจ์ ์ปดํฌ๋ํธ ๋ด๋ถ์์ ๋ฐ์ํ๋ ์ฐ์ฐ์ ์ต์ ํํ๋ ์ฉ๋๋ก ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ด๋ค.
const memoizedValue = useMemo(() => {
// ์ฌ๊ธฐ์ ๊ณ์ฐ ๋น์ฉ์ด ๋์ ์์
์ ์ํํฉ๋๋ค.
// ๊ทธ ๊ฒฐ๊ณผ๋ฅผ memoizedValue์ ์ ์ฅํ๊ณ ์ฌ์ฌ์ฉํฉ๋๋ค.
return someComputedValue;
}, [dependency1, dependency2]);
useMemo์ ์ฒซ๋ฒ์งธ ๋งค๊ฐ๋ณ์๋ ํจ์๋ฅผ ๋ฐ๊ณ ์ด ํจ์ ์์์ ๊ณ์ฐ ๋น์ฉ์ด ๋์ ์์
์ ์ํ ํ ๋ฐํํ๋ค.
๋๋ฒ์งธ ๋งค๊ฐ๋ณ์๋ ์์กด์ฑ ๋ฐฐ์ด์ธ๋ฐ ์ด ๋ฐฐ์ด์ ํฌํจ๋ ๊ฐ์ ๋ณํํ ๋๋ง useMomo ๋ด๋ถ์ ํจ์๊ฐ ๋ค์ ์คํ๋๋ค. ๋ง์ฝ ๋ณํ๊ฐ ์๋ค๋ฉด ์ด์ ์ ๊ณ์ฐํ ๊ฒฐ๊ณผ๋ฅผ ์ฌ์ฌ์ฉํ๋ค.
์ฌ์ค useMemo์ ์ฌ์ฉ์ฒ๊ฐ ์ด๋๊น ๊ถ๊ธํ๋๋ฐ ๋ง์นจ GPT๊ฐ ์ข์ ์์๋ฅผ ๋ค์ด ์ฃผ์๋ค.
๐ค : useMemo๋ React์์ ์ฌ์ฉํ๋ ๋๊ตฌ ์ค ํ๋๋ก, ์ฃผ๋ก ๊ณ์ฐ์ด ๋ณต์กํ๊ฑฐ๋ ์๊ฐ์ด ์ค๋ ๊ฑธ๋ฆฌ๋ ์์ ์ ๊ฒฐ๊ณผ๋ฅผ ์ ์ฅํ๊ณ ํ์ํ ๋ ์ฌ์ฌ์ฉํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. ์ด๋ฅผ ํตํด ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฑ๋ฅ์ ํฅ์์ํฌ ์ ์์ต๋๋ค.
๊ฐ๋จํ ์๋ฅผ ๋ค์ด๋ณด๊ฒ ์ต๋๋ค. ๊ฐ์์ ์ผํ ์น์ฌ์ดํธ๋ฅผ ๋ง๋ ๋ค๊ณ ์์ํด๋ด ์๋ค. ์ฌ์ฉ์๊ฐ ์ํ ๋ชฉ๋ก์ ์ด ๋๋ง๋ค ๋ชจ๋ ์ํ์ ๊ฐ๊ฒฉ์ ๋ค์ ๊ณ์ฐํ๋ค๋ฉด ๋งค๋ฒ ์๊ฐ์ด ์ค๋ ๊ฑธ๋ฆด ๊ฒ์ ๋๋ค. ์ด๋ฐ ์ํฉ์์ useMemo๋ฅผ ์ฌ์ฉํ๋ฉด ์ฒ์ ํ ๋ฒ ๊ฐ๊ฒฉ์ ๊ณ์ฐํ ๋ค์, ๊ทธ ๊ฐ์ ์ ์ฅํ๊ณ , ์ฌ์ฉ์๊ฐ ๋ชฉ๋ก์ ๋ค์ ์ด ๋๋ง๋ค ๊ณ์ฐ์ ๋ค์ ํ์ง ์๊ณ ์ ์ฅ๋ ๊ฐ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
๊ฐ๋จํ ๋งํด, useMemo๋ ๋น์ผ ๊ณ์ฐ์ ์ ๋ ดํ๊ฒ ๋ง๋ค์ด์ฃผ๋ ๋๊ตฌ์ ๋๋ค. ๊ณ์ฐ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ์ตํ๊ณ , ํ์ํ ๋ ๋น ๋ฅด๊ฒ ์ฌ์ฉํ ์ ์๋๋ก ๋์์ค๋๋ค.
useCallback์ useMemo์ ๋น์ทํ๋ฐ, ๋ ๋๋ง ์ฑ๋ฅ์ ์ต์ ํํด์ผ ํ๋ ์ํฉ์์ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ด๋ค.
์ด ์น๊ตฌ๋ useMemo์ฒ๋ผ ์ฒซ๋ฒ์งธ ๋งค๊ฐ๋ณ์๋ก ํจ์๋ฅผ ์ ๋ฌํ๊ณ , ๋๋ฒ์งธ ๋งค๊ฐ๋ณ์๋ ์์กด์ฑ ๋ฐฐ์ด์ด๋ค.
function ParentComponent() {
const handleClick = useCallback(() => {
console.log('Button clicked');
}, []); // ์์กด์ฑ ๋ฐฐ์ด์ด ๋น์ด์์ผ๋ฏ๋ก ํญ์ ๊ฐ์ ํจ์๋ฅผ ์ฌ์ฌ์ฉํฉ๋๋ค.
return <ChildComponent onClick={handleClick} />;
}
function ChildComponent({ onClick }) {
return <button onClick={onClick}>Click me</button>;
}
useCallback์ ์ฌ์ฉํ ์์์ธ๋ฐ, handleClick ํจ์๊ฐ ParentComponent๊ฐ ๋ ๋๋ง๋ ๋๋ง๋ค ์๋ก ์์ฑ๋์ง ์๊ณ ํ ๋ฒ ์์ฑ๋ ํจ์๋ฅผ ๊ณ์ ์ฌ์ฌ์ฉํ ์ ์๊ฒ ๋์์ค๋ค. ์ด๋ ๊ฒ ์ฑ๋ฅ ๊ฐ์ ํ๋๋ฐ ๋์์ด ๋๋ค๋ ๊ฒ!
useRef๋ ์์ง์ ํฌ์ปค์ค๋ฅผ ์๋์ผ๋ก ๊ฐ๊ฒ ํ๋ ์ํฉ์์๋ฐ์ ์ฌ์ฉ์ ์ํ๋ค.
๋ค๋ฅธ ์์ ๋ ๋๋ถ๋ถ์ด ์คํ ํฌ์ปค์ค๋ฅผ ์ฌ์ฉํ ๋ ์ฌ์ฉํด์ ๐
์ด ๋ถ๋ถ์ ์ข ๋ ๋ฆฌ์กํธ๋ฅผ ๋ค๋ค๋ณด๋ฉด์ ํ์ฅ ์์ผ ๋๊ฐ์ผ ๊ฒ ๋ค.
import { useRef } from "react";
import ReactDOM from "react-dom/client";
function App() {
const inputElement = useRef();
const focusInput = () => {
inputElement.current.focus();
};
return (
<>
<input type="text" ref={inputElement} />
<button onClick={focusInput}>Focus Input</button>
</>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
์์ ๊ฐ์ด ์คํ ํฌ์ปค์ค๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํ input์ ref๋ฅผ ๋๊ณ useRef๋ฅผ ์ฌ์ฉํ๊ฒ ๋ค๊ณ ์ง์ ํ๋ค.
๊ทธ๋ฆฌ๊ณ ์ด๋ฒคํธ๋ฅผ ์ด์ฉํด์ current.focus();๋ก ์ง์ ํ๋ฉด ์๋์ผ๋ก ํฌ์ปค์ค๊ฐ ๋ด๊ฐ ์ง์ ํ๋ input์ผ๋ก ์ด๋ํ๊ฒ ๋๋ค.
์ฌ๊ธฐ์ ํฌ์ธํธ๋ current.focus() ํด์ผ ๋๋ค๋ ๊ฒ! current๊ฐ ๊ผญ๊ผญ ํ์ํ๋ค.