⚡리액트 개발을 위해 꼭 알아야할 자바스크립트 - 1.7 타입스크립트
1.7.1 타입스크립트란
TypeScript is JavaScript with syntax for types
- 기존 자바스크립트 문법에 타입을 가미한 것
- 자바스크립트는 동적 타입의 언어이기 때문에 대부분의 에러를 코드를 실행했을 때만 확인할 수 있다는 문제점
- 동적 타입 언어라는 점은 개발자에게 자유를 주기도 하지만 코드의 규모가 커질수록 오히려 발목을 잡는 경우도 많음
- 타입스크립트는 타입 체크를 정적으로 런타임이 아닌 빌드 타입에 수행할 수 있게 해줌
- 모든 함수와 변수에 타입 확인 연산자인 typeof를 적용해서 체크하는 것은 너무 번거롭고 코드의 크기를 과도하게 키움
function test(a,b) {
return a/b
}
test(5,2) // 2.5
test('안녕하세요','하이')// NaN
function test(a,b) {
if (typeof a !== 'number' || typeof b !== 'number'){
throw new Error('a,b 모두 숫자여야함!')
}
return a/b
}
🌓인수가 객체라면 내부 프로퍼티에 대해 모두 체크할 것인가?
function test(a: number, b: number) {
return a / b
}
test('안녕하세요', '하이')
- 위의 코드에서 a와 b 변수에 number 타입을 지정하면 런타임까지 가지 않더라도 코드를 빌드하는 시점에 에러가 발생할 수 있는 코드를 확인할 수 있음
- 타입스크립트는 자바스크립트의 슈퍼셋으로서 함수의 반환 타입, 배열, enum 등 기존에는 사용하기 어려웠던 타입 관련 작업들을 손쉽게 처리할 수 있음
- 자바스크립트에서 불가능한 일은 타입스크립트에서도 마찬가지로 불가능
1.7.2 리액트 코드를 효과적으로 작성하기 위한 타입스크립트 활용법
🌚 any 대신 unknown를 사용하자
- 처음 작성할 때 저지르는 실수 : any를 자주 사용
- any는 정말 불가피할 때만 사용해야 하는 타입
- any를 사용한다는 것은 타입스크립트가 제공하는 정적 타이핑의 이점을 모두 버리는 것
function doSomething(callback: any) {
callback()
}
// 타입스크립트에서 에러가 발생하지 않는다. 그러나 이 코드는 실행 시 에러가 발생한다.
doSomething(1)
doSomething은 callback을 인수로 받는데 해당 타입이 any로 되어 있어서 실제 함수가 아닌 값이 들어가도 타입스크립트가 에러를 발생시키지 않음
- any는 자바스크립트에서 타입스크립트로 넘어가는 과도기와 같은 정말로 예외적인 경우에만 사용하는 것이 좋음
- 타입을 단정할 수 없는 경우에는 unknown 사용하는 것이 좋음
- unknown은 모든 값을 할당할 수 있는 top type으로, 어떠한 값을 할당할 수 있지만 any와 다르게 바로 사용하는 것은 불가능함
function doSomething(callback: unknown) {
callback() // 'callback' is of type 'unknown'
}
callback은 unknown, 즉 아직 알 수 없는 값이기 때문에 사용할 수 없다는 내용
unknown으로 선언된 변수를 사용하기 위해서는 type narrowing, 즉 타입이 원래 의도했던 대로 적절히 좁혀야 함
function doSomething(callback: unknown) {
if (typeof callback === 'function') {
callback()
return
}
throw new Error('callback은 함수여야 합니다.')
}
- unknown을 사용하는 것은 예상치 못한 타입을 받아들일 수 있어 사용하는 쪽에서도 더욱 안전하게 사용할 수 있음
- any보다는 unknown을 사용하는 습관을 들이는 것이 좋음
- unknown과는 반대로 never타입이 있는데, never는 어떠한 타입도 들어올 수 없음을 의미
실제 리액트 코드에서는 props는 없지만 state가 존재하는 상황에서 빈 props를 통해 어떤 props도 받지 않는다는 의미로 사용
import React from "react";
import logo from "./logo.svg";
import "./App.css";
type Props = Record<string, never>;
type State = {
counter: 0;
};
class SampleComponent extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
counter: 0,
};
}
render() {
return <>...</>;
}
}
function App() {
return (
<>
<SampleComponent />
<SampleComponent hello="world" /> //에러를 발생시킴
</>
);
}
export default App;
위 SampleComponent는 어떠한 props도 받을 수 없는 대신 state가 존재함
React.Component의 제네릭은 Props와 State를 순서대로 작성해야 하는데, Props의 경우 Record<string,never>로 작성해 어떠한 props도 받을 수 없도록 타입 스크립트로 처리할 수 있음
🌚 타입 가드를 적극 활용하자
- 타입을 사용하는 쪽에서는 최대한 타입을 좁히는 것이 좋음
- 타입 가드 : 타입을 좁히는데 도움을 주는것
- 조건문과 함께 타입가드를 사용하면 타입을 효과적으로 좁힐 수 있어 조금 더 변수나 함수를 사용할 수 있음
🌓 instanceof와 typeof
- instanceof : 지정한 인스턴스가 특정 클래스의 인스턴스인지 확인할 수 있는 연산자
class UnAuthorizedError extends Error {
constructor() {
super()
}
get message() {
return '인증에 실패했습니다.'
}
}
class UnExpectedError extends Error {
constructor() {
super()
}
get message() {
return '예상치 못한 에러가 발생했습니다.'
}
}
async function fetchSomething() {
try {
const response = await fetch('/api/something');
return await response.json();
} catch (e) {
// e는 unknown이다.
// UnAuthorizedError를 위한 타입 가드 조건문
if (e instanceof UnAuthorizedError) {
...
}
// UnExpectedError를 위한 타입 가드 조건문
if (e instanceof UnExpectedError) {
...
}
throw e;
}
}
- unknown으로 내려오는 에러에 대해 타입 가드를 통해 타입을 좁힘으로써 각 에러에 따라 원하는 처리 내용을 추가 할 수 있음
- typeof 연산자는 특정 요소에 대해 자료형을 확인하는데 사용함
function logging(value: string | undefined) {
if (typeof value === 'string') {
console.log(value);
}
if (typeof value === 'undefined') {
return;
}
}
🌓 in
- property in object로 사용
- 어떤 객체에 키가 존재하는지 확인하는 용도
- 타입에 여러가지 객체가 존재할 수 있는 경우가 유용함
interface Student {
age: number;
score: number;
}
interface Teacher {
name: string;
}
function doSchool(person: Student | Teacher) {
if ('age' in person) {
person.age // person은 Student
person.score
}
if ('name' in person) {
person.name // person은 Teacher
}
}
🌚제네릭
- 함수나 클래스내부에서 단일 타입이 아닌 다양한 타입에 대응할 수 있도록 도와주는 도구
- 제네릭을 사용하면 타입만 다른 비슷한 작업을 하는 컴포넌트를 단일 제네릭 컴포넌트로 선언해 작성할 수 있음
function getFirstAndLast<T>(list: T[]): [T, T] {
return [list[0], list[list.length - 1]]
}
const [first, last] = getFirstAndLast([1, 2, 3, 4, 5]);
first // number
last // number
const [first, last] = getFirstAndLast(['a', 'b', 'c', 'd', 'e']);
first // string
last // string
💡 리액트에서 제네릭을 사용할 수 있는 코드 > useState
useState에 제네릭으로 타입을 선언한다면 state사용과 기본값 선언을 더 명확하게 할 수 있음
제네릭을 하나이상 사용할 수 있음 단, 제네릭을 사용할 땐 적절한 네이밍하는 것이 좋음
🌚 인덱스 시그니처
- 객체의 키를 정의하는 방식
- 키에 원하는 타입을 부여할 수 있음
- 동적인 객체를 정의할 때 유용하나, 존재하지 않는 키로 접근하면 undefined를 반환 할 수 있음
type Hello = {
[key: string] : string //인덱스 시그니처
}
const hello: Hello = {
hello: 'hello',
hi: 'hi',
}
hello['hi'] // hi
hello['안녕하세요'] // undefined
객체의 키는 동적으로 선언되는 경우를 최대한 지양해야 하고, 객체의 타입도 필요에 따라 좁혀야 함
💡 객체의 키를 좁히는 두가지 방법
// record를 사용
type Hello = Record<'hello'|'hi', string>
const hello: Hello = {
hello: 'hello',
hi: 'hi',
}
// 타입을 사용한 인덱스 시그니처
type Hello = { [key in 'hello'|'hi']: string }
const hello: Hello = {
hello: 'hello',
hi: 'hi',
}
💡인덱스 시그니처를 사용할 때 마주하는 이슈
Object.keys(hello).map((key) => {
// Element implicitly has an 'any' type because expression of type 'string'
// can't be used to index type 'hello'
// No index signature with a parameter of type 'string' was found on type 'Hello'
const value = hello[key];
return value;
});
🌓해결방법
//1. bejct.keys(hello)를 as로 타입 단언
(Object.keys(hello) as Array<keyof Hello>).map((key) => {
const value = hello[key];
return value;
});
//2. 타입스크립트의 Object.keys에 대한 반환 타입을 string[] 대신 개발자가 단언한 타입으로 강제하는 방법
// 타입가드 함수를 만드는 방법
function keysOf<T extends Object>(obj: T): Array<keyof T> {
return Array.from(Object.keys(obj)) as Array<keyof T>;
}
keysOf(hello).map((key) => {
const value = hello[key];
return value;
})
//3. 가져온 key를 단언 하는 방법
Object.keys(hello).map((key) => {
const value = hello[key as keyof Hello];
return value;
})
'Study > 모던 리액트 Deep Dive' 카테고리의 다른 글
| 모던 리액트 Deep Dive - 가상 DOM과 리액트 파이버 (0) | 2025.09.30 |
|---|---|
| 모던 리액트 Deep Dive - JSX (0) | 2025.09.30 |
| 모던 리액트 Deep Dive - 자주 사용되는 자바스크립트 문법 (0) | 2025.09.23 |
| 모던 리액트 Deep Dive - 이벤트 루프와 비동기 통신의 이해 (0) | 2025.09.23 |
| 모던 리액트 Deep Dive - 클로저 (1) | 2025.09.15 |