타입스크립트를 적용한 nextjs로 프로젝트를 진행중이다.
카테고리를 고른 후 인풋창에 검색어를 입력하면 검색어에 해당하는 데이터를 실시간으로 불러와야하는데,
onChange를 사용하니 카테고리를 선택할 때마다 데이터를 불러오는 현상이 있었다.
물론 원하지 않은 데이터를 불러오지는 않는다. 그냥 기존에 불러온 게시물들이 깜빡이는 현상만 있을 뿐이다.
그래서 useRef를 사용하여 ref가 달린 value값을 input의 변화가 생길 때마다 state에 담으려고 했다.
역시 한 번에 되진 않는다. type이 가로막았다.
useRef의 타입을 지정해주고, searchOptionRef를 받는 tsx파일에서도 타입을 지정해줬다.
첫번째, 두번째 사진처럼 타입을 지정해주고 colsole.log로 value를 찍어보면 타입에러도 없고 잘 나온다.
근데, setState로 state에 담으려고 하면 세번째 사진처럼 타입에러가 뜬다.
물론 any를 사용한다면 아무문제가 없겠지만, 난 any를 사용하기 싫었고 구글링을 해보니, as로 타입 단언을 해주는 코드를 보았다.
as를 사용하니 에러도 없고 잘 된다!
잘 된다고 그냥 넘어가면 안된다.
useRef의 타입과, as를 공부해야한다.
useRef의 타입
useRef는 React Hook의 일종으로, 인자로 넘어온 초깃값을 useRef 객체의 .current 프로퍼티에 저장한다. DOM 객체를 직접 가리켜서 내부 값을 변경하거나 focus() 메소드를 사용하거나 하는 때에 주로 사용하고, 변경되어도 컴포넌트가 리렌더링되지 않도록 하기 위한 값들을 저장하기 위해서도 사용한다. (이는 useRef가 내용이 변경되어도 이를 알려주지 않기 때문이다. .current 프로피터를 변경시키는 것은 리렌더링을 발생시키지 않고, 따라서 로컬 변수 용도로 사용할 수 있다.)
위의 말은 useRef의 반환 타입인 MutableRefObject와 RefObject의 정의를 보면 더욱 명확하게 이해할 수 있다.
interface MutableRefObject<T> {
current: T;
}
interface RefObject<T> {
readonly current: T | null;
}
그저 함수 초깃값을 .current에 저장할 뿐이다.
@types/react의 index.d.ts를 보면 useRef는 3개의 정의가 아래처럼 오버로딩되어있는 것을 확인할 수 있다.
useRef<T>(initialValue: T): MutableRefObject<T>;
인자의 타입과 제네릭의 타입이 T로 일치하는 경우, MutableRefObject를 반환한다.
MutableRefObject의 경우, 이름에서도 볼 수 있고 위의 정의에서도 확인할 수 있듯 current 프로퍼티 그 자체를 직접 변경할 수 있다.
useRef<T>(initialValue: T|null): RefObject<T>;
인자의 타입이 null을 허용하는 경우, RefObject를 반환한다.
RefObject는 위에서 보았듯 current 프로퍼티를 직접 수정할 수 없다.
useRef<T = undefined>(): MutableRefObject<T | undefined>;
제네릭의 타입이 undefined인 경우(타입을 제공하지 않은 경우), MutableRefObject<T | undefined>를 반환한다.
위 내용들을 정리하자면 아래와 같다.
const localVarRef = useRef<number>(0);
```tsx
로컬 변수 용도로 useRef를 사용하는 경우, MutableRefObject<T>를 사용해야 하므로 제네릭 타입과 같은 타입의 초깃값을 넣어주자.
```tsx
const inputRef = useRef<HTMLInputElement>(null);
DOM을 직접 조작하기 위해 프로퍼티로 useRef 객체를 사용할 경우, RefObject를 사용해야 하므로 초깃값으로 null을 넣어주자.
참고 https://driip.me/7126d5d5-1937-44a8-98ed-f9065a7c35b5
TypeScript - as
Type Assertion(타입 단언)은 내가 TypeScript보다 어떤 값의 타입을 보다 명확하게 알고 있을 때 활용한다.
const searchOptionRef = useRef<HTMLSelectElement>();
(중략)
setSearchOption(searchOptionRef.current?.value);
// 'string | undifined' 형식의 인수는 'SetStateAction<string>' 형식의 매게 변수에 할당될 수 없습니다.
예를 들어 위 코드처럼 에러가 발생하는 경우, 난 절대로 undifined가 생기지 않을 것이라고 알고 있기 때문에
아래처럼 as를 사용하여 타입 단언을 해주면 에러가 사라진다.
const searchOptionRef = useRef() as React.MutableRefObject<HTMLSelectElement>;
위처럼 Type Assertion을 활용하면 보다 구체적으로 명시할 수 있다.
그리고 Type Assertion은 컴파일러에 의하여 제거되며 코드의 런타임 동작에는 영향을 주지 않는다.
const str = "hi" as number;
// Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
TypeScript에서는 보다 구체적인 또는 덜 구체적인 버전의 타입으로 변환하는 타입 단언만이 허용된다.
이러한 규칙은 위와 같은 “불가능한” 강제 변환을 방지한다.