setTimeout๊ณผ ์“ฐ๋กœํ‹€๋ง

9/16/2022

์ž‘์„ฑ์ž : ํ™์›๋ฐฐ

์ด์Šˆ

nav๋ฐ”๋Š” sticky๋‹ค
nav๋ฐ”๋Š” sticky๋‹ค
ย 
stickyํšจ๊ณผ๋กœ nav ๋ฐ”์˜ ์ด๋™ ํšจ๊ณผ๋ฅผ ์ฃผ๋ คํ•œ๋‹ค.
ํฐ ํ™”๋ฉด ๋งˆ์ง€๋ง‰ ์ˆ˜ํ‰์„ ์— ๋‹ค๋‹ค๋ž์„๋•Œ interactiveํ•˜๊ฒŒ ์‚ฌ๋ผ์ง€๋Š” ํšจ๊ณผ๋ฅผ ์ฃผ๊ธฐ ์œ„ํ•ด ์Šคํฌ๋กค์„ ๊ฐ์ง€ํ•ด์„œ ๋‹ค์‹œ ์Šคํƒ€์ผ์ด ๋ฐ”๋€Œ๊ฒŒ ๊ตฌํ˜„ํ•ด๋ณด์•˜๋‹ค
ย 
import { useEffect, useState } from 'react'; import styles from './nav.module.scss'; import cx from 'classnames'; export default function Nav(){ const [isScroll, setIsScroll] = useState(false); useEffect(() => { const handleScroll= () => setTimeout(() => { window.scrollY !== 0 ? setIsScroll(true) : setIsScroll(false); }, 200); window.addEventListener("scroll", handleScroll); return () => { window.removeEventListener("scroll", handleScroll); }; }, []); return ( <div className={cx(styles.container, { [styles.transNav]: isScroll })}> <div className={styles.title}> . . }
์ฝ”๋“œ๋Š” ์ผ๋ถ€๋ถ„๋งŒ์„ ๋‚˜ํƒ€๋‚ธ๋‹ค
ย 
์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๋ฅผ ํ†ตํ•ด์„œ ์Šคํฌ๋กค์ด ์›€์ง์˜€์„ ๋•Œ transNav ํšจ๊ณผ๋ฅผ ์ฃผ๊ฒŒ ๊ตฌํ˜„ํ–ˆ๋‹ค. ๋˜ํ•œ, setTimeout์„ ํ†ตํ•ด 200ms ํ›„์— ์ด๋ฒคํŠธ์˜ ์ฝœ๋ฐฑํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋˜๊ฒŒ ์ ์šฉํ–ˆ๋‹ค. 200ms์˜ ์ œํ•œ์„ ๊ฑธ์–ด๋‘” ๊ฒƒ์€ ๋ฌดํ•œ์ • ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋Š” ์Šคํฌ๋กค์„ ์ œํ•œํ•˜๊ธฐ ์œ„ํ•จ์ด๋‹ค.
ย 
์ดํŽ™ํŠธ๊ฐ€ ์ผ์–ด๋‚˜๊ฒŒ ํ•˜๋Š”๋ฐ๋Š” ๋ฌธ์ œ๊ฐ€ ์—†์ง€๋งŒ, ์ด ์ฝ”๋“œ๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ๋‹ค
ย 
ย 
๋ฌด์ˆ˜ํžˆ ์‹คํ–‰๋˜๊ณ  ์žˆ๋Š” setTimeout์˜ ์ฝœ๋ฐฑ
๋ฌด์ˆ˜ํžˆ ์‹คํ–‰๋˜๊ณ  ์žˆ๋Š” setTimeout์˜ ์ฝœ๋ฐฑ
ย 
์Šคํฌ๋กค์ด ์ผ์–ด๋‚ ๋•Œ๋งˆ๋‹ค setTimeout์˜ ์ฝœ๋ฐฑ๋“ค์ด ๋ฌด์ˆ˜ํžˆ ๋งŽ์ด ๋“ฑ๋ก๋˜์–ด ์‹คํ–‰ ๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
์ด๊ฒƒ์€ ํ”„๋ก ํŠธ์—”๋“œ ์ตœ์ ํ™”์˜ ์•…์˜ํ–ฅ์„ ์ด์–ด์ ธ ํผํฌ๋จผ์Šค๋ฅผ ๋‚ฎ์ถœ ์ˆ˜ ์žˆ๋‹ค.
ย 
๋ฌธ์ œ๋Š” setTimeout์˜ ๋นˆ๋ฒˆํ•œ ์‹คํ–‰์ด๋‹ค. ๊ทธ๋Ÿฌ๋ฏ€๋กœ ํ•ด๋‹นํ•จ์ˆ˜์˜ ์ง€์†์ ์ธ ์‹คํ–‰์— ์ œ์•ฝ์„ ๊ฑธ์–ด์•ผ ํ•œ๋‹ค. ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•ด์ค„ ์ˆ˜ ์žˆ์„๊นŒ?
ย 

์“ฐ๋กœํ‹€๋ง

์ด๋Ÿด ๊ฒฝ์šฐ ์ฒ˜๋ฆฌํ•ด์ค„ ์ˆ˜ ์žˆ๋Š” ์ด๋ฒคํŠธ ์ œ์–ด๋ฐฉ๋ฒ•์ด ์“ฐ๋กœํ‹€๋ง์ด๋‹ค.
์“ฐ๋กœํ‹€๋ง์€ ๋งˆ์ง€๋ง‰ ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋œ ํ›„ ์ผ์ • ์‹œ๊ฐ„์ด ์ง€๋‚˜๊ธฐ ์ „์— ๋‹ค์‹œ ํ˜ธ์ถœ๋˜์ง€ ์•Š๋„๋ก ํ•˜๋Š” ์ด๋ฒคํŠธ ์ œ์–ด๋ฐฉ๋ฒ•์œผ๋กœ, ํ”ํžˆ ๊ฒŒ์ž„์—์„œ ๋งํ•˜๋Š” ์Šคํ‚ฌ ์ฟจํƒ€์ž„๊ณผ ๋น„์Šทํ•˜๋‹ค. ํ•œ๋ฒˆ ์‚ฌ์šฉํ•˜๊ณ  ๋‚˜๋ฉด ์ผ์ •์‹œ๊ฐ„ ๋™์•ˆ ์ œํ•œ์ด ๊ฑธ๋ ค ์Šคํ‚ฌ์„ ์‚ฌ์šฉํ•˜์ง€ ๋ชปํ•˜๋Š” ๊ฒƒ์ด๋ผ ์‰ฝ๊ฒŒ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋‹ค.
ย 
์ด์™ธ์— ๋””๋ฐ”์šด์‹ฑ ๊ธฐ๋ฒ•์ด๋ž€ ๊ฒƒ๋„ ์กด์žฌํ•˜๋Š”๋ฐ, ์—ฐ์†์œผ๋กœ ํ˜ธ์ถœ๋˜๋Š” ํ•จ์ˆ˜๋“ค ์ค‘์— ๋งˆ์ง€๋ง‰์— ํ˜ธ์ถœ๋˜๋Š” ํ•จ์ˆ˜๋งŒ ์‹คํ–‰๋˜๊ฒŒ ํ•˜๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ ajax ํ†ต์‹ ์„ ํ•  ๋•Œ ํ˜ธ์ถœ ํšŸ์ˆ˜๋ฅผ ๋‚ฎ์ถ”๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ๋‹ค. ์ ์šฉ๋ฐฉ๋ฒ•์€ ํ•ด๋‹น๊ธฐ๋ฒ•์„ ํ™œ์šฉํ•ด APIํ˜ธ์ถœ์„ ์ตœ์ ํ™”ํ–ˆ๋˜ ๊ณผ๊ฑฐ ํ”„๋กœ์ ํŠธ๋ฅผ ์ฐธ๊ณ ํ•˜์ž [์ฐธ์กฐ์ฝ”๋“œ- useDebounce ์ด์šฉํ•œ ์ปดํฌ๋„ŒํŠธ]
ย 
ย 

์ฝ”๋“œ ์ ์šฉ

์ด์ œ ์“ฐ๋กœํ‹€๋ง์„ ์ด์šฉํ•ด ์ฝ”๋“œ๋ฅผ ๊ตฌํ˜„ํ•ด๋ณด์ž.
ย 
useEffect(() => { let timer: null | ReturnType<typeof setTimeout>; const throttling = () => { if (!timer) { timer = setTimeout(() => { timer = null; window.scrollY > 180 ? setIsScroll(true) : setIsScroll(false); }, 200); } }; window.addEventListener('scroll', throttling); return () => { window.removeEventListener('scroll', throttling); }; }, []);
๋ฐ”๊พธ๋ฉด์„œ ํƒ€์ž…๋„ ์ ์šฉํ–ˆ๋‹ค
ย 
์Šคํฌ๋กค์ด ์ผ์–ด๋‚ ๋•Œ๋งˆ๋‹ค throttling์ด ์ฝœ๋ฐฑํ•จ์ˆ˜๋กœ ์‹คํ–‰๋˜์ง€๋งŒ, ์ฝœ๋ฐฑํ•จ์ˆ˜ ๋‚ด์—์„œ ์ด๋ฒคํŠธ๊ฐ€ ๋“ฑ๋ก๋œ timer๊ฐ€ ์—†์„ ๊ฒฝ์šฐ์—๋งŒ setTimeout์ด ๋“ฑ๋ก๋˜๋„๋ก ๊ตฌํ˜„ํ–ˆ๋‹ค. ํƒ€์ด๋จธ๊ฐ€ ์žˆ๋‹ค๋ฉด ์•„๋ฌด๋Ÿฐ ํ–‰๋™์„ ํ•˜์ง€์•Š๊ณ  ์ด๋ฒคํŠธ ์ฝœ๋ฐฑ์€ ์ข…๋ฃŒ๋œ๋‹ค. ์“ฐ๋กœํ‹€๋ง ํ•ด๋ฒ•์— ๋งž๊ฒŒ ์ ์šฉ๋˜์—ˆ๋‹ค. [์ฐธ์กฐ์ฝ”๋“œ]
ย 

๊ฒฐ๊ณผ

ย 
notion image
ย 
๋น„์Šทํ•œ ํ™˜๊ฒฝ์—์„œ ์‹คํ—˜ํ•œ ๊ฒฐ๊ณผ, setTImeout์„ ์ฐพ์•„๋ณด๊ธฐ ํž˜๋“ค์–ด์กŒ๋‹ค๋Š” ๊ฑธ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ๋‹ค
ย 

setTimeout์„ ์ด์šฉํ•œ ๋˜ ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•

ย 
setTimeout์„ ์ด์šฉํ•˜์—ฌ ๋™์ผ ํ”„๋กœ์ ํŠธ์˜ ๋กœ๋”ฉํ™”๋ฉด๋„ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค
import Router, { useRouter } from 'next/router'; import { useEffect, useState } from 'react'; export const usePageLoading = () => { const { query: { title }, } = useRouter(); const [isPageLoading, setIsPageLoading] = useState(false); useEffect(() => { const routeEventStart = () => { setIsPageLoading(true); }; const routeEventEnd = () => { setIsPageLoading(false); }; Router.events.on('routeChangeStart', routeEventStart); Router.events.on('routeChangeComplete', routeEventEnd); Router.events.on('routeChangeError', routeEventEnd); return () => { Router.events.off('routeChangeStart', routeEventStart); Router.events.off('routeChangeComplete', routeEventEnd); Router.events.off('routeChangeError', routeEventEnd); }; }, []); if (!title) return false; return isPageLoading; };
route๊ฐ€ ์ด๋™ํ• ๋•Œ๋งˆ๋‹ค ์ด๋ฒคํŠธ๋ฅผ ๊ฐ์ง€ํ•ด์„œ Loading ์ƒํƒœ๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋Š” ์ปค์Šคํ…€ ํ›…์ด๋‹ค. [์ฐธ์กฐ์ฝ”๋“œ]
ย 
๋ฌธ์ œ๋Š” ์ฒด๊ฐ์ƒ ์•„์ฃผ ์ž ๊น ์ด๋™ํ•˜๋Š” ํŽ˜์ด์ง€ ์ „ํ™˜์— ์žˆ์–ด์„œ๋„ ๋กœ๋”ฉ ์Šคํ”ผ๋„ˆ๋ฅผ ๊ณ„์†ํ•ด์„œ ๋„์šด๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. ์œ ์ €์ž…์žฅ์—์„œ 3~4์ดˆ ์ •๋„ ๊ฑธ๋ฆฌ๋Š” ํ™”๋ฉด์€ โ€˜๋กœ๋”ฉ์ด ๋˜๊ณ  ์žˆ๊ตฌ๋‚˜โ€™ํ•˜๊ณ  ์ธ์‹ํ•˜๊ณ  ํŽธ์•ˆํ•จ์„ ๋Š๋ผ์ง€๋งŒ, 0์ดˆ ๋Œ€๋กœ ์›€์ง์ด๋Š” ํ™”๋ฉด์—๋„ ๋กœ๋”ฉ์ฐฝ์ด ๋นˆ๋ฒˆํ•˜๊ฒŒ ๋œจ๋Š” ๊ฒƒ์€ UX๋ฅผ ์•…ํ™”์‹œํ‚จ๋‹ค๊ณ  ํŒ๋‹จํ–ˆ๋‹ค
ย 
ย 
์•ž์—์„œ ์ ์šฉํ•œ setTimeout์„ ์ด์šฉํ•ด ๊ฐ€์ง€๊ณ  ๋กœ๋”ฉ์— ์ œ์•ฝ์„ ๊ฑธ์–ด๋ณด์ž
ย 
import Router from 'next/router'; import { useEffect, useState } from 'react'; export const usePageLoading = ({timeout}: number) => { const [isPageLoading, setIsPageLoading] = useState(false); useEffect(() => { let timer: ReturnType<typeof setTimeout> | null; const routeEventStart = () => { timer = setTimeout(() => { setIsPageLoading(true); }, timeout); }; const routeEventEnd = () => { clearTimeout(timer!); timer = null; setIsPageLoading(false); }; Router.events.on('routeChangeStart', routeEventStart); Router.events.on('routeChangeComplete', routeEventEnd); Router.events.on('routeChangeError', routeEventEnd); return () => { Router.events.off('routeChangeStart', routeEventStart); Router.events.off('routeChangeComplete', routeEventEnd); Router.events.off('routeChangeError', routeEventEnd); }; }, []); return isPageLoading; };
route๊ฐ€ ์ด๋™ํ•  ๋•Œ timer์„ ๊ฑธ๊ณ  route ์ด๋™์ด ๋๋‚ ๋•Œ timer๊ฐ€ ํ•ด์ œ๋œ๋‹ค
ย 

๋งˆ์นจ

setTimeout API๋Š” ์‰ฝ๊ณ  ๊ธฐ๋ณธ์ ์ธ ๊ฐœ๋…์ด์ง€๋งŒ, ํ”„๋กœ์ ํŠธ๋‚ด์—์„œ ๋น„๋™๊ธฐ์ ์ธ ์ด๋ฒคํŠธ๋ฅผ ์ œ์–ดํ•˜๋Š”๋ฐ ํฐ ๋„์›€์ด ๋˜์—ˆ๊ธฐ์— ์ •๋ฆฌ๋ฅผ ํ•ด๋ณด์•˜๋‹ค.
๊ทธ๋Ÿฐ๋ฐ, ํ”„๋กœ์ ํŠธ๋ฅผ ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉด์„œ ๋“œ๋Š” ์˜๋ฌธ์ด ์žˆ์—ˆ๋‹ค. ๋งŒ์•ฝ, ๋ณต์žกํ•œ task๋“ค์ด ์Œ“์—ฌ์žˆ์„ ๋•Œ, setTimeout์œผ๋กœ ํ˜ธ์ถœ๋œ ์ฝœ๋ฐฑํ•จ์ˆ˜๊ฐ€ ์ตœ์šฐ์„ ์œผ๋กœ ์ฒ˜๋ฆฌ๋˜๊ฒŒ ํ•ด์ฃผ๊ณ  ์‹ถ์„ ๋•Œ๋Š” ์–ด๋–ป๊ฒŒ ํ•  ์ˆ˜ ์žˆ์„๊นŒ?
๋ธŒ๋ผ์šฐ์ €์˜ ๋™์ž‘๋ฐฉ์‹ ๊ด€์ ์—์„œ setTimeout์˜ ์ฝœ๋ฐฑ์€ ๋งˆ์ดํฌ๋กœ ํƒœ์Šคํฌ ํ์— ๋น„ํ•ด์„œ ํ›„์†์ฒ˜๋ฆฌ ๋˜๋Š” ๋งคํฌ๋กœ ํƒœ์Šคํฌ ํ์— ์˜ํ•ด ์ฒ˜๋ฆฌ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฒคํŠธ ๋ฃจํ”„์˜ ์ตœ์šฐ์„  ์ฒ˜๋ฆฌ๋Œ€์ƒ์ด ์•„๋‹ˆ๋‹ค.
ย 
์กฐ์‚ฌํ•ด๋ณธ ๋ฐ”๋กœ๋Š” ์ด๋Ÿฌํ•œ ์ˆœ์„œ์— ์šฐ์„ ์ˆœ์œ„๋ฅผ ๋งค๊ฒจ์„œ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด NodeJS์—์„œ setImmedate์™€ process.nextTick์ด๋ผ๊ณ  ํ•œ๋‹ค. ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ๋‹ค๋ฃฐ ๋•Œ๋งˆ๋‹ค ๊ด€๋ จ ๊ฐœ๋…์œผ๋กœ NodeJS๊ฐ€ ์ด์–ด์ง€๋Š” ํฌ์ŠคํŠธ๋“ค์„ ๋งŽ์ด ๋ณด๊ณค ํ•œ๋‹ค. ๊ทธ๋ ‡๊ธฐ์— NodeJS์— ๋Œ€ํ•ด์„œ๋„ ํ˜ธ๊ธฐ์‹ฌ์ด ์ปค์ง„๋‹ค. ์–ด๋–ค ๊ด€๋ จ ํ”„๋กœ์ ํŠธ๋กœ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ์„์ง€ ๊ณ ๋ฏผ์„ ํ•ด๋ด์•ผ๊ฒ ๋‹ค.
ย 
+๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ์‹ฌํ™”๊ฐ•์˜๋ฅผ ์ฐพ์•˜๋‹ค
๋ณธ ํฌ์ŠคํŠธ๋ฅผ ๋ณด๋Š” ๋ถ„๋“ค์—๊ฒŒ ์ถ”์ฒœ ๋“œ๋ฆฝ๋‹ˆ๋‹ค
    ํƒœ๊ทธ :