React

Implementing useAutofocus custom hooks that does the same thing as the autoFocus property

Asset Type
hooks
TypeScript
File Type
When to use
2021/10/22
Reference
Created by
Created time
2021/12/26 10:48
Last edited time
2022/03/13 15:51
input의 자동 포커싱은 이렇게 autoFocus 속성을 줘도 간단히 가능하다. 하지만 hooks로 구현해보려고 했다.
<input // ref={searchBarRef} className="search-bar" placeholder="Search" value={keyword} onChange={(e) => updateField("keyword", e.target.value)} autoFocus />
TypeScript
복사
(참고한 코드)
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(() => { // 할당한 DOM 요소가 불러지면 (마운트 되면) searchBarRef는 아직 null이다. // return에서 렌더하면 그때 searchBarRef.current에 뭔가 할당되므로 // 여기서 null일 수도 있다는 에러를 던진다. if (searchBarRef.current !== null) searchBarRef.current.focus(); }, []);
TypeScript
복사
이렇게 구현을 했다가 여러 번 사용될 것으로 생각되서 auto focus를 해줄 수 있는 커스텀 훅을 만들어야겠다는 생각이 들었다. 이미 잘 만들어진 것들이 있겠지만 혼자 구현해보면서 이해하는 것이 도움이 될 것 같았다.
useAutofocus hook을 만들었더니 사용하는 곳에서 에러를 냈다. 왜냐하면 공용으로 사용해야 해서 useRef<HTMLElement>로 type scope를 넓혀서 지정해놓았기 때문이다.
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>} ... />
TypeScript
복사
이렇게 하면 사용할 때마다 타입 단언으로 정상 작동하게 만들 수 있지만 타입스크립트는 이럴 때 사용할 수 있는 개념을 이미 가지고 있다. 그게 두번째다.
두번째로는 제네릭(Generics)을 사용하는 방식이 있다.

제네릭(Generics)의 사전적 정의

제네릭은 C#, Java 등의 언어에서 재사용성이 높은 컴포넌트를 만들 때 자주 활용되는 특징입니다. 특히, 한가지 타입보다 여러 가지 타입에서 동작하는 컴포넌트를 생성하는데 사용됩니다.
제네릭이란 타입을 마치 함수의 파라미터처럼 사용하는 것을 의미합니다.
기본적인 예제는 아래와 같다.
제네릭 타입을 적용하면 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; }
TypeScript
복사
리액트와 타입스크립트 구조를 자주 사용하고 있는데 이번 에러를 해결하면서 타입스크립트 핸드북을 종종 봐야한다는 생각과 이펙티브 타입스크립트가 유명하던데 그 책을 사서 공부해볼까 싶었다. 최근 들어 기본기를 다지기 위해서 바닐라 자바스크립트 위주로 공부해서 헷갈리는 부분들이 있었는데 역시 꾸준히 공부해야 한다.