Skip to content

Latest commit

ย 

History

History
1609 lines (1210 loc) ยท 71.8 KB

File metadata and controls

1609 lines (1210 loc) ยท 71.8 KB
title Effect๋กœ ๋™๊ธฐํ™”ํ•˜๊ธฐ

์ผ๋ถ€ ์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” ์™ธ๋ถ€ ์‹œ์Šคํ…œ๊ณผ ๋™๊ธฐํ™”ํ•ด์•ผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด React์˜ state์„ ๊ธฐ์ค€์œผ๋กœ React์™€ ์ƒ๊ด€์—†๋Š” ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ์ œ์–ดํ•˜๊ฑฐ๋‚˜, ์„œ๋ฒ„ ์—ฐ๊ฒฐ์„ ์„ค์ •ํ•˜๊ฑฐ๋‚˜, ๊ตฌ์„ฑ ์š”์†Œ๊ฐ€ ํ™”๋ฉด์— ๋‚˜ํƒ€๋‚  ๋•Œ ๋ถ„์„ ๋ชฉ์ ์˜ ๋กœ๊ทธ๋ฅผ ์ „์†กํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. Effect๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ Œ๋”๋ง ํ›„ ํŠน์ • ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜์—ฌ React ์™ธ๋ถ€์˜ ์‹œ์Šคํ…œ๊ณผ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋™๊ธฐํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • Effect๊ฐ€ ๋ฌด์—‡์ธ์ง€
  • Effect๊ฐ€ ์ด๋ฒคํŠธ์™€ ๋‹ค๋ฅธ ์ 
  • ์ปดํฌ๋„ŒํŠธ์—์„œ Effect๋ฅผ ์„ ์–ธํ•˜๋Š” ๋ฐฉ๋ฒ•
  • ๋ถˆํ•„์š”ํ•œ Effect ์žฌ์‹คํ–‰์„ ๊ฑด๋„ˆ๋›ฐ๋Š” ๋ฐฉ๋ฒ•
  • ๊ฐœ๋ฐœ ์ค‘์— Effect๊ฐ€ ๋‘ ๋ฒˆ ์‹คํ–‰๋˜๋Š” ์ด์œ ์™€ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

Effect๋ž€ ๋ฌด์—‡์ด๊ณ  ์ด๋ฒคํŠธ์™€๋Š” ์–ด๋–ป๊ฒŒ ๋‹ค๋ฅธ๊ฐ€์š”? {/what-are-effects-and-how-are-they-different-from-events/}

Effect์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์•Œ์•„๋ณด๊ธฐ ์ „์—, ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์˜ 2๊ฐ€์ง€ ๋กœ์ง ์œ ํ˜•์— ๋Œ€ํ•ด ์•Œ์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  • ๋ Œ๋”๋ง ์ฝ”๋“œ(UI ํ‘œํ˜„ํ•˜๊ธฐ์— ์†Œ๊ฐœ๋จ)๋ฅผ ์ฃผ๊ด€ํ•˜๋Š” ๋กœ์ง์€ ์ปดํฌ๋„ŒํŠธ์˜ ์ตœ์ƒ๋‹จ์— ์œ„์น˜ํ•˜๋ฉฐ, props์™€ state๋ฅผ ์ ์ ˆํžˆ ๋ณ€ํ˜•ํ•ด ๊ฒฐ๊ณผ์ ์œผ๋กœ JSX๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๋ Œ๋”๋ง ์ฝ”๋“œ ๋กœ์ง์€ ์ˆœ์ˆ˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ˆ˜ํ•™ ๊ณต์‹์ฒ˜๋Ÿผ ๊ฒฐ๊ณผ๋งŒ ๊ณ„์‚ฐํ•ด์•ผ ํ•˜๊ณ , ๊ทธ ์™ธ์—๋Š” ์•„๋ฌด๊ฒƒ๋„ ํ•˜์ง€ ๋ง์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  • ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ(์ƒํ˜ธ์ž‘์šฉ ๋”ํ•˜๊ธฐ์— ์†Œ๊ฐœ๋จ)๋Š” ๋‹จ์ˆœํ•œ ๊ณ„์‚ฐ ์šฉ๋„๊ฐ€ ์•„๋‹Œ ๋ฌด์–ธ๊ฐ€๋ฅผ ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์˜ ์ค‘์ฒฉ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋Š” ์ž…๋ ฅ ํ•„๋“œ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๊ฑฐ๋‚˜, ์ œํ’ˆ์„ ๊ตฌ์ž…ํ•˜๊ธฐ ์œ„ํ•ด HTTP POST ์š”์ฒญ์„ ๋ณด๋‚ด๊ฑฐ๋‚˜, ์‚ฌ์šฉ์ž๋ฅผ ๋‹ค๋ฅธ ํ™”๋ฉด์œผ๋กœ ์ด๋™์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์—๋Š” ํŠน์ • ์‚ฌ์šฉ์ž ์ž‘์—…(์˜ˆ: ๋ฒ„ํŠผ ํด๋ฆญ ๋˜๋Š” ์ž…๋ ฅ)์œผ๋กœ ์ธํ•ด ๋ฐœ์ƒํ•˜๋Š” "๋ถ€์ˆ˜ ํšจ๊ณผ"(์ด๋Ÿฌํ•œ ๋ถ€์ˆ˜ ํšจ๊ณผ๊ฐ€ ํ”„๋กœ๊ทธ๋žจ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.)๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.

๊ฐ€๋”์€ ์ด๊ฒƒ์œผ๋กœ ์ถฉ๋ถ„ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ™”๋ฉด์— ๋ณด์ผ ๋•Œ๋งˆ๋‹ค ์ฑ„ํŒ… ์„œ๋ฒ„์— ์ ‘์†ํ•ด์•ผ ํ•˜๋Š” ChatRoom ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ƒ๊ฐํ•ด ๋ณด์„ธ์š”. ์„œ๋ฒ„์— ์ ‘์†ํ•˜๋Š” ๊ฒƒ์€ ์ˆœ์ˆ˜ํ•œ ๊ณ„์‚ฐ์ด ์•„๋‹ˆ๊ณ  ๋ถ€์ˆ˜ ํšจ๊ณผ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๊ธฐ ๋•Œ๋ฌธ์— ๋ Œ๋”๋ง ์ค‘์—๋Š” ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ํด๋ฆญ ํ•œ ๋ฒˆ์œผ๋กœ ChatRoom์ด ํ‘œ์‹œ๋˜๋Š” ํŠน์ • ์ด๋ฒคํŠธ๋Š” ํ•˜๋‚˜๋„ ์—†์Šต๋‹ˆ๋‹ค.

Effect๋Š” ๋ Œ๋”๋ง ์ž์ฒด์— ์˜ํ•ด ๋ฐœ์ƒํ•˜๋Š” ๋ถ€์ˆ˜ ํšจ๊ณผ๋ฅผ ํŠน์ •ํ•˜๋Š” ๊ฒƒ์œผ๋กœ, ํŠน์ • ์ด๋ฒคํŠธ๊ฐ€ ์•„๋‹Œ ๋ Œ๋”๋ง์— ์˜ํ•ด ์ง์ ‘ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์ฑ„ํŒ…์—์„œ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ด๋Š” ๊ฒƒ์€ ์ด๋ฒคํŠธ์ž…๋‹ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด ์ด๊ฒƒ์€ ์‚ฌ์šฉ์ž๊ฐ€ ํŠน์ • ๋ฒ„ํŠผ์„ ํด๋ฆญํ•จ์— ๋”ฐ๋ผ ์ง์ ‘์ ์œผ๋กœ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์„œ๋ฒ„ ์—ฐ๊ฒฐ ์„ค์ •์€ Effect์ž…๋‹ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด ์ด๊ฒƒ์€ ์ปดํฌ๋„ŒํŠธ์˜ ํ‘œ์‹œ๋ฅผ ์ฃผ๊ด€ํ•˜๋Š” ์–ด๋–ค ์ƒํ˜ธ ์ž‘์šฉ๊ณผ๋„ ์ƒ๊ด€์—†์ด ๋ฐœ์ƒํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Effect๋Š” ์ปค๋ฐ‹์ด ๋๋‚œ ํ›„์— ํ™”๋ฉด ์—…๋ฐ์ดํŠธ๊ฐ€ ์ด๋ฃจ์–ด์ง€๊ณ  ๋‚˜์„œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ์ด ์‹œ์ ์ด React ์ปดํฌ๋„ŒํŠธ๋ฅผ ์™ธ๋ถ€ ์‹œ์Šคํ…œ(๋„คํŠธ์›Œํฌ ๋˜๋Š” ์จ๋“œํŒŒํ‹ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์™€ ๊ฐ™์€)๊ณผ ๋™๊ธฐํ™”ํ•˜๊ธฐ ์ข‹์€ ํƒ€์ด๋ฐ์ž…๋‹ˆ๋‹ค.

์ด ํ…์ŠคํŠธ์—์„œ์˜ ๋Œ€๋ฌธ์ž "Effect"๋Š” ์œ„์—์„œ ์–ธ๊ธ‰ํ•œ React์— ํŠนํ™”๋œ ์ •์˜๋ฅผ ๋‚˜ํƒ€๋‚ด๋ฉฐ, ๊ณง ๋ Œ๋”๋ง์— ์˜ํ•œ ๋ถ€์ˆ˜ ํšจ๊ณผ๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ๋ณด๋‹ค ์ผ๋ฐ˜์ ์ธ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๊ฐœ๋…์„ ์–ธ๊ธ‰ํ•  ๋•Œ์—๋Š” "๋ถ€์ˆ˜ ํšจ๊ณผ"๋ผ๊ณ  ๋งํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

Effect๊ฐ€ ํ•„์š” ์—†์„์ง€๋„ ๋ชจ๋ฆ…๋‹ˆ๋‹ค {/you-might-not-need-an-effect/}

์ปดํฌ๋„ŒํŠธ์— Effect๋ฅผ ๋ฌด์ž‘์ • ์ถ”๊ฐ€ํ•˜์ง€ ๋งˆ์„ธ์š”. Effect๋Š” ์ฃผ๋กœ React ์ฝ”๋“œ๋ฅผ ๋ฒ—์–ด๋‚œ ํŠน์ • ์™ธ๋ถ€ ์‹œ์Šคํ…œ๊ณผ ๋™๊ธฐํ™”ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๋ธŒ๋ผ์šฐ์ € API, ์„œ๋“œ ํŒŒํ‹ฐ ์œ„์ ฏ, ๋„คํŠธ์›Œํฌ ๋“ฑ์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ๋‹น์‹ ์˜ Effect๊ฐ€ ๋‹จ์ˆœํžˆ ๋‹ค๋ฅธ ์ƒํƒœ์— ๊ธฐ๋ฐ˜ํ•˜์—ฌ ์ผ๋ถ€ ์ƒํƒœ๋ฅผ ์กฐ์ •ํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” Effect๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Effect๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋ฒ• {/how-to-write-an-effect/}

Effect๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋‹ค์Œ ์„ธ ๋‹จ๊ณ„๋ฅผ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค.

  1. Effect ์„ ์–ธ. ๊ธฐ๋ณธ์ ์œผ๋กœ Effect๋Š” ๋ชจ๋“  commit ์ดํ›„์— ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
  2. Effect ์˜์กด์„ฑ ์ง€์ •. ๋Œ€๋ถ€๋ถ„์˜ Effect๋Š” ๋ชจ๋“  ๋ Œ๋”๋ง ํ›„๊ฐ€ ์•„๋‹Œ ํ•„์š”ํ•  ๋•Œ๋งŒ ๋‹ค์‹œ ์‹คํ–‰๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ํŽ˜์ด๋“œ ์ธ ์• ๋‹ˆ๋ฉ”์ด์…˜์€ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋‚˜ํƒ€๋‚  ๋•Œ์—๋งŒ ํŠธ๋ฆฌ๊ฑฐ ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ฑ„ํŒ… ๋ฐฉ์— ์—ฐ๊ฒฐ, ์—ฐ๊ฒฐ ํ•ด์ œํ•˜๋Š” ๊ฒƒ์€ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋‚˜ํƒ€๋‚˜๊ฑฐ๋‚˜ ์‚ฌ๋ผ์งˆ ๋•Œ ๋˜๋Š” ์ฑ„ํŒ… ๋ฐฉ์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋งŒ ๋ฐœ์ƒํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์˜์กด์„ฑ์„ ์ง€์ •ํ•˜์—ฌ ์ด๋ฅผ ์ œ์–ดํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ฐฐ์šฐ๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค.
  3. ํ•„์š”ํ•œ ๊ฒฝ์šฐ ํด๋ฆฐ์—… ํ•จ์ˆ˜ ์ถ”๊ฐ€. ์ผ๋ถ€ Effect๋Š” ์ˆ˜ํ–‰ ์ค‘์ด๋˜ ์ž‘์—…์„ ์ค‘์ง€, ์ทจ์†Œ ๋˜๋Š” ์ •๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ง€์ •ํ•ด์•ผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, "์—ฐ๊ฒฐ"์€ "์—ฐ๊ฒฐ ํ•ด์ œ"๊ฐ€ ํ•„์š”ํ•˜๋ฉฐ, "๊ตฌ๋…"์€ "๊ตฌ๋… ์ทจ์†Œ"๊ฐ€ ํ•„์š”ํ•˜๊ณ , "๋ถˆ๋Ÿฌ์˜ค๊ธฐ(fetch)"๋Š” "์ทจ์†Œ" ๋˜๋Š” "๋ฌด์‹œ"๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฐ ๊ฒฝ์šฐ์— Effect์—์„œ *ํด๋ฆฐ์—… ํ•จ์ˆ˜(cleanup function)*๋ฅผ ๋ฐ˜ํ™˜ํ•˜์—ฌ ์–ด๋–ป๊ฒŒ ์ˆ˜ํ–‰ํ•˜๋Š”์ง€ ๋ฐฐ์šฐ๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๊ฐ ๋‹จ๊ณ„๋ฅผ ์ž์„ธํžˆ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

1๋‹จ๊ณ„: Effect ์„ ์–ธํ•˜๊ธฐ {/step-1-declare-an-effect/}

์ปดํฌ๋„ŒํŠธ ๋‚ด์—์„œ Effect๋ฅผ ์„ ์–ธํ•˜๋ ค๋ฉด, React์—์„œ useEffect ํ›…์„ import ํ•˜์„ธ์š”.

import { useEffect } from 'react';

๊ทธ๋Ÿฐ ๋‹ค์Œ, ์ปดํฌ๋„ŒํŠธ์˜ ์ตœ์ƒ์œ„ ๋ ˆ๋ฒจ์—์„œ ํ˜ธ์ถœํ•˜๊ณ  Effect ๋‚ด๋ถ€์— ์ฝ”๋“œ๋ฅผ ๋„ฃ์œผ์„ธ์š”.

function MyComponent() {
  useEffect(() => {
    // ์ด๊ณณ์˜ ์ฝ”๋“œ๋Š” *๋ชจ๋“ * ๋ Œ๋”๋ง ํ›„์— ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค
  });
  return <div />;
}

์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ Œ๋”๋ง ๋  ๋•Œ๋งˆ๋‹ค React๋Š” ํ™”๋ฉด์„ ์—…๋ฐ์ดํŠธํ•œ ๋‹ค์Œ useEffect ๋‚ด๋ถ€์˜ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์‹œ ๋งํ•ด, useEffect๋Š” ํ™”๋ฉด์— ๋ Œ๋”๋ง์ด ๋ฐ˜์˜๋  ๋•Œ๊นŒ์ง€ ์ฝ”๋“œ ์‹คํ–‰์„ "์ง€์—ฐ"์‹œํ‚ต๋‹ˆ๋‹ค.

์ด์ œ ์™ธ๋ถ€ ์‹œ์Šคํ…œ๊ณผ ๋™๊ธฐํ™”ํ•˜๊ธฐ ์œ„ํ•ด ์–ด๋–ป๊ฒŒ Effect๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. <VideoPlayer>๋ผ๋Š” React ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด ์ปดํฌ๋„ŒํŠธ๋ฅผ isPlaying์ด๋ผ๋Š” props๋ฅผ ํ†ตํ•ด ์žฌ์ƒ ์ค‘์ธ์ง€ ์ผ์‹œ ์ •์ง€ ์ƒํƒœ์ธ์ง€ ์ œ์–ดํ•˜๋Š” ๊ฒƒ์ด ์ข‹์•„ ๋ณด์ด๋„ค์š”.

<VideoPlayer isPlaying={isPlaying} />;

์ปค์Šคํ…€ VideoPlayer ์ปดํฌ๋„ŒํŠธ๋Š” ๋‚ด์žฅ ๋ธŒ๋ผ์šฐ์ € <video> ํƒœ๊ทธ๋ฅผ ๋ Œ๋”๋ง ํ•ฉ๋‹ˆ๋‹ค.

function VideoPlayer({ src, isPlaying }) {
  // TODO: isPlaying์„ ํ™œ์šฉํ•˜์—ฌ ๋ฌด์–ธ๊ฐ€ ์ˆ˜ํ–‰ํ•˜๊ธฐ
  return <video src={src} />;
}

๊ทธ๋Ÿฌ๋‚˜ <video> ํƒœ๊ทธ์—๋Š” isPlaying prop์ด ์—†์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ์ œ์–ดํ•˜๋Š” ์œ ์ผํ•œ ๋ฐฉ๋ฒ•์€ DOM ์š”์†Œ์—์„œ ์ˆ˜๋™์œผ๋กœ play() ๋ฐ pause() ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. isPlaying prop์˜ ๊ฐ’(ํ˜„์žฌ ๋น„๋””์˜ค๊ฐ€ ์žฌ์ƒ ์ค‘์ธ์ง€ ์—ฌ๋ถ€)์„ play() ๋ฐ pause()์™€ ๊ฐ™์€ ํ˜ธ์ถœ๊ณผ ๋™๊ธฐํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋จผ์ € <video> DOM ๋…ธ๋“œ์˜ ref๋ฅผ ๊ฐ€์ ธ์™€์•ผ ํ•ฉ๋‹ˆ๋‹ค.

play() ๋˜๋Š” pause()๋ฅผ ๋ Œ๋”๋ง ์ค‘์— ํ˜ธ์ถœํ•˜๋ ค๊ณ  ์‹œ๋„ํ•  ์ˆ˜ ์žˆ๊ฒ ์ง€๋งŒ, ์ด๋Š” ์˜ฌ๋ฐ”๋ฅธ ์ ‘๊ทผ์ด ์•„๋‹™๋‹ˆ๋‹ค.

import { useState, useRef, useEffect } from 'react';

function VideoPlayer({ src, isPlaying }) {
  const ref = useRef(null);

  if (isPlaying) {
    ref.current.play();  // ๋ Œ๋”๋ง ์ค‘์— ์ด๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์ด ํ—ˆ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  } else {
    ref.current.pause(); // ์—ญ์‹œ ์ด๋ ‡๊ฒŒ ํ˜ธ์ถœํ•˜๋ฉด ๋ฐ”๋กœ ์œ„์˜ ํ˜ธ์ถœ๊ณผ ์ถฉ๋Œ์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.
  }

  return <video ref={ref} src={src} loop playsInline />;
}

export default function App() {
  const [isPlaying, setIsPlaying] = useState(false);
  return (
    <>
      <button onClick={() => setIsPlaying(!isPlaying)}>
        {isPlaying ? '์ผ์‹œ์ •์ง€' : '์žฌ์ƒ'}
      </button>
      <VideoPlayer
        isPlaying={isPlaying}
        src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
      />
    </>
  );
}
button { display: block; margin-bottom: 20px; }
video { width: 250px; }

์ด ์ฝ”๋“œ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์€ ์ด์œ ๋Š” ๋ Œ๋”๋ง ์ค‘์— DOM ๋…ธ๋“œ๋ฅผ ์กฐ์ž‘ํ•˜๋ ค๊ณ  ์‹œ๋„ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. React์—์„œ๋Š” ๋ Œ๋”๋ง์ด JSX์˜ ์ˆœ์ˆ˜ํ•œ ๊ณ„์‚ฐ์ด์–ด์•ผ ํ•˜๋ฉฐ, DOM ์ˆ˜์ •๊ณผ ๊ฐ™์€ ๋ถ€์ˆ˜ ํšจ๊ณผ๋ฅผ ํฌํ•จํ•ด์„œ๋Š” ์•ˆ๋ฉ๋‹ˆ๋‹ค.

๊ฒŒ๋‹ค๊ฐ€, ์ฒ˜์Œ์œผ๋กœ VideoPlayer๊ฐ€ ํ˜ธ์ถœ๋  ๋•Œ ํ•ด๋‹น DOM์ด ์•„์ง ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค! React๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ JSX๋ฅผ ๋ฐ˜ํ™˜ํ•  ๋•Œ๊นŒ์ง€ ์–ด๋–ค DOM์„ ์ƒ์„ฑํ• ์ง€ ๋ชจ๋ฅด๊ธฐ ๋•Œ๋ฌธ์— play() ๋˜๋Š” pause()๋ฅผ ํ˜ธ์ถœํ•  DOM ๋…ธ๋“œ๊ฐ€ ์•„์ง ์—†์Šต๋‹ˆ๋‹ค.

ํ•ด๊ฒฐ์ฑ…์€ ๋ถ€์ˆ˜ ํšจ๊ณผ๋ฅผ ๋ Œ๋”๋ง ์—ฐ์‚ฐ์—์„œ ๋ถ„๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด useEffect๋กœ ๊ฐ์‹ธ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

import { useEffect, useRef } from 'react';

function VideoPlayer({ src, isPlaying }) {
  const ref = useRef(null);

  useEffect(() => {
    if (isPlaying) {
      ref.current.play();
    } else {
      ref.current.pause();
    }
  });

  return <video ref={ref} src={src} loop playsInline />;
}

DOM ์—…๋ฐ์ดํŠธ๋ฅผ Effect๋กœ ๊ฐ์‹ธ๋ฉด React๊ฐ€ ํ™”๋ฉด์„ ์—…๋ฐ์ดํŠธํ•œ ๋‹ค์Œ์— Effect๊ฐ€ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

VideoPlayer ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ Œ๋”๋ง ๋  ๋•Œ(์ฒ˜์Œ ํ˜ธ์ถœํ•˜๊ฑฐ๋‚˜ ๋‹ค์‹œ ๋ Œ๋”๋ง ํ•  ๋•Œ) ๋ช‡ ๊ฐ€์ง€ ์ผ์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ๋จผ์ € React๋Š” ํ™”๋ฉด์„ ์—…๋ฐ์ดํŠธํ•˜์—ฌ <video> ํƒœ๊ทธ๊ฐ€ ์˜ฌ๋ฐ”๋ฅธ ์†์„ฑ๊ณผ ํ•จ๊ป˜ DOM์— ์žˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ React๋Š” Effect๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๋งˆ์ง€๋ง‰์œผ๋กœ Effect์—์„œ๋Š” isPlaying ๊ฐ’์— ๋”ฐ๋ผ play() ๋˜๋Š” pause()๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

"์žฌ์ƒ" ๋˜๋Š” "์ผ์‹œ ์ •์ง€"๋ฅผ ์—ฌ๋Ÿฌ ๋ฒˆ ๋ˆŒ๋Ÿฌ๋ณด๊ณ  ๋น„๋””์˜ค ํ”Œ๋ ˆ์ด์–ด๊ฐ€ isPlaying ๊ฐ’๊ณผ ๋™๊ธฐํ™”๋˜๋Š”์ง€ ํ™•์ธํ•ด ๋ณด์„ธ์š”.

import { useState, useRef, useEffect } from 'react';

function VideoPlayer({ src, isPlaying }) {
  const ref = useRef(null);

  useEffect(() => {
    if (isPlaying) {
      ref.current.play();
    } else {
      ref.current.pause();
    }
  });

  return <video ref={ref} src={src} loop playsInline />;
}

export default function App() {
  const [isPlaying, setIsPlaying] = useState(false);
  return (
    <>
      <button onClick={() => setIsPlaying(!isPlaying)}>
        {isPlaying ? '์ผ์‹œ ์ •์ง€' : '์žฌ์ƒ'}
      </button>
      <VideoPlayer
        isPlaying={isPlaying}
        src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
      />
    </>
  );
}
button { display: block; margin-bottom: 20px; }
video { width: 250px; }

์ด ์˜ˆ์‹œ์—์„œ React ์ƒํƒœ์™€ ๋™๊ธฐํ™”๋œ "์™ธ๋ถ€ ์‹œ์Šคํ…œ"์€ ๋ธŒ๋ผ์šฐ์ € ๋ฏธ๋””์–ด API์˜€์Šต๋‹ˆ๋‹ค. ์ด์™€ ๋น„์Šทํ•œ ์ ‘๊ทผ ๋ฐฉ์‹์œผ๋กœ React๊ฐ€ ์•„๋‹Œ ๋ ˆ๊ฑฐ์‹œ ์ฝ”๋“œ(์˜ˆ: jQuery ํ”Œ๋Ÿฌ๊ทธ์ธ)๋ฅผ ์„ ์–ธ์ ์ธ React ์ปดํฌ๋„ŒํŠธ๋กœ ๊ฐ์‹ธ๋Š” ๋ฐ์—๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์‹ค์ œ๋กœ ๋น„๋””์˜ค ํ”Œ๋ ˆ์ด์–ด๋ฅผ ์ œ์–ดํ•˜๋Š” ๊ฒƒ์€ ํ›จ์”ฌ ๋ณต์žกํ•ฉ๋‹ˆ๋‹ค. play()๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์ด ์‹คํŒจํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์‚ฌ์šฉ์ž๋Š” ์ปดํฌ๋„ŒํŠธ์˜ UI๊ฐ€ ์•„๋‹Œ ๋ธŒ๋ผ์šฐ์ € ๋‚ด์žฅ ์ปจํŠธ๋กค์„ ์‚ฌ์šฉํ•˜์—ฌ ๋™์˜์ƒ์„ ์žฌ์ƒ ๋˜๋Š” ์ผ์‹œ ์ •์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์˜ˆ์‹œ๋Š” ๋งค์šฐ ๋‹จ์ˆœํ™”๋˜์—ˆ๊ณ  ๋ถˆ์™„์ „ํ•œ ๊ฒƒ์ž„์„ ์œ ์˜ํ•ด์ฃผ์„ธ์š”.

๊ธฐ๋ณธ์ ์œผ๋กœ, Effect๋Š” ๋ชจ๋“  ๋ Œ๋”๋ง ํ›„์— ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์ด์œ ๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฝ”๋“œ๋Š” ๋ฌดํ•œ ๋ฃจํ”„๋ฅผ ๋งŒ๋“ค์–ด๋‚ผ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

const [count, setCount] = useState(0);
useEffect(() => {
  setCount(count + 1);
});

Effect๋Š” ๋ Œ๋”๋ง์˜ ๊ฒฐ๊ณผ๋กœ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. state๋ฅผ ์„ค์ •ํ•˜๋ฉด ๋ Œ๋”๋ง์ด ํŠธ๋ฆฌ๊ฑฐ๋ฉ๋‹ˆ๋‹ค. Effect ์•ˆ์—์„œ ์ฆ‰์‹œ ์ƒํƒœ๋ฅผ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์€ ๊ธฐ๊ณ„์˜ ์ „์› ํ”Œ๋Ÿฌ๊ทธ๋ฅผ ๊ธฐ๊ณ„ ๊ทธ ์ž์ฒด์— ์—ฐ๊ฒฐํ•˜๋Š” ๊ฒƒ๊ณผ ๋น„์Šทํ•ฉ๋‹ˆ๋‹ค. Effect๊ฐ€ ์‹คํ–‰๋˜๊ณ  ์ƒํƒœ๊ฐ€ ์„ค์ •๋˜๋ฉด ์žฌ๋ Œ๋”๋ง์ด ๋ฐœ์ƒํ•˜๊ณ , Effect๊ฐ€ ๋‹ค์‹œ ์‹คํ–‰๋˜๊ณ  ์ƒํƒœ๊ฐ€ ์„ค์ •๋˜๋ฉด ๋˜ ๋‹ค๋ฅธ ์žฌ๋ Œ๋”๋ง์ด ๋ฐœ์ƒํ•˜๋ฉฐ, ์ด๋Ÿฐ ์‹์œผ๋กœ ๊ณ„์†๋ฉ๋‹ˆ๋‹ค.

Effect๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์™ธ๋ถ€ ์‹œ์Šคํ…œ๊ณผ ๋™๊ธฐํ™”ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์™ธ๋ถ€ ์‹œ์Šคํ…œ์ด ์—†๊ณ  ๋‹ค๋ฅธ ์ƒํƒœ์— ๊ธฐ๋ฐ˜ํ•˜์—ฌ ์ƒํƒœ๋ฅผ ์กฐ์ •ํ•˜๋ ค๋Š” ๊ฒฝ์šฐ์—๋Š” Effect๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

2๋‹จ๊ณ„: Effect์˜ ์˜์กด์„ฑ ์ง€์ •ํ•˜๊ธฐ {/step-2-specify-the-effect-dependencies/}

๊ธฐ๋ณธ์ ์œผ๋กœ, Effect๋Š” ๋ชจ๋“  ๋ Œ๋”๋ง ํ›„์— ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์ข…์ข… ์›ํ•˜๋Š” ๋™์ž‘์ด ์•„๋‹ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

  • ๋•Œ๋•Œ๋กœ ๋А๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์™ธ๋ถ€ ์‹œ์Šคํ…œ๊ณผ ๋™๊ธฐํ™”ํ•˜๋Š” ๊ฒƒ์ด ํ•ญ์ƒ ์ฆ‰์‹œ ์ด๋ฃจ์–ด์ง€์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ํ•„์š”ํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ์—๋Š” ์‹คํ–‰์„ ๊ฑด๋„ˆ๋›ฐ๊ณ  ์‹ถ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋ชจ๋“  ํ‚ค ์ž…๋ ฅ๋งˆ๋‹ค ์ฑ„ํŒ… ์„œ๋ฒ„์— ๋‹ค์‹œ ์—ฐ๊ฒฐํ•˜๊ธธ ์›ํ•˜์ง€ ์•Š์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.
  • ๋•Œ๋•Œ๋กœ ์ž˜๋ชป๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋ชจ๋“  ํ‚ค ์ž…๋ ฅ๋งˆ๋‹ค ์ปดํฌ๋„ŒํŠธ fade-in ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ํŠธ๋ฆฌ๊ฑฐํ•˜๊ธธ ์›ํ•˜์ง€ ์•Š์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์• ๋‹ˆ๋ฉ”์ด์…˜์€ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ฒ˜์Œ ๋‚˜ํƒ€๋‚  ๋•Œ์—๋งŒ ํ•œ ๋ฒˆ ์‹คํ–‰๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ด ๋ฌธ์ œ๋ฅผ ์„ค๋ช…ํ•˜๊ธฐ ์œ„ํ•ด ์ด์ „ ์˜ˆ์‹œ์— ๋ช‡ ๊ฐ€์ง€ console.log ํ˜ธ์ถœ๊ณผ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์˜ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š” ํ…์ŠคํŠธ ์ž…๋ ฅ์„ ์ถ”๊ฐ€ํ•œ ์˜ˆ์‹œ๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์ž…๋ ฅํ•  ๋•Œ Effect๊ฐ€ ๋‹ค์‹œ ์‹คํ–‰๋˜๋Š” ๊ฒƒ์„ ์ฃผ๋ชฉํ•˜์„ธ์š”.

import { useState, useRef, useEffect } from 'react';

function VideoPlayer({ src, isPlaying }) {
  const ref = useRef(null);

  useEffect(() => {
    if (isPlaying) {
      console.log('video.play() ํ˜ธ์ถœ');
      ref.current.play();
    } else {
      console.log('video.pause() ํ˜ธ์ถœ');
      ref.current.pause();
    }
  });

  return <video ref={ref} src={src} loop playsInline />;
}

export default function App() {
  const [isPlaying, setIsPlaying] = useState(false);
  const [text, setText] = useState('');
  return (
    <>
      <input value={text} onChange={e => setText(e.target.value)} />
      <button onClick={() => setIsPlaying(!isPlaying)}>
        {isPlaying ? '์ผ์‹œ ์ •์ง€' : '์žฌ์ƒ'}
      </button>
      <VideoPlayer
        isPlaying={isPlaying}
        src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
      />
    </>
  );
}
input, button { display: block; margin-bottom: 20px; }
video { width: 250px; }

React์—๊ฒŒ Effect๋ฅผ ๋ถˆํ•„์š”ํ•˜๊ฒŒ ๋‹ค์‹œ ์‹คํ–‰ํ•˜์ง€ ์•Š๋„๋ก ์ง€์‹œํ•˜๋ ค๋ฉด useEffect ํ˜ธ์ถœ์˜ ๋‘ ๋ฒˆ์งธ ์ธ์ž๋กœ ์˜์กด์„ฑ(dependencies) ๋ฐฐ์—ด์„ ์ง€์ •ํ•˜์„ธ์š”. ๋จผ์ € ์œ„์˜ ์˜ˆ์‹œ์— ๋นˆ [] ๋ฐฐ์—ด์„ 14๋ฒˆ์งธ ์ค„์— ์ถ”๊ฐ€ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

  useEffect(() => {
    // ...
  }, []);

'isPlaying'์— ๋Œ€ํ•œ ์˜์กด์„ฑ์ด ๋ˆ„๋ฝ๋˜์—ˆ๋‹ค๋Š” ์˜ค๋ฅ˜๊ฐ€ ํ‘œ์‹œ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

import { useState, useRef, useEffect } from 'react';

function VideoPlayer({ src, isPlaying }) {
  const ref = useRef(null);

  useEffect(() => {
    if (isPlaying) {
      console.log('video.play() ํ˜ธ์ถœ');
      ref.current.play();
    } else {
      console.log('video.pause() ํ˜ธ์ถœ');
      ref.current.pause();
    }
  }, []); // ์ด ์ฝ”๋“œ๋Š” ์—๋Ÿฌ๋ฅผ ์œ ๋ฐœํ•ฉ๋‹ˆ๋‹ค

  return <video ref={ref} src={src} loop playsInline />;
}

export default function App() {
  const [isPlaying, setIsPlaying] = useState(false);
  const [text, setText] = useState('');
  return (
    <>
      <input value={text} onChange={e => setText(e.target.value)} />
      <button onClick={() => setIsPlaying(!isPlaying)}>
        {isPlaying ? '์ผ์‹œ ์ •์ง€' : '์žฌ์ƒ'}
      </button>
      <VideoPlayer
        isPlaying={isPlaying}
        src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
      />
    </>
  );
}
input, button { display: block; margin-bottom: 20px; }
video { width: 250px; }

๋ฌธ์ œ๋Š” Effect ๋‚ด๋ถ€์˜ ์ฝ”๋“œ๊ฐ€ ์–ด๋–ค ์ž‘์—…์„ ์ˆ˜ํ–‰ํ• ์ง€ ๊ฒฐ์ •ํ•˜๊ธฐ ์œ„ํ•ด isPlaying prop์— ์˜์กดํ•˜์ง€๋งŒ ์ด ์˜์กด์„ฑ์ด ๋ช…์‹œ์ ์œผ๋กœ ์„ ์–ธ๋˜์ง€ ์•Š์•˜๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค๋ฉด ์˜์กด์„ฑ ๋ฐฐ์—ด์— isPlaying์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”.

  useEffect(() => {
    if (isPlaying) { // ์—ฌ๊ธฐ์„œ ์‚ฌ์šฉํ•˜๋‹ˆ๊นŒ...
      // ...
    } else {
      // ...
    }
  }, [isPlaying]); // ...์—ฌ๊ธฐ์— ์„ ์–ธ๋˜์–ด์•ผ๊ฒ ๋„ค!

์ด์ œ ๋ชจ๋“  ์˜์กด์„ฑ์ด ์˜์กด์„ฑ ๋ฐฐ์—ด ์•ˆ์— ์„ ์–ธ๋˜์–ด ์˜ค๋ฅ˜๊ฐ€ ์—†์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์˜์กด์„ฑ ๋ฐฐ์—ด๋กœ [isPlaying]์„ ์ง€์ •ํ•˜๋ฉด React์—๊ฒŒ ์ด์ „ ๋ Œ๋”๋ง ์ค‘์— isPlaying์ด ์ด์ „๊ณผ ๋™์ผํ•˜๋‹ค๋ฉด Effect๋ฅผ ๋‹ค์‹œ ์‹คํ–‰ํ•˜์ง€ ์•Š๋„๋ก ํ•ด์•ผ ํ•œ๋‹ค๊ณ  ์•Œ๋ ค์ค๋‹ˆ๋‹ค. ์ด ๋ณ€๊ฒฝ์œผ๋กœ ์ž…๋ ฅ๋ž€์— ์ž…๋ ฅ์„ ์ž…๋ ฅํ•˜๋ฉด Effect๊ฐ€ ๋‹ค์‹œ ์‹คํ–‰๋˜์ง€ ์•Š๊ณ , ์žฌ์ƒ/์ผ์‹œ ์ •์ง€ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด Effect๊ฐ€ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

import { useState, useRef, useEffect } from 'react';

function VideoPlayer({ src, isPlaying }) {
  const ref = useRef(null);

  useEffect(() => {
    if (isPlaying) {
      console.log('video.play() ํ˜ธ์ถœ');
      ref.current.play();
    } else {
      console.log('video.pause() ํ˜ธ์ถœ');
      ref.current.pause();
    }
  }, [isPlaying]);

  return <video ref={ref} src={src} loop playsInline />;
}

export default function App() {
  const [isPlaying, setIsPlaying] = useState(false);
  const [text, setText] = useState('');
  return (
    <>
      <input value={text} onChange={e => setText(e.target.value)} />
      <button onClick={() => setIsPlaying(!isPlaying)}>
        {isPlaying ? '์ผ์‹œ ์ •์ง€' : '์žฌ์ƒ'}
      </button>
      <VideoPlayer
        isPlaying={isPlaying}
        src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
      />
    </>
  );
}
input, button { display: block; margin-bottom: 20px; }
video { width: 250px; }

์˜์กด์„ฑ ๋ฐฐ์—ด์—๋Š” ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ข…์†์„ฑ์„ ํฌํ•จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. React๋Š” ์ง€์ •ํ•œ ๋ชจ๋“  ์ข…์†์„ฑ์ด ์ด์ „ ๋ Œ๋”๋ง์˜ ๊ทธ๊ฒƒ๊ณผ ์ •ํ™•ํžˆ ๋™์ผํ•œ ๊ฐ’์„ ๊ฐ€์ง„ ๊ฒฝ์šฐ์—๋งŒ Effect๋ฅผ ๋‹ค์‹œ ์‹คํ–‰ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. React๋Š” Object.is ๋น„๊ต๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ข…์†์„ฑ ๊ฐ’์„ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ useEffect ์ฐธ์กฐ ๋ฌธ์„œ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

์˜์กด์„ฑ์„ "์„ ํƒ"ํ•  ์ˆ˜ ์—†๋‹ค๋Š” ์ ์— ์œ ์˜ํ•˜์„ธ์š”. ์˜์กด์„ฑ ๋ฐฐ์—ด์— ์ง€์ •ํ•œ ์ข…์†์„ฑ์ด Effect ๋‚ด๋ถ€์˜ ์ฝ”๋“œ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ React๊ฐ€ ๊ธฐ๋Œ€ํ•˜๋Š” ๊ฒƒ๊ณผ ์ผ์น˜ํ•˜์ง€ ์•Š์œผ๋ฉด ๋ฆฐํŠธ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์ฝ”๋“œ ๋‚ด์˜ ๋งŽ์€ ๋ฒ„๊ทธ๋ฅผ ์žก์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฝ”๋“œ๊ฐ€ ๋‹ค์‹œ ์‹คํ–‰๋˜๊ธธ ์›ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ, Effect ๋‚ด๋ถ€๋ฅผ ์ˆ˜์ •ํ•˜์—ฌ ๊ทธ ์ข…์†์„ฑ์ด "ํ•„์š”"ํ•˜์ง€ ์•Š๋„๋ก ๋งŒ๋“œ์„ธ์š”.

์˜์กด์„ฑ ๋ฐฐ์—ด์ด ์—†๋Š” ๊ฒฝ์šฐ์™€ ๋นˆ [] ์˜์กด์„ฑ ๋ฐฐ์—ด์ด ์žˆ๋Š” ๊ฒฝ์šฐ์˜ ๋™์ž‘์ด ๋‹ค๋ฆ…๋‹ˆ๋‹ค.

useEffect(() => {
  // ๋ชจ๋“  ๋ Œ๋”๋ง ํ›„์— ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค
});

useEffect(() => {
  // ๋งˆ์šดํŠธ๋  ๋•Œ๋งŒ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค (์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋‚˜ํƒ€๋‚  ๋•Œ)
}, []);

useEffect(() => {
 // ๋งˆ์šดํŠธ๋  ๋•Œ ์‹คํ–‰๋˜๋ฉฐ, *๋˜ํ•œ* ๋ Œ๋”๋ง ์ดํ›„์— a ๋˜๋Š” b ์ค‘ ํ•˜๋‚˜๋ผ๋„ ๋ณ€๊ฒฝ๋œ ๊ฒฝ์šฐ์—๋„ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค
}, [a, b]);

๋‹ค์Œ ๋‹จ๊ณ„์—์„œ "๋งˆ์šดํŠธ(mount)"๊ฐ€ ๋ฌด์—‡์„ ์˜๋ฏธํ•˜๋Š”์ง€ ์ž์„ธํžˆ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์™œ ref๋Š” ์˜์กด์„ฑ ๋ฐฐ์—ด์—์„œ ์ƒ๋žตํ•ด๋„ ๋˜๋‚˜์š”? {/why-was-the-ref-omitted-from-the-dependency-array/}

์ด Effect๋Š” ref์™€ isPlaying์„ ๋ชจ๋‘ ์‚ฌ์šฉํ•˜์ง€๋งŒ, ์˜์กด์„ฑ ๋ฐฐ์—ด ์•ˆ์— ์„ ์–ธ๋œ ๊ฒƒ์€ isPlaying ๋ฟ์ž…๋‹ˆ๋‹ค.

function VideoPlayer({ src, isPlaying }) {
  const ref = useRef(null);
  useEffect(() => {
    if (isPlaying) {
      ref.current.play();
    } else {
      ref.current.pause();
    }
  }, [isPlaying]);

์ด๊ฒƒ์€ ref ๊ฐ์ฒด๊ฐ€ *์•ˆ์ •๋œ ์‹๋ณ„์„ฑ(stable identity)*์„ ๊ฐ€์ง€๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. React๋Š” ๋™์ผํ•œ useRef ํ˜ธ์ถœ์—์„œ ํ•ญ์ƒ ๊ฐ™์€ ๊ฐ์ฒด๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Œ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ฐ์ฒด๋Š” ์ ˆ๋Œ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์ž์ฒด์ ์œผ๋กœ Effect๋ฅผ ๋‹ค์‹œ ์‹คํ–‰์‹œํ‚ค์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ref๋Š” ์˜์กด์„ฑ ๋ฐฐ์—ด์— ํฌํ•จํ•˜๋“  ํฌํ•จํ•˜์ง€ ์•Š๋“  ์ƒ๊ด€์—†์Šต๋‹ˆ๋‹ค. ํฌํ•จํ•ด๋„ ๋ฌธ์ œ์—†์Šต๋‹ˆ๋‹ค.

function VideoPlayer({ src, isPlaying }) {
  const ref = useRef(null);
  useEffect(() => {
    if (isPlaying) {
      ref.current.play();
    } else {
      ref.current.pause();
    }
  }, [isPlaying, ref]);

useState๋กœ ๋ฐ˜ํ™˜๋˜๋Š” set ํ•จ์ˆ˜๋“ค๋„ ์•ˆ์ •๋œ ์‹๋ณ„์„ฑ์„ ๊ฐ€์ง€๊ธฐ ๋•Œ๋ฌธ์—, ์ข…์ข… ์ด๋Ÿฌํ•œ ํ•จ์ˆ˜๋“ค๋„ ์˜์กด์„ฑ์—์„œ ์ƒ๋žต๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฆฐํ„ฐ๊ฐ€ ์˜์กด์„ฑ์„ ์ƒ๋žตํ•ด๋„ ์˜ค๋ฅ˜๋ฅผ ํ‘œ์‹œํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด ๊ทธ๋ ‡๊ฒŒ ํ•ด๋„ ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค.

์•ˆ์ •๋œ ์‹๋ณ„์„ฑ์„ ๊ฐ€์ง„ ์˜์กด์„ฑ์„ ์ƒ๋žตํ•˜๋Š” ๊ฒƒ์€ ๋ฆฐํ„ฐ๊ฐ€ ํ•ด๋‹น ๊ฐ์ฒด๊ฐ€ ์•ˆ์ •์ ์ž„์„ "์•Œ ์ˆ˜" ์žˆ๋Š” ๊ฒฝ์šฐ์—๋งŒ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ref๊ฐ€ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์—์„œ ์ „๋‹ฌ๋˜์—ˆ๋‹ค๋ฉด, ์˜์กด์„ฑ ๋ฐฐ์—ด์— ๋ช…์‹œํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์ข‹์€ ์ ‘๊ทผ ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ•ญ์ƒ ๋™์ผํ•œ ref๋ฅผ ์ „๋‹ฌํ•˜๋Š”์ง€ ๋˜๋Š” ์—ฌ๋Ÿฌ ref ์ค‘ ํ•˜๋‚˜๋ฅผ ์กฐ๊ฑด๋ถ€๋กœ ์ „๋‹ฌํ•˜๋Š”์ง€ ์•Œ ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋‹น์‹ ์˜ Effect๋Š” ์ „๋‹ฌ๋˜๋Š” ref์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์งˆ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

3๋‹จ๊ณ„: ํ•„์š”ํ•˜๋‹ค๋ฉด ํด๋ฆฐ์—…์„ ์ถ”๊ฐ€ํ•˜์„ธ์š” {/step-3-add-cleanup-if-needed/}

๋‹ค๋ฅธ ์˜ˆ์‹œ๋ฅผ ๊ณ ๋ คํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž์—๊ฒŒ ํ‘œ์‹œ๋  ๋•Œ ์ฑ„ํŒ… ์„œ๋ฒ„์— ์—ฐ๊ฒฐํ•ด์•ผ ํ•˜๋Š” ChatRoom ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ž‘์„ฑ ์ค‘์ž…๋‹ˆ๋‹ค. createConnection() API๊ฐ€ ์ฃผ์–ด์ง€๋ฉฐ, ์ด API๋Š” connect() ๋ฐ disconnect() ๋ฉ”์„œ๋“œ๋ฅผ ๊ฐ€์ง„ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž์—๊ฒŒ ํ‘œ์‹œ๋˜๋Š” ๋™์•ˆ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ฑ„ํŒ… ์„œ๋ฒ„์™€์˜ ์—ฐ๊ฒฐ์„ ์œ ์ง€ํ•˜๋ ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ• ๊นŒ์š”?

๋จผ์ € Effect๋ฅผ ์ž‘์„ฑํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

useEffect(() => {
  const connection = createConnection();
  connection.connect();
});

๋งค๋ฒˆ ์žฌ๋ Œ๋”๋ง ํ›„์— ์ฑ„ํŒ… ์„œ๋ฒ„์— ์—ฐ๊ฒฐํ•˜๋Š” ๊ฒƒ์€ ๋А๋ฆฌ๋ฏ€๋กœ ์˜์กด์„ฑ ๋ฐฐ์—ด์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

useEffect(() => {
  const connection = createConnection();
  connection.connect();
}, []);

Effect ๋‚ด๋ถ€์˜ ์ฝ”๋“œ๋Š” ์–ด๋– ํ•œ props๋‚˜ ์ƒํƒœ๋„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ, ์˜์กด์„ฑ ๋ฐฐ์—ด์€ [] (๋นˆ ๋ฐฐ์—ด)์ž…๋‹ˆ๋‹ค. ์ด๋Š” React์—๊ฒŒ ์ด ์ฝ”๋“œ๋ฅผ ์ปดํฌ๋„ŒํŠธ๊ฐ€ "๋งˆ์šดํŠธ"๋  ๋•Œ๋งŒ ์‹คํ–‰ํ•˜๋„๋ก ์•Œ๋ ค์ค๋‹ˆ๋‹ค. ์ฆ‰, ํ™”๋ฉด์— ์ฒ˜์Œ์œผ๋กœ ๋‚˜ํƒ€๋‚  ๋•Œ์—๋งŒ ์‹คํ–‰๋˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

์ด ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

import { useEffect } from 'react';
import { createConnection } from './chat.js';

export default function ChatRoom() {
  useEffect(() => {
    const connection = createConnection();
    connection.connect();
  }, []);
  return <h1>์ฑ„ํŒ…์— ์˜ค์‹ ๊ฑธ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค!</h1>;
}
export function createConnection() {
  // ์‹ค์ œ ๊ตฌํ˜„์€ ์ •๋ง๋กœ ์ฑ„ํŒ… ์„œ๋ฒ„์— ์—ฐ๊ฒฐํ•˜๋Š” ๊ฒƒ์ด ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  return {
    connect() {
      console.log('โœ… ์—ฐ๊ฒฐ ์ค‘...');
    },
    disconnect() {
      console.log('โŒ ์—ฐ๊ฒฐ์ด ๋Š๊ฒผ์Šต๋‹ˆ๋‹ค.');
    }
  };
}
input { display: block; margin-bottom: 20px; }

์ด Effect๋Š” ๋งˆ์šดํŠธ๋  ๋•Œ๋งŒ ์‹คํ–‰๋˜๋ฏ€๋กœ ์ฝ˜์†”์— "โœ… ์—ฐ๊ฒฐ ์ค‘..."์ด ํ•œ ๋ฒˆ ์ถœ๋ ฅ๋  ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ฝ˜์†”์„ ํ™•์ธํ•ด ๋ณด๋ฉด "โœ… ์—ฐ๊ฒฐ ์ค‘..."์ด ๋‘ ๋ฒˆ ์ถœ๋ ฅ๋ฉ๋‹ˆ๋‹ค. ์™œ ๊ทธ๋Ÿด๊นŒ์š”?

ChatRoom ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์—ฌ๋Ÿฌ ํ™”๋ฉด์œผ๋กœ ๊ตฌ์„ฑ๋œ ํฐ ์•ฑ์˜ ์ผ๋ถ€๋ผ๊ณ  ๊ฐ€์ •ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ChatRoom ํŽ˜์ด์ง€์—์„œ ์—ฌ์ •์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งˆ์šดํŠธ๋˜๊ณ  connection.connect()๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ ์‚ฌ์šฉ์ž๊ฐ€ ๋‹ค๋ฅธ ํ™”๋ฉด์œผ๋กœ ์ด๋™ํ•œ๋‹ค๊ณ  ์ƒ์ƒํ•ด๋ณด์„ธ์š”. ์˜ˆ๋ฅผ ๋“ค์–ด, ์„ค์ • ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ChatRoom ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งˆ์šดํŠธ ํ•ด์ œ๋ฉ๋‹ˆ๋‹ค. ๋งˆ์ง€๋ง‰์œผ๋กœ ์‚ฌ์šฉ์ž๊ฐ€ ๋’ค๋กœ ๊ฐ€๊ธฐ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๊ณ  ChatRoom์ด ๋‹ค์‹œ ๋งˆ์šดํŠธ๋ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ๋˜๋ฉด ๋‘ ๋ฒˆ์งธ ์—ฐ๊ฒฐ์ด ์„ค์ •๋˜์ง€๋งŒ ์ฒซ ๋ฒˆ์งธ ์—ฐ๊ฒฐ์€ ์ข…๋ฃŒ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค! ์‚ฌ์šฉ์ž๊ฐ€ ์•ฑ์„ ํƒ์ƒ‰ํ•˜๋Š” ๋™์•ˆ ์—ฐ๊ฒฐ์€ ์ข…๋ฃŒ๋˜์ง€ ์•Š๊ณ  ๊ณ„์† ์Œ“์ผ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ด์™€ ๊ฐ™์€ ๋ฒ„๊ทธ๋Š” ์•ฑ์˜ ์ด๊ณณ์ €๊ณณ์„ ์ˆ˜๋™์œผ๋กœ ํ…Œ์ŠคํŠธํ•ด๋ณด์ง€ ์•Š์œผ๋ฉด ๋†“์น˜๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋ฅผ ๋น ๋ฅด๊ฒŒ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ๋„๋ก React๋Š” ๊ฐœ๋ฐœ ๋ชจ๋“œ์—์„œ ์ดˆ๊ธฐ ๋งˆ์šดํŠธ ํ›„ ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ•œ ๋ฒˆ ๋‹ค์‹œ ๋งˆ์šดํŠธํ•ฉ๋‹ˆ๋‹ค.

"โœ… ์—ฐ๊ฒฐ ์ค‘..." ๋กœ๊ทธ๊ฐ€ ๋‘ ๋ฒˆ ์ถœ๋ ฅ๋˜๋Š” ๊ฒƒ์„ ๋ณด๋ฉด ๊ฒฐ๊ตญ ๋ฌด์—‡์ด ๋ฌธ์ œ์ธ์ง€ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งˆ์šดํŠธ ํ•ด์ œ๋  ๋•Œ ์—ฐ๊ฒฐ์„ ๋‹ซ์ง€ ์•Š๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐ”๋กœ ๊ทธ๊ฒƒ์ด์ฃ .

์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค๋ฉด Effect์—์„œ ํด๋ฆฐ์—… ํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

  useEffect(() => {
    const connection = createConnection();
    connection.connect();
    return () => {
      connection.disconnect();
    };
  }, []);

React๋Š” Effect๊ฐ€ ๋‹ค์‹œ ์‹คํ–‰๋˜๊ธฐ ์ „๋งˆ๋‹ค ํด๋ฆฐ์—… ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๊ณ , ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งˆ์šดํŠธ ํ•ด์ œ(์ œ๊ฑฐ)๋  ๋•Œ์—๋„ ๋งˆ์ง€๋ง‰์œผ๋กœ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ํด๋ฆฐ์—… ํ•จ์ˆ˜๊ฐ€ ๊ตฌํ˜„๋œ ๊ฒฝ์šฐ ์–ด๋–ค ์ผ์ด ์ผ์–ด๋‚˜๋Š”์ง€ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

export default function ChatRoom() {
  useEffect(() => {
    const connection = createConnection();
    connection.connect();
    return () => connection.disconnect();
  }, []);
  return <h1>์ฑ„ํŒ…์— ์˜ค์‹ ๊ฑธ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค!</h1>;
}
export function createConnection() {
  // ์‹ค์ œ ๊ตฌํ˜„์€ ์ •๋ง๋กœ ์ฑ„ํŒ… ์„œ๋ฒ„์— ์—ฐ๊ฒฐํ•˜๋Š” ๊ฒƒ์ด ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  return {
    connect() {
      console.log('โœ… ์—ฐ๊ฒฐ ์ค‘...');
    },
    disconnect() {
      console.log('โŒ ์—ฐ๊ฒฐ ํ•ด์ œ๋จ');
    }
  };
}
input { display: block; margin-bottom: 20px; }

์ด์ œ ๊ฐœ๋ฐœ ๋ชจ๋“œ์—์„œ ์„ธ ๊ฐœ์˜ ์ฝ˜์†” ๋กœ๊ทธ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

  1. "โœ… ์—ฐ๊ฒฐ ์ค‘..."
  2. "โŒ ์—ฐ๊ฒฐ ํ•ด์ œ๋จ"
  3. "โœ… ์—ฐ๊ฒฐ ์ค‘..."

์ด๊ฒƒ์ด ๊ฐœ๋ฐœ ๋ชจ๋“œ์—์„œ ์˜ฌ๋ฐ”๋ฅธ ๋™์ž‘์ž…๋‹ˆ๋‹ค. ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‹ค์‹œ ๋งˆ์šดํŠธํ•จ์œผ๋กœ์จ React๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ๋‹ค๋ฅธ ๋ถ€๋ถ„์„ ํƒ์ƒ‰ํ•˜๊ณ  ๋‹ค์‹œ ๋Œ์•„์™€๋„ ์ฝ”๋“œ๊ฐ€ ๊นจ์ง€์ง€ ์•Š์„ ๊ฒƒ์ž„์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์—ฐ๊ฒฐ์„ ํ•ด์ œํ•˜๊ณ  ๋‹ค์‹œ ์—ฐ๊ฒฐํ•˜๋Š” ๊ฒƒ์ด ๋ฐ”๋กœ ์ผ์–ด๋‚˜๋Š” ์ผ์ž…๋‹ˆ๋‹ค! ํด๋ฆฐ์—…์„ ์ž˜ ๊ตฌํ˜„ํ•˜๋ฉด Effect๋ฅผ ํ•œ ๋ฒˆ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ๊ณผ ์‹คํ–‰, ํด๋ฆฐ์—…, ์ดํ›„ ๋‹ค์‹œ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ ์‚ฌ์ด์— ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ณด์ด๋Š” ์ฐจ์ด๊ฐ€ ์—†์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ ์ค‘์—๋Š” ์—ฐ๊ฒฐ/ํ•ด์ œ ํ˜ธ์ถœ์ด ํ•˜๋‚˜ ๋” ์žˆ๋Š”๋ฐ, ์ด๋Š” React๊ฐ€ ๊ฐœ๋ฐœ ์ค‘์— ์ฝ”๋“œ๋ฅผ ๊ฒ€์‚ฌํ•˜์—ฌ ๋ฒ„๊ทธ๋ฅผ ์ฐพ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์ •์ƒ์ ์ธ ๋™์ž‘์ž…๋‹ˆ๋‹ค - ์ด๊ฒƒ์„ ์—†์• ๋ ค๊ณ  ํ•˜์ง€ ๋งˆ์„ธ์š”!

๋ฐฐํฌ ํ™˜๊ฒฝ์—์„œ๋Š” "โœ… ์—ฐ๊ฒฐ ์ค‘..."์ด ํ•œ ๋ฒˆ๋งŒ ์ถœ๋ ฅ๋ฉ๋‹ˆ๋‹ค. ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‹ค์‹œ ๋งˆ์šดํŠธํ•˜๋Š” ๊ฒƒ์€ ๊ฐœ๋ฐœ ์ค‘์—๋งŒ ๋ฐœ์ƒํ•˜๋ฉฐ ํด๋ฆฐ์—…์ด ํ•„์š”ํ•œ Effect๋ฅผ ์ฐพ์•„์ฃผ๋Š” ๋ฐ ๋„์›€์„ ์ค๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ ๋™์ž‘์—์„œ ๋ฒ—์–ด๋‚˜๋ ค๋ฉด Strict Mode๋ฅผ ๋„๋Š” ๊ฒƒ๋„ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, ์ผœ๋‘˜ ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์œ„์™€ ๊ฐ™์€ ๋งŽ์€ ๋ฒ„๊ทธ๋ฅผ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฐœ๋ฐœ ์ค‘์— Effect๊ฐ€ ๋‘ ๋ฒˆ ์‹คํ–‰๋˜๋Š” ๊ฒฝ์šฐ๋ฅผ ๋‹ค๋ฃจ๋Š” ๋ฐฉ๋ฒ• {/how-to-handle-the-effect-firing-twice-in-development/}

React๋Š” ๋งˆ์ง€๋ง‰ ์˜ˆ์‹œ์™€ ๊ฐ™์€ ๋ฒ„๊ทธ๋ฅผ ์ฐพ๊ธฐ ์œ„ํ•ด ๊ฐœ๋ฐœ ์ค‘์— ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ๋‹ค์‹œ ๋งˆ์šดํŠธํ•ฉ๋‹ˆ๋‹ค. "Effect๋ฅผ ํ•œ ๋ฒˆ ์‹คํ–‰ํ•˜๋Š” ๋ฐฉ๋ฒ•"์ด ์•„๋‹ˆ๋ผ "์–ด๋–ป๊ฒŒ Effect๊ฐ€ ๋‹ค์‹œ ๋งˆ์šดํŠธ๋œ ํ›„์—๋„ ์ž‘๋™ํ•˜๋„๋ก ๊ณ ์น  ๊ฒƒ์ธ๊ฐ€"๋ผ๋Š” ๊ฒƒ์ด ์˜ณ์€ ์งˆ๋ฌธ์ž…๋‹ˆ๋‹ค.

์ผ๋ฐ˜์ ์œผ๋กœ ์ •๋‹ต์€ ํด๋ฆฐ์—… ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํด๋ฆฐ์—… ํ•จ์ˆ˜๋Š” Effect๊ฐ€ ์ˆ˜ํ–‰ํ•˜๋˜ ์ž‘์—…์„ ์ค‘๋‹จํ•˜๊ฑฐ๋‚˜ ๋˜๋Œ๋ฆฌ๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ ์›์น™์€ ์‚ฌ์šฉ์ž๊ฐ€ Effect๊ฐ€ ํ•œ ๋ฒˆ ์‹คํ–‰๋˜๋Š” ๊ฒƒ(๋ฐฐํฌ ํ™˜๊ฒฝ๊ณผ ๊ฐ™์ด)๊ณผ ์„ค์ • โ†’ ํด๋ฆฐ์—… โ†’ ์„ค์ • ์ˆœ์„œ(๊ฐœ๋ฐœ ์ค‘์— ๋ณผ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ) ๊ฐ„์— ์ฐจ์ด๋ฅผ ๋А๋ผ์ง€ ๋ชปํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ž‘์„ฑํ•  ๋Œ€๋ถ€๋ถ„์˜ Effect๋Š” ์•„๋ž˜์˜ ์ผ๋ฐ˜์ ์ธ ํŒจํ„ด ์ค‘ ํ•˜๋‚˜์— ํ•ด๋‹น๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

Effect๊ฐ€ ๋‘ ๋ฒˆ ์‹คํ–‰๋˜๋Š” ๊ฒƒ์„ ๋ง‰๊ธฐ์œ„ํ•ด ref๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ๋งˆ์„ธ์š” {/dont-use-refs-to-prevent-effects-from-firing/}

Effect๊ฐ€ ๊ฐœ๋ฐœ ๋ชจ๋“œ์—์„œ ๋‘ ๋ฒˆ ์‹คํ–‰๋˜๋Š” ๊ฒƒ์„ ๋ง‰์œผ๋ ค๋‹ค ํ”ํžˆ ๋น ์ง€๋Š” ํ•จ์ •์€ ref๋ฅผ ์‚ฌ์šฉํ•ด Effect๊ฐ€ ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰๋˜๋„๋ก ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์œ„์˜ ๋ฒ„๊ทธ๋ฅผ useRef๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ "์ˆ˜์ •"ํ•˜๋ ค๊ณ  ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค:

  const connectionRef = useRef(null);
  useEffect(() => {
    // ๐Ÿšฉ ๋ฒ„๊ทธ๋ฅผ ์ˆ˜์ •ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค!!!
    if (!connectionRef.current) {
      connectionRef.current = createConnection();
      connectionRef.current.connect();
    }
  }, []);

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ฐœ๋ฐœ ๋ชจ๋“œ์—์„œ "โœ… ์—ฐ๊ฒฐ ์ค‘..."์ด ํ•œ ๋ฒˆ๋งŒ ๋ณด์ด์ง€๋งŒ ๋ฒ„๊ทธ๊ฐ€ ์ˆ˜์ •๋œ ๊ฑด ์•„๋‹™๋‹ˆ๋‹ค.

When the user navigates away, the connection still isn't closed and when they navigate back, a new connection is created. As the user navigates across the app, the connections would keep piling up, the same as it would before the "fix".

๋ฒ„๊ทธ๋ฅผ ์ˆ˜์ •ํ•˜๊ธฐ ์œ„ํ•ด์„  Effect๋ฅผ ๋‹จ์ˆœํžˆ ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰๋˜๋„๋ก ๋งŒ๋“œ๋Š” ๊ฒƒ์œผ๋กœ๋Š” ๋ถ€์กฑํ•ฉ๋‹ˆ๋‹ค. Effect๋Š” ์œ„์— ์žˆ๋Š” ์˜ˆ์‹œ๊ฐ€ ์—ฐ๊ฒฐ์„ ํด๋ฆฐ์—… ํ•œ๊ฒƒ์ฒ˜๋Ÿผ ๋‹ค์‹œ ๋งˆ์šดํŠธ๋œ ์ดํ›„์—๋„ ์ œ๋Œ€๋กœ ๋™์ž‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์•„๋ž˜์— ์žˆ๋Š” ์ผ๋ฐ˜์ ์ธ ํŒจํ„ด์„ ๋‹ค๋ฃจ๋Š” ์˜ˆ์‹œ๋ฅผ ์‚ดํŽด๋ณด์„ธ์š”.

React๋กœ ์ž‘์„ฑ๋˜์ง€ ์•Š์€ ์œ„์ ฏ ์ œ์–ดํ•˜๊ธฐ {/controlling-non-react-widgets/}

๊ฐ€๋”์”ฉ React๋กœ ์ž‘์„ฑ๋˜์ง€ ์•Š์€ UI ์œ„์ ฏ์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•  ๋•Œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ํŽ˜์ด์ง€์— ์ง€๋„ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด ์ง€๋„ ์ปดํฌ๋„ŒํŠธ์—๋Š” setZoomLevel() ๋ฉ”์„œ๋“œ๊ฐ€ ์žˆ์œผ๋ฉฐ, zoomLevel state ๋ณ€์ˆ˜์™€ ๋™๊ธฐํ™”ํ•˜๋ ค๊ณ  ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. Effect๋Š” ๋‹ค์Œ๊ณผ ๋น„์Šทํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

useEffect(() => {
  const map = mapRef.current;
  map.setZoomLevel(zoomLevel);
}, [zoomLevel]);

์ด ๊ฒฝ์šฐ์—๋Š” ํด๋ฆฐ์—…์ด ํ•„์š”ํ•˜์ง€ ์•Š์Œ์„ ์œ ์˜ํ•˜์„ธ์š”. ๊ฐœ๋ฐœ ๋ชจ๋“œ์—์„œ React๋Š” Effect๋ฅผ ๋‘ ๋ฒˆ ํ˜ธ์ถœํ•˜์ง€๋งŒ, ๋™์ผํ•œ ๊ฐ’์„ ๊ฐ€์ง€๊ณ  setZoomLevel์„ ๋‘ ๋ฒˆ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์€ ์•„๋ฌด๋Ÿฐ ๋ฌธ์ œ๊ฐ€ ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์•ฝ๊ฐ„ ๋А๋ฆด ์ˆ˜ ์žˆ์ง€๋งŒ, ์ด๊ฒƒ์€ ์ œํ’ˆ ํ™˜๊ฒฝ์—์„œ ๋ถˆํ•„์š”ํ•˜๊ฒŒ ๋‹ค์‹œ ๋งˆ์šดํŠธ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๋ฌธ์ œ๊ฐ€ ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ผ๋ถ€ API๋Š” ์—ฐ์†ํ•ด์„œ ๋‘ ๋ฒˆ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์„ ํ—ˆ์šฉํ•˜์ง€ ์•Š์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋‚ด์žฅ๋œ <dialog> ์š”์†Œ์˜ showModal ๋ฉ”์„œ๋“œ๋Š” ๋‘ ๋ฒˆ ํ˜ธ์ถœํ•˜๋ฉด ์˜ˆ์™ธ๋ฅผ ๋˜์ง‘๋‹ˆ๋‹ค. ํด๋ฆฐ์—… ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ  ์ด ํ•จ์ˆ˜์—์„œ ๋Œ€ํ™” ์ƒ์ž๋ฅผ ๋‹ซ๋„๋ก ๋งŒ๋“ค์–ด๋ณด์„ธ์š”.

useEffect(() => {
  const dialog = dialogRef.current;
  dialog.showModal();
  return () => dialog.close();
}, []);

๊ฐœ๋ฐœ ์ค‘์—๋Š” Effect๊ฐ€ showModal()์„ ํ˜ธ์ถœํ•œ ๋‹ค์Œ ์ฆ‰์‹œ close()๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ๋‹ค์‹œ showModal()์„ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์‚ฌ์šฉ์ž๊ฐ€ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ๋™์ž‘์ด๋ฉฐ ์ œํ’ˆ ํ™˜๊ฒฝ์—์„œ ๋ณผ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ๊ณผ ๋™์ผํ•ฉ๋‹ˆ๋‹ค.

์ด๋ฒคํŠธ ๊ตฌ๋…ํ•˜๊ธฐ {/subscribing-to-events/}

๋งŒ์•ฝ Effect๊ฐ€ ์–ด๋–ค ๊ฒƒ์„ ๊ตฌ๋…ํ•œ๋‹ค๋ฉด, ํด๋ฆฐ์—… ํ•จ์ˆ˜์—์„œ ๊ตฌ๋…์„ ํ•ด์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

useEffect(() => {
  function handleScroll(e) {
    console.log(window.scrollX, window.scrollY);
  }
  window.addEventListener('scroll', handleScroll);
  return () => window.removeEventListener('scroll', handleScroll);
}, []);

๊ฐœ๋ฐœ ์ค‘์—๋Š” Effect๊ฐ€ addEventListener()๋ฅผ ํ˜ธ์ถœํ•œ ๋‹ค์Œ ์ฆ‰์‹œ removeEventListener()๋ฅผ ํ˜ธ์ถœํ•˜๊ณ , ๊ทธ๋‹ค์Œ ๋™์ผํ•œ ํ•ธ๋“ค๋Ÿฌ๋กœ addEventListener()๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ํ•œ ๋ฒˆ์— ํ•˜๋‚˜์˜ ํ™œ์„ฑ ๊ตฌ๋…๋งŒ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์ œํ’ˆ ํ™˜๊ฒฝ์—์„œ ํ•œ ๋ฒˆ addEventListener()๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ๊ณผ ๋™์ผํ•œ ๋™์ž‘์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค.

์• ๋‹ˆ๋ฉ”์ด์…˜ ํŠธ๋ฆฌ๊ฑฐ {/triggering-animations/}

Effect๊ฐ€ ์–ด๋–ค ์š”์†Œ๋ฅผ ์• ๋‹ˆ๋ฉ”์ด์…˜์œผ๋กœ ํ‘œ์‹œํ•˜๋Š” ๊ฒฝ์šฐ, ํด๋ฆฐ์—… ํ•จ์ˆ˜์—์„œ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์ดˆ๊ธฐ ๊ฐ’์œผ๋กœ ์žฌ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

useEffect(() => {
  const node = ref.current;
  node.style.opacity = 1; // Trigger the animation
  return () => {
    node.style.opacity = 0; // Reset to the initial value
  };
}, []);

๊ฐœ๋ฐœ ์ค‘์—๋Š” ๋ถˆํˆฌ๋ช…๋„๊ฐ€ 1๋กœ ์„ค์ •๋˜๊ณ , ๊ทธ๋Ÿฐ ๋‹ค์Œ 0์œผ๋กœ ์„ค์ •๋˜๊ณ , ๋‹ค์‹œ 1๋กœ ์„ค์ •๋ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์ œํ’ˆ ํ™˜๊ฒฝ์—์„œ 1๋กœ ์ง์ ‘ ์„ค์ •ํ•˜๋Š” ๊ฒƒ๊ณผ ๋™์ผํ•œ ๋™์ž‘์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค. tweening์„ ์ง€์›ํ•˜๋Š” ์„œ๋“œํŒŒํ‹ฐ ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ํด๋ฆฐ์—… ํ•จ์ˆ˜์—์„œ ํƒ€์ž„๋ผ์ธ์„ ์ดˆ๊ธฐ ์ƒํƒœ๋กœ ์žฌ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋ฐ์ดํ„ฐ ํŽ˜์นญ {/fetching-data/}

๋งŒ์•ฝ Effect๊ฐ€ ์–ด๋–ค ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค๋ฉด, ํด๋ฆฐ์—… ํ•จ์ˆ˜์—์„œ๋Š” fetch๋ฅผ ์ค‘๋‹จํ•˜๊ฑฐ๋‚˜ ๊ฒฐ๊ณผ๋ฅผ ๋ฌด์‹œํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

useEffect(() => {
  let ignore = false;

  async function startFetching() {
    const json = await fetchTodos(userId);
    if (!ignore) {
      setTodos(json);
    }
  }

  startFetching();

  return () => {
    ignore = true;
  };
}, [userId]);

์ด๋ฏธ ๋ฐœ์ƒํ•œ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์„ "์‹คํ–‰ ์ทจ์†Œ"ํ•  ์ˆ˜๋Š” ์—†์ง€๋งŒ, ํด๋ฆฐ์—… ํ•จ์ˆ˜๋Š” ๋” ์ด์ƒ ๊ด€๋ จ์ด ์—†๋Š” ํŽ˜์น˜๊ฐ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๊ณ„์† ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€ ์•Š๋„๋ก ๋ณด์žฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. userId๊ฐ€ 'Alice'์—์„œ 'Bob'์œผ๋กœ ๋ณ€๊ฒฝ๋˜๋ฉด ํด๋ฆฐ์—…์€ 'Bob'์ดํ›„์— ๋„์ฐฉํ•˜๋”๋ผ๋„ 'Alice' ์‘๋‹ต์„ ๋ฌด์‹œํ•˜๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

๊ฐœ๋ฐœ ์ค‘์—๋Š” ๋„คํŠธ์›Œํฌ ํƒญ์—์„œ ๋‘ ๊ฐœ์˜ ํŽ˜์น˜๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๋ฌธ์ œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์œ„์˜ ์ ‘๊ทผ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๋ฉด ์ฒซ ๋ฒˆ์งธ Effect๋Š” ์ฆ‰์‹œ ํด๋ฆฐ์—…๋˜์–ด ignore ๋ณ€์ˆ˜์˜ ๋ณต์‚ฌ๋ณธ์ด true๋กœ ์„ค์ •๋ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ถ”๊ฐ€ ์š”์ฒญ์ด ์žˆ๋”๋ผ๋„ if (!ignore) ๊ฒ€์‚ฌ ๋•๋ถ„์— state์— ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ œํ’ˆ ํ™˜๊ฒฝ์—์„œ๋Š” ํ•˜๋‚˜์˜ ์š”์ฒญ๋งŒ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ ์ค‘์— ๋‘ ๋ฒˆ์งธ ์š”์ฒญ์ด ๋ฌธ์ œ๋ผ๋ฉด, ๊ฐ€์žฅ ์ข‹์€ ๋ฐฉ๋ฒ•์€ ์ค‘๋ณต ์š”์ฒญ์„ ์ œ๊ฑฐํ•˜๊ณ  ์ปดํฌ๋„ŒํŠธ ๊ฐ„์— ์‘๋‹ต์„ ์บ์‹œํ•˜๋Š” ์†”๋ฃจ์…˜์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค:

function TodoList() {
  const todos = useSomeDataLibrary(`/api/user/${userId}/todos`);
  // ...

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์„ ๊ฐœ์„ ํ•˜๋Š”๋ฐ ๋„์›€์ด ๋  ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ฐ˜์‘ ์†๋„๋„ ํ–ฅ์ƒ๋ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์‚ฌ์šฉ์ž๊ฐ€ ๋’ค๋กœ ๊ฐ€๊ธฐ ๋ฒ„ํŠผ์„ ๋ˆŒ๋ €์„ ๋•Œ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์‹œ ๋กœ๋“œํ•˜๋Š” ๊ฒƒ์„ ๊ธฐ๋‹ค๋ฆด ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๊ฐ€ ์บ์‹œ๋˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์บ์‹œ๋ฅผ ์ง์ ‘ ๊ตฌ์ถ•ํ•˜๊ฑฐ๋‚˜ ๋น„์Šทํ•œ ํšจ๊ณผ๋ฅผ ๋ˆ„๋ฆด ์ˆ˜ ์žˆ๋Š” ์—ฌ๋Ÿฌ ๋Œ€์•ˆ ์ค‘ ํ•˜๋‚˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Effect์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์ข‹์€ ๋Œ€์•ˆ์€ ๋ฌด์—‡์ธ๊ฐ€์š”? {/what-are-good-alternatives-to-data-fetching-in-effects/}

Effect ์•ˆ์—์„œ fetch ํ˜ธ์ถœ์„ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์ธ๊ธฐ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค, ํŠนํžˆ ์™„์ „ํžˆ ํด๋ผ์ด์–ธํŠธ ์ธก ์•ฑ์—์„œ๋Š”์š”. ํ•˜์ง€๋งŒ ์ด๋Š” ๋งค์šฐ ์ˆ˜๋™์ ์ธ ์ ‘๊ทผ ๋ฐฉ์‹์ด๋ฉฐ ์ค‘์š”ํ•œ ๋‹จ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

  • Effect๋Š” ์„œ๋ฒ„์—์„œ ์‹คํ–‰๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ดˆ๊ธฐ ์„œ๋ฒ„ ๋ Œ๋”๋ง๋œ HTML์€ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†๋Š” ๋กœ๋”ฉ ์ƒํƒœ๋งŒ ํฌํ•จํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ ์ปดํ“จํ„ฐ๋Š” ๋ชจ๋“  JavaScript๋ฅผ ๋‹ค์šด๋กœ๋“œํ•˜๊ณ  ์•ฑ์„ ๋ Œ๋”๋งํ•ด์•ผ๋งŒ ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ๋“œํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋Š” ํšจ์œจ์ ์ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • Effect ์•ˆ์—์„œ ์ง์ ‘ ๊ฐ€์ ธ์˜ค๋ฉด "๋„คํŠธ์›Œํฌ ํญํฌ"๋ฅผ ์‰ฝ๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋งํ•˜๋ฉด ์ผ๋ถ€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋งํ•œ ๋‹ค์Œ ๊ทธ๋“ค์ด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. ๋„คํŠธ์›Œํฌ๊ฐ€ ๋น ๋ฅด์ง€ ์•Š์œผ๋ฉด ์ด๋Š” ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ‘๋ ฌ๋กœ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ๋ณด๋‹ค ํ›จ์”ฌ ๋А๋ฆฝ๋‹ˆ๋‹ค.
  • Effect ์•ˆ์—์„œ ์ง์ ‘ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ์€ ์ผ๋ฐ˜์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฏธ๋ฆฌ ๋กœ๋“œํ•˜๊ฑฐ๋‚˜ ์บ์‹œํ•˜์ง€ ์•Š์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งˆ์šดํŠธ ํ•ด์ œ๋˜๊ณ  ๋‹ค์‹œ ๋งˆ์šดํŠธ๋˜๋ฉด ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์‹œ ๊ฐ€์ ธ์™€์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ทธ๋ฆฌ ํŽธ๋ฆฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. fetch ํ˜ธ์ถœ์„ ์ž‘์„ฑํ•  ๋•Œ ๊ฒฝ์Ÿ ์ƒํƒœ์™€ ๊ฐ™์€ ๋ฒ„๊ทธ์— ์˜ํ–ฅ์„ ๋ฐ›์ง€ ์•Š๋Š” ๋ฐฉ์‹์œผ๋กœ ์ž‘์„ฑํ•˜๋Š” ๋ฐ ๊ฝค ๋งŽ์€ ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ ์ฝ”๋“œ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

์ด ๋‹จ์  ๋ชฉ๋ก์€ React์—๋งŒ ํ•ด๋‹น๋˜๋Š” ๊ฒƒ์€ ์•„๋‹™๋‹ˆ๋‹ค. ์–ด๋–ค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ๋“  ๋งˆ์šดํŠธ ์‹œ์— ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค๋ฉด ๋น„์Šทํ•œ ๋‹จ์ ์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค. ๋งˆ์šดํŠธ ์‹œ์— ๋ฐ์ดํ„ฐ๋ฅผ ํŽ˜์นญํ•˜๋Š” ๊ฒƒ๋„ ๋ผ์šฐํŒ…๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์ž˜ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์–ด๋ ค์šด ์ž‘์—…์ด๋ฏ€๋กœ ๋‹ค์Œ ์ ‘๊ทผ ๋ฐฉ์‹์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

  • ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค๋ฉด, ๊ทธ ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๋‚ด์žฅ ๋ฐ์ดํ„ฐ ํŒจ์นญ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜์„ธ์š”. ์ตœ์‹  React ํ”„๋ ˆ์ž„์›Œํฌ๋Š” ํšจ์œจ์ ์ธ ๋ฐ์ดํ„ฐ ํŒจ์นญ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ๋‚ด์žฅํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ์•ž์„œ ์–ธ๊ธ‰ํ•œ ๋‹จ์ ์„ ๊ฒช์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด, ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ์ง์ ‘ ๊ตฌ์ถ•ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•˜์„ธ์š”. ์ธ๊ธฐ์žˆ๋Š” ์˜คํ”ˆ์†Œ์Šค ์†”๋ฃจ์…˜์œผ๋กœ๋Š” React Query, useSWR, React Router 6.4+๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ง์ ‘ ๊ตฌํ˜„ํ•  ์ˆ˜๋„ ์žˆ๋Š”๋ฐ, ์ด ๊ฒฝ์šฐ์—๋Š” Effects๋ฅผ ์‚ฌ์šฉํ•˜๋˜, ์š”์ฒญ ์ค‘๋ณต ์ œ๊ฑฐ, ์‘๋‹ต ์บ์‹ฑ, ๋„คํŠธ์›Œํฌ ์›Œํ„ฐํด ๋ฐฉ์ง€๋ฅผ ์œ„ํ•œ ๋กœ์ง์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค(๋ฐ์ดํ„ฐ๋ฅผ ๋ฏธ๋ฆฌ ๋กœ๋“œํ•˜๊ฑฐ๋‚˜, ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์œ„ ๋ผ์šฐํŠธ๋กœ ํ˜ธ์ด์ŠคํŒ…ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ).

์ด๋Ÿฌํ•œ ์ ‘๊ทผ ๋ฐฉ์‹ ์ค‘ ์–ด๋А ๊ฒƒ๋„ ์ ํ•ฉํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ, Effect ๋‚ด์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ง์ ‘ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ์„ ๊ณ„์†ํ•˜์…”๋„ ๋ฉ๋‹ˆ๋‹ค.

๋ถ„์„ ๋ณด๋‚ด๊ธฐ {/sending-analytics/}

ํŽ˜์ด์ง€ ๋ฐฉ๋ฌธ ์‹œ ๋ถ„์„ ์ด๋ฒคํŠธ๋ฅผ ๋ณด๋‚ด๋Š” ๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ๊ณ ๋ คํ•ด๋ณด์„ธ์š”.

useEffect(() => {
  logVisit(url); // POST ์š”์ฒญ์„ ๋ณด๋ƒ„
}, [url]);

๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋Š” logVisit๊ฐ€ ๊ฐ URL์— ๋Œ€ํ•ด ๋‘ ๋ฒˆ ํ˜ธ์ถœ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์ด๋ฅผ ์ˆ˜์ •ํ•˜๊ณ  ์‹ถ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ์ด ์ฝ”๋“œ๋ฅผ ๊ทธ๋Œ€๋กœ ์œ ์ง€ํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. ์ด์ „ ์˜ˆ์‹œ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ํ•œ ๋ฒˆ ์‹คํ–‰ํ•˜๊ฑฐ๋‚˜ ๋‘ ๋ฒˆ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ ์‚ฌ์ด์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ๋ณผ ์ˆ˜ ์žˆ๋Š” ๋™์ž‘ ์ฐจ์ด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์‹ค์ œ๋กœ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋Š” logVisit๊ฐ€ ์•„๋ฌด ์ž‘์—…๋„ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์˜ ๋กœ๊ทธ๊ฐ€ ์ œํ’ˆ ์ง€ํ‘œ๋ฅผ ์™œ๊ณก์‹œํ‚ค์ง€ ์•Š๋„๋ก ํ•˜๊ธฐ ์œ„ํ•จ์ž…๋‹ˆ๋‹ค. ์ปดํฌ๋„ŒํŠธ๋Š” ํŒŒ์ผ์„ ์ €์žฅํ•  ๋•Œ๋งˆ๋‹ค ์žฌ๋งˆ์šดํŠธ๋˜๋ฏ€๋กœ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋Š” ์ถ”๊ฐ€์ ์ธ ๋ฐฉ๋ฌธ ๊ธฐ๋ก์„ ๋กœ๊ทธ์— ๋‚จ๊ธฐ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

์ œํ’ˆ ํ™˜๊ฒฝ์—์„œ๋Š” ์ค‘๋ณต๋œ ๋ฐฉ๋ฌธ ๋กœ๊ทธ๊ฐ€ ์—†์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋ณด๋‚ด๋Š” ๋ถ„์„ ์ด๋ฒคํŠธ๋ฅผ ๋””๋ฒ„๊น…ํ•˜๋ ค๋ฉด ์•ฑ์„ ์Šคํ…Œ์ด์ง• ํ™˜๊ฒฝ(์ œํ’ˆ ๋ชจ๋“œ๋กœ ์‹คํ–‰)์— ๋ฐฐํฌํ•˜๊ฑฐ๋‚˜ Strict Mode๋ฅผ ์ผ์‹œ์ ์œผ๋กœ ์‚ฌ์šฉ ์ค‘์ง€ํ•˜์—ฌ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์ „์šฉ์˜ ์žฌ๋งˆ์šดํŒ… ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ Effect ๋Œ€์‹  ๋ผ์šฐํŠธ ๋ณ€๊ฒฝ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์—์„œ ๋ถ„์„์„ ๋ณด๋‚ผ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๋” ์ •๋ฐ€ํ•œ ๋ถ„์„์„ ์œ„ํ•ด Intersection Observer๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์–ด๋–ค ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ทฐํฌํŠธ์— ์žˆ๋Š”์ง€์™€ ์–ผ๋งˆ๋‚˜ ์˜ค๋ž˜ ๋ณด์ด๋Š”์ง€ ์ถ”์ ํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Effect๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ดˆ๊ธฐํ™” {/not-an-effect-initializing-the-application/}

์ผ๋ถ€ ๋กœ์ง์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘ ์‹œ์— ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋กœ์ง์€ ์ปดํฌ๋„ŒํŠธ ์™ธ๋ถ€์— ๋ฐฐ์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

if (typeof window !== 'undefined') { // ๋ธŒ๋ผ์šฐ์ €์—์„œ ์‹คํ–‰ ์ค‘์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
  checkAuthToken();
  loadDataFromLocalStorage();
}

function App() {
  // ...
}

์œ„์™€ ๊ฐ™์ด ์ปดํฌ๋„ŒํŠธ ์™ธ๋ถ€์—์„œ ํ•ด๋‹น ๋กœ์ง์„ ์‹คํ–‰ํ•˜๋ฉด, ํ•ด๋‹น ๋กœ์ง์€ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ํŽ˜์ด์ง€๋ฅผ ๋กœ๋“œํ•œ ํ›„ ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰๋จ์ด ๋ณด์žฅ๋ฉ๋‹ˆ๋‹ค.

Effect๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ: ์ œํ’ˆ ๊ตฌ์ž…ํ•˜๊ธฐ {/not-an-effect-buying-a-product/}

๊ฐ€๋”์€ ํด๋ฆฐ์—… ํ•จ์ˆ˜๋ฅผ ์ž‘์„ฑํ•˜๋”๋ผ๋„ Effect๊ฐ€ ๋‘ ๋ฒˆ ์‹คํ–‰๋˜๋Š” ๊ฒƒ์— ๋Œ€ํ•ด ์‚ฌ์šฉ์ž๊ฐ€ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒฐ๊ณผ๋ฅผ ๋ฐฉ์ง€ํ•  ๋ฐฉ๋ฒ•์ด ์—†์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์•„๋ž˜์™€ ๊ฐ™์ด ์ œํ’ˆ์„ ๊ตฌ๋งคํ•˜๋Š” POST ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” Effect๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

useEffect(() => {
  // ๐Ÿ”ด ์ž˜๋ชป๋œ ๋ฐฉ๋ฒ•: ์ด Effect๋Š” ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ ๋‘ ๋ฒˆ ์‹คํ–‰๋˜๋ฉฐ ์ฝ”๋“œ์— ๋ฌธ์ œ๊ฐ€ ๋“œ๋Ÿฌ๋‚ฉ๋‹ˆ๋‹ค.
  fetch('/api/buy', { method: 'POST' });
}, []);

์‚ฌ์šฉ์ž๋Š” ์ œํ’ˆ์„ ๋‘ ๋ฒˆ ๊ตฌ๋งคํ•˜๊ณ  ์‹ถ์ง€ ์•Š์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด๊ฒƒ์€ ์ด๋Ÿฌํ•œ ๋กœ์ง์„ Effect์— ๋„ฃ์ง€ ์•Š์•„์•ผ ํ•˜๋Š” ์ด์œ ์ž…๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ๋‹ค๋ฅธ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•œ ๋‹ค์Œ ๋’ค๋กœ ๊ฐ€๊ธฐ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋Š” ๊ฒฝ์šฐ ์–ด๋–ป๊ฒŒ ๋ ๊นŒ์š”? Effect๊ฐ€ ๋‹ค์‹œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ํŽ˜์ด์ง€๋ฅผ ๋ฐฉ๋ฌธํ•  ๋•Œ ์ œํ’ˆ์„ ๊ตฌ๋งคํ•˜๋ ค๊ณ  ํ•˜์ง€ ์•Š์œผ๋ฉฐ, ์‚ฌ์šฉ์ž๊ฐ€ "๊ตฌ๋งค" ๋ฒ„ํŠผ์„ ํด๋ฆญํ•  ๋•Œ ์ œํ’ˆ์„ ๊ตฌ๋งคํ•˜๊ณ  ์‹ถ์€ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๊ตฌ๋งค๋Š” ๋ Œ๋”๋ง์— ์˜ํ•ด ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ํŠน์ • ์ƒํ˜ธ ์ž‘์šฉ์— ์˜ํ•ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅผ ๋•Œ๋งŒ ์‹คํ–‰๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Effect๋ฅผ ์‚ญ์ œํ•˜๊ณ  /api/buy ์š”์ฒญ์„ Buy ๋ฒ„ํŠผ์˜ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋กœ ์ด๋™ํ•˜์„ธ์š”.

  function handleClick() {
    // โœ… ๊ตฌ๋งค๋Š” ํŠน์ • ์ƒํ˜ธ ์ž‘์šฉ์— ์˜ํ•ด ๋ฐœ์ƒํ•˜๋Š” ์ด๋ฒคํŠธ์ž…๋‹ˆ๋‹ค.
    fetch('/api/buy', { method: 'POST' });
  }

๋งŒ์•ฝ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‹ค์‹œ ๋งˆ์šดํŠธํ–ˆ์„ ๋•Œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋กœ์ง์ด ๊นจ์ง„๋‹ค๋ฉด, ๊ธฐ์กด์— ์กด์žฌํ•˜๋˜ ๋ฒ„๊ทธ๊ฐ€ ๋“œ๋Ÿฌ๋‚œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž์˜ ๊ด€์ ์—์„œ ํŽ˜์ด์ง€๋ฅผ ๋ฐฉ๋ฌธํ•˜๋Š” ๊ฒƒ๊ณผ ํŽ˜์ด์ง€๋ฅผ ๋ฐฉ๋ฌธํ•˜๊ณ , ๋งํฌ๋ฅผ ํด๋ฆญํ•œ ๋‹ค์Œ, ๋’ค๋กœ ๊ฐ€๊ธฐ ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ์„œ ๋‹ค์‹œ ํŽ˜์ด์ง€๋กœ ๋Œ์•„์˜จ๊ฒƒ ๊ณผ ์ฐจ์ด๊ฐ€ ์—†์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. React๋Š” ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ•œ ๋ฒˆ ๋‹ค์‹œ ๋งˆ์šดํŠธํ•˜์—ฌ ์ด ์›์น™์„ ์ค€์ˆ˜ํ•˜๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

์œ„์—์„œ ์„ค๋ช…ํ•œ ๋ชจ๋“  ๊ฒƒ๋“ค ์ ์šฉํ•ด๋ณด๊ธฐ {/putting-it-all-together/}

์ด ํ”Œ๋ ˆ์ด๊ทธ๋ผ์šด๋“œ๋ฅผ ์‚ดํŽด๋ณด๋ฉด ์‹ค์ œ๋กœ Effect๊ฐ€ ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€์— ๋Œ€ํ•œ "๋А๋‚Œ์„ ์–ป์„" ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ์˜ˆ์‹œ๋Š” setTimeout์„ ์‚ฌ์šฉํ•˜์—ฌ Effect๊ฐ€ ์‹คํ–‰๋œ ํ›„ 3์ดˆ ํ›„์— ์ž…๋ ฅ ํ…์ŠคํŠธ์™€ ํ•จ๊ป˜ ์ฝ˜์†” ๋กœ๊ทธ๊ฐ€ ํ‘œ์‹œ๋˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ํด๋ฆฐ์—… ํ•จ์ˆ˜๋Š” ์‹คํ–‰์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ํƒ€์ž„์•„์›ƒ์„ ์ทจ์†Œํ•ฉ๋‹ˆ๋‹ค. "์ปดํฌ๋„ŒํŠธ ๋งˆ์šดํŠธ" ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ ์‹œ์ž‘ํ•˜์„ธ์š”.

import { useState, useEffect } from 'react';

function Playground() {
  const [text, setText] = useState('a');

  useEffect(() => {
    function onTimeout() {
      console.log('โฐ ' + text);
    }

    console.log('๐Ÿ”ต ์Šค์ผ€์ค„ ๋กœ๊ทธ "' + text);
    const timeoutId = setTimeout(onTimeout, 3000);

    return () => {
      console.log('๐ŸŸก ์ทจ์†Œ ๋กœ๊ทธ "' + text);
      clearTimeout(timeoutId);
    };
  }, [text]);

  return (
    <>
      <label>
        What to log:{' '}
        <input
          value={text}
          onChange={e => setText(e.target.value)}
        />
      </label>
      <h1>{text}</h1>
    </>
  );
}

export default function App() {
  const [show, setShow] = useState(false);
  return (
    <>
      <button onClick={() => setShow(!show)}>
        ์ปดํฌ๋„ŒํŠธ {show ? '๋งˆ์šดํŠธ ํ•ด์ œ' : '๋งˆ์šดํŠธ'}
      </button>
      {show && <hr />}
      {show && <Playground />}
    </>
  );
}

์ฒ˜์Œ์—๋Š” Schedule "a" log, Cancel "a" log, ๊ทธ๋ฆฌ๊ณ  ๋‹ค์‹œ Schedule "a" log ๋ผ๋Š” ์„ธ ๊ฐ€์ง€ ๋กœ๊ทธ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋ช‡ ์ดˆ ํ›„์—๋Š” a๋ผ๋Š” ๋กœ๊ทธ๊ฐ€ ๋‚˜ํƒ€๋‚  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด์ „์— ๋ฐฐ์šด ๋‚ด์šฉ์ฒ˜๋Ÿผ ์ถ”๊ฐ€๋œ ์Šค์ผ€์ค„/์ทจ์†Œ ์Œ์€ React๊ฐ€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฐœ๋ฐœ ์ค‘์— ํ•œ ๋ฒˆ ๋‹ค์‹œ ๋งˆ์šดํŠธํ•˜์—ฌ ์ •๋ฆฌ๋ฅผ ์ œ๋Œ€๋กœ ๊ตฌํ˜„ํ–ˆ๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

์ด์ œ ์ž…๋ ฅ๋ž€์„ abc๋กœ ์ˆ˜์ •ํ•ด ๋ณด์„ธ์š”. ์ถฉ๋ถ„ํžˆ ๋น ๋ฅด๊ฒŒ ์ž…๋ ฅํ•˜๋ฉด Schedule "ab" log ๋ฐ”๋กœ ๋’ค์— Cancel "ab" log์™€ Schedule "abc" log๊ฐ€ ๋‚˜ํƒ€๋‚  ๊ฒƒ์ž…๋‹ˆ๋‹ค. React๋Š” ํ•ญ์ƒ ์ด์ „ ๋ Œ๋”์˜ Effect๋ฅผ ๋‹ค์Œ ๋ Œ๋”์˜ Effect๋ณด๋‹ค ๋จผ์ € ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋น ๋ฅด๊ฒŒ ์ž…๋ ฅํ•˜๋”๋ผ๋„ ํ•œ ๋ฒˆ์— ์ตœ๋Œ€ ํ•˜๋‚˜์˜ ํƒ€์ž„์•„์›ƒ๋งŒ ์˜ˆ์•ฝ๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ž…๋ ฅ์„ ๋ช‡ ๋ฒˆ ํ•ด๋ณด๋ฉด์„œ Effect๊ฐ€ ์–ด๋–ป๊ฒŒ ์ •๋ฆฌ๋˜๋Š”์ง€ ๋А๊ปด๋ณด์„ธ์š”.

์ž…๋ ฅ๋ž€์— ๋ฌด์–ธ๊ฐ€๋ฅผ ์ž…๋ ฅํ•œ ๋‹ค์Œ "์ปดํฌ๋„ŒํŠธ ๋งˆ์šดํŠธ ํ•ด์ œ"๋ฅผ ๋ˆŒ๋Ÿฌ๋ณด์„ธ์š”. ๋งˆ์šดํŠธ ํ•ด์ œ๊ฐ€ ๋งˆ์ง€๋ง‰ ๋ Œ๋”์˜ Effect๋ฅผ ์ •๋ฆฌํ•จ์„ ์ฃผ๋ชฉํ•˜์„ธ์š”. ์—ฌ๊ธฐ์„œ๋Š” ํƒ€์ž„์•„์›ƒ์ด ์‹คํ–‰๋˜๊ธฐ ์ „์— ๋งˆ์ง€๋ง‰ ํƒ€์ž„์•„์›ƒ์ด ์ทจ์†Œ๋ฉ๋‹ˆ๋‹ค.

๋งˆ์ง€๋ง‰์œผ๋กœ ์œ„ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ˆ˜์ •ํ•˜๊ณ  ์ •๋ฆฌ ํ•จ์ˆ˜๋ฅผ ์ฃผ์„ ์ฒ˜๋ฆฌํ•˜์—ฌ ํƒ€์ž„์•„์›ƒ์ด ์ทจ์†Œ๋˜์ง€ ์•Š๋„๋ก ํ•ด๋ณด์„ธ์š”. abcde๋ฅผ ๋น ๋ฅด๊ฒŒ ์ž…๋ ฅํ•ด ๋ณด์„ธ์š”. ๋ช‡ ์ดˆ ํ›„์— ๋ฌด์—‡์ด ๊ธฐ๋Œ€๋˜๋Š”์ง€ ์ƒ๊ฐํ•ด ๋ณด์„ธ์š”. ํƒ€์ž„์•„์›ƒ ๋‚ด๋ถ€์˜ console.log(text)๊ฐ€ ๊ฐ€์žฅ ์ตœ๊ทผ์˜ text๋ฅผ ์ถœ๋ ฅํ•˜๊ณ  ๋‹ค์„ฏ ๋ฒˆ์˜ abcde ๋กœ๊ทธ๊ฐ€ ์ƒ์„ฑ๋ ๊นŒ์š”? ์ง์ ‘ ์‹œ๋„ํ•˜์—ฌ ํ™•์ธํ•ด ๋ณด์„ธ์š”!

์ˆ˜ ์ดˆ ํ›„์— a, ab, abc, abcd, ๊ทธ๋ฆฌ๊ณ  abcde๋ผ๋Š” ์ผ๋ จ์˜ ๋กœ๊ทธ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ฐ Effect๋Š” ํ•ด๋‹น ๋ Œ๋”์˜ text ๊ฐ’์„ "์บก์ฒ˜"ํ•ฉ๋‹ˆ๋‹ค. text ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ๋Š”์ง€ ์—ฌ๋ถ€๋Š” ์ค‘์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. text = 'ab' ๋ Œ๋”์˜ Effect์—์„œ๋Š” ํ•ญ์ƒ 'ab'๋ฅผ ๋ณผ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋‹ค์‹œ ๋งํ•ด, ๊ฐ ๋ Œ๋”์˜ Effect๋Š” ์„œ๋กœ ๊ฒฉ๋ฆฌ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์ž‘๋™ ๋ฐฉ์‹์— ๋Œ€ํ•ด์„œ ๊ถ๊ธˆํ•˜๋‹ค๋ฉด ํด๋กœ์ €์— ๋Œ€ํ•ด ์ฝ์–ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฐ๊ฐ์˜ ๋ Œ๋”๋ง์€ ๊ฐ๊ฐ์˜ ๊ณ ์œ ํ•œ Effect๋ฅผ ๊ฐ–์Šต๋‹ˆ๋‹ค. {/each-render-has-its-own-effects/}

useEffect๋ฅผ ๋ Œ๋”๋ง ๊ฒฐ๊ณผ๋ฌผ์— "๋ถ€์ฐฉ"ํ•˜๋Š” ๊ฒƒ์œผ๋กœ ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ๊ณผ ๊ฐ™์€ Effect๋ฅผ ๊ณ ๋ คํ•ด ๋ณด์„ธ์š”.

export default function ChatRoom({ roomId }) {
  useEffect(() => {
    const connection = createConnection(roomId);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

  return <h1>Welcome to {roomId}!</h1>;
}

์ด์ œ ์‚ฌ์šฉ์ž๊ฐ€ ์•ฑ์„ ํƒ์ƒ‰ํ•˜๋Š” ๋™์•ˆ ์ •ํ™•ํžˆ ์–ด๋–ค ์ผ์ด ์ผ์–ด๋‚˜๋Š”์ง€ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์ดˆ๊ธฐ ๋ Œ๋”๋ง {/initial-render/}

์‚ฌ์šฉ์ž๊ฐ€ <ChatRoom roomId="general" />์„ ๋ฐฉ๋ฌธํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ, roomId๋ฅผ 'general'๋กœ ๋ฉ˜ํƒˆ๋ชจ๋ธ ์œ„์—์„œ ๋Œ€์ฒดํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

  // ์ฒซ ๋ฒˆ์งธ ๋ Œ๋”๋ง์— ๋Œ€ํ•œ JSX (roomId = "general")
  return <h1>Welcome to general!</h1>;

Effect ๋˜ํ•œ ๋ Œ๋”๋ง ๊ฒฐ๊ณผ๋ฌผ์˜ ์ผ๋ถ€์ž…๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ ๋ Œ๋”๋ง์˜ Effect๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  // ์ฒซ ๋ฒˆ์งธ ๋ Œ๋”๋ง์— ๋Œ€ํ•œ ์ดํŽ™ํŠธ (roomId = "general")
  () => {
    const connection = createConnection('general');
    connection.connect();
    return () => connection.disconnect();
  },
  // ์ฒซ ๋ฒˆ์งธ ๋ Œ๋”๋ง์˜ ์˜์กด์„ฑ (roomId = "general")
  ['general']

React๋Š” ์ด Effect๋ฅผ ์‹คํ–‰ํ•˜๋ฉฐ, 'general' ์ฑ„ํŒ…๋ฐฉ์— ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค.

๊ฐ™์€ ์˜์กด์„ฑ ์‚ฌ์ด์—์„œ์˜ ์žฌ๋ Œ๋”๋ง {/re-render-with-same-dependencies/}

<ChatRoom roomId="general" />๊ฐ€ ๋‹ค์‹œ ๋ Œ๋”๋ง๋œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ด…์‹œ๋‹ค. JSX ๊ฒฐ๊ณผ๋ฌผ์€ ๋™์ผํ•ฉ๋‹ˆ๋‹ค.

  // ๋‘ ๋ฒˆ์งธ ๋ Œ๋”๋ง์— ๋Œ€ํ•œ JSX (roomId = "general")
  return <h1>Welcome to general!</h1>;

React๋Š” ๋ Œ๋”๋ง ์ถœ๋ ฅ์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์— DOM์„ ์—…๋ฐ์ดํŠธํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๋‘ ๋ฒˆ์งธ ๋ Œ๋”๋ง์—์„œ์˜ Effect๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  // ๋‘ ๋ฒˆ์งธ ๋ Œ๋”๋ง์— ๋Œ€ํ•œ Effect (roomId = "general")
  () => {
    const connection = createConnection('general');
    connection.connect();
    return () => connection.disconnect();
  },
  // ๋‘ ๋ฒˆ์งธ ๋ Œ๋”๋ง์— ๋Œ€ํ•œ ์˜์กด์„ฑ (roomId = "general")
  ['general']

React๋Š” ๋‘ ๋ฒˆ์งธ ๋ Œ๋”๋ง์—์„œ์˜ ['general']๋ฅผ ์ฒซ ๋ฒˆ์งธ ๋ Œ๋”๋ง์—์„œ์˜ ['general']์™€ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋“  ์˜์กด์„ฑ์ด ๋™์ผํ•˜๋ฏ€๋กœ React๋Š” ๋‘ ๋ฒˆ์งธ ๋ Œ๋”๋ง์—์„œ์˜ Effect๋ฅผ ๋ฌด์‹œํ•ฉ๋‹ˆ๋‹ค. ํ•ด๋‹น Effect๋Š” ํ˜ธ์ถœ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๋‹ค๋ฅธ ์˜์กด์„ฑ์œผ๋กœ ์žฌ๋ Œ๋”๋ง {/re-render-with-different-dependencies/}

๊ทธ๋Ÿผ, ์‚ฌ์šฉ์ž๊ฐ€ <ChatRoom roomId="travel" />์„ ํƒ์ƒ‰ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฒˆ์—๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋‹ค๋ฅธ JSX๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

  // ์„ธ ๋ฒˆ์งธ ๋ Œ๋”๋ง์— ๋Œ€ํ•œ JSX (roomId = "travel")
  return <h1>Welcome to travel!</h1>;

React๋Š” DOM์„ ์—…๋ฐ์ดํŠธํ•˜์—ฌ "Welcome to general"์„ "Welcome to travel"๋กœ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.

์„ธ ๋ฒˆ์งธ ๋ Œ๋”๋ง์—์„œ์˜ Effect๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

  // ์„ธ ๋ฒˆ์งธ ๋ Œ๋”๋ง์— ๋Œ€ํ•œ Effect (roomId = "travel")
  () => {
    const connection = createConnection('travel');
    connection.connect();
    return () => connection.disconnect();
  },
  // ์„ธ ๋ฒˆ์งธ ๋ Œ๋”๋ง์— ๋Œ€ํ•œ ์˜์กด์„ฑ (roomId = "travel")
  ['travel']

React๋Š” ์„ธ ๋ฒˆ์งธ ๋ Œ๋”๋ง์—์„œ์˜ ['travel']์™€ ๋‘ ๋ฒˆ์งธ ๋ Œ๋”๋ง์—์„œ์˜ ['general']๋ฅผ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค. ํ•˜๋‚˜์˜ ์˜์กด์„ฑ์ด ๋‹ค๋ฆ…๋‹ˆ๋‹ค: Object.is('travel', 'general')์€ false์ž…๋‹ˆ๋‹ค. Effect๋Š” ๊ฑด๋„ˆ๋›ธ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

React๋Š” ์„ธ ๋ฒˆ์งธ ๋ Œ๋”๋ง์˜ Effect๋ฅผ ์ ์šฉํ•˜๊ธฐ ์ „์— ๋จผ์ € ์‹คํ–‰๋œ Effect๋ฅผ ์ •๋ฆฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋‘ ๋ฒˆ์งธ ๋ Œ๋”๋ง์˜ Effect๊ฐ€ ๊ฑด๋„ˆ๋›ฐ์–ด์กŒ๊ธฐ ๋•Œ๋ฌธ์—, React๋Š” ์ฒซ ๋ฒˆ์งธ ๋ Œ๋”๋ง์˜ Effect๋ฅผ ์ •๋ฆฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ฒ˜์Œ ๋ Œ๋”๋ง๋˜์—ˆ์„ ๋•Œ ์Šคํฌ๋กคํ•˜๋ฉด, createConnection('general')๋กœ ์ƒ์„ฑ๋œ ์—ฐ๊ฒฐ์— ๋Œ€ํ•ด disconnect()๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋กœ์จ ์•ฑ์€ 'general' ์ฑ„ํŒ…๋ฐฉ๊ณผ์˜ ์—ฐ๊ฒฐ์ด ํ•ด์ œ๋ฉ๋‹ˆ๋‹ค.

๊ทธ ํ›„์— React๋Š” ์„ธ ๋ฒˆ์งธ ๋ Œ๋”๋ง์˜ Effect๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. 'travel' ์ฑ„ํŒ…๋ฐฉ์— ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค.

๋งˆ์šดํŠธ ํ•ด์ œ {/unmount/}

๋งˆ์ง€๋ง‰์œผ๋กœ, ์‚ฌ์šฉ์ž๊ฐ€ ๋‹ค๋ฅธ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๊ฒŒ ๋˜์–ด ChatRoom ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งˆ์šดํŠธ ํ•ด์ œ๋ฉ๋‹ˆ๋‹ค. React๋Š” ๋งˆ์ง€๋ง‰ Effect์˜ ํด๋ฆฐ์—… ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๋งˆ์ง€๋ง‰ Effect๋Š” ์„ธ ๋ฒˆ์งธ ๋ Œ๋”๋ง์—์„œ ์˜จ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์„ธ ๋ฒˆ์งธ ๋ Œ๋”๋ง์˜ ํด๋ฆฐ์—…์€ createConnection('travel') ์—ฐ๊ฒฐ์„ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์•ฑ์€ 'travel' ์ฑ„ํŒ…๋ฐฉ๊ณผ์˜ ์—ฐ๊ฒฐ์„ ํ•ด์ œํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋งŒ์˜ ๋™์ž‘ {/development-only-behaviors/}

Strict Mode๊ฐ€ ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ, React๋Š” ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ•œ ๋ฒˆ ๋งˆ์šดํŠธํ•œ ํ›„์— ๋‹ค์‹œ ๋งˆ์šดํŠธํ•ฉ๋‹ˆ๋‹ค(state์™€ DOM์€ ๋ณด์กด๋ฉ๋‹ˆ๋‹ค). ์ด๋Š” ํด๋ฆฐ์—…์ด ํ•„์š”ํ•œ Effect๋ฅผ ์ฐพ๋Š” ๋ฐ ๋„์›€์ด ๋˜๋ฉฐ ๊ฒฝ์Ÿ ์กฐ๊ฑด๊ณผ ๊ฐ™์€ ๋ฒ„๊ทธ๋ฅผ ์ดˆ๊ธฐ์— ๋“œ๋Ÿฌ๋‚  ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค. ๊ฒŒ๋‹ค๊ฐ€ React๋Š” ๊ฐœ๋ฐœ ์ค‘ ํŒŒ์ผ์„ ์ €์žฅํ•  ๋•Œ๋งˆ๋‹ค Effect๋ฅผ ๋‹ค์‹œ ๋งˆ์šดํŠธํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋‘ ๊ฐ€์ง€ ๋™์ž‘์€ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋งŒ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.

  • ์ด๋ฒคํŠธ์™€ ๋‹ฌ๋ฆฌ Effect๋Š” ํŠน์ • ์ƒํ˜ธ์ž‘์šฉ์ด ์•„๋‹Œ ๋ Œ๋”๋ง ์ž์ฒด์— ์˜ํ•ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.
  • Effect๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ปดํฌ๋„ŒํŠธ๋ฅผ ์™ธ๋ถ€ ์‹œ์Šคํ…œ(ํƒ€์‚ฌ API, ๋„คํŠธ์›Œํฌ ๋“ฑ)๊ณผ ๋™๊ธฐํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๊ธฐ๋ณธ์ ์œผ๋กœ Effect๋Š” ๋ชจ๋“  ๋ Œ๋”๋ง(์ดˆ๊ธฐ ๋ Œ๋”๋ง ํฌํ•จ) ํ›„์— ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
  • React๋Š” ๋ชจ๋“  ์˜์กด์„ฑ์ด ๋งˆ์ง€๋ง‰ ๋ Œ๋”๋ง๊ณผ ๋™์ผํ•œ ๊ฐ’์„ ๊ฐ€์ง€๋ฉด Effect๋ฅผ ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค.
  • ์˜์กด์„ฑ์„ "์„ ํƒ"ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์˜์กด์„ฑ์€ Effect ๋‚ด๋ถ€์˜ ์ฝ”๋“œ์— ์˜ํ•ด ๊ฒฐ์ •๋ฉ๋‹ˆ๋‹ค.
  • ๋นˆ ์˜์กด์„ฑ ๋ฐฐ์—ด([])์€ ์ปดํฌ๋„ŒํŠธ "๋งˆ์šดํŒ…"(ํ™”๋ฉด์— ์ถ”๊ฐ€๋จ)์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.
  • Strict Mode์—์„œ React๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‘ ๋ฒˆ ๋งˆ์šดํŠธํ•ฉ๋‹ˆ๋‹ค(๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋งŒ!) ์ด๋Š” Effect์˜ ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.
  • Effect๊ฐ€ ๋‹ค์‹œ ๋งˆ์šดํŠธ๋กœ ์ธํ•ด ์ค‘๋‹จ๋œ ๊ฒฝ์šฐ ํด๋ฆฐ์—… ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • React๋Š” Effect๊ฐ€ ๋‹ค์Œ์— ์‹คํ–‰๋˜๊ธฐ ์ „์— ์ •๋ฆฌ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋ฉฐ, ๋งˆ์šดํŠธ ํ•ด์ œ ์ค‘์—๋„ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

๋งˆ์šดํŠธ์‹œ input ํ•„๋“œ์— ํฌ์ปค์Šคํ•˜๊ธฐ {/focus-a-field-on-mount/}

์ด ์˜ˆ์‹œ์—์„œ๋Š” ํผ์ด <MyInput /> ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค.

ํ™”๋ฉด์— ๋‚˜ํƒ€๋‚  ๋•Œ MyInput์ด ์ž๋™์œผ๋กœ ํฌ์ปค์Šค๋˜๋„๋ก ์ž…๋ ฅ์˜ focus() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. ์ด๋ฏธ ์ฃผ์„ ์ฒ˜๋ฆฌ๋œ ๊ตฌํ˜„์ด ์žˆ์ง€๋งŒ ์ œ๋Œ€๋กœ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์™œ ์ž‘๋™ํ•˜์ง€ ์•Š๋Š”์ง€ ํ™•์ธํ•˜๊ณ  ์ˆ˜์ •ํ•ด ๋ณด์„ธ์š”. (autoFocus ์†์„ฑ์€ ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์œผ๋กœ ๊ฐ€์ •ํ•˜์„ธ์š”. ์šฐ๋ฆฌ๋Š” ์ฒ˜์Œ๋ถ€ํ„ฐ ๋™์ผํ•œ ๊ธฐ๋Šฅ์„ ๋‹ค์‹œ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.)

import { useEffect, useRef } from 'react';

export default function MyInput({ value, onChange }) {
  const ref = useRef(null);

  // TODO: This doesn't quite work. Fix it.
  // ref.current.focus()

  return (
    <input
      ref={ref}
      value={value}
      onChange={onChange}
    />
  );
}
import { useState } from 'react';
import MyInput from './MyInput.js';

export default function Form() {
  const [show, setShow] = useState(false);
  const [name, setName] = useState('Taylor');
  const [upper, setUpper] = useState(false);
  return (
    <>
      <button onClick={() => setShow(s => !s)}>form {show ? '์ˆจ๊ธฐ๊ธฐ' : '๋ณด๊ธฐ'}</button>
      <br />
      <hr />
      {show && (
        <>
          <label>
            ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”:
            <MyInput
              value={name}
              onChange={e => setName(e.target.value)}
            />
          </label>
          <label>
            <input
              type="checkbox"
              checked={upper}
              onChange={e => setUpper(e.target.checked)}
            />
            ๋Œ€๋ฌธ์ž๋กœ ๋งŒ๋“ค๊ธฐ
          </label>
          <p>์•ˆ๋…•ํ•˜์„ธ์š”, <b>{upper ? name.toUpperCase() : name}๋‹˜</b></p>
        </>
      )}
    </>
  );
}
label {
  display: block;
  margin-top: 20px;
  margin-bottom: 20px;
}

body {
  min-height: 150px;
}

์†”๋ฃจ์…˜์ด ์ œ๋Œ€๋กœ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๋ ค๋ฉด "form ๋ณด๊ธฐ"๋ฅผ ๋ˆ„๋ฅด๊ณ  ์ž…๋ ฅ๋ž€์ด ํฌ์ปค์Šค๋˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.(๊ฐ•์กฐ ํ‘œ์‹œ, ์ปค์„œ๊ฐ€ ๋‚ด๋ถ€์— ๋ฐฐ์น˜๋จ). "form ์ˆจ๊ธฐ๊ธฐ"๋ฅผ ๋ˆ„๋ฅด๊ณ  ๋‹ค์‹œ "form ๋ณด๊ธฐ"๋ฅผ ๋ˆŒ๋Ÿฌ ์ž…๋ ฅ๋ž€์ด ๋‹ค์‹œ ๊ฐ•์กฐ ํ‘œ์‹œ๋˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.

MyInput์€ ๋ Œ๋”๋ง ํ›„ ๋งค๋ฒˆ ํฌ์ปค์Šค๋˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ๋งˆ์šดํŠธ ์‹œ์—๋งŒ ํฌ์ปค์Šค๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋™์ž‘์ด ์˜ฌ๋ฐ”๋ฅธ์ง€ ํ™•์ธํ•˜๋ ค๋ฉด "form ๋ณด๊ธฐ"๋ฅผ ๋ˆ„๋ฅธ ๋‹ค์Œ "๋Œ€๋ฌธ์ž๋กœ ๋งŒ๋“ค๊ธฐ" ์ฒดํฌ๋ฐ•์Šค๋ฅผ ๋ฐ˜๋ณตํ•ด์„œ ํด๋ฆญํ•˜์„ธ์š”. ์ฒดํฌ๋ฐ•์Šค๋ฅผ ํด๋ฆญํ•ด๋„ ์ƒ๋‹จ์˜ ์ž…๋ ฅ๋ž€์€ ํฌ์ปค์Šค๊ฐ€ ๋˜์ง€ ์•Š์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋ Œ๋”๋ง ์ค‘์— ref.current.focus()๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์€ ์ ์ ˆํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ถ€์ˆ˜ ํšจ๊ณผ์ด๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๋ถ€์ˆ˜ ํšจ๊ณผ๋Š” ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ๋‚ด๋ถ€์— ๋ฐฐ์น˜ํ•˜๊ฑฐ๋‚˜ useEffect๋กœ ์„ ์–ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ์—๋Š” ๋ถ€์ž‘์šฉ์ด ํŠน์ • ์ƒํ˜ธ์ž‘์šฉ์ด ์•„๋‹ˆ๋ผ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋‚˜ํƒ€๋‚˜๋Š” ๊ฒƒ์— ์˜ํ•ด ๋ฐœ์ƒ๋˜๊ธฐ ๋•Œ๋ฌธ์— Effect ๋‚ด๋ถ€์— ๋„ฃ๋Š” ๊ฒƒ์ด ๋งž์Šต๋‹ˆ๋‹ค.

์‹ค์ˆ˜๋ฅผ ๊ณ ์น˜๋ ค๋ฉด ref.current.focus() ํ˜ธ์ถœ์„ Effect ์„ ์–ธ์œผ๋กœ ๊ฐ์‹ธ์„ธ์š”. ๊ทธ๋Ÿฐ ๋‹ค์Œ, ์ด Effect๊ฐ€ ๋ Œ๋”๋ง ํ›„ ๋งค๋ฒˆ ์‹คํ–‰๋˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ๋งˆ์šดํŠธ ์‹œ์—๋งŒ ์‹คํ–‰๋˜๋„๋ก ํ•˜๋ ค๋ฉด ๋นˆ [] ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”.

import { useEffect, useRef } from 'react';

export default function MyInput({ value, onChange }) {
  const ref = useRef(null);

  useEffect(() => {
    ref.current.focus();
  }, []);

  return (
    <input
      ref={ref}
      value={value}
      onChange={onChange}
    />
  );
}
import { useState } from 'react';
import MyInput from './MyInput.js';

export default function Form() {
  const [show, setShow] = useState(false);
  const [name, setName] = useState('Taylor');
  const [upper, setUpper] = useState(false);
  return (
    <>
      <button onClick={() => setShow(s => !s)}>form {show ? '์ˆจ๊ธฐ๊ธฐ' : '๋ณด๊ธฐ'} form</button>
      <br />
      <hr />
      {show && (
        <>
          <label>
            ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”:
            <MyInput
              value={name}
              onChange={e => setName(e.target.value)}
            />
          </label>
          <label>
            <input
              type="checkbox"
              checked={upper}
              onChange={e => setUpper(e.target.checked)}
            />
            ๋Œ€๋ฌธ์ž๋กœ ๋งŒ๋“ค๊ธฐ
          </label>
          <p>์•ˆ๋…•ํ•˜์„ธ์š”, <b>{upper ? name.toUpperCase() : name}</b>๋‹˜</p>
        </>
      )}
    </>
  );
}
label {
  display: block;
  margin-top: 20px;
  margin-bottom: 20px;
}

body {
  min-height: 150px;
}

์กฐ๊ฑด๋ถ€๋กœ input ํ•„๋“œ์— ํฌ์ปค์Šคํ•˜๊ธฐ {/focus-a-field-conditionally/}

์ด ํผ์€ ๋‘ ๊ฐœ์˜ <MyInput /> ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค.

"form ๋ณด๊ธฐ"๋ฅผ ๋ˆ„๋ฅด๋ฉด ๋‘ ๋ฒˆ์งธ ํ•„๋“œ๊ฐ€ ์ž๋™์œผ๋กœ ํฌ์ปค์Šค๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๋‘ <MyInput /> ์ปดํฌ๋„ŒํŠธ ๋ชจ๋‘ ๋‚ด๋ถ€์˜ ํ•„๋“œ์— ํฌ์ปค์Šค๋ฅผ ์ฃผ๋ ค๊ณ  ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๋‘ ๊ฐœ์˜ ์ž…๋ ฅ ํ•„๋“œ์— ์—ฐ์†ํ•ด์„œ focus()๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ๋งˆ์ง€๋ง‰ ํ˜ธ์ถœ์ด ํ•ญ์ƒ "์Šน๋ฆฌํ•˜๊ฒŒ" ๋ฉ๋‹ˆ๋‹ค.

์ด์ œ ์ฒซ ๋ฒˆ์งธ ํ•„๋“œ์— ํฌ์ปค์Šค๋ฅผ ์ฃผ๋ ค๋ฉด ์ฒซ ๋ฒˆ์งธ MyInput ์ปดํฌ๋„ŒํŠธ๊ฐ€ true๋กœ ์„ค์ •๋œ shouldFocus prop์„ ๋ฐ›๋„๋ก ๋ณ€๊ฒฝํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ณ€๊ฒฝ๋œ ๋กœ์ง์— ๋”ฐ๋ผ MyInput์ด ๋ฐ›์€ shouldFocus prop์ด true์ผ ๋•Œ์—๋งŒ focus()๊ฐ€ ํ˜ธ์ถœ๋˜๋„๋ก ๋ณ€๊ฒฝํ•ด ๋ณด์„ธ์š”.

import { useEffect, useRef } from 'react';

export default function MyInput({ shouldFocus, value, onChange }) {
  const ref = useRef(null);

  // TODO: shouldFocus๊ฐ€ true์ผ๋•Œ๋งŒ ํ˜ธ์ถœ๋˜๋„๋ก
  useEffect(() => {
    ref.current.focus();
  }, []);

  return (
    <input
      ref={ref}
      value={value}
      onChange={onChange}
    />
  );
}
import { useState } from 'react';
import MyInput from './MyInput.js';

export default function Form() {
  const [show, setShow] = useState(false);
  const [firstName, setFirstName] = useState('Taylor');
  const [lastName, setLastName] = useState('Swift');
  const [upper, setUpper] = useState(false);
  const name = firstName + ' ' + lastName;
  return (
    <>
      <button onClick={() => setShow(s => !s)}>form {show ? '์ˆจ๊ธฐ๊ธฐ' : '๋ณด๊ธฐ'}</button>
      <br />
      <hr />
      {show && (
        <>
          <label>
            ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”:
            <MyInput
              value={firstName}
              onChange={e => setFirstName(e.target.value)}
              shouldFocus={true}
            />
          </label>
          <label>
            ์„ฑ์„ ์ž…๋ ฅํ•˜์„ธ์š”:
            <MyInput
              value={lastName}
              onChange={e => setLastName(e.target.value)}
              shouldFocus={false}
            />
          </label>
          <p>์•ˆ๋…•ํ•˜์„ธ์š”, <b>{upper ? name.toUpperCase() : name}</b>๋‹˜</p>
        </>
      )}
    </>
  );
}
label {
  display: block;
  margin-top: 20px;
  margin-bottom: 20px;
}

body {
  min-height: 150px;
}

ํ•ด๋‹น ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ์ฃผ์–ด์ง„ ๊ฒ€์ฆ ๋ฐฉ๋ฒ•์„ ๋”ฐ๋ผ ์ง„ํ–‰ํ•ด ๋ด…์‹œ๋‹ค. "form ๋ณด๊ธฐ" ๋ฒ„ํŠผ์„ ๋ฐ˜๋ณต์ ์œผ๋กœ ๋ˆ„๋ฅด๊ณ  "form ์ˆจ๊ธฐ๊ธฐ" ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์—ฌ ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํผ์ด ๋‚˜ํƒ€๋‚  ๋•Œ, ์ฒซ ๋ฒˆ์งธ ์ž…๋ ฅ ํ•„๋“œ์—๋งŒ ํฌ์ปค์Šค๊ฐ€ ์„ค์ •๋ฉ๋‹ˆ๋‹ค. ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ฒซ ๋ฒˆ์งธ ์ž…๋ ฅ ํ•„๋“œ๋ฅผ shouldFocus={true}๋กœ ๋ Œ๋”๋งํ•˜๊ณ  ๋‘ ๋ฒˆ์งธ ์ž…๋ ฅ ํ•„๋“œ๋ฅผ shouldFocus={false}๋กœ ๋ Œ๋”๋งํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๋˜ํ•œ ๋‘ ์ž…๋ ฅ ํ•„๋“œ ๋ชจ๋‘ ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•˜๋ฉฐ, ๋‘˜ ๋‹ค ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์กฐ๊ฑด๋ถ€๋กœ useEffect๋ฅผ ์„ ์–ธํ•  ์ˆ˜๋Š” ์—†์ง€๋งŒ, useEffect ๋‚ด๋ถ€์— ์กฐ๊ฑด๋ถ€ ๋กœ์ง์„ ํฌํ•จ์‹œ์ผœ ์›ํ•˜๋Š” ๋™์ž‘์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์กฐ๊ฑด๋ถ€ ๋กœ์ง์„ Effect ๋‚ด๋ถ€๋กœ ๋„ฃ์–ด์ฃผ์„ธ์š”. shouldFocus๋ฅผ Effect ๋‚ด์—์„œ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ์ด๋ฅผ ์˜์กด์„ฑ์œผ๋กœ ๋ช…์‹œํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. (๋งŒ์•ฝ ์–ด๋–ค input์˜ shouldFocus๊ฐ€ false์—์„œ true๋กœ ๋ณ€๊ฒฝ๋œ๋‹ค๋ฉด, ๋งˆ์šดํŠธ ํ›„์— ํฌ์ปค์Šค๊ฐ€ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค.)

import { useEffect, useRef } from 'react';

export default function MyInput({ shouldFocus, value, onChange }) {
  const ref = useRef(null);

  useEffect(() => {
    if (shouldFocus) {
      ref.current.focus();
    }
  }, [shouldFocus]);

  return (
    <input
      ref={ref}
      value={value}
      onChange={onChange}
    />
  );
}
import { useState } from 'react';
import MyInput from './MyInput.js';

export default function Form() {
  const [show, setShow] = useState(false);
  const [firstName, setFirstName] = useState('Taylor');
  const [lastName, setLastName] = useState('Swift');
  const [upper, setUpper] = useState(false);
  const name = firstName + ' ' + lastName;
  return (
    <>
      <button onClick={() => setShow(s => !s)}>form {show ? '์ˆจ๊ธฐ๊ธฐ' : '๋ณด๊ธฐ'}</button>
      <br />
      <hr />
      {show && (
        <>
          <label>
            ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”:
            <MyInput
              value={firstName}
              onChange={e => setFirstName(e.target.value)}
              shouldFocus={true}
            />
          </label>
          <label>
            ์„ฑ์„ ์ž…๋ ฅํ•˜์„ธ์š”:
            <MyInput
              value={lastName}
              onChange={e => setLastName(e.target.value)}
              shouldFocus={false}
            />
          </label>
          <p>์•ˆ๋…•ํ•˜์„ธ์š”, <b>{upper ? name.toUpperCase() : name}๋‹˜</b></p>
        </>
      )}
    </>
  );
}
label {
  display: block;
  margin-top: 20px;
  margin-bottom: 20px;
}

body {
  min-height: 150px;
}

๋‘ ๋ฒˆ ์‹คํ–‰๋˜๋Š” interval ๊ณ ์น˜๊ธฐ {/fix-an-interval-that-fires-twice/}

์•„๋ž˜ Counter ์ปดํฌ๋„ŒํŠธ๋Š” ๋งค ์ดˆ๋งˆ๋‹ค ์ฆ๊ฐ€ํ•˜๋Š” ์นด์šดํ„ฐ๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งˆ์šดํŠธ๋  ๋•Œ setInterval์„ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ์ด๋กœ ์ธํ•ด onTick ํ•จ์ˆ˜๊ฐ€ ๋งค ์ดˆ๋งˆ๋‹ค ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. onTick ํ•จ์ˆ˜๋Š” ์นด์šดํ„ฐ๋ฅผ ์ฆ๊ฐ€์‹œํ‚ต๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ 1์ดˆ๋งˆ๋‹ค ํ•œ ๋ฒˆ์”ฉ ์ฆ๊ฐ€ํ•˜๋Š” ๋Œ€์‹  ๋‘ ๋ฒˆ์”ฉ ์ฆ๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์™œ ๊ทธ๋Ÿด๊นŒ์š”? ๋ฒ„๊ทธ์˜ ์›์ธ์„ ์ฐพ์•„ ์ˆ˜์ •ํ•˜์„ธ์š”.

setInterval์€ interval ID๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š”๋ฐ, ์ด๋ฅผ clearInterval ํ•จ์ˆ˜์— ์ „๋‹ฌํ•˜์—ฌ interval์„ ์ค‘์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import { useState, useEffect } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    function onTick() {
      setCount(c => c + 1);
    }

    setInterval(onTick, 1000);
  }, []);

  return <h1>{count}</h1>;
}
import { useState } from 'react';
import Counter from './Counter.js';

export default function Form() {
  const [show, setShow] = useState(false);
  return (
    <>
      <button onClick={() => setShow(s => !s)}>์นด์šดํ„ฐ {show ? '์ˆจ๊ธฐ๊ธฐ' : '๋ณด๊ธฐ'}</button>
      <br />
      <hr />
      {show && <Counter />}
    </>
  );
}
label {
  display: block;
  margin-top: 20px;
  margin-bottom: 20px;
}

body {
  min-height: 150px;
}

Strict Mode๊ฐ€ ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ (์ด ์‚ฌ์ดํŠธ์˜ ์ฝ”๋“œ ์˜ˆ์‹œ ์ƒŒ๋“œ๋ฐ•์Šค์ฒ˜๋Ÿผ), React๋Š” ๊ฐœ๋ฐœ ์ค‘์— ๊ฐ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ•œ ๋ฒˆ์”ฉ ๋ฆฌ๋งˆ์šดํŠธํ•ฉ๋‹ˆ๋‹ค. ์ด๋กœ ์ธํ•ด ๊ฐ„๊ฒฉ์ด ๋‘ ๋ฒˆ ์„ค์ •๋˜์–ด ๋งค ์ดˆ๋งˆ๋‹ค ์นด์šดํ„ฐ๊ฐ€ ๋‘ ๋ฒˆ ์ฆ๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ React์˜ ๋™์ž‘์ด ๋ฒ„๊ทธ์˜ ์›์ธ์€ ์•„๋‹™๋‹ˆ๋‹ค. ๋ฒ„๊ทธ๋Š” ์ฝ”๋“œ์— ์žˆ์Šต๋‹ˆ๋‹ค. React์˜ ๋™์ž‘์€ ๋ฒ„๊ทธ๋ฅผ ๋” ๋ˆˆ์— ๋„๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ์‹ค์ œ ๋ฌธ์ œ๋Š” ์ด Effect๊ฐ€ ํ”„๋กœ์„ธ์Šค๋ฅผ ์‹œ์ž‘ํ•œ ํ›„์— ํด๋ฆฐ์—…ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ด ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•˜๋ ค๋ฉด setInterval์— ์˜ํ•ด ๋ฐ˜ํ™˜๋œ interval ID๋ฅผ ์ €์žฅํ•˜๊ณ , clearInterval์„ ์‚ฌ์šฉํ•˜์—ฌ ํด๋ฆฐ์—… ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ•˜์„ธ์š”.

import { useState, useEffect } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    function onTick() {
      setCount(c => c + 1);
    }

    const intervalId = setInterval(onTick, 1000);
    return () => clearInterval(intervalId);
  }, []);

  return <h1>{count}</h1>;
}
import { useState } from 'react';
import Counter from './Counter.js';

export default function App() {
  const [show, setShow] = useState(false);
  return (
    <>
      <button onClick={() => setShow(s => !s)}>์นด์šดํ„ฐ {show ? '์ˆจ๊ธฐ๊ธฐ' : '๋ณด๊ธฐ'}</button>
      <br />
      <hr />
      {show && <Counter />}
    </>
  );
}
label {
  display: block;
  margin-top: 20px;
  margin-bottom: 20px;
}

body {
  min-height: 150px;
}

๊ฐœ๋ฐœ ์ค‘์— React๋Š” ์—ฌ์ „ํžˆ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ•œ ๋ฒˆ ๋ฆฌ๋งˆ์šดํŠธํ•˜์—ฌ ํด๋ฆฐ์—…์ด ์ž˜ ๊ตฌํ˜„๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ตœ์ดˆ์˜ setInterval ํ˜ธ์ถœ ์ดํ›„์—, ๋ฐ”๋กœ ๋‹ค์Œ clearInterval, ๊ทธ๋ฆฌ๊ณ  ๋‹ค์‹œ setInterval ํ˜ธ์ถœ์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ํ”„๋กœ๋•์…˜(์šด์˜ ํ™˜๊ฒฝ)์—์„œ๋Š” setInterval ํ˜ธ์ถœ์ด ํ•œ ๋ฒˆ๋งŒ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ ํ™˜๊ฒฝ๊ณผ ์šด์˜ ํ™˜๊ฒฝ ๋ชจ๋‘ ์‚ฌ์šฉ์ž๊ฐ€ ๋ณผ ์ˆ˜ ์žˆ๋Š” ๋™์ž‘์€ ๋™์ผํ•ฉ๋‹ˆ๋‹ค. ์นด์šดํ„ฐ๊ฐ€ 1์ดˆ๋งˆ๋‹ค ํ•œ ๋ฒˆ์”ฉ ์ฆ๊ฐ€ํ•˜๋Š” ๊ฒƒ์ด์ฃ .

Effect ๋‚ด๋ถ€์—์„œ์˜ ์ž˜๋ชป๋œ ๋ฐ์ดํ„ฐ ํŽ˜์นญ ๊ณ ์น˜๊ธฐ {/fix-fetching-inside-an-effect/}

์ด ์ปดํฌ๋„ŒํŠธ๋Š” select ํƒœ๊ทธ๋กœ ์„ ํƒํ•œ ์‚ฌ๋žŒ์˜ ์ผ๋Œ€๊ธฐ๋ฅผ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. ์ด ์ปดํฌ๋„ŒํŠธ๋Š” ์„ ํƒ๋œ person์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค, ๋˜ํ•œ ๋งˆ์šดํŠธ๋  ๋•Œ๋งˆ๋‹ค ๋น„๋™๊ธฐ ํ•จ์ˆ˜ fetchBio(person)๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์ผ๋Œ€๊ธฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ต๋‹ˆ๋‹ค. ์ด ๋น„๋™๊ธฐ ํ•จ์ˆ˜๋Š” Promise๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉฐ, ์ด Promise๋Š” ๊ฒฐ๊ตญ ๋ฌธ์ž์—ด๋กœ resolve๋ฉ๋‹ˆ๋‹ค. ๋ถˆ๋Ÿฌ์˜ค๊ธฐ๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด setBio๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ํ•ด๋‹น ๋ฌธ์ž์—ด์„ select์˜ option์œผ๋กœ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.

{/* not the most efficient, but this validation is enabled in the linter only, so it's fine to ignore it here since we know what we're doing */}

import { useState, useEffect } from 'react';
import { fetchBio } from './api.js';

export default function Page() {
  const [person, setPerson] = useState('Alice');
  const [bio, setBio] = useState(null);

  useEffect(() => {
    setBio(null);
    fetchBio(person).then(result => {
      setBio(result);
    });
  }, [person]);

  return (
    <>
      <select value={person} onChange={e => {
        setPerson(e.target.value);
      }}>
        <option value="Alice">Alice</option>
        <option value="Bob">Bob</option>
        <option value="Taylor">Taylor</option>
      </select>
      <hr />
      <p><i>{bio ?? 'Loading...'}</i></p>
    </>
  );
}
export async function fetchBio(person) {
  const delay = person === 'Bob' ? 2000 : 200;
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('์ด๊ฒƒ์€ ' + person + '์˜ ์ผ๋Œ€๊ธฐ์ž…๋‹ˆ๋‹ค.');
    }, delay);
  })
}

์ด ์ฝ”๋“œ์— ๋ฒ„๊ทธ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. "Alice"๋ฅผ ์„ ํƒํ•œ ๋‹ค์Œ "Bob"์„ ์„ ํƒํ•œ ๋‹ค์Œ ๋ฐ”๋กœ "Taylor"์„ ์„ ํƒํ•˜๋ฉด ๋ฒ„๊ทธ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์ถฉ๋ถ„ํžˆ ๋น ๋ฅด๊ฒŒ ์ด ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋ฉด ๋ฒ„๊ทธ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Taylor๊ฐ€ ์„ ํƒ๋˜์—ˆ์ง€๋งŒ ์•„๋ž˜ ๋‹จ๋ฝ์—๋Š” "์ด๊ฒƒ์€ Bob์˜ ์ผ๋Œ€๊ธฐ์ž…๋‹ˆ๋‹ค."๋ผ๊ณ  ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ํ˜„์ƒ์ด ๋ฐœ์ƒํ•˜๋Š” ์ด์œ ๋Š” ๋ฌด์—‡์ผ๊นŒ์š”? ์ด Effect ๋‚ด๋ถ€์˜ ๋ฒ„๊ทธ๋ฅผ ์ˆ˜์ •ํ•˜์„ธ์š”.

Effect๊ฐ€ ๋น„๋™๊ธฐ๋กœ ๋ฌด์–ธ๊ฐ€๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒฝ์šฐ ์ผ๋ฐ˜์ ์œผ๋กœ ํด๋ฆฐ์—…์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

๋ฒ„๊ทธ๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•˜๋ ค๋ฉด ๋‹ค์Œ ์ˆœ์„œ๋Œ€๋กœ ์ง„ํ–‰๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค:

  • 'Bob'์„ ์„ ํƒํ•˜๋ฉด fetchBio('Bob')๊ฐ€ ํŠธ๋ฆฌ๊ฑฐ๋ฉ๋‹ˆ๋‹ค.
  • 'Taylor'์„ ์„ ํƒํ•˜๋ฉด fetchBio('Taylor')๊ฐ€ ํŠธ๋ฆฌ๊ฑฐ๋ฉ๋‹ˆ๋‹ค.
  • 'Taylor'์˜ ์ผ๋Œ€๊ธฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์ž‘์—…์ด 'Bob'์˜ ์ผ๋Œ€๊ธฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์ž‘์—…๋ณด๋‹ค ๋จผ์ € ์™„๋ฃŒ๋ฉ๋‹ˆ๋‹ค.
  • 'Taylor' ๋ Œ๋”๋ง์˜ Effect๊ฐ€ setBio('This is Taylorโ€™s bio')๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.
  • 'Bob'์˜ ์ผ๋Œ€๊ธฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์ž‘์—…์ด ์™„๋ฃŒ๋ฉ๋‹ˆ๋‹ค.
  • 'Bob' ๋ Œ๋”๋ง์˜ Effect๊ฐ€ setBio('This is Bobโ€™s bio')๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด Taylor๊ฐ€ ์„ ํƒ๋˜์—ˆ์Œ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  Bob์˜ ์ผ๋Œ€๊ธฐ๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ์ด์™€ ๊ฐ™์€ ๋ฒ„๊ทธ๋Š” ๋‘ ๊ฐœ์˜ ๋น„๋™๊ธฐ ์ž‘์—…์ด "๊ฒฝ์Ÿ(race)"ํ•˜๋ฉฐ ์ž‘์—… ์™„๋ฃŒ์˜ ์ˆœ์„œ๋ฅผ ์˜ˆ์ƒํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ์Ÿ ์กฐ๊ฑด(race condition)์ด๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

์ด ๊ฒฝ์Ÿ ์กฐ๊ฑด์„ ํ•ด๊ฒฐํ•˜๋ ค๋ฉด ํด๋ฆฐ์—… ํ•จ์ˆ˜๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”.

{/* not the most efficient, but this validation is enabled in the linter only, so it's fine to ignore it here since we know what we're doing */}

import { useState, useEffect } from 'react';
import { fetchBio } from './api.js';

export default function Page() {
  const [person, setPerson] = useState('Alice');
  const [bio, setBio] = useState(null);
  useEffect(() => {
    let ignore = false;
    setBio(null);
    fetchBio(person).then(result => {
      if (!ignore) {
        setBio(result);
      }
    });
    return () => {
      ignore = true;
    }
  }, [person]);

  return (
    <>
      <select value={person} onChange={e => {
        setPerson(e.target.value);
      }}>
        <option value="Alice">Alice</option>
        <option value="Bob">Bob</option>
        <option value="Taylor">Taylor</option>
      </select>
      <hr />
      <p><i>{bio ?? 'Loading...'}</i></p>
    </>
  );
}
export async function fetchBio(person) {
  const delay = person === 'Bob' ? 2000 : 200;
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('์ด๊ฒƒ์€ ' + person + '์˜ ์ผ๋Œ€๊ธฐ์ž…๋‹ˆ๋‹ค.');
    }, delay);
  })
}

๊ฐ ๋ Œ๋”๋ง์˜ Effect๋Š” ์ž์ฒด ignore ๋ณ€์ˆ˜๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ฒ˜์Œ์— ignore ๋ณ€์ˆ˜๋Š” false๋กœ ์„ค์ •๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ Effect๊ฐ€ ํด๋ฆฐ์—…๋˜๋ฉด(์˜ˆ: ๋‹ค๋ฅธ ์‚ฌ๋žŒ์„ ์„ ํƒํ•  ๋•Œ), ํ•ด๋‹น Effect์˜ ignore ๋ณ€์ˆ˜๋Š” true๋กœ ์„ค์ •๋ฉ๋‹ˆ๋‹ค. ์ด์ œ ์–ด๋–ค ์ˆœ์„œ๋กœ ์š”์ฒญ์ด ์™„๋ฃŒ๋˜๋Š”์ง€๋Š” ์ค‘์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋งˆ์ง€๋ง‰ ์‚ฌ๋žŒ์˜ Effect๋งŒ ignore๊ฐ€ false๋กœ ์„ค์ •๋˜๋ฏ€๋กœ setBio(result)๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ์ด์ „ Effect๋Š” ์ •๋ฆฌ๋˜์—ˆ์œผ๋ฏ€๋กœ if (!ignore) ๊ฒ€์‚ฌ๊ฐ€ setBio ํ˜ธ์ถœ์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค:

  • 'Bob'์„ ์„ ํƒํ•˜๋ฉด fetchBio('Bob')๊ฐ€ ํŠธ๋ฆฌ๊ฑฐ๋ฉ๋‹ˆ๋‹ค.
  • 'Taylor'์„ ์„ ํƒํ•˜๋ฉด fetchBio('Taylor')๊ฐ€ ํŠธ๋ฆฌ๊ฑฐ๋˜๋ฉฐ ์ด์ „(Bob์˜) Effect๊ฐ€ **์ •๋ฆฌ(cleaned up)**๋ฉ๋‹ˆ๋‹ค.
  • 'Taylor'์˜ ์ผ๋Œ€๊ธฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์ž‘์—…์ด 'Bob'์˜ ์ผ๋Œ€๊ธฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์ž‘์—…๋ณด๋‹ค ๋จผ์ € ์™„๋ฃŒ๋ฉ๋‹ˆ๋‹ค.
  • 'Taylor' ๋ Œ๋”๋ง์˜ Effect๊ฐ€ setBio('This is Taylorโ€™s bio')๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.
  • 'Bob'์˜ ์ผ๋Œ€๊ธฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์ž‘์—…์ด ์™„๋ฃŒ๋ฉ๋‹ˆ๋‹ค.
  • 'Bob' ๋ Œ๋”๋ง์˜ Effect๋Š” ignore ํ”Œ๋ž˜๊ทธ๊ฐ€ true๋กœ ์„ค์ •๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์•„๋ฌด ์ผ๋„ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์˜ค๋ž˜๋œ API ํ˜ธ์ถœ์˜ ๊ฒฐ๊ณผ๋ฅผ ๋ฌด์‹œํ•˜๋Š” ๊ฒƒ ์™ธ์—๋„ ๋” ์ด์ƒ ํ•„์š”ํ•˜์ง€ ์•Š์€ ์š”์ฒญ์„ ์ทจ์†Œํ•˜๊ธฐ ์œ„ํ•ด AbortController๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด๊ฒƒ๋งŒ์œผ๋กœ๋Š” ๊ฒฝ์Ÿ ์กฐ๊ฑด์— ๋Œ€ํ•œ ์ถฉ๋ถ„ํ•œ ๋ณดํ˜ธ๊ฐ€ ์ด๋ค„์ง€์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ”ผ์น˜ ๋ชปํ•  ์ƒํ™ฉ์—์„œ๋Š” ์ถ”๊ฐ€์ ์ธ ๋น„๋™๊ธฐ ์ž‘์—…์ด ํ›„ํ–‰ํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ignore์™€ ๊ฐ™์€ ๋ช…์‹œ์  ํ”Œ๋ž˜๊ทธ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ด๋Ÿฌํ•œ ์ข…๋ฅ˜์˜ ๋ฌธ์ œ๋ฅผ ๊ฐ€์žฅ ์•ˆ์ „ํ•˜๊ฒŒ ํ•ด๊ฒฐํ•˜๋Š” ๊ฐ€์žฅ ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.