๐ŸŽŠ

12. useDeferredValue

12.1. useDeferredValue๋ž€?

12.1.1. useDeferredValue ๊ธฐ๋ณธ ์„ค๋ช…

useDeferredValue๋Š” React v18.0์— ์ƒˆ๋กœ ๋‚˜์˜จ Hook์œผ๋กœ, ๋ฆฌ๋ Œ๋”๋ง์˜ ์šฐ์„ ์ˆœ์œ„์— ๋”ฐ๋ผ ์˜๋„์ ์œผ๋กœ ๋ Œ๋”๋ง์„ ์ง€์—ฐํ•  ์ˆ˜ ์žˆ๋Š” Hook์ž…๋‹ˆ๋‹ค. useDeferredValue๋Š” ๊ฐ’์˜ ์—…๋ฐ์ดํŠธ ์šฐ์„ ์ˆœ์œ„๋ฅผ ์ง€์ •ํ•˜์—ฌ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์€ ์ž‘์—…์„ ์‹คํ–‰ํ•˜๋Š” ๋™์•ˆ useMemo์™€ ๊ฐ™์ด ๊ธฐ์กด์˜ ๊ฐ’์„ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฉฐ ์—…๋ฐ์ดํŠธ๋ฅผ ์ง€์—ฐ์‹œํ‚ต๋‹ˆ๋‹ค.
์ด๋Ÿฌํ•œ ๊ธฐ๋Šฅ์€ ์ดํ›„ ์ ˆ์—์„œ ๋‹ค๋ฃฐ debounce, throttle๊ณผ ๋น„์Šทํ•ด ๋ณด์ด์ง€๋งŒย useDeferredValue Hook์„ ์ด์šฉํ•˜๋ฉด ๋”œ๋ ˆ์ด ์‹œ๊ฐ„์„ ๊ณ ์ •ํ•˜์ง€ ์•Š๊ณ  ๋” ๊ธด๊ธ‰ํ•œ ์š”์ฒญ์ด ๋๋‚œ ํ›„ ๋ฐ”๋กœ ์ง€์—ฐ๋œ ๋ Œ๋”๋ง์„ ์‹คํ–‰ํ•œ๋‹ค๋Š” ์ด์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
const deferredValue = useDeferredValue(value);
ย 
useDeferredValue๋Š” ์ฒซ ๋ฒˆ์งธ ์ธ์ž๋กœ ์ง€์—ฐํ•˜๊ธธ ์›ํ•˜๋Š” ๊ฐ’์„ ๋ฐ›๊ณ  ํ•ด๋‹น ๊ฐ’์˜ ๋ณต์‚ฌ๋ณธ์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
ย 

12.1.2 useDeferredValue ์‚ฌ์šฉ๋ฒ•

๋ธŒ๋ผ์šฐ์ €์—์„œ ๋” ์šฐ์„ ์ ์œผ๋กœ ์ถœ๋ ฅ๋˜์–ด์•ผ ํ•  ๊ฐ’์„ ๊ณ„์‚ฐํ•˜๋Š” ๋™์•ˆย useDeferredValue๋ฅผ ์ด์šฉํ•˜์—ฌ DOM ํŠธ๋ฆฌ์—์„œ ๊ธด๊ธ‰ํ•˜์ง€ ์•Š์€ ๋ถ€๋ถ„์˜ ๋ฆฌ๋ Œ๋”๋ง์„ ์˜๋„์ ์œผ๋กœ ์ง€์—ฐ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด ํ˜„์žฌ ์‹คํ–‰๋˜๋Š” ๋ Œ๋”๋ง์ด input์—์„œ์™€ ๊ฐ™์ด ํƒ€์ดํ•‘ ์ฆ‰์‹œ ํ™”๋ฉด์— ๋ฐ˜์˜๋˜์–ด์•ผ ํ•˜๋Š” ๋ Œ๋”๋ง์ด๋ผ๋ฉด React์—์„œ๋Š” ์ด๋Ÿฌํ•œ ๊ธด๊ธ‰ํ•œ ๋ Œ๋”๋ง์„ ์™„๋ฃŒํ•˜๊ธฐ ์ „๊นŒ์ง€๋Š” ์ง€์—ฐ๋œ ๊ฐ’์„ ๊ธฐ์กด์˜ ๊ฐ’์œผ๋กœ ์ถœ๋ ฅํ•˜๋‹ค๊ฐ€, ๊ธด๊ธ‰ํ•œ ๋ Œ๋”๋ง์ด ๋๋‚˜๋ฉด ์ƒˆ๋กœ์šด ๊ฐ’์„ ์ถœ๋ ฅํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
๊ฐ„๋‹จํ•œ ์˜ˆ์ œ๋ฅผ ํ†ตํ•ด useDeferredValue ์‚ฌ์šฉ๋ฒ•์„ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 
import React, { useState } from 'react'; export default function App() { let heavyArray = new Array(10000).fill(0); const [type, setType] = useState(0); const typeChange = (e) => { setType(e.target.value); }; return ( <div> <input type="text" onChange={typeChange} /> { heavyArray.map(() => { return <div>{type}</div>; })} </div>); }
App.jsx
ย 
์œ„ ์ฝ”๋“œ๋Š” input์— ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ๊ฐ™์€ ๋‚ด์šฉ์ด 10000๋ฒˆ ์ถœ๋ ฅ๋˜๋Š” ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค. ๊ฐ„๋‹จํ•œ ์ฝ”๋“œ์ง€๋งŒ ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ๋ชจ๋“  ๊ฐ’์— ๋Œ€ํ•ด 10000๊ฐœ์˜ ๊ฒฐ๊ณผ๋ฅผ ๋ฆฌ๋ Œ๋”๋งํ•˜๋ฉด์„œ ์„ฑ๋Šฅ์— ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์ด ๋•Œย useDeferredValue๋ฅผ ์ด์šฉํ•œ๋‹ค๋ฉดย heavyArray์˜ ์ถœ๋ ฅ์„ ์ง€์—ฐ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
import React, { useState, useDeferredValue } from 'react'; export default function App() { let heavyArray = new Array(10000).fill(0); const [type, setType] = useState(0); let deferredType = useDeferredValue(type); const typeChange = (e) => { setType(e.target.value); }; return ( <div> <input type="text" onChange={typeChange} /> { heavyArray.map(() => { return <div>{deferredType}</div>; })} </div>); }
App.jsx
ย 
๊ฐ™์€ ์ฝ”๋“œ์—์„œย useDeferredValue๋ฅผ ์‚ฌ์šฉํ•œย deferredType๊ฐ’์„ ๋„ฃ์–ด์ฃผ๋ฉดย type๊ฐ’์ด ๋ณ€๊ฒฝ๋˜๋Š” ์‹œ์ ์ด ์ง€์—ฐ๋˜์–ด ํ•ด๋‹น ๊ฐ’์„ ์‚ฌ์šฉํ•˜์—ฌ ์ถœ๋ ฅ๋˜๋Š” ์š”์†Œ๋“ค์˜ ๋ Œ๋”๋ง์€ ํ›„์— ์ฒ˜๋ฆฌ๋  ์ˆ˜ ์žˆ์–ด ์„ฑ๋Šฅ ๊ฐœ์„ ์— ๋„์›€์„ ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์ด์ฒ˜๋Ÿผย useDeferredValue๋ฅผ ์ด์šฉํ•˜๋ฉด ๊ธด๊ธ‰ํ•œ ๋ Œ๋”๋ง์„ ์šฐ์„ ์ ์œผ๋กœ ์‹คํ–‰ํ•˜๋„๋ก ์›ํ•˜๋Š” ๊ฐ’์˜ ๋ Œ๋”๋ง์„ ์ง€์—ฐ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ useDeferredValue๋Š” ๊ฐ’์˜ ์šฐ์„ ์ˆœ์œ„๋งŒ์„ ์ง€์ •ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ž์‹ ์ปดํฌ๋„ŒํŠธ์˜ ๊ฐ’์ด๋‚˜ ์ƒํƒœ์˜ ์—…๋ฐ์ดํŠธ๋ฅผ ์ง€์—ฐ์‹œํ‚ค๊ธฐ ์œ„ํ•ด์„œ๋Š” useMemo ํ˜น์€ React.memo๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด์— ๋Œ€ํ•ด์„œ๋Š” ์ดํ›„ 12.4์—์„œ ์‚ดํŽด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
์ด์–ด์ง€๋Š” ๋‚ด์šฉ์œผ๋กœ๋Š” React v18.0 ์ถœ์‹œ ์ด์ „์— ์˜๋„์ ์œผ๋กœ ๋ Œ๋”๋ง์„ ์ง€์—ฐํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๋Š” ๊ธฐ๋ฒ•์ธ debounce์™€ throttle๋ฅผ ๋จผ์ € ์‚ดํŽด๋ณธ ๋’ค, useDeferredValue Hook์— ๋Œ€ํ•ด ์ง์ ‘ ์˜ˆ์‹œ ์ฝ”๋“œ๋กœ ์‚ดํŽด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 

12.2 debounce

12.2.1 debounce๋ž€?

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

12.2.2 debounce ์‚ฌ์šฉํ•ด๋ณด๊ธฐ

๋‹ค์Œ์€ ์ž…๋ ฅํ•œ ์ˆซ์ž์˜ ๋ฐฐ์ˆ˜๋ฅผ ์ฐพ์•„์ฃผ๋Š” ์˜ˆ์‹œ๋ฅผ ๊ตฌํ˜„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.
ย 
import { useState, useEffect } from "react"; function App() { const [value, setValue] = useState(""); const [searchResult, setSearchResult] = useState([]); const findNumber = (value) => { const numberGenerater = Array(10000) .fill() .map((value, index) => index + 1); setSearchResult(numberGenerater.filter((num) => !(num % parseInt(value)))); }; useEffect(() => { if (value) { findNumber(value); } else { setSearchResult([]); } }, [value]); return ( <div> <p>์ž…๋ ฅํ•œ ์ˆซ์ž์˜ ๋ฐฐ์ˆ˜๋ฅผ ์ฐพ์•„๋ณด์•„์š”~๐ŸŽˆ</p> <form action="" onSubmit={(e) => e.preventDefault()}> <label htmlFor="">์ž…๋ ฅ</label> <input type="number" placeholder="์ˆซ์ž๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”" value={value} onChange={(e) => setValue(e.target.value)} /> </form> <ul style={{ listStyle: "none" }}> ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ {searchResult.map((num, index) => ( <li key={index}>{num}</li> ))} </ul> </div> ); } export default App;
App.jsx
ย 
์œ„ ์˜ˆ์ œ์— ๋Œ€ํ•˜์—ฌ ๊ฐ„๋žตํžˆ ์„ค๋ช…ํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.
  1. input ์š”์†Œ์— ๊ฐ’์„ ๋„ฃ์œผ๋ฉด onChange์— ์˜ํ•˜์—ฌ value๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค setValue๋ฅผ ํ†ตํ•˜์—ฌ value๊ฐ’์ด ๋ณ€๊ฒฝ๋ฉ๋‹ˆ๋‹ค.
  1. value๊ฐ’์ด ๋ณ€๋™๋จ์— ๋”ฐ๋ผ ๊ฐ์‹œํ•˜๊ณ  ์žˆ๋˜ useEffect๊ฐ€ ๋™์ž‘ํ•˜๊ฒŒ ๋˜๊ณ , value์— ๊ฐ’์ด ์žˆ์„ ๊ฒฝ์šฐ findNumber ํ•จ์ˆ˜๊ฐ€ ๋™์ž‘ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
  1. findNumber ํ•จ์ˆ˜๊ฐ€ ๋™์ž‘ํ•จ์— ๋”ฐ๋ผ 10,000๊ฐœ์˜ ๊ฐ’์ด ๋“ค์–ด์žˆ๋Š” ๋ฐฐ์—ด์ด ์ƒ์„ฑ๋˜๊ณ , ๊ทธ ์ค‘ value์˜ ๋ฐฐ์ˆ˜์ธ ๊ฐ’๋“ค๋งŒ โ€œ๊ฒ€์ƒ‰๊ฒฐ๊ณผโ€์— ๋‚˜ํƒ€๋‚˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
ย 
์ˆซ์ž๋ฅผ input์— ์ž…๋ ฅํ•˜๋ฉด, ๊ฐ’์ด ์ž…๋ ฅ๋  ๋•Œ๋งˆ๋‹ค findNumber ํ•จ์ˆ˜๊ฐ€ ๋™์ž‘ํ•˜๊ฒŒ ๋จ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜์˜ ๊ทธ๋ฆผ์€ ๊ทธ์— ๋Œ€ํ•œ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค.
ย 
๊ทธ๋ฆผ 12-1๊ทธ๋ฆผ 12-1
๊ทธ๋ฆผ 12-1
๊ทธ๋ฆผ 12-2๊ทธ๋ฆผ 12-2
๊ทธ๋ฆผ 12-2
ย 
๊ทธ๋ฆผ 12-3๊ทธ๋ฆผ 12-3
๊ทธ๋ฆผ 12-3
๊ทธ๋ฆผ 12-4๊ทธ๋ฆผ 12-4
๊ทธ๋ฆผ 12-4
ย 
์ด์ œ debounce๋ฅผ ์ด์šฉํ•œ ์˜ˆ์ œ๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 
import { useState, useEffect } from "react"; function App() { const [value, setValue] = useState(""); const [searchResult, setSearchResult] = useState([]); const findNumber = (value) => { const numberGenerater = Array(1000) .fill() .map((value, index) => index + 1); setSearchResult(numberGenerater.filter((num) => !(num % parseInt(value)))); }; useEffect(() => { const debounce = setTimeout(() => { return value ? findNumber(value) : setSearchResult([]); }, 500); return () => clearTimeout(debounce); }, [value]); return ( <div> <p>์ž…๋ ฅํ•œ ์ˆซ์ž์˜ ๋ฐฐ์ˆ˜๋ฅผ ์ฐพ์•„๋ณด์•„์š”~</p> <form action="" onSubmit={(e) => e.preventDefault()}> <label htmlFor="">์ž…๋ ฅ</label> <input type="number" placeholder="์ˆซ์ž๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”" value={value} onChange={(e) => setValue(e.target.value)} /> </form> <ul style={{ listStyle: "none" }}> ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ {searchResult.map((num, index) => ( <li key={index}>{num}</li> ))} </ul> </div> ); } export default App;
App.jsx
ย 
๋ณ€๊ฒฝ๋œ ๋ถ€๋ถ„์€ useEffect ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค. ์„ค์ • ์‹œ๊ฐ„์ธ 500ms ๋™์•ˆ findNumber ํ•จ์ˆ˜ ๋™์ž‘์— ์ง€์—ฐ์„ ์ธ์œ„์ ์œผ๋กœ ์ œํ•œํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  500ms๊ฐ€ ์ง€๋‚˜๋ฉด debounce์˜ setTimeout ์ฝœ๋ฐฑํ•จ์ˆ˜๋ฅผ ์ดˆ๊ธฐํ™” ์‹œ์ผœ์ค๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด debounce๋ฅผ ๊ณ ๋ คํ•˜์ง€ ์•Š์•˜์„ ๋•Œ์™€ ๊ฐ™์ด โ€œ3421โ€์„ input์— ์ž…๋ ฅํ•˜์˜€์„ ๋•Œ ๋งค ์ž…๋ ฅ์— ๋Œ€ํ•ด ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜ํƒ€๋‚˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ โ€œ3421โ€์„ ๋ชจ๋‘ ์ž…๋ ฅํ•˜๊ณ  500ms๊ฐ€ ์ง€๋‚ฌ์„ ๋•Œ์—๋งŒ, ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜ํƒ€๋‚˜๊ฒŒ ๋จ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
๊ทธ๋ฆผ 12-5๊ทธ๋ฆผ 12-5
๊ทธ๋ฆผ 12-5
ย 
์ฆ‰, debounce๋ฅผ ์ด์šฉํ•˜์—ฌ ์„ค์ •ํ•œ ์‹œ๊ฐ„๋งŒํผ ํ•จ์ˆ˜์˜ ๋™์ž‘์„ ์ง€์—ฐ์‹œํ‚ฌ ์ˆ˜ ์žˆ๊ณ , ์ด๋ฅผ ํ†ตํ•ด ๋ถˆํ•„์š”ํ•œ ํ•จ์ˆ˜ ๋™์ž‘์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

12.3 throttle

12.3.1 throttle์ด๋ž€?

throttle์€ ์ด๋ฒคํŠธ๋ฅผ ์ผ์ •ํ•œ ์ฃผ๊ธฐ๋งˆ๋‹ค ๋ฐœ์ƒํ•˜๋„๋ก ํ•˜๋Š” ๊ธฐ๋ฒ•์ž…๋‹ˆ๋‹ค. callback ํ•จ์ˆ˜๊ฐ€ ์„ค์ •ํ•œ time ๋’ค์— ์‹คํ–‰๋˜๊ณ , time์ด ์ง€๋‚˜๊ธฐ ์ „์— ๋‹ค์‹œ ํ˜ธ์ถœ๋  ๊ฒฝ์šฐ์—๋Š” callback์„ ์‹คํ–‰์‹œํ‚ค์ง€ ์•Š๊ณ  ํ•จ์ˆ˜๋ฅผ ์ข…๋ฃŒํ•˜๋Š” ํ˜•ํƒœ๋กœ ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ, ์„ค์ •์‹œ๊ฐ„์„ 200ms๋กœ ์„ค์ •ํ•˜๋ฉด ํ•ด๋‹น ์ด๋ฒคํŠธ๋Š” 200ms ๋™์•ˆ ์ตœ๋Œ€ ํ•œ ๋ฒˆ๋งŒ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.
์ด๋ฒคํŠธ์˜ ํ˜ธ์ถœ์„ ์„ค์ •ํ•œ ์‹œ๊ฐ„์„ ๊ธฐ์ค€์œผ๋กœ ์ผ์ •ํ•˜๊ฒŒ ์‹คํ–‰ํ•˜๋„๋ก ์ œํ•œ์„ ๋‘๋ฉด ๊ณผ๋„ํ•œ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์˜ ์‹คํ–‰๋˜๋Š” ๋นˆ๋„๋ฅผ ์ค„์—ฌ ์›น ์„ฑ๋Šฅ์ด ์ €ํ•˜๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๋Š” ์„ฑ๋Šฅ์ ์ธ ์ด์ ์„ ๊ฐ€์ ธ๊ฐˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 

12.3.2 throttle ์‚ฌ์šฉํ•ด๋ณด๊ธฐ

12.3.1์ ˆ์—์„œ throttle์˜ ๊ฐœ๋…์— ๋Œ€ํ•ด ์‚ดํŽด๋ณด์•˜์Šต๋‹ˆ๋‹ค. ์ง€๊ธˆ๋ถ€ํ„ฐ๋Š” ์˜ˆ์ œ ์ฝ”๋“œ๋ฅผ ํ†ตํ•ด ์ผ๋ฐ˜ ์ด๋ฒคํŠธ์™€ throttle์„ ์ ์šฉํ•œ ์ด๋ฒคํŠธ๊ฐ€ ์–ด๋–ป๊ฒŒ ๋‹ค๋ฅด๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€ ์‚ดํŽด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜๋Š” ํŠธ๋ฆฌ๊ฑฐ ๋ฐ•์Šค ์š”์†Œ ์œ„์— ๋งˆ์šฐ์Šค๊ฐ€ ์žˆ๋Š” ๋™์•ˆ ์ด๋™ํ•  ๋•Œ๋งˆ๋‹ค ๊ฐ๊ฐ raw ์ƒํƒœ์™€ throttled ์ƒํƒœ๊ฐ€ 1์”ฉ ์ฆ๊ฐ€ํ•˜๋Š” ์˜ˆ์ œ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.
ย 
import { useCallback, useRef, useState } from 'react'; const App = () => { const [raw, setRaw] = useState(0); const [throttled, setThrottled] = useState(0); const lastRan = useRef(Date.now()); const handleOnMouseMove = useCallback(() => { setRaw((r) => r + 1); if (Date.now() - lastRan.current >= 1000) { setThrottled((t) => t + 1); lastRan.current = Date.now(); } }, []); return ( <div> <div style={{ width: '100px', height: '100px', backgroundColor: 'rosybrown', }} onMouseMove={handleOnMouseMove} > ํŠธ๋ฆฌ๊ฑฐ ์˜์—ญ </div> <p>์ผ๋ฐ˜ ์ด๋ฒคํŠธ: {raw}</p> <p>์“ฐ๋กœํ‹€ ์ด๋ฒคํŠธ: {throttled}</p> </div> ); }; export default App;
App.jsx
ย 
๊ทธ๋ฆผ 12-6 ์˜ˆ์ œ ์ฝ”๋“œ ์‹คํ–‰ ๊ณผ์ • 3๋‹จ๊ณ„๊ทธ๋ฆผ 12-6 ์˜ˆ์ œ ์ฝ”๋“œ ์‹คํ–‰ ๊ณผ์ • 3๋‹จ๊ณ„
๊ทธ๋ฆผ 12-6 ์˜ˆ์ œ ์ฝ”๋“œ ์‹คํ–‰ ๊ณผ์ • 3๋‹จ๊ณ„
ย 
์œ„์˜ ์ฝ”๋“œ์—์„œ ๋‘ ์ƒํƒœ๋Š” ์ดˆ๊ธฐ ์ƒํƒœ๋กœ ๊ฐ™์€ 0์œผ๋กœ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.
๋จผ์ €, ์ผ๋ฐ˜ ์ด๋ฒคํŠธ์— ๋Œ€ํ•ด ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. raw ์ƒํƒœ๋Š” ํŠธ๋ฆฌ๊ฑฐ ์˜์—ญ ๋ฐ•์Šค ์œ„์—์„œ ๋งˆ์šฐ์Šค๊ฐ€ ์›€์ง์ผ ๋•Œ๋งˆ๋‹ค handleOnMouseMove ์ฝœ๋ฐฑํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ๊ทธ ๊ฒฐ๊ณผ๋กœ 261๊นŒ์ง€ ๋ณ€ํ™”ํ•œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๊ณ , ์ด๋Š” App ์ปดํฌ๋„ŒํŠธ๊ฐ€ 261๋ฒˆ ๋ฆฌ๋ Œ๋”๋ง๋˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ๋ฌด๊ฑฐ์šด ๊ณ„์‚ฐ์ด๋‚˜ ๊ธฐํƒ€ DOM ์กฐ์ž‘๊ณผ ๊ฐ™์€ ์ž‘์—…์„ ๊ณผํ•˜๊ฒŒ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒฝ์šฐ ์ด๋Š” ์„ฑ๋Šฅ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๊ณ  ์‚ฌ์šฉ์ž ๊ฒฝํ—˜๊นŒ์ง€ ๋–จ์–ด๋œจ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
if (Date.now() - lastRan.current >= 1000) { setThrottled((t) => t + 1); lastRan.current = Date.now(); }
Date.now()๋ฅผ ํ™œ์šฉํ•œ throttle ๊ธฐ๋ฒ•
ย 
๋‹ค์Œ์œผ๋กœ throttle์„ ์ ์šฉํ•œ ์ด๋ฒคํŠธ์— ๋Œ€ํ•ด ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. throttled ์ƒํƒœ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ํŠธ๋ฆฌ๊ฑฐ ์˜์—ญ ๋ฐ•์Šค ์œ„์—์„œ ๋งˆ์šฐ์Šค๊ฐ€ ์›€์ง์ผ ๋•Œ๋งˆ๋‹ค handleOnMouseMove ์ฝœ๋ฐฑํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, raw ์ƒํƒœ์™€๋Š” ๋‹ค๋ฅด๊ฒŒ ์œ„ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์—ฌ if ๋ฌธ์—์„œ 1000ms๋งˆ๋‹ค ํ•œ ๋ฒˆ์”ฉ ์‹คํ–‰๋˜๋„๋ก ์„ค์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ raw ์ƒํƒœ๊ฐ€ 261๋กœ ์—…๋ฐ์ดํŠธ ๋˜๋Š” ๋™์•ˆ throttled ์ƒํƒœ๋Š” 3์œผ๋กœ ์—…๋ฐ์ดํŠธ ๋˜์–ด ๊ณผ๋„ํ•˜๊ฒŒ ๋ฆฌ๋ Œ๋”๋ง ๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 

12.4 useDeferredValue ์‚ฌ์šฉํ•ด๋ณด๊ธฐ

ํ‚ค๋ณด๋“œ๋ฅผ ํ™œ์šฉํ•œ ์‚ฌ์šฉ์ž ์ž…๋ ฅ ๋“ฑ ์งง์€ ์‹œ๊ฐ„ ๋™์•ˆ ์ด๋ฒคํŠธ๊ฐ€ ๋งŽ์ด ์ผ์–ด๋‚˜๋Š” ๊ฒฝ์šฐ ํ™”๋ฉด์ด ๋Š๊ธฐ๋Š” ํ˜„์ƒ์ด ์ผ์–ด๋‚  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฒ€์ƒ‰์–ด ์ž๋™ ์™„์„ฑ์„ ๊ทธ ์˜ˆ๋กœ ๋“ค ์ˆ˜ ์žˆ๋Š”๋ฐ, ์‚ฌ์šฉ์ž ์ž…๋ ฅ ํ•œ ๋ฒˆ์— ๊ฒ€์ƒ‰์–ด ์—…๋ฐ์ดํŠธ๊ฐ€ ํ•œ ๋ฒˆ์”ฉ ์ผ์–ด๋‚œ๋‹ค๋ฉด ๋ถˆํ•„์š”ํ•œ ๋ฆฌ๋ Œ๋”๋ง์ด ์ดˆ๋‹น ์ˆ˜ ๋ฒˆ์”ฉ ๋ฐœ์ƒํ•˜๋ฉฐ, ์‚ฌ์šฉ์ž๊ฐ€ ์›ํ•˜๋Š” ๊ฒ€์ƒ‰์–ด์— ๋Œ€ํ•œ ์ž๋™์™„์„ฑ์ด ๋Š๋ ค์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์‚ฌ์šฉ์ž๊ฐ€ input ์ฐฝ์— ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ํ•ด๋‹น ํ…์ŠคํŠธ๋ฅผ 10000๊ฐœ๋ฅผ ๋ฐ˜๋ณตํ•˜๋Š” ์˜ˆ์ œ๋ฅผ ์ž‘์„ฑํ•˜๊ณ , ๊ทธ ์„ฑ๋Šฅ์„ ์ธก์ •ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 
.app { margin: 20px auto; width: 300px; } .input { width: 200px; font-size: 20px; background-color: sandybrown; border-radius: 5px; } .search-div { width: 200px; font-size: 15px; padding: 3px; border: 0.1px solid black; border-radius: 5px; }
App.css
ย 
import React, { useState, useCallback } from 'react'; import './App.css'; export default function App() { const [name, setName] = useState(''); const onChange = useCallback((e) => { setName(e.target.value); }, []); return ( <div className='app'> <div>๊ฒ€์ƒ‰์ฐฝ</div> <input className='input' value={name} onChange={onChange} /> {name ? Array(10000) .fill() .map((v, i) => ( <div className='search-div' key={i}> {name} </div> )) : null} </div> ); }
App.jsx
ย 
๊ทธ๋ฆผ 12-7๊ทธ๋ฆผ 12-7
๊ทธ๋ฆผ 12-7
ย 
๊ทธ๋ฆผ 12-8๊ทธ๋ฆผ 12-8
๊ทธ๋ฆผ 12-8
ย 
์‚ฌ์šฉ์ž๋Š” ์œ„ ๊ฒ€์ƒ‰์ฐฝ์— โ€˜Hello worldโ€™ ๋ผ๋Š” ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•˜๋Š” ๋™์•ˆ ์ง๊ด€์ ์œผ๋กœ๋„ ํ™”๋ฉด์˜ ๋Š๊น€ ํ˜„์ƒ์„ ๋งˆ์ฃผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 10000๊ฐœ์˜ ํ…์ŠคํŠธ๋ฅผ ๋ฐ˜๋ณตํ•˜๋Š” ๋™์•ˆ, ๋ถˆํ•„์š”ํ•œ ๋ฆฌ๋ Œ๋”๋ง์ด ๋ฐœ์ƒํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. Chrome์—์„œ ์ œ๊ณตํ•˜๋Š” ๊ฐœ๋ฐœ์ž ๋„๊ตฌ์˜ Perfomance ํƒญ์—์„œ โ€˜Hello worldโ€™ ํ…์ŠคํŠธ๋ฅผ ๋ชจ๋‘ ๋ Œ๋”๋งํ•˜๋Š” ๋ฐ ๊ฑธ๋ฆฐ ์‹œ๊ฐ„์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 2000ms๋™์•ˆ โ€˜Hello worldโ€™ ํ…์ŠคํŠธ๊ฐ€ ๋ชจ๋‘ ๋ Œ๋”๋ง ๋˜๋Š” ๋ฐ ๊ฑธ๋ฆฐ ์‹œ๊ฐ„์€ 586ms์ž…๋‹ˆ๋‹ค.
๊ทธ๋ ‡๋‹ค๋ฉด ์ด๋ฒˆ์—” useDefferedValue Hook์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฒ€์ƒ‰์ฐฝ์˜ ์„ฑ๋Šฅ์„ ์ธก์ •ํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 
import React, { useState, useCallback, useMemo, useDeferredValue } from 'react'; import './App.css'; export default function App() { const [name, setName] = useState(''); const deferredName = useDeferredValue(name); const result = useMemo(() => deferredName, [deferredName]); const onChange = useCallback((e) => { setName(e.target.value); }, []); return ( <div className='app'> <div>๊ฒ€์ƒ‰์ฐฝ</div> <input className='input' value={name} onChange={onChange} /> {deferredName ? Array(10000) .fill() .map((v, i) => ( <div className='search-div' key={i}> {result} </div> )) : null} </div> ); }
App.jsx
ย 
์œ„ App.jsx์—์„œ๋Š” useMemo Hook์„ ์‚ฌ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค. name์ด ๋ณ€๊ฒฝ๋  ๋•Œ๊ฐ€ ์•„๋‹Œ, deferredName์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋งŒ ๋ฆฌ๋ Œ๋”๋ง์„ ์ง„ํ–‰ํ•˜๋ฉด์„œ ๋ถˆํ•„์š”ํ•œ ๋ฆฌ๋ Œ๋”๋ง์„ ๋ง‰์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. useDeferredValue ์‚ฌ์šฉ ์‹œ์— useMemo๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด์„œ ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ๋‚˜ ์ƒํƒœ์˜ ์—…๋ฐ์ดํŠธ๋ฅผ ์ง€์—ฐ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
๊ทธ๋ฆผ 12-9๊ทธ๋ฆผ 12-9
๊ทธ๋ฆผ 12-9
ย 
์ด๋ฒˆ์—๋Š” โ€˜Hello worldโ€™ ๋ผ๋Š” ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•˜๋Š” ๋™์•ˆ, 10000๊ฐœ์˜ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์ถœ๋ ฅ๋˜๋Š” ๋ฐ ๋Š๊น€ ํ˜„์ƒ์ด ํ›จ์”ฌ ์™„ํ™”๋˜์—ˆ์Œ์„ ์ฒด๊ฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ๊ฐœ๋ฐœ์ž ๋„๊ตฌ์—์„œ ์ด 2000ms๋™์•ˆ ๋ Œ๋”๋ง ํ•˜๋Š”๋ฐ ๊ฑธ๋ฆฐ ์‹œ๊ฐ„์€ 376ms๋กœ, useDefferedValue๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•˜์„ ๋•Œ๋ณด๋‹ค ์•ฝ 1.5๋ฐฐ ๋น ๋ฅธ ์„ฑ๋Šฅ์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.
ย 
๋ถˆํ•„์š”ํ•œ ๋ฆฌ๋ Œ๋”๋ง์„ ๋ง‰๊ธฐ ์œ„ํ•ด debounce๋‚˜ throttle ๊ฐ™์€ ์ฒ˜๋ฆฌ๋ฅผ ์ง„ํ–‰ํ•˜๋Š”๋ฐ, debounce์™€ throttle์—์„œ ์ ์ ˆํ•œ delay๋ฅผ ์„ ํƒํ•˜๋Š” ๊ฒƒ์€ ๊ฝค๋‚˜ ์–ด๋ ค์šด ์ผ์ž…๋‹ˆ๋‹ค.
React v18.0์€ fiber๋ผ๋Š” ์—”์ง„์„ ๊ฐœ์„ ํ•˜์—ฌ ์ž์ฒด์ ์ธ ์Šค์ผ€์ค„๋Ÿฌ๋ฅผ ๊ฐ€์ง€๊ฒŒ ๋˜์—ˆ๊ณ , ์ž‘์—…์˜ ์šฐ์„ ์ˆœ์œ„๋ฅผ ์ •ํ•˜์—ฌ ์ˆœ์ฐจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ธฐ๋Šฅ์„ ๋‚ดํฌํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„๋ฅผ ์ง€์ •ํ•˜๊ธฐ ์œ„ํ•œ useDeferredValue๋ผ๋Š” ๋นŒํŠธ์ธ ํ›…์„ ์‚ฌ์šฉํ•˜์—ฌ ์œ„์˜ ์˜ˆ์ œ๋ฅผ ๋‹ค๋ค„๋ณด๊ณ , ์„ฑ๋Šฅ์„ ํ™•์ธํ•˜์˜€์Šต๋‹ˆ๋‹ค.
ย