이펙티브 타입스크립트 읽고 정리
아이템 1 ~ 5 보러가기
아이템 6 ~ 8 보러가기
아이템 9 ~ 13 보러가기
아이템 14 ~ 16 보러가기
아이템 17 ~ 19 보러가기
2️⃣0️⃣ 아이템 20. 다른 타입에는 다른 변수 사용하기
자바스크립트에서는 변수에 타입이 존재하지 않기 때문에 어떠한 타입의 값도 저장할 수 있다. 하지만 타입스크립트에서는 변수에 타입을 지정하고 해당 타입의 값만 사용하는 것이 좋다.
let value1 = "10";
value1 = 10;
let value2: string = "10";
value2 = 10; // 타입 에러 발생
이를 통해 얻을 수 있는 장점은 변수의 값을 어느 정도 예측가능하기 때문에 이를 통해서 변수를 사용했을 때 발생할 수 있는 문제를 미연에 방지할 수 있다.
그리고 여러 타입을 값을 하나의 변수에 저장하고 사용하는 경우 값의 타입 체크 하는 코드가 추가로 들어갈 수밖에 없다..
let value: number | string = 10;
if (typeof value === 'string') {
value = Number(value) = 1;
} else {
value += 1;
}
✨ 하나의 변수에는 하나의 타입을 사용하는 것이 좋다. 다른 목적의 값을 하나의 변수에 담지 말기!
2️⃣1️⃣ 아이템 21. 타입 넓히기
타입스크립트는 타입 추론을 할 때 현재 상황에서 가장 넓은 범위의 타입으로 추론을 한다.
let value = 'x'
function fn(value: 'x') {}
fn(value);
위의 valule는 문자열 'x' 값이 할당되어 있다. 그래서 fn 함수의 'x' 타입에 할당 가능할 것처럼 보인다. 하지만 위의 코드는 타입 에러가 발생한다.
타입스크립트는 사용자의 의도를 정확히 모르기 때문에 가능한 한 넓은 타입으로 추론을 하게 된다. value는 let 키워드로 선언되어 있기 때문에 사용자의 의도와 상관없이 추후 변경될 가능성이 존재한다.
그래서 value는 ‘x’ 타입이 아닌 문자열 타입으로 확장해서 추론하게 된다.
const value = 'x'
위와 같이 const로 변수를 선언하면 값이 변경될 수 없기 때문에 value는 'x' 값만 가질 수 있다. 그래서 ‘x’ 타입을 가지게 된다.
원시 타입의 경우에는 const를 통해서 타입을 제한할 수 있다. 하지만 객체에는 이러한 방식이 통하지 않는다. 왜냐하면 변수에 새로운 값을 재할당하는 것은 불가능하지만 객체에 포함된 속성 값을 변경하는 것은 가능하기 때문이다.
const obj = {
value: 1,
};
obj.name = '';
delete obj.value;
/*
const obj: {
value: number;
}
*/
obj 객체의 value 속성의 타입은 1이 아니라 number가 된다. 그리고 리터럴로 정의된 속성 이외에 추가, 삭제가 되면 타입 에러가 발생한다.
만약 속성의 타입을 선언당시의 값으로 지정하고 싶은 경우 const 단언문을 사용하면 된다.
const obj = {
value: 'x',
} as const;
function fn(value: 'x') {}
fn(obj.value); // 정상
속성의 타입을 확인해 보면 readonly가 추가된 걸 확인할 수 있다.
✨ 객체의 경우에는 위와 같은 특성 때문에 변수에 타입을 명시적으로 작성해 주는 것이 좋다.
2️⃣2️⃣ 아이템 22. 타입 좁히기
타입 좁히기는 기존 할당된 타입에서 좁은 범위로 진행하는 것을 말한다. 예를 들어 string | null 타입에서 string 타입으로 줄이는 걸 말한다.
보통 조건문을 통해 타입을 좁힐 수 있다.
// 1.
let value: string | null = '1';
if (value) {
// null 및 undefined 타입은 제거
}
// 2.
let str: String | Number = '10';
if (str instanceof String) {
// String 타입으로만 타입 좁히기
}
// 3.
const ab: { a: number } | { b: number } = {
a: 1
};
if ('a' in ab) {
// { a: number } 타입으로 좁히기
}
배열의 filter메서드를 많이 사용하는데, 배열의 filter를 사용 후 생각했던 대로 타입 좁히기가 되지 않는 경험을 한 적이 많을 것이다.
interface A {
name?: string;
}
const list: A[] = [
{},
{ name: 'hello' },
{ name: 'hi' }
];
const newList = list
.filter(({ name }) => name)
.map(({ name }) => name);
위의 코드는 리스트에서 존재하는 name으로 구성된 새로운 배열을 만드는 코드이다. 원하는 타입은 string[]이었지만 타입 추론된 결과는 (string | undefined)[] 가 되었다.
({ name }) => name 이 부분의 코드를 ({ name }) => name ≠= undefiend로 변경하여도 마찬가지로 추론은 원하는 결과가 나오지 않는다.
위와 같은 코드를 통해 우리는 filter를 사용해도 타입추론이 정상적으로 되지 않는다고 생각하게 된다. 그럼 필터에서 타입추론을 할 수 있도록 하는 방법은 무엇일까?
아래와 같이 타입가드를 통해 진행하게 되면 정상적으로 추론이 되는 것을 확인할 수 있다.
function isDefinedName(value: A): value is Required<A> {
return value.name !== undefined;
}
const newList = list
.filter(isDefinedName)
.map(({ name }) => name);
아래의 코드는 배열에 담긴 값자체에 대해서 조건이 실행된다. 이러한 경우에는 우리가 원하던 대로 타입추론이 된다.
const list = [1, 'a'];
const numbers = list.filter((value) => typeof value === 'number');
✨ filter에서 원하는 타입으로 추론하고 싶다면 filter에 전달된 콜백함수의 반환 타입이 지정되어 있어야 한다. 단순 객체의 속성으로 평가되는 건 타입 추론에 영향을 주지 않는다.
2️⃣3️⃣ 아이템 23. 한꺼번에 객체 생성하기
변수에 할당된 타입은 변경되지 않는다. 그래서 다른 객체의 속성을 복사할 때 사용되는 Object.assign을 사용하더라도 처음 선언할 때 타입으로만 작동한다.
const a = { a: 1 };
const b = { b: 1 };
const c = {};
Object.assign(c, a, b);
console.log(c.a); // 타입 에러 발생
- 새로운 타입으로 할당하기 위해서는 Object.assign 메서드 호출 후 반환되는 값을 새로운 변수에 담아 사용해야 한다.
다른 객체의 속성을 복사하고 싶다면 전개 연산자를 사용하는 것도 좋다. 아래와 같이 간결하게 사용할 수 있다.
const a = { a: 1 };
const b = { b: 1 };
const c = { ...a, ...b }; // { a: number; b: number; }
console.log(c.a);
2️⃣4️⃣ 아이템 24. 일관성 있는 별칭 사용하기
객체의 속성을 새로운 변수에 저장했다면 두 개는 별개의 타입 체크를 진행한다.
interface Vector {
x?: number;
y?: number;
}
const fn = (): Vector => {
return {
x: 1,
y: 1,
}
}
const vector = fn();
let x = vector.x;
if (vector.x) {
x += 1; // 에러발생
}
위의 코드에서 조건문이 실행되는 건 vector.x이지 x가 아니기 때문에 x에 대한 타입 체크는 하지 않은 게 된다. 그래서 x는 여전히 number | undefined 타입이기 때문에 에러가 발생한다.
이러한 문제를 발생시키지 않기 위해서는 일관성 있는 별칭을 사용해야 한다. 변수 x에 담아서 사용하기로 했으니 이후에도 x를 사용해야 한다.
이후 if 문 이전에 다른 코드가 추가돼서 변경이 발생될 수도 있기 때문에 일관되게 사용해야 한다.
if (x) {
x += 1;
}
그래서 이러한 경우에는 비구조화할당을 사용하는 것이 더 낫다.
const { x } = vector;
하지만 만약 vector가 undefined 또는 null 일 수 있다면 위의 코드를 실행하면 타입 에러가 발생한다. 이점에 유의해서 사용해야 한다.
2️⃣5️⃣ 아이템 25. 비동기 코드에는 콜백 대신 async 함수 사용하기
비동기 함수에 async를 붙여주면 자동으로 반환 타입이 Promise가 된다. 이를 통해 비동기함수의 일관된 타입을 지정할 수 있다.
const fn = async () => {
return fetch();
}
만약 하나의 함수에 동기처리와 비동기 처리가 공존할 때 async를 사용하지 않으면 Promise와 일반 타입이 유니온으로 반환된다. 이러한 처리를 함수를 호출하는 곳에서 반환 값에 따라 달리 처리하는 로직을 구성해야 한다.
function fn(type: boolean): 1 | Promise<number>;
function fn(type: boolean) {
if (type) {
return 1;
} else {
return Promise.resolve(1);
}
}
하지만 async를 사용하면 항상 Promise를 반환하게 되어 있으니 일관된 코드를 작성할 수 있게 된다.
async function fn(type: boolean) {
if (type) {
return 1;
} else {
return Promise.resolve(1);
}
}
'Typescript' 카테고리의 다른 글
[TypeScript] 이펙티브 타입스크립트 정리 - 아이템 32 ~ 42 (0) | 2025.07.06 |
---|---|
[TypeScript] 이펙티브 타입스크립트 정리 - 아이템 26 ~ 31 (0) | 2025.06.28 |
[TypeScript] 이펙티브 타입스크립트 정리 - 아이템 17 ~ 19 (0) | 2025.06.15 |
[TypeScript] 이펙티브 타입스크립트 정리 - 아이템 14 ~ 16 (0) | 2025.06.14 |
[TypeScript] 이펙티브 타입스크립트 정리 - 아이템 9 ~ 13 (0) | 2025.06.08 |