[TypeScript] React에서 TypeScript 적용하기 & 오류 해결하기

dosilv·2021년 6월 20일
59
post-thumbnail
post-custom-banner

객체지향에 대해 깊이 공부하기 전에! 기존에 하던 JS+React 프로젝트를 TS+React로 변경해 보기로 했다. 증말 아무것도 모르지만 어찌어찌 해냄...! thanks to 구글...


💎 생성

💍 새 TS 프로젝트를 생성하는 경우

먼저 CRA를 받은 후

npm install craete-react-app

npx 실행 시 --template typescript라는 키워드를 붙여 준다.

npx create-react-app [프로젝트 폴더명] --template typescript

그러면 index.tsx, App.tsx를 비롯해 필요한 파일들이 생성됨! 여기서 알 수 있듯이 TS를 사용하는 리액트 컴포넌트는 .tsx라는 확장자를 사용한다.

react-router-dom, styled-components 등 필요한 모듈이 있다면 아래처럼 모듈 이름 앞에 @types/를 붙여서 타입스크립트 버전으로도 설치해야 함!

npm i --save-dev @types/styled-components
npm i --save-dev @types/styled-components

💍 기존 JS 프로젝트를 TS로 바꾸는 경우

해당 프로젝트 폴더에서 다음 명령어를 실행한다.

npm install --save typescript @types/node @types/react @types/react-dom @types/jest

마찬가지로 기존에 설치되어 있는 모듈들도 TS버전으로 업데이트 시켜 주기~~

npm i --save-dev @types/react-router-dom
npm i --save-dev @types/styled-components

그리고 TS로 변경할 JS 파일들의 확장자를 하나씩 .tsx로 변경해 준다. (처음부터 다 바꿀 필요는 ❌❌)

그 뒤에 서버를 재실행하면... 엄청난 오류 파티...!☠🎉☠🎉 이유도 내용도 제각각이기 때문에 vsc의 quick fix나 구글링을 통해 해결해야 한다....ㅎ.ㅎ 대부분은 타입이 지정되어 있지 않아서 나는 오류들...
머리 아프면 그냥 새로 생성하는 게 속편할지도><;;



💎 해결한 오류들 정리

💍 Parameter 'props' implicitly has an 'any' type.

props가 암묵적으로 'any' 타입으로 지정되어 있다는 경고..! 부모로부터 받아올 props의 타입을 명시해 줘야 하는데 아무것도 적지 않아서 뜨는 에러이다.

🧐 How I solved it

👍 방법 1

받아오는 props에 맞춰 interface를 선언하고, 이를 props의 타입으로 지정해 준다. interface는 컴포넌트 위에, 그리고 props가 객체이기 때문에 객체형태로 작성해 줘야 한다.
나는 onClick 이벤트에 걸기 위해서 scrollDown이라는 메서드를 넘겨 줬는데, 얘는 React.MouseEventHandler<HTMLDivElement>이라는 타입이었음!

interface명은 이렇게 사용할 이름 앞에 I를 붙여주는 경우가 많다고 한다.

👎 방법 2

말그대로 'implicit'하게 any 타입을 가진 게 문제였으니까.. explicit하게 any라고 타입을 지정해 준다!ㅎㅎ;; 사실 타입 미지정 오류가 발생했을 때 : any는 만능 치트키지만 그렇게 되면 TS를 쓰는 이유가 없어지기 때문에... 정말정말 도무지 무슨 타입인지 모르겠거나 명시하기 어려운 경우가 아니면 사용하지 말 것 😬😬



💍 Object is possibly 'null'.

다른 부분에서 발생한 같은 에러...! useRef에 넣어준 HTML Element에 대해 null 처리가 필요하다. (렌더링이 끝나기 전엔 null로 들어가기 때문에!)

🧐 How I solved it

👍 방법 1

optional chaining(?.)을 사용한다. 옵셔널 체이닝은 간단하게 말하면 .? 앞 부분이 null이나 undefined면 그대로 undefined를 리턴하고, 아닐 경우에만 .? 뒤에 이어지는 코드를 실행하는 ES2020 신문법!
.? 앞의 대상은 반드시 선언되어 있는 변수여야 하고, 뒷부분은 존재하지 않아도 괜찮은 부분이어야 한다. 그렇지 않으면 에러를 발견하기 어려워서 디버깅이 빡세진다고 한다...

하지만 옵셔널 체이닝의 치명적인 한계.. 할당연산자(=) 왼쪽에서는 사용할 수 없음!!!!ㅠ

스타일을 할당하는 부분에서 똑같이 ?.를 붙여 줬지만 빨간 줄은 사라지지 않고... 아래처럼 다른 종류의 에러로 바뀌었다.


👎 방법 2

그래서 이를 해결하기 위해 사용한 다른 방법... type assertion(타입 단언) 쓰기. type assertion은 확실한 상황에서 타입을 강요하는 것이다. 특정 타입을 단언해서 관련된 속성에 접근하거나, 관련 메서드를 사용할 수 있도록 할 때 사용한다.
이것 또한 디버깅에 어려움을 줄 수 있기 때문에 확실한 상황이 아니라면 사용을 지양할 것...

이렇게 소괄호와 as를 사용해 (단언할 부분 as 단언할 타입)형태로 표현해도 되고,

여기서는 null이 아니라는 것만 체크해 주면 되기 때문에 non-null assertion(.!)으로 표현해도 해결되었다.



💍 Type 'HTMLImageElement | null' is not assignable to type 'never'.

InnerScreen은 HTMLImageElement인데, 얘한테 ref 속성을 주려고 하니까 not assignable to type 'never'라고 경고한다. 이건 바로

screens에 할당된 useRef의 타입을 지정해 주지 않아서 발생한 문제...!(라고 추측) 근데 초깃값으로 빈 배열([])을 줬는데 왜 never가 되지? hooks는 타입 추론이 안 되나??? 궁금해 하다가 스택오버플로우에서 답을 찾을 수 있었다.

array 타입을 정확히 지정하지 않고 그냥 빈 배열을 할당해 버리면 자동으로 never[]가 되나 보다...!

🧐 How I solved it

👍 방법 1

useRef에 들어올 요소에 맞춰 타입을 지정해 준다! useState, useRef등 hooks는 일반 변수처럼 useRef: 타입 형태가 아니라 useRef<타입> 형태로 명시해야 함.
그 타입이 무엇이냐가 문젠데...
"Type 'HTMLImageElement | null' is not assignable to type 'never'." 이라는 에러 문구에서 영감(??)을 받아서 HTMLImageElement 또는 null을 담는 배열로 지정하면 되지 않을까...?! 싶어서 아래처럼 타입을 지정해 줬더니 해결됨~~~🥳


👎 방법 2

마찬가지로 치트키 any를 이용하는 방법... (사실 첨엔 이렇게 고침ㅎ.ㅎ)
근데 그냥 아예 any로 쓰는 건 아니고, 배열인 건 아니까 아무거나 담을 수 있는 배열 any[]라고 지정해 주는 게 그나마 쪼금 더 나을 듯! 그러면 any로 지정된 변수에 forEach나 map 같은 걸 사용했을 때 발생하는 오류도 없앨 수 있다.



💍 No overload matches this call.

이건 TS + styled-components를 사용하면서 발생한 문제 😢
정확히 말하면 스타일드 컴포넌트에서 내가 임의로 만든 props를 전달하려고 하니까 발생한 문제...! 근데 overload는 뭘까?

👀 overload란?

함수와 관련된 개념! 정확히 말하면 Function overloading.
동일한 이름의 함수에서 들어오는 매개변수의 type에 따라 다른 프로세스를 실행하는 것을 말한다. 다른 언어에서는 함수명만 같으면 되지만, TS에서는 각 함수의 매개변수의 개수도 같아야 함!

function plus(a: number, b: number): void;

function plus(a: string, b: string): void;

function plus(a: any, b: any): void {
  if (typeof a === 'number') {
    console.log(a + b);
  }
  if (typeof a === 'string') {
    console.log(`${a} ${b}`);
  }
}

이렇게 정의된 걸 overloaded function이라고 하고,

plus('hello', 'typescript'); //hello typescript
plus(1, 2); //3

매개변수의 타입에 따라 결과를 출력하는 걸 확인할 수 있다.

그런데 여기서 a에는 string, b에는 number 데이터를 인자로 넣어주면

plus('high', 2); //Error: No overload matches this call.

두둥..! No overload matches this call 에러가 발생한다.

그러니까 정리하자면, overloaded function에서 지정한 매개변수들의 타입 형식(여기서는 [number, number] 또는 [string, string])과 실제 전달한 인자의 타입 형식이 일치하지 않으면 뜨는 에러이다.

캡쳐에서는 position에만 경고 밑줄이 그어져 있는데, position을 삭제하면 revese에 밑줄이 뜨고, reverse도 삭제하면 reverseFloat에 뜨고 그랬다.
아무튼 ref, src는 실제로 역할과 형식이 있는 속성이지만 나머지는 내가 필요에 의해 커스텀한 것이기 때문에 스타일드 컴포넌트가 기대하는 속성이 아니라서 에러가 난 것 같다.

JSX뿐만 아니라 스타일드 컴포넌트가 정의된 곳으로 내려가 보면,

여기서도 수많은 밑줄들과 함께 Property 'position' does not exist on type ...라는 에러가 떴다.

위에서 전달이 안 되니까 밑에서 당연히 그런 속성 없다고 하는 그런 노답 현상...!

🧐 How I solved it

👍 방법

넣고 싶은 속성들을 넣어서 InnerScreen에 대한 interface를 생성해 준다.

그리고 해당 스타일드 컴포넌트를 선언하는 부분에서 styled.태그이름 뒤에 <인터페이스 이름>을 추가해서 생성한 인터페이스로 타입을 지정해 주면 됨!



타입스크립트 배우면서 느끼는 점... 이렇게 개고생해서 짰으니 런타임 오류가 안 난다는 거구나~~~!ㅎㅎ



🔹Reference🔹

Adding TypeScript | Create React App
Using React with Typescript 🏢
What is "not assignable to parameter of type never" error in typescript? - Stack Overflow
TypeScript errors for humans: "no overload matches this call" | Laura Chan
Using styled-components and Props With Typescript React | by Ndukwe Armstrong | Apr, 2021 | Dev Genius

profile
DevelOpErUN 성장일기🌈
post-custom-banner

1개의 댓글

comment-user-thumbnail
2022년 5월 5일

정리 잘하셨네요. 잘보고 갑니다!

답글 달기