useAutofocus() custom hook implementation

notion image
(์ฐธ๊ณ ํ•œ ์ฝ”๋“œ)
notion image
autocomplete searchBar๋ฅผ ๊ตฌํ˜„ํ•˜๋˜ ์ค‘์— ํŽ˜์ด์ง€์— ๋“ค์–ด๊ฐ”์„ ๋•Œ auto focusing๋˜๋Š” ๊ฒƒ์„ ๊ตฌํ˜„ํ•ด๋ณด๋ ค๊ณ  ํ–ˆ๋‹ค. useRef๋ฅผ ์‚ฌ์šฉํ•ด์„œ input ์—˜๋ฆฌ๋จผํŠธ์˜ ref๋ฅผ ์žก์•„์„œ focus()๋ฅผ ํ˜ธ์ถœํ•ด์ฃผ๋ ค๊ณ  ํ–ˆ์ง€๋งŒ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ ์šฉํ•œ ๋ฆฌ์•กํŠธ ํ”„๋กœ์ ํŠธ์—ฌ์„œ ์œ„์˜ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.
ย 
DOM ์š”์†Œ๊ฐ€ ๋งˆ์šดํŠธ๋œ ์ดํ›„์— focus()์‹œํ‚ค๋ ค๊ณ  ํ–ˆ์ง€๋งŒ useRef(null)์„ ํ• ๋‹นํ•˜๋ฉด ๊ฐ์ฒด searchBarRef๊ฐ€ null์ด ๋œ ์ƒํƒœ์—์„œ useEffect()๊ฐ€ ํ˜ธ์ถœ๋˜๋Š” ์ˆœ์„œ์ด๋‹ค. ์‹ค์ œ๋กœ ๊ทธ๋ ‡๊ฒŒ ๋™์ž‘ํ•˜์ง„ ์•Š์ง€๋งŒ. ์ฝ๋Š” ์ˆœ์„œ๋Š” ๊ทธ๋ ‡๋‹ค.
๋ฐ‘์—์„œ return ๋ฉ”์†Œ๋“œ ๋‚ด์˜ DOM ์š”์†Œ๋“ค์ด ๋ถˆ๋Ÿฌ์ง€๊ณ  ๋‚˜์„œ useEffect๋Š” ํ˜ธ์ถœ๋  ๊ฒƒ์ด๋‹ค. ๋”ฐ๋ผ์„œ ์•„๋ž˜์˜ ๊ธ€์—์„œ๋Š” ์ด๊ฒƒ๋งŒ ์ž‘์„ฑํ•ด๋„ ์ž˜ ๋™์ž‘ํ•œ๋‹ค๊ณ  ํ–ˆ์ง€๋งŒ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋Š” ์—๋Ÿฌ๋ฅผ ๋ณด์—ฌ์คฌ๋‹ค.
ํƒ€์ž…์„ ๋งž์ถฐ์ฃผ๋ ค๊ณ  useRef์— HTMLInputElement๋ฅผ ์ฃผ๊ณ  ๋‚ด๋ถ€์—์„œ ์กฐ๊ฑด๋ฌธ์œผ๋กœ ์ฒดํฌ๋„ ํ•ด์ฃผ์—ˆ๋‹ค. (์•„๋‹ˆ๋ฉด if๋ฌธ์œผ๋กœ null์„ ๊ฒ€์‚ฌํ•œ ๊ฒƒ์€ ์˜ต์…”๋„ ์—ฐ์‚ฐ์ž ?๋กœ searchBarRef.current?.focus()๋กœ ์ฒดํฌํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค.)
const searchBarRef = React.useRef<HTMLInputElement>(null);
    React.useEffect(() => {
        
ย 
์ด๋ ‡๊ฒŒ ๊ตฌํ˜„์„ ํ–ˆ๋‹ค๊ฐ€ ์—ฌ๋Ÿฌ ๋ฒˆ ์‚ฌ์šฉ๋  ๊ฒƒ์œผ๋กœ ์ƒ๊ฐ๋˜์„œ auto focus๋ฅผ ํ•ด์ค„ ์ˆ˜ ์žˆ๋Š” ์ปค์Šคํ…€ ํ›…์„ ๋งŒ๋“ค์–ด์•ผ๊ฒ ๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค. ์ด๋ฏธ ์ž˜ ๋งŒ๋“ค์–ด์ง„ ๊ฒƒ๋“ค์ด ์žˆ๊ฒ ์ง€๋งŒ ํ˜ผ์ž ๊ตฌํ˜„ํ•ด๋ณด๋ฉด์„œ ์ดํ•ดํ•˜๋Š” ๊ฒƒ์ด ๋„์›€์ด ๋  ๊ฒƒ ๊ฐ™์•˜๋‹ค.
useAutofocus hook์„ ๋งŒ๋“ค์—ˆ๋”๋‹ˆ ์‚ฌ์šฉํ•˜๋Š” ๊ณณ์—์„œ ์—๋Ÿฌ๋ฅผ ๋ƒˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด ๊ณต์šฉ์œผ๋กœ ์‚ฌ์šฉํ•ด์•ผ ํ•ด์„œ useRef<HTMLElement>๋กœ type scope๋ฅผ ๋„“ํ˜€์„œ ์ง€์ •ํ•ด๋†“์•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
notion image
๐Ÿ’ก
RefObject<HTMLElement> is not assignable to type 'LegacyRef<HTMLInputElement> | undefined.
ย 
์ด๊ฑธ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•์€ 2๊ฐ€์ง€๊ฐ€ ์žˆ๋‹ค.
(ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ํ•ธ๋“œ๋ถ์„ ์ฐธ๊ณ ํ–ˆ๋‹ค. https://joshua1988.github.io/)
์ฒซ๋ฒˆ์งธ๋Š” as๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๊ตฌ์ฒด์ ์œผ๋กœ ํƒ€์ž…์„ ์ง€์ •ํ•ด์ฃผ์–ด์•ผ ํ–ˆ๋Š”๋ฐ ์ด๊ฒƒ์€ ํƒ€์ž… ๋‹จ์–ธ(Type Assertion)์ด๋ผ ํ•œ๋‹ค.

ํƒ€์ž… ๋‹จ์–ธ(Type Assertion)

ํƒ€์ž… ๋‹จ์ •์€ ๊ฐœ๋ฐœ์ž๊ฐ€ ํ•ด๋‹น ํƒ€์ž…์— ๋Œ€ํ•ด ํ™•์‹ ์ด ์žˆ์„ ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ํƒ€์ž… ์ง€์ • ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ์–ธ์–ด์˜ ํƒ€์ž… ์บ์ŠคํŒ…๊ณผ ๋น„์Šทํ•œ ๊ฐœ๋…์ด๋ฉฐ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ์—์„œ ํŠน๋ณ„ํžˆ ํƒ€์ž…์„ ์ฒดํฌํ•˜์ง€ ์•Š๊ณ , ๋ฐ์ดํ„ฐ์˜ ๊ตฌ์กฐ๋„ ์‹ ๊ฒฝ์“ฐ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.(ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ํ•ธ๋“œ๋ถ)
import React from "react";

export function useAutofocus() {
    const ref = React.useRef(null);
    React.useEffect(() => {
        ref.current?.focus();
    }, [ref]);

    return ref;
}
// searchBar.tsx
const searchBarRef = useAutofocus();
...
<input
    ref={searchBarRef as React.RefObject<HTMLDivElement>} 
...
/>
์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์‚ฌ์šฉํ•  ๋•Œ๋งˆ๋‹ค ํƒ€์ž… ๋‹จ์–ธ์œผ๋กœ ์ •์ƒ ์ž‘๋™ํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์ง€๋งŒ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋Š” ์ด๋Ÿด ๋•Œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐœ๋…์„ ์ด๋ฏธ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค. ๊ทธ๊ฒŒ ๋‘๋ฒˆ์งธ๋‹ค.
ย 
๋‘๋ฒˆ์งธ๋กœ๋Š” ์ œ๋„ค๋ฆญ(Generics)์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹์ด ์žˆ๋‹ค.

์ œ๋„ค๋ฆญ(Generics)์˜ ์‚ฌ์ „์  ์ •์˜

์ œ๋„ค๋ฆญ์€ C#, Java ๋“ฑ์˜ ์–ธ์–ด์—์„œ ์žฌ์‚ฌ์šฉ์„ฑ์ด ๋†’์€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค ๋•Œ ์ž์ฃผ ํ™œ์šฉ๋˜๋Š” ํŠน์ง•์ž…๋‹ˆ๋‹ค. ํŠนํžˆ, ํ•œ๊ฐ€์ง€ ํƒ€์ž…๋ณด๋‹ค ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ํƒ€์ž…์—์„œ ๋™์ž‘ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ƒ์„ฑํ•˜๋Š”๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
์ œ๋„ค๋ฆญ์ด๋ž€ ํƒ€์ž…์„ ๋งˆ์น˜ ํ•จ์ˆ˜์˜ ํŒŒ๋ผ๋ฏธํ„ฐ์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.
๊ธฐ๋ณธ์ ์ธ ์˜ˆ์ œ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.
notion image
ย 
์ œ๋„ค๋ฆญ ํƒ€์ž…์„ ์ ์šฉํ•˜๋ฉด HTMLElementํƒ€์ž…์— ํฌํ•จ๋˜๋Š” T๋กœ ๋ฐ›์•„ ์ปค์Šคํ…€ํ›…์„ ๊ตฌ์„ฑ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.
์ด ๊ฒฝ์šฐ ์—๋Ÿฌ๊ฐ€ ์‚ฌ๋ผ์ง€๊ฒŒ ๋œ๋‹ค.
const searchBarRef = useAutofocus<HTMLInputElement>();
...
<input
	ref={searchBarRef}
/>
...
// useAutofocus()
import React from "react";

export function useAutofocus<T extends HTMLElement>() {
    const ref = React.useRef<T>(null);
    React.useEffect(() => {
        ref.current?.focus();
    }, [ref]);

    return ref;
}
๋ฆฌ์•กํŠธ์™€ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ๊ตฌ์กฐ๋ฅผ ์ž์ฃผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š”๋ฐ ์ด๋ฒˆ ์—๋Ÿฌ๋ฅผ ํ•ด๊ฒฐํ•˜๋ฉด์„œ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ํ•ธ๋“œ๋ถ์„ ์ข…์ข… ๋ด์•ผํ•œ๋‹ค๋Š” ์ƒ๊ฐ๊ณผ ์ดํŽ™ํ‹ฐ๋ธŒ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์œ ๋ช…ํ•˜๋˜๋ฐ ๊ทธ ์ฑ…์„ ์‚ฌ์„œ ๊ณต๋ถ€ํ•ด๋ณผ๊นŒ ์‹ถ์—ˆ๋‹ค. ์ตœ๊ทผ ๋“ค์–ด ๊ธฐ๋ณธ๊ธฐ๋ฅผ ๋‹ค์ง€๊ธฐ ์œ„ํ•ด์„œ ๋ฐ”๋‹๋ผ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์œ„์ฃผ๋กœ ๊ณต๋ถ€ํ•ด์„œ ํ—ท๊ฐˆ๋ฆฌ๋Š” ๋ถ€๋ถ„๋“ค์ด ์žˆ์—ˆ๋Š”๋ฐ ์—ญ์‹œ ๊พธ์ค€ํžˆ ๊ณต๋ถ€ํ•ด์•ผ ํ•œ๋‹ค.
ย