※ 아이템 10 객체 래퍼 타입 피하기
▶ 자바스크립트 기본형 값의 7개 타입
string, number, boolean, null, udefined, symbol, bigint
자바스크립트에는 메서드를 가지는 String '객체' 타입이 존재한다
기본형들은 불변이고, 메서드를 갖지 않는 점에서 객체와 구분된다.
근데 기본형 string 은 메서드를 갖고있는 것 처럼 보인다 . .?
▶ 자바스크립트 문자열의 기본형과 객체타입
자바스크립트는 기본형과 객체 타입을 서로 자유롭게 변환한다.
▷ 기본형 string
- 문자열 값 자체를 나타낸다. 문자열은 불변한다.
const str = "Hello Sian !";
const str = "Hello Sian !";
const res = str.toUpperCase();
console.log(res); //"HELLO SIAN !"
console.log(str); //"Hello Sian !" => 기본형 문자열은 그대로 유지된다.
기본형 문자열은 String 객체의 메서드를 호출할 때 임시객체를 생성해 호출한다.
메서드 호출이 끝나면 임시 객체는 파괴되어 메모리를 절약한다.
▷ 객체타입 String
- String 객체는 문자열을 객체로 나타낸다.
객체를 생성하기 위해 new 연산자를 사용해야한다.
const str = new String("Hello Sian !")
문자열 객체는 메서드를 직접 호출할 수 있다.
const str = new String("Hello Sian !")
const result = str.toUpperCase();
console.log(result); //"HELLO SIAN !"
문자열 객체는 불변이 아니기때문에 속성을 변경할 수 있다.
객체로 문자열을 다루면 메모리 사용량이 늘어나 문자열 기본을 사용하는 것이 좋다.
string 기본형에 charAt 같은 메서드를 사용할 때,
1. 자바스크립트는 기본형을 String 객체로 래핑하고
2. 메서드를 호출하고
3. 마지막에 래핑한 객체를 버린다.
객체 래퍼 타입 : 기본형 값을 감싸서 객체로 만들어주는 역할을 한다.
charAt() 메서드
문자열에서 특정 위치의 문자를 반환하는 메서드이다.
let str = "Hello"
let char1 = str.charAt(0);
let char2 = str.charAt(1);
let char3 = str.charAt(2);
console.log(char1); //H
console.log(char2); //e
console.log(char3); //l
몽키 패치 : 런타임에 프로그램의 어떤 기능을 수정해서 사용하는 기법.
아래는 String 객체의 charAt() 메서드를 몽키패치 하여 charAt() 이 호출될 때 마다
객체 자신(this), 객체 자신의 타입(typeof this) 와 인자로 받은 인덱스(pos) 를 출력하는 예제이다.
const originalCharAt = String.prototype.charAt; //변수에 charAt() 메서드 저장
String.prototype.charAt = function(pos){ //charAt() 오버라이딩
console.log(this, typeof this, pos); //[String: 'primitive'] object 3
return originalCharAt.call(this, pos); //상수에 저장된 charAt() 메서드 호출
};
console.log('primitive'.charAt(3));
//재정의된 charAt()메서드가 실행되어 해당 메서드가 출력하는 문자열과 인덱스 정보가 함께 출력됨
- this : 객체 자신 (string 기본형이 아닌 String 객체 래퍼)
- typeof this : 객체 자신의 타입
- pos : 전달받은 위치 (index)
타입스크립트는 기본형과 객체 래퍼 타입을 별도로 모델링한다.
string 을 매개변수로 받는 메서드에 String 객체를 전달하면 문제가 발생한다.
function isGreeting(phrase : String){
return [
'hello',
'good day'
].includes(phrase);
}
오류메세지 : String 형식의 인수는 string 형식의 매개변수에 할당될 수 없습니다. string 은 기본 개체이지만 String 은 래퍼 개체입니다. 가능한 경우 string 을 사용하세요.
string 은 String 에 할당할 수 있지만
String 은 string 에 할당할 수 없다.
런타임의 값은 객체가 아니고 기본형이다. 기본형 타입은 객체 래퍼에 할당할 수 있기 때문에 타입스크립트는 기본형 타입을 객체 래퍼에 할당하는 선언을 허용한다.
※ 아이템 11 잉여속성 체크의 한계인지하기
타입이 명시된 변수에 객체 리터럴을 할당할 때 타입스크립트는 해당 타입의 속성이 있는지, 그리고 그 외의 속성은 없는지 확인한다.
예제 1 )
interface Room {
numDoors : number;
ceilingHeightFt : number;
}
const r : Room = {
numDoors: 1,
ceilingHeightFt : 10,
elephant : 'present',
};
오류메세지 : 개체리터럴은 알려진 속성만 지정할 수 있으며 'Room' 형식에 elephant 가 없습니다.
=> 구조적 타이핑관점으로 생각해보면 오류가 발생하지 않아야한다.
예제 2 )
interface Room {
numDoors : number;
ceilingHeightFt : number;
}
const obj = {
numDoors : 1,
ceilingHeightFt : 10,
elephant : 'present'
}
const r : Room = obj;
obj 객체는 Room 타입 할당이 가능하다.
obj 타입은 Room 타입의 부분집합 (numbDoors, ceilingHeightFt) 을 포함하므로,
Room 에 할당 가능하며 타입체커를 통과할 수 있다.
▷ 예제 1과 2의 차이점
예제 1에서는 구조적 타입 시스템에서 발생할 수 있는 오류를 잡을 수 있도록 '잉여속성 체크' 라는 과정이 수행되었다. 잉여속성체크가 할당 가능검사와는 별도의 과정이라는 것을 알아야 타입시스템 개념을 정확하게 잡을 수 있다.
▶ 잉여속성체크
객체 리터럴을 변수에 할당하거나 함수에 매개변수로 전달할 때 잉여속성체크가 수행된다.
타입스크립트에서는 객체 리터럴의 프로퍼티에 대해 정적 타입검사를 수행한다. 개체 리터럴에 포함되지 않은 속성이 객체에 할당되려고 할때, 타입스크립트는 이를 '잉여속성' 이라고 판단하고 컴파일 오류를 발생시킨다.
interface Member {
name : string
age : number
}
const person : Member = {
name : 'Sian',
age : 24,
unit : 'unit1' // 오류발생
}
Member 인터페이스에 unit 프로퍼티가 없기 때문에 컴파일러는 이를 잉여속성으로 판단하고 오류를 발생시킨다.
잉여속성체크를 이용하면 타입 시스템의 구조적 본질을 해치지 않으면서도 객체 리터럴에 알 수 없는 속성을 허용하지 않음으로 예제 1 같은 문제점을 방지할 수 있다.
interface Options{
title : string;
darkMode?:boolean;
}
const o1 : Options = document;
const o2 : Options = new HTMLAnchorElement;
document 와 HTMLAnchorElement 의 인스턴스 모두 string 타입인 title 속성을 가지고 있기 때문에 할당문은 정상이다.
Options 내의 darkMode 는 선택적 속성으로, 객체에 포함시키거나 포함시키지 않을 수 있다.
타입스크립트의 구조적 타입 시스템은 객체의 구조를 기반으로 타입 호환성을 결정하기 때문에 document 와 HTMLAnchorElement 객체는 Options 타입으로 취급할 수 있다.
const o : Options = {darkmode : true, title : 'Ski Free'}; //오류
//{darkmode : boolean; title:string;} 형식은 Options 형식에 할당할 수 없습니다. 개체 리터럴은
//알려진 속성만 지정할 수 있지만, Options 형식에 darkmode 가 없습니다
객체 리터럴을 변수에 할당할 때 잉여속성체크가 수행되어 에러문구를 보여주고 있다.
Options 타입에 포함되지 않은 darkmode 속성이 할당되려고한다. 타입스크립트는 darkmode 를 '잉여속성' 이라고 판단하고 컴파일 오류를 발생시켰다.
임시변수를 도입하면 잉여속성체크를 건너뛸 수 있다. 아래를 보자.
const intermediate = {darkmode : true, title : 'Ski Free'};
const o : Options = intermediate; //정상
1의 오른쪽은 객체 리터럴이지만, 2의 오른쪽은 객체리터럴이 아니다. 따라서 잉여 속성 체크가 적용되지 않고, 오류는 사라진다.
잉여속성체크는 구조적 타이핑 시스템에서 허용되는 속성 이름의 오타같은 실수를 잡는데 효과적인 방법이다.
잉여속성체크를 원하지 않는다면 인덱스 시그니처를 사용해서 타입스크립트가 추가적인 속성을 예상하도록 할 수 있다.
interface Options {
darkMode?: boolean;
[otherOptions : string] : unknown; //인덱스 시그니처
}
const o : Options = {darkmode : true}; //정상
▷ 인덱스 시그니처
타입스크립트에서 객체의 속성이름과 타입을 동적으로 정의하는 방법.
대괄호 안에 속성 이름의 타입을 지정하고, 해당 속성의 값을 타입을 지정한다.
인덱스 시그니처를 사용하면 객체의 일부 속성은 명시적으로 정의할 수 있지만, 다른 속성은 인덱스 시그니처를 통해 동적으로 정의할 수 있다.
위 예제에서 Options 인터페이스에는 `[otherOptions : string] : unknown` 인덱스 시그니처가 있다.
인덱스 시그니처를 통해 객체의 다른 속성에 대한 타입을 아래와 같이 동적으로 정의할 수 있다.
interface Options {
darkMode?: boolean;
[otherOptions: string]: unknown;
}
const options: Options = {
darkMode: true,
fontSize: 14,
showMenu: false
};
interface LineChartOptions {
logscle?:boolean;
invertedYAxis?: boolean;
areaChart?:boolean;
}
const opts = {logScale: true};
const o : LineChartOptions = opts;
//오류메세지 : {logScale : boolean;} 유형에 LineChartOptions 유형과 공통적인 속성이 없습니다
구조적 관점에서 LineChartOptions 타입은 모든 속성이 선택적이므로 모든 객체를 포함할 수 있다.
이렇게 약한 타입에 대해서 타입스크립트는 값타입과 선언 타입에 공통된 속성이 있는지 확인하는 별도의 체크를 수행한다.
▶ 공통속성체크
- 오타를 잡는데 효과적이다. (logscale != logScale)
- 구조적으로 엄격하지 않다.
- 약한 타입과 관련된 할당문마다 수행된다.
- 임시변수를 제거하더라도 공통 속성체크는 여전히 동작한다.
const o : LineChartOptions = {logScale: true};
//{logScale:boolean} 형식은 LineChartOptions 형식에 할당할 수 없습니다.
//개체 리터럴은 알려진 속성만 지정할 수 있지만, LineChartOptions 형식에 logScale이 없습니다.
//logscale 을 쓰려고 했습니까 ?
※ 아이템 12 함수 표현식에 타입 적용하기
자바스크립트(와 타입스크립트) 에서는 함수문장(statement) 과 함수 표현식(expression) 을 다르게 인식한다.
▶ 함수 문장 (statement)
- function 키워드로 선언됨
- 코드블록 내 어디서든 호출할 수 있다.
function add(x: number, y: number) : number {
return x + y ;
}
▶ 함수 표현식 (expression)
- 변수에 할당된 함수 표현식으로 정의된다.
- 따라서 변수에 먼자 할당되어야 하고, 그 변수가 선언되기 이전에는 사용할 수 있다.
const add = function(x: number, y:number) : number {
return x + y;
};
타입스크립트에서는 함수표현식을 사용하는 것이 좋다 .
매개변수 ~ 반환값 까지 전체를 함수 타입으로 선언하여 함수 표현식에 재사용할 수 있기 때문이다.
▷ 함수타입 선언
- 불필요한 코드의 반복을 줄인다.
function add (a : number, b: number) {return a + b; }
function sub (a : number, b: number) {return a - b; }
function mul (a : number, b: number) {return a * b; }
function div (a : number, b: number) {return a / b; }
type BinaryFn = (a : number, b:number) => number;
const add : BinaryFn = (a,b) => a+b;
const sub : BinaryFn = (a,b) => a-b;
const mul : BinaryFn = (a,b) => a*b;
const div : BinaryFn = (a,b) => a/b;
함수 타입 선언을 이용했던 예제보다 타입 구문이 적다.
함수 구현부가 분리되어있어 로직이 보다 분명해진다.
=> 매개변수나 반환값에 타입을 명시하기보다는 함수 표현식 전체에 타입구문을 적용하는 것이 좋다.
라이브러리는 공통함수 시그니처를 타입으로 제공한다.
리액트는 함수의 매개변수에 명시하는 MouseEvent 타입 대신에 함수 전체에 적용할 수 있는 MouseEventHandleer 타입을 제공한다.
type MouseEventHandler<T = Element> = EventHandler<MouseEvent<T>>;
웹 브라우저에서 fetch 함수는 특정 리소스에 HTTP 요청을 보낸다.
const responseP = fetch('/quote?by=Mark+Twain') // Type is Promise<Response>
async function getQuote() {
const response = await fetch('/quote?by=Mark+Twain')
const quote = await response.json()
return quote
}
/quote 가 존재하지 않는 API 라면 '404 Not Found' 가 응답된다.
응답은 json 이 아닐 수 있으며 response.json() 은 JSON 형식이 아니라는 새로운 오류 메시지를 담아 거절된 프로미스를 반환한다 (rejected)
호출한 곳에서는 새로운 오류 메시지가 전달되어 실제 오류인 404가 감춰진다.
fetch 가 실패하면 거절된 프로미스를 응답하지 않는다 따라서 상태 체크를 수행해줄 함수를 작성한다.
함수문장ver.
async function checkedFetch(input: RequestInfo, init?: RequestInit){
const response = await fetch(input, init);
if(!response.ok){
throw new Error('Request failed: '+ response.status);
}
return response;
}
함수의 매개변수에 타입선언을 하는 것 보다 함수 표현식 전체 타입을 정의하는 것이 코드도 간결하고 안전하다.
함수 표현식ver.
const checkedFetch: typeof fetch = async (input, init)=>{
const response = await fetch(input, init);
if(!response.ok){
throw new Error('Request failed: '+response.status);
}
return response;
}
타입스크립트가 input 과 init 의 타입을 추론할 수 있도록 함수 전체에 typeof fetch 를 적용했다.
또한 checkedFetch 의 반환 타입을 보장하고, fetch 와 동일하다.
//fetch 의 타입선언
declare function fetch(
input : RequuestInfo, init?: RequestInit
): Promise<Response>;
▷ 함수 전체의 타입 선언을 적용해야 하는 경우
- 매개변수의 타입과 반환 타입을 반복해서 작성하지 말자
- 다른 함수의 시그니처와 동일한 타입을 가지는 새 함수를 작성할 때
- 동일한 타입을 가지는 새함수를 작성할 때
- 동일한 타입 시그니처를 가지는 여러개의 함수를 작성할 때
'Typescript' 카테고리의 다른 글
[Typescript] 이펙티브 타입스크립트 2장 아이템14 (0) | 2023.03.05 |
---|---|
[Typescript] 이펙티브 타입스크립트 2장 아이템13 (0) | 2023.03.05 |
[Typescript] typeORM (0) | 2022.12.13 |
[Typescript] 클래스 / 제네릭 / 유틸리티 타입 (0) | 2022.12.03 |
[Typescript] 리터럴 타입 / 유니온 타입 / 교차 타입 (0) | 2022.12.03 |