You will learn
- ๋ถํ์ํ effect๋ฅผ ์ ์ ๊ฑฐํด์ผ ํ๋์ง, ๊ทธ๋ฆฌ๊ณ ์ด๋ป๊ฒ ์ ๊ฑฐํ๋์ง
- effect ์์ด ๋ฌด๊ฑฐ์ด ๊ณ์ฐ์ ์บ์ฑํ๋ ๋ฐฉ๋ฒ
- effect ์์ด state๋ฅผ ์ด๊ธฐํํ๊ณ ์กฐ์ ํ๋ ๋ฐฉ๋ฒ
- ์ด๋ฒคํธ ํธ๋ค๋ฌ๊ฐ์ ๊ฐ์ ๋ก์ง์ ๊ณต์ ํ๋ ๋ฐฉ๋ฒ
- effect๊ฐ ์๋ ์ด๋ฒคํธ ํธ๋ค๋ฌ์ ์์ด์ผ ํ๋ ๋ก์ง์ ๋ฌด์์ธ์ง
- ๋ถ๋ชจ ์ปดํฌ๋ํธ์๊ฒ ๋ณ๊ฒฝ ์ฌํญ์ ์๋ฆฌ๋ ๋ฐฉ๋ฒ
์ดํํธ๋ ํ์ถ๊ตฌ
์ดํํธ๋ ๋ฆฌ์กํธ ๋ฐ์ผ๋ก ๋์ ๋ค๋ฅธ ๊ฒ๋ค๊ณผ ๋ฆฌ์กํธ ์ปดํฌ๋ํธ๋ค์ ๋๊ธฐํ ์์ผ์ฃผ๋ ๊ธฐ๋ฅ์ด๊ธฐ ๋๋ฌธ์, ์ธ๋ถ ์์คํ ๊ณผ ๊ด๋ จ์ด ์๋ค๋ฉด ์ดํํธ๋ฅผ ๋จ์ฉํ์ง ์์์ผ ํ๋ค. ํ์ ์๋ ์ดํํธ๋ ๊ฐ๋ ์ฑ์ ๋จ์ด๋จ๋ฆฌ๊ณ , ์ฑ๋ฅ์ ๋ฎ์ถ๊ณ , ์๋ฌ๋ฅผ ์ ๋ฐํ๋ค.
๋ํ์ ์ผ๋ก ์ดํํธ๋ฅผ ์ฌ์ฉํ์ง ์๋ ์ผ์ด์ค ๋ ๊ฐ์ง :
- ๋ ๋๋ง์ ํ์ํ ๋ฐ์ดํฐ ๋ณํํ๊ธฐ -
์ํ ๋ณ๊ฒฝ -> ๋ ๋ -> ์ปค๋ฐ -> ์ดํํธ -> (๋) ์ํ ๋ณ๊ฒฝ -> (๋) ๋ ๋ -> ...์ด๋ ๊ฒ ๋ถํ์ํ ๋ ๋๋ง ์ฌ์ดํด์ ํ๋ฒ ๋ ๋ฐ๋ณตํ๊ฒ ๋๋ค. (์๋์ 'ํ์ ์๋ ์ดํํธ ์ ๊ฑฐํ๊ธฐ'์ 1๋ฒ ์ฌ๋ก ์ฐธ๊ณ ) - ์ฌ์ฉ์ ์ด๋ฒคํธ ์ฒ๋ฆฌ
์ค์ง ์ธ๋ถ ์์คํ ๊ณผ ๋๊ธฐํ ํ ๋๋ง ์ฌ์ฉ๋์ด์ผ ํ๋ค. ์๋ฅผ๋ค์ด jQuery ์์ ฏ์ ๋ฆฌ์กํธ ์ํ์ ๋๊ธฐํ ์์ผ์ผ ํ๊ฑฐ๋, ๊ฒ์์ด์ ๋ง๊ฒ ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ์ ธ์ค๊ฑฐ๋(๋ฐ์ดํฐ ํจ์นญ) ํ๋ ๊ฒฝ์ฐ๋ค.
ํ์ ์๋ ์ดํํธ ์ ๊ฑฐํ๊ธฐ
๋ค์์ ์ฌ๋ก๋ค์ ๋ฆฌ์กํธ๋ฅผ ๊ฐ๋ฐํ๋ฉด์ ํํ๋ค ์ดํํธ๋ฅผ ์ฌ์ฉํด ๊ตฌํ, ํด๊ฒฐํ๋ ค๊ณ ํ์ง๋ง ๋ ํจ์จ์ ์ด๊ณ ์ ํฉํ ๋ฐฉ๋ฒ์ด ์๋ ์ฌ๋ก๋ค์ด๋ค. ์ฆ ์๋์ ๋์ด๋ ๋ก์ง์ ๊ตฌํํ๋๋ฐ ์ดํํธ๋ฅผ ์ฌ์ฉํ๊ณ ์๋ค๋ฉด ๋งค์ฐ ๋์ ํ๋ฅ ๋ก ์ข์ง ์์ ์ฝ๋๋ผ๋ ๊ฒ์ด๋ค.
1. props๋ state์ ๋ฐ๋ผ state ๋ณ๊ฒฝํ๊ธฐ
function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
// ๐ด Avoid: redundant state and unnecessary Effect
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);
// ...
}
์ฐ์ fullName ์ด๋ผ๋ ์ํ๋ฅผ ๋ง๋ค ํ์๋ ์์ผ๋ฉฐ, ๊ทธ๋ฆฌ๊ณ ๊ทธ๊ฒ์ ์ดํํธ์์ ๋ณ๊ฒฝํ ํ์๋ ์๋ค.
function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
// โ
Good: calculated during rendering
const fullName = firstName + ' ' + lastName;
// ...
}
2. ๋ฌด๊ฑฐ์ด ๊ณ์ฐ ์บ์ฑํ๊ธฐ : useMemo
function TodoList({ todos, filter }) {
const [newTodo, setNewTodo] = useState('');
// ๐ด Avoid: redundant state and unnecessary Effect
const [visibleTodos, setVisibleTodos] = useState([]);
useEffect(() => {
setVisibleTodos(getFilteredTodos(todos, filter));
}, [todos, filter]);
// ...
}
์์ ์ฝ๋๋ props ๋ก ๋ฐ์ todos์ filter๋ฅผ ์ ์ฉํด์ visibleTodos๋ผ๋ ์ํ๋ฅผ ๋ง๋ค๊ณ ์๋ค.
์์ ์ฌ๋ก์ ๋ง์ฐฌ๊ฐ์ง๋ก, ๋ ๋๋ง ์ค์ ๊ณ์ฐํด๋ผ ์ ์๋ ๊ฐ์ ์ํ๋ก ๊ด๋ฆฌ ํ ํ์๊ฐ ์๊ณ , ๊ทธ๊ฒ์ ์ดํํธ์์ ๋ณ๊ฒฝํ๋๊ฑด ๋ ๋นํจ์จ์ ์ด๋ค.
๊ทธ๋ฌ๋ ํํฐ๋ง๊ณผ ๊ฐ์ ๋ก์ง์ ๋ฌด๊ฑฐ์์ ๋งค ๋ ๋๋ง๋ง๋ค ๊ณ์ฐํ๋๊ฒ์ด ๋ถ๋ด์ด ๋ ์ ์๋ค. ๊ทธ๋์ ์์์ ์ฒ๋ผ useEffect ์์์ ๊ณ์ฐํด ๋งค ๋ ๋๋ง ๋ง๋ค ๊ณ์ฐ์ ํ์ง ์๋๋ก ์ฝ๋๋ฅผ ์์ฑํ๋ ์ฌ๋ก๊ฐ ์๋๊ฒ์ด๋ค.
์ด๋ด ๋ ์ฌ์ฉํ๋๊ฒ useMemo ํ
์ด๋ค. (์์ ์ด ํ
์ ์ฌ์ฉํ์ง ์์๋ ๋ฆฌ์กํธ ์ปดํ์ผ๋ฌ๊ฐ ์๋์ผ๋ก ๋ฌด๊ฑฐ์ด ๊ณ์ฐ์ ๋ฉ๋ชจ์ด์ ์ด์
์ ์ ์ฉํ๋ค๊ณ ํ๋ค.)
function TodoList({ todos, filter }) {
const [newTodo, setNewTodo] = useState('');
const visibleTodos = useMemo(() => {
// โ
Does not re-run unless todos or filter change
return getFilteredTodos(todos, filter);
}, [todos, filter]);
// ...
}
useMemo ํ
์ผ๋ก ๊ณ์ฐํ ๊ฐ์ ์ด ์ปดํฌ๋ํธ์ props์ธ todos๋ filter๊ฐ ๋ณํ์ง ์๋ ํ ๋ค์ ๊ณ์ฐํ์ง ์๋๋ค.
useMemoํ ๋ ๋ ๋๋ง ์ค์ ์คํ๋๋ ํจ์์ด๊ธฐ ๋๋ฌธ์ ๋ถ์ํจ๊ณผ๊ฐ ์๋ ์์ ๊ณ์ฐ์ผ๋ก๋ง ์ด๋ฃจ์ด์ ธ์ผ ํ๋ค.
3. prop ๋ณ๊ฒฝ ์ state ์ด๊ธฐํ ํ๊ธฐ : key prop
export default function ProfilePage({ userId }) {
const [comment, setComment] = useState('');
// ๐ด Avoid: Resetting state on prop change in an Effect
useEffect(() => {
setComment('');
}, [userId]);
// ...
}
์์ ์ฝ๋์ ์๋๋ ๋ค๋ฅธ ํ๋กํ ํ์ด์ง๋ก ์ด๋ํ์ ๋์๋ ์์ฑ์ค์ด๋ comment ์ํ๊ฐ ์ด๊ธฐํ ๋์ง ์๋ ๋ฒ๊ทธ๋ฅผ ์์ ํ๋ ค๊ณ userId์ ๋ณ๊ฒฝ์ ๋ง์ถฐ comment๋ฅผ ๋น ๋ฌธ์์ด๋ก ์
๋ฐ์ดํธ ํ๋ ค๋ ๊ฒ์ด๋ค.
์ ์ฝ๋์ ๊ฐ์ฅ ํฐ ๋ฌธ์ ๋ ๋ถํ์ํ ์ถ๊ฐ ๋ ๋๋ง์ ์ด๋ฐํ๋ค๋๊ฒ. ๊ทธ๋ฆฌ๊ณ comment ๋ฟ๋ง ์๋๋ผ ์ด๊ธฐํํด์ผ ํ ๋ค๋ฅธ ์ํ๋ค์ด ๋ ์๋ค๋ฉด ๊ทธ ์ํ๋ค๋ ์ผ์ผ์ด ๋ณ๊ฒฝํด์ฃผ์ด์ผ ํ๋ค.
export default function ProfilePage({ userId }) {
return (
<Profile
userId={userId}
key={userId}
/>
);
}
function Profile({ userId }) {
// โ
This and any other state below will reset on key change automatically
const [comment, setComment] = useState('');
// ...
}
๋์ key prop์ ์ฌ์ฉํ๋ฉด ๋ฆฌ์กํธ๋ ํด๋น ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฌ์ฉํ์ง ์๊ณ ์์ ํ ์๋ก์ด ์ธ์คํด์ค๋ฅผ ์์ฑํ๋ค(์ํ๋ค๋ ์ด๊ธฐ๊ฐ์ผ๋ก ์ ๋ถ ์ด๊ธฐํ ๋๋ค). ๊ทธ๋์ ์ํ๋ค์ด ๋จ์์๋ ๋ฒ๊ทธ๊ฐ ๋ฐ์ํ์ง ์๋๋ค.
4. prop ๋ณ๊ฒฝ์ ๋ฐ๋ผ ์ผ๋ถ state ๋ณ๊ฒฝํ๊ธฐ
key ๊ฐ์ ์ฌ์ฉํด ์ปดํฌ๋ํธ๋ฅผ ์ฒ์๋ถํฐ ๋ง๋ค๊ธฐ ๋ฌด๊ฑฐ์ด ์ปดํฌ๋ํธ๊ฐ ์์ ์ ์๋ค. ์๋๋ฉด ๋๋ถ๋ถ์ ์ํ๋ ๊ฐ๋งํ ๋๊ณ ์ผ๋ถ ์ํ๋ง ์ด๊ธฐํ ํ๊ฑฐ๋ ๋ณ๊ฒฝํ๊ณ ์ถ์ ์ ์๋ค.
function List({ items }) {
const [isReverse, setIsReverse] = useState(false);
const [selection, setSelection] = useState(null);
// ๐ด Avoid: Adjusting state on prop change in an Effect
useEffect(() => {
setSelection(null);
}, [items]);
// ...
}
3๋ฒ ์ฌ๋ก์ฒ๋ผ ์ํ ์ด๊ธฐํ๋ฅผ ์ดํํธ์์ ํ๊ณ ์์ผ๋ฉฐ ์ด๋ ์๋ชป๋ ํจํด์ด๋ค.
Better
function List({ items }) {
const [isReverse, setIsReverse] = useState(false);
const [selection, setSelection] = useState(null);
// Better: Adjust the state while rendering
const [prevItems, setPrevItems] = useState(items);
if (items !== prevItems) {
setPrevItems(items);
setSelection(null);
}
// ...
}
ํน์ดํ๊ฒ ์ด๋ ๊ฒ ์ด์ ๋ ๋์ ์ ๋ณด๋ฅผ ์ ์ฅํ๋ ๋ฐฉ๋ฒ์ ํตํด ๊ฐ์ ํ ์๋ ์๋ค.
useEffect์ ์์กด์ฑ ๋ฐฐ์ด์ ํตํด items์ํ๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง ์ ํ๋ ์์ดํ
์ ํด์ ํ๋ ๊ทธ ๋ฐฉ๋ฒ์ prevItems๋ผ๋ ์๋ก์ด ์ํ๋ฅผ ๋์
ํด ๊ตฌํํ๋ค.
Best
function List({ items }) {
const [isReverse, setIsReverse] = useState(false);
const [selectedId, setSelectedId] = useState(null);
// โ
Best: Calculate everything during rendering
const selection = items.find(item => item.id === selectedId) ?? null;
// ...
}
5. ์ด๋ฒคํธ ํธ๋ค๋ฌ ๊ฐ ๋ก์ง ๊ณต์
function ProductPage({ product, addToCart }) {
// ๐ด Avoid: Event-specific logic inside an Effect
useEffect(() => {
if (product.isInCart) {
showNotification(`Added ${product.name} to the shopping cart!`);
}
}, [product]);
function handleBuyClick() {
addToCart(product);
}
function handleCheckoutClick() {
addToCart(product);
navigateTo('/checkout');
}
// ...
}
๋ ์ด๋ฒคํธ ํธ๋ค๋ฌ ๋ชจ๋ addCart๋ก ์ฅ๋ฐ๊ตฌ๋์ ์ํ์ ๋ฃ๋๋ฐ, ๊ทธ ๋ ๋ง๋ค ์๋ฆผ์ฐฝ์ ๋ณด์ฌ์ฃผ๊ธฐ ์ํด์ product๊ฐ ๋ฐ๋ ๋ ๋ง๋ค ์คํ๋๋ ์ดํํธ๋ฅผ ์์ฑํ๋ค.
๋ ๋๋ง๊ณผ ๊ด๋ จ์ด ์๊ณ ํน์ ์ด๋ฒคํธ์ ์ํด์๋ง ์ด๋ฐ๋๋ event-specificํ ๋ก์ง์ด๋ผ ์ดํํธ์ ์์ฑํ๋๊ฑด ์ ์ ํ์ง ์๋ค. ์ด๋ป๊ฒ ๊ณ ์ณ์ผํ ๊น? ๊ฐ๋จํ๊ณ ์ง๊ด์ ์ด๋ค. ํน์ ์ด๋ฒคํธ์๋ง ์คํ๋๋ ์ฝ๋๋ผ๋ฉด ์ด๋ฒคํธ ํธ๋ค๋ฌ ์์ ์์ฑ๋์ด์ผ ํ๋ค.
function ProductPage({ product, addToCart }) {
// โ
Good: Event-specific logic is called from event handlers
function buyProduct() {
addToCart(product);
showNotification(`Added ${product.name} to the shopping cart!`);
}
function handleBuyClick() {
buyProduct();
}
function handleCheckoutClick() {
buyProduct();
navigateTo('/checkout');
}
// ...
}
6. POST ์์ฒญ ๋ณด๋ด๊ธฐ
5๋ฒ ์ฌ๋ก์ ๋น์ทํ ๋ด์ฉ์ด๋ผ์ ์๋ต. ๊ฐ์ post ์์ฒญ์ด์ง๋ง ์ด๋ค ์์ฒญ์ ์ดํํธ์ ์์ฑํด์ผ ํ๊ณ ์ด๋ค ์์ฒญ์ ์ด๋ฒคํธ ํธ๋ค๋ฌ์ ์์ฑํด์ผ ํ๋๊ฐ์ ๋ํ ์ค๋ช . ํน์ ์ด๋ฒคํธ์ ์ข ์์ ์ด๋ผ๋ฉด ์ด๋ฒคํธ ํจ๋ค๋ฌ ์์์ ์์ฒญ์ ๋ณด๋ด์ผํ๋ค.
7. ์ฐ์ ๊ณ์ฐ
function Game() {
const [card, setCard] = useState(null);
const [goldCardCount, setGoldCardCount] = useState(0);
const [round, setRound] = useState(1);
const [isGameOver, setIsGameOver] = useState(false);
// ๐ด Avoid: Chains of Effects that adjust the state solely to trigger each other
useEffect(() => {
if (card !== null && card.gold) {
setGoldCardCount(c => c + 1);
}
}, [card]);
useEffect(() => {
if (goldCardCount > 3) {
setRound(r => r + 1)
setGoldCardCount(0);
}
}, [goldCardCount]);
useEffect(() => {
if (round > 5) {
setIsGameOver(true);
}
}, [round]);
useEffect(() => {
alert('Good game!');
}, [isGameOver]);
function handlePlaceCard(nextCard) {
if (isGameOver) {
throw Error('Game already ended.');
} else {
setCard(nextCard);
}
}
// ...
์ด ์ฝ๋์ ์๋๋ ์ด๋ค ์ํ(card) ๋ณํ์ ๋ ๊ทธ ์ํ์ ๋ณํ์ ๊ธฐ๋ฐํด ๋ค๋ฅธ ์ํ๋ฅผ(goldCardCount, round, isGameOver) ๋ณ๊ฒฝํ๋ ค๋ ๊ฒ์ด๋ค.
์ด๋ ๊ฒ ์ดํํธ๋ก ์ํ๋ฅผ ์ฐ์์ ์ผ๋ก ๋ณ๊ฒฝํ๋ค๋ฉด ๋ถํ์ํ ๋ฆฌ๋ ๋๋ง์ด ์ฌ๋ฌ๋ฒ ๋ฐ์ํ๊ฒ ๋๋ค : card ๋ณ๊ฒฝ -> ๋ฆฌ๋ ๋๋ง -> goldCardCount ๋ณ๊ฒฝ -> ๋ฆฌ๋ ๋๋ง -> ....
state์ ๋ํ ๋ค๋ฅธ ๋ฌธ์์์๋ ๋ณด์์ง๋ง, ๋ค๋ฅธ ์ํ์ ์ํด์ ๊ณ์ฐ๋ ์ ์๋ ๊ฐ์ ๊ตณ์ด ์ํ๋ก ๊ด๋ฆฌํ์ง ์๋๊ฒ์ด ์ข๊ณ , ์นด๋๋ฅผ ํ์ธํ๋ ์ด๋ฒคํธ์ ์ข ์์ ์ธ ๋ก์ง๋ค์ด๋๊น ์ด๋ฒคํธ ํธ๋ค๋ฌ์์ ์คํ๋์ด์ผ ํ๋ค.
function Game() {
const [card, setCard] = useState(null);
const [goldCardCount, setGoldCardCount] = useState(0);
const [round, setRound] = useState(1);
// โ
Calculate what you can during rendering
const isGameOver = round > 5;
function handlePlaceCard(nextCard) {
if (isGameOver) {
throw Error('Game already ended.');
}
// โ
Calculate all the next state in the event handler
setCard(nextCard);
if (nextCard.gold) {
if (goldCardCount < 3) {
setGoldCardCount(goldCardCount + 1);
} else {
setGoldCardCount(0);
setRound(round + 1);
if (round === 5) {
alert('Good game!');
}
}
}
}
// ...
8. ์ ํ๋ฆฌ์ผ์ด์ ์ด๊ธฐ ์์
์ ํ๋ฆฌ์ผ์ด์ ์ ์ฒ์์ ์ด์์๋ ์คํ๋๋ ๋ก์ง๋ค์ด ์๋ค. ์ธ์ ์ ๋ณด๋ ๋ก์ปฌ ์คํ ๋ฆฌ์ง์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๋ฑ์ ๋ก์ง๋ค. ๊ทธ๋ ๋ค๋ฉด ์ด ๋ก์ง๋ค์ ๋ฃจํธ ์ปดํฌ๋ํธ์ ์ดํํธ์ ์์ฑํด์ผ ํ ๊น? ๋ฃจํธ ์ปดํฌ๋ํธ๊ฐ ๋ ๋๋ง ๋๋ค๋๊ฒ ๊ณง ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฒ์ ์คํํ๋ค๋ ๋ป์ด๋๊น.
์ดํํธ์ ์์ฑํ๋ค๋ฉด ๋ง์ดํธ๋น ํ๋ฒ์ฉ ์คํ๋์ง ์๋๋ก ์ต์์ ํ๋๊ทธ ๋ณ์๋ฅผ ์ฌ์ฉํ๋ค.
let didInit = false;
function App() {
useEffect(() => {
if (!didInit) {
didInit = true;
// โ
Only runs once per app load
loadDataFromLocalStorage();
checkAuthToken();
}
}, []);
// ...
}
ํน์ ์ต์์ ๋ ๋ฒจ์ ์์ฑํ ์๋ ์๋ค. ์ต์์ ๋ ๋ฒจ์ ์ฝ๋๋ ์ปดํฌ๋ํธ๊ฐ ๋ ๋๋ง ๋๊ธฐ ์ด์ ์, import ํ ๋ ๋ฏธ๋ฆฌ ์คํํ๋ค.
if (typeof window !== 'undefined') { // Check if we're running in the browser.
// โ
Only runs once per app load
checkAuthToken();
loadDataFromLocalStorage();
}
function App() {
// ...
}
9. state ๋ณ๊ฒฝ์ ๋ถ๋ชจ์๊ฒ ์๋ฆฌ๊ธฐ
function Toggle({ onChange }) {
const [isOn, setIsOn] = useState(false);
// ๐ด Avoid: The onChange handler runs too late
useEffect(() => {
onChange(isOn);
}, [isOn, onChange])
function handleClick() {
setIsOn(!isOn);
}
function handleDragEnd(e) {
if (isCloserToRightEdge(e)) {
setIsOn(true);
} else {
setIsOn(false);
}
}
// ...
}
isOn ์ํ๊ฐ ๋ณ๊ฒฝ๋ ๋ ๋ถ๋ชจ์๊ฒ ์๋ฆฌ๊ธฐ ์ํด onChange ํจ์๋ฅผ ์ดํํธ ์์์ ์คํํ๊ณ ์๋ค. ์ด๋ ๊ฒ ํ๋ฉด ๋ค์๊ณผ ๊ฐ์ ๋นํจ์จ์ ์ธ ๊ณผ์ ์ ์ง๋๋ค : isOn ๋ณ๊ฒฝ -> ๋ฆฌ๋ ๋๋ง -> onChange ํธ์ถ -> ๋ถ๋ชจ์ ์ํ ๋ณ๊ฒฝ -> ๋ถ๋ชจ ๋ฆฌ๋ ๋๋ง.
๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด์๋ ์ด๋ฒคํธ ํธ๋ค๋ฌ์์ onChange๋ฅผ ํธ์ถํ๋ ๋ฐฉ๋ฒ์ด ์๋ค.
function updateToggle(nextIsOn) {
// โ
Good: Perform all updates during the event that caused them
setIsOn(nextIsOn);
onChange(nextIsOn);
}
์ด๋ ๊ฒ ํ๋ฉด setIsOn์ด ์ด๋ฐํ ๋ฆฌ๋ ๋๋ง๊ณผ onChange๊ฐ ์ด๋ฐํ ๋ฆฌ๋ ๋๋ง์ด ๋ฆฌ์กํธ์ ๋ฐฐ์นญ ์ฒ๋ฆฌ ๋ฐฉ์์ ์ํด ํ๋ฒ์ ์ผ์ด๋์ ๋ ํจ์จ์ ์ด๋ค.
๋ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ผ๋ก๋ "Lifting state up" ๋ฐฉ์์ ์ฌ์ฉํด isOn๋ prop์ผ๋ก ๊ฑด๋ด์ฃผ๊ณ ๋ถ๋ชจ ์ปดํฌ๋ํธ์์ ๊ด๋ฆฌํ๋ ๊ฒ.
10. ๋ถ๋ชจ์๊ฒ ๋ฐ์ดํฐ ์ ๋ฌํ๊ธฐ
์ด ์ฌ๋ก๋ ๋ฐ๋ก ์์ 9๋ฒ ์ฌ๋ก์ ๋น์ทํ๋ค. ๋ณธ์ง์ ์ผ๋ก ์์ ์ปดํฌ๋ํธ์์ ์ดํํธ๋ฅผ ํตํด ๋ถ๋ชจ ์ปดํฌ๋ํธ์ ์ํ๋ฅผ ๋ณ๊ฒฝํ๋ ค๊ณ ํ๋ค. 9๋ฒ ์ฌ๋ก์์๋ ๋ ๋๋ง ํจ์จ์ฑ์ ๋ํด ์ด์ผ๊ธฐํ๋ค๋ฉด ์ฌ๊ธฐ๋ ์ถ๊ฐ์ ์ผ๋ก ๋ฆฌ์กํธ์ ๋ฐ์ดํฐ ํ๋ฆ์ ๊ฑฐ์ค๋ฅธ๋ค๋ ๋ฌธ์ ์ ์ ๋ํด ์ด์ผ๊ธฐํ๋ค. ๋ฆฌ์กํธ์์๋ ์ธ์ ๋ ๋ถ๋ชจ์์ ์์์ผ๋ก ๋ฐ์ดํฐ๊ฐ ํ๋ฌ๊ฐ์ผ ํ๋ค. ๋ง์ฝ ๋ฐ๋ ๋ฐฉํฅ์ ์ค๊ณ๊ฐ ์ค๊ฐ์ ์์ฌ์๋ค๋ฉด ๋ฌธ์ ๊ฐ ๋ฐ์ํ์๋ ์์ธ์ ์ฐพ์๋ด๊ธฐ ํ๋ค์ด์ง๋ค.
11. ์ธ๋ถ ์ ์ฅ์ ๊ตฌ๋
: useSyncExternalStore
์ธ๋ถ ์ ์ฅ์๋ ๋ฆฌ์กํธ๊ฐ ์๋ ๋ธ๋ผ์ฐ์ API๋ ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ(zustand ๋ฑ)๋ฅผ ๋งํ๋ค. ์ด๋ค ์ปดํฌ๋ํธ๋ ๋ฆฌ์กํธ state ๋์ ๋ค๋ฅธ ๊ณณ์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์์ผ ํ ํ์๊ฐ ์์ ์ ์๋ค.
function useOnlineStatus() {
// Not ideal: Manual store subscription in an Effect
const [isOnline, setIsOnline] = useState(true);
useEffect(() => {
function updateState() {
setIsOnline(navigator.onLine);
}
updateState();
window.addEventListener('online', updateState);
window.addEventListener('offline', updateState);
return () => {
window.removeEventListener('online', updateState);
window.removeEventListener('offline', updateState);
};
}, []);
return isOnline;
}
function ChatIndicator() {
const isOnline = useOnlineStatus();
// ...
}
์์ ์ฌ๋ก๋ฅผ ๋ณด๋ฉด ์ปค์คํ
ํ
์ ๋ง๋ค์ด์ navigator.onLine ์ด๋ผ๋ ๋ธ๋ผ์ฐ์ API๋ฅผ ํตํด ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ณ ์๋ค.
๋ฆฌ์กํธ์์๋ ์ด ์ฉ๋์ ๋ง๋ ์ ์ฉ ํ
์ ์ฌ์ฉํ๊ธฐ๋ฅผ ๊ถ์ฅํ๋ค.
useSyncExternalStore ์ ๋ํด
[!faq] "์ธ๋ถ ์์คํ ๊ณผ์ ๋๊ธฐํ"๋ผ๋ ์ดํํธ์ ๋ชฉ์ ์ ์๋ง๋ ์ฌ์ฉ๋ฒ์ผ๋ก ๋ณด์ด๋๋ฐ ๋ฌด์์ด ๋ฌธ์ ์ผ๊น?
12. ๋ฐ์ดํฐ ํจ์นญ
์ด ์ฌ๋ก๋ ์ดํํธ๋ฅผ ์ ๊ฑฐํด์ผ ํ๋ ์ฌ๋ก๊ฐ ์๋๋ผ, ์ดํํธ ์ฌ์ฉ์์ ์ฃผ์์ ์ ๋ํด ์ด์ผ๊ธฐ ํ๊ณ ์๋ค. ๋ ์ด์ค ์ปจ๋์ ์ ์๋ฐฉ ํ๊ธฐ ์ํด ํด๋ฆฐ์ ํจ์๋ฅผ ์ค์ ํด์ผ ํ๋ค.
์์ ๋ฌธ์
1. ์ดํํธ ์์ด ๋ฐ์ดํฐ ๋ณ๊ฒฝํ๊ธฐ
export default function TodoList() {
const [todos, setTodos] = useState(initialTodos);
const [showActive, setShowActive] = useState(false);
const [activeTodos, setActiveTodos] = useState([]);
const [visibleTodos, setVisibleTodos] = useState([]);
const [footer, setFooter] = useState(null);
useEffect(() => {
setActiveTodos(todos.filter(todo => !todo.completed));
}, [todos]);
useEffect(() => {
setVisibleTodos(showActive ? activeTodos : todos);
}, [showActive, todos, activeTodos]);
useEffect(() => {
setFooter(
<footer>
{activeTodos.length} todos left
</footer>
);
}, [activeTodos]);
//...
activeTodos์ visibleTodos ๋ชจ๋ ๋ ๋๋ง ์ค์ ๊ณ์ฐ ๊ฐ๋ฅํ ๊ฐ์ด๊ธฐ ๋๋ฌธ์ ์ํ๋ก ๊ด๋ฆฌ ํ ํ์๊ฐ ์์ผ๋ฉฐ, ๊ทธ๋์ ์ดํํธ๋ ํ์๊ฐ ์๋ค.
footer๋ ์ด์ฐจํผ activeTodos์ ๋ณ๊ฒฝ๊ณผ ํจ๊ป ๋ฆฌ๋ ๋๋ง ๋๋ฉด์ ์ต์ ๊ฐ์ ๊ฐ์ง๊ธฐ ๋๋ฌธ์ ์ดํํธ๋ฅผ ์ฌ์ฉํ ํ์๊ฐ ์๋ค.
2. ์ดํํธ ์์ด ์บ์ฑํ๊ธฐ
export default function TodoList() {
const [todos, setTodos] = useState(initialTodos);
const [showActive, setShowActive] = useState(false);
const [text, setText] = useState('');
const [visibleTodos, setVisibleTodos] = useState([]);
useEffect(() => {
setVisibleTodos(getVisibleTodos(todos, showActive));
}, [todos, showActive]);
function handleAddClick() {
setText('');
setTodos([...todos, createTodo(text)]);
}
visibleTodos๋ ๊ณ์ฐ ๊ฐ๋ฅํ ๊ฐ์ด์ง๋ง, ๋ชจ๋ ๋ ๋๋ง๋ง๋ค ๊ณ์ฐ๋ ํ์๋ ์๋ค. ๊ทธ๋ฆฌ๊ณ ๋ฐ์ดํฐ๊ฐ ๋ง์ ๊ฒฝ์ฐ ๊ณ์ฐ์ด ๋ฌด๊ฑฐ์์ง๊ธฐ ๋๋ฌธ์ useEffect๋ฅผ ํตํด ์์กดํ์ง ์๋ ์ํ์ ๋ณ๊ฒฝ์๋ ๊ณ์ฐ์ ์คํํ์ง ์๋๋ก ์์ฑํ ์ฝ๋๋ค.
ํ์ง๋ง ์ดํํธ๋ฅผ ์ฌ์ฉํด ์ํ๋ฅผ ๋ณ๊ฒฝํ๋๊ฑด ์ํฐํจํด์ด๊ธฐ ๋๋ฌธ์ ์ด๋ด๋ ์ฌ์ฉํ๋useMemo ํ
์ ์ฌ์ฉํด ๊ณ์ฐ๋ ๊ฐ์ ๊ธฐ์ตํ๋ค.
const visibleTodos = useMemo(
() => getVisibleTodos(todos, showActive),
[todos, showActive]
);
์ฌ์ค ๋ ๊ฐ๋จํ ๋ฐฉ๋ฒ์ด ์๋๋ฐ text ์ํ๋ฅผ ๊ด๋ฆฌํ๋ ์์ ์ปดํฌ๋ํธ๋ฅผ ๋ง๋๋ ๊ฒ์ด๋ค(1๋ฒ ๋ฌธ์ ์ ์์ ์ฝ๋์ฒ๋ผ). ๊ทธ๋ฌ๋ฉด ํ
์คํธ ์
๋ ฅ์๋ง๋ค ๋ฆฌ๋ ๋๋ง ๋์ง ์์ผ๋ useMemo ํ
์์ด ์ผ๋ฐ์ ์ธ ๋ณ์๋ก visibleTodos๋ฅผ ๊ด๋ฆฌํด๋ ๊ฐ์ ํจ๊ณผ๋ฅผ ๋ธ๋ค.
3. ์ดํํธ ์์ด ์ํ ์ด๊ธฐํ
export default function EditContact({ savedContact, onSave }) {
const [name, setName] = useState(savedContact.name);
const [email, setEmail] = useState(savedContact.email);
useEffect(() => {
setName(savedContact.name);
setEmail(savedContact.email);
}, [savedContact]);
//...
key prop์ ์ฌ์ฉํด์ ์ดํํธ ์์ด ์ํ๋ฅผ ์ด๊ธฐํ ํด์ผ ํ๋๋ฐ key prop์ ์ฃผ๊ธฐ ์ํด ์ปดํฌ๋ํธ ๊ณ์ธต์ ๋๋ ์ผ ํ๋ค.
export default function EditContact(props) {
return (
<EditForm
{...props}
key={props.savedContact.id}
/>
);
}
function EditForm({ savedContact, onSave }) {
const [name, setName] = useState(savedContact.name);
const [email, setEmail] = useState(savedContact.email);
return (
//...

