ts에서 변수에 값을 할당하고 타입을 부여하는 방법은 두가지가 있다.
interface Person { name: string };
const hee: Person = { name: 'hee' }; // 타입 선언
const jinu = { name: 'jinu' } as Person; // 타입 단언
타입 선언은 할당되는 값이 인터페이스를 만족하는지 검사하는데 타입 단언은 그렇지 못한다. 그로므로
💥타입 단언은 안전성이 체크되지 않으니, 더 안전한 타입 선언을 사용하자.
화살표 함수에서 반환되는 값의 타입이 모호할 때가 있다. 따라서 화살표 함수로 추론된 타입이 모호한 경우를 대응하기 위해 타입 선언을 해주어야 한다. 타입 단언을 사용하는 경우엔 런타임에서 문제가 발생할 수 있으므로 지양한다.
(💢런타임에러(Run time error) : 이미 컴파일이 완료되어 프로그램이 실행중임에도 불구하고, 의도치 않은 예외 상황으로 인하여 프로그램 실행 중에 발생하는 오류 형태를 의미한다.)
const pepole = ['hee', 'jinu', 'ruby'].map(name => ({name}));
// Person [] 이 아닌 { name: string; }[] ...
/* 타입 단언 */
const pepole = ['hee', 'jinu', 'ruby'].map(name => ({name} as Person));
// Person []
const pepole = ['hee', 'jinu', 'ruby'].map(name => ({} as Person));
// 런타임 에러, Person []
/* 타입 선언 */
// 화살표 함수가 Person을 반환할 것이라고 타입 선언
const pepole = ['hee', 'jinu', 'ruby'].map((name): Person => ({name}));
// Person []
interface Station {
name: string
}
const stations = ['짱구', '철수'].map(name => ({name}))
// typeof locations === {name: string}[]
바로 위에 경우 아래처럼 타입 단언을 사용하여 해결 할 수 있다.
const stations = ['정자', '미금'].map(name => ({name} as Station)) // Station[]
하지만 타입단언은 타입체커가 화살표함수의 반환 타입을 검증하지 않도록 하기 때문에 좋은 방법은 아니다. 보다 자연스러운 방법은 화살표 함수의 반환 타입을 선언하는 것이다.
const stations = ['정자', '미금'].map((name):Station => ({name})) // Station[]
타입 체커가 추론한 타입 보다 개발자가 판단하는 타입이 더 정확할 때 타입 단언을 사용한다.
(DOM 이란? 문서 객체 모델, 즉 DOM은 웹 페이지(HTML이나 XML 문서)의 콘텐츠 및 구조, 그리고 스타일 요소를 구조화 시켜 표현하여 프로그래밍 언어가 해당 문서에 접근하여 읽고 조작할 수 있도록 API를 제공하는 일종의 인터페이스이다.)
아래 코드의 경우 ts는 DOM에 접근할 수 없기 때문에 버튼엘리먼트인지 알 수 없다. 이런 경우 타입단언을 쓰는 것이 적절하다.
document.getElementById('button').addEventListener('click', e => {
const button = e.currentTarget // typeof button === EventTarget | null
console.log(button.tagName)
})
단언될 수 있는 타입은, 덜 구체적(슈퍼)이거나 더 구체적인(서브) 클래스이다. 이는 불가능한 강제변환을 막기 위함이다.
interface Person {
name: string
}
const body = document.body as Person // type Error
만약 꼭 임의의 타입으로 강제변환해야 한다면 모든 타입의 슈퍼 클래스인 unknown 로 변환하고, 다시 임의의 클래스로 변환한다.
const body = document.body as unknown as Person
이는 unknown 타입을 사용한 이상 위험한 동작을 하고 있다는 걸 알 수 있게 한다.
타입 단언보다(as Type) 타입 선언(: Type)
화살표 함수의 반환 타입 명시하는 방법 터득
ts보다 타입에 대해 더 잘 알고 있다면 타입 단언문과 null 아님 단언문 사용
원시값을 객체처럼 메서드를 사용할 수 있는 이유는 자바스크립트 엔진이 래퍼 객체로 일시적으로 원시값을 래핑하고 메서드를 호출하고 마지막엔 래퍼 객체를 버리기 때문이다.
객체 레퍼는 종종 아래와 같이 당황스러운 결과를 보여준다.
(원시 값이란 객체가 아니면서 메서드도 가지지 않는 데이터이다.)
(레퍼 객체란 숫자, 문자열, 불리언 등 원시 타입의 프로퍼티에 접근하려고 할 때 생성되는 임시 객체이다.)
function hasName(input: String) {
return ['A','B'].includes(input) // Type Error
}
위 오류의 이유는 String 에 string 을 할당할 순 있지만, string 에 String 을 할당할 수 없기 때문이다.
타입스크립트는 원시값 타입과 래퍼 객체 타입을 별도로 모델링한다.
원시값 타입은 래퍼 객체에 할당하는 것을 허용하지만, 오해하기 싶고 굳이 그렇게 할 필요가 없다.
const s: String = "primitive"; // 정상이지만 안티 패턴
//안티 패턴: 실제 많이 사용되는 패턴이지만 비효율적이거나 비생산적인 패턴
ts의 string과 래퍼 객체의 String을 혼동해서 타입에 적용하면 문제가 생길 수 있다. string은 String에 대입할 수 있지만 그 반대는 오류가 나기 때문이다.
let str1: String = "tomato";
let str2: string = "tomato";
// 가능 ( "String" -> "string" )
str1 = str2;
// 오류 ( "string" -> "String" )
str2 = str1;
ts에서 래퍼 객체 타입은 지양하고 원시값 타입(기본형 타입)을 사용해야 한다.
잉여 속성 체크란 타입이 명시된 변수에 객체 리터럴을 할당할 때 해당 타입이 존재하는지 그리고 그 이외의 타입이 존재하지는 않는지확인하는 것을 말한다.
잉여 속성 체크란 아래와 같은 경우에 실행되는데
type Person = { name: string };
// 아래와 같은 경우 잉여 속성 체크를 통해 "age"가 선언됨은 인지하고 오류를 발생
const person: Person = { name: "alice", age: 25 };
아래와 같은 경우는 실행되지 않는다.
type Person = { name: string };
const person1 = { name: "alice", age: 25 };
// 잉여 속성 체크 실행 X ( 즉, "age"가 추가로 있어도 문제 없이 동작... ( 구조적 타이핑 ) )
const person2: Person = person1;
즉, 잉여 속성 체크는 객체 리터럴 할당의 경우에만 실행된다.
(여기서 객체란 js에서 데이터를 표현하는 방식 중 하나로 key,value 쌍으로 구성된다.
객체 리터럴 방식은 객체 생성 방식 중 가장 일반적이고 간단한 방법으로, 컨텐츠를 그대로 대입하는 방법을 말한다.)
여기서 객체 리터럴 할당에서도 피할수 있는 방법이 있다.
interface Person { name: string };
// 1. 타입 단언 ( as )
const person1 = { name: "alice", age: 26 } as Person;
// 2. 인덱스 시그니처 사용
interface Person { [index: string]: any };
const person2 = { name: "alice", age: 26 };
따라서 ts는 의도와 다르게 작성된 코드까지 찾기 위해 잉여 속성 체크를 하는데 타입시스템의 구조적 타이핑을 해치지 않기 위해서 타입에 객체 리터럴 을 할당할 때 잉여 속성 체크를 수행한다.
잉여속성 체크를 이용하면 객체 리터럴에 알 수 없는 속성을 허용하지 않으므로 위 문제점을 방지할 수 있다.
값 타입과 선언 타입에 공통된 속성이 있는지 확인하는 별도의 체크
약한 타입에서 실행되며 오타 체크에 유용하다.
(약한 타입이란 모든 속성이 optional인 타입을 의미한다.)
interface Person { name: string; age: number; }
// typescript utility 사용 -> 모든 속성을 optional로 만들어 줍니다.
type OptionalPerson = Partial<Person>;
// TS 경고 발생 ( 'Partial<Person>' 형식에 'Name'이(가) 없습니다. 'name'을(를) 쓰려고 했습니까? ) ( <-- 공통 속성 체크 )
const person: OptionalPerson = { Name: "alice" };
person의 경우 OptionalPerson타입 즉 약한 타입이기 때문에 공통 속성 체크가 실행된다.
✅이러한 약한 타입에 대해서는 ts는 값 타입과 선언 타입에 공통된 속성이 있는지 확인하는 별도의 체크를 수행한다.