role: "user"
일때, expireTime: 600000
이고, role: "admin"
일때는 expireTime
이 없다고 했을때 Type을 아래와 같이 설정 할 수 있다.type User = {
role: "user" | "admin";
expireTime?: number;
}
이 경우, role이 user 일때 expireTime이 있는지, role이 admin일때 expireTime이 있는지 알 방법이 없다.
이를 명확하게 하기 위해, role의 값에 따라, expireTime 을 명시 해주는 타입을 각각 만들어주는 타입을 만들어줘야한다
type User = {
role: "user";
expireTime: number;
}
type Admin = {
role: "admin";
}
type Character = User | Admin;
태그된 유니온 기법을 사용 하는 것인데, 이러면 Character 타입의 값들은 role
에 따른 유효한 타입
체크를 할 수 있다.
아래는 네트워크 요청에 따른 예시 이다.
interface RequestPending {
state: 'pending';
}
interface RequestError {
state: "error";
error: string;
}
interface RequestSuccess {
state: "success";
pageText: string;
}
type RequestState = RequestPending | RequestError | RequestSuccess
interface State {
currentPage: string;
requests: { [page: string]: RequestState };
}
// 태그된 유니온 기법 (네트워크 요청 과정 각각의 상태를 명시적으로 모델링)
const render = (state: State) => {
const { currentPage } = state;
const requestState = state.requests[currentPage];
// state: "pending" | "error" | "success" 으로 자동 추론
if (requestState.state === 'pending') {
// const requestState: RequestPending
return `Loaindg... ${currentPage}`
}
if (requestState.state === 'error') {
// const requestState: RequestError
return `Error in ${currentPage}, ${requestState.error}`
}
if (requestState.state === 'success') {
// const requestState: RequestSuccess
return `<h1>${currentPage}</h1> ${requestState.pageText}`
}
return `Unknown Erorr`
}
const changePage = async (state: State, page: string) => {
state.requests[page] = { state: "pending" };
state.currentPage = page;
try {
const res = await fetch(page);
if (!res.ok) {
throw new Error("Error in " + state.currentPage + ":" +res.statusText);
}
const pageText = await res.text();
state.requests[page] = { state: "success", pageText };
} catch (err) {
state.requests[page] = { state: "error", error: `${err}` };
}
}
라이브러리를 만들 때, 사용자에게 다양한 매개변수를 사용 할 수 있도록 Optional
로 설정 하거나, 다양한 타입을 union
타입으로 지정 해주게 되는 경우가 많다.
하지만, 반환 타입이 여러개가 된다면 사용자에 입장
에서, 타입체커의 입장
에서 함수의 반환 타입을 확실하게 하지 못해 제대로 사용 할수 없다.
이를 위해 반환 할때는 명확하게 반환 할 수 있도록 설계를 해야 한다.
/** 사용하기는 쉽게, 생성할 때는 어렵게
* - 매개변수의 타입은 넓게 들어오도록
* - 반환 타입은 되도록 하나의 타입을 반환 하도록 설계하자
*/
type LngLat =
{
lng: number;
lat: number;
} |
{
lon: number;
lat: number;
} |
[number, number]
type CameraOptions = {
center?: LngLat;
zoom?: number;
bearing?: number;
pitch?: number;
}
type LngLatBounds =
{
northeast: LngLat;
southwest: LngLat;
} |
[LngLat, LngLat] |
[number, number, number, number]
declare function setCamera(camera: CameraOptions): void;
declare function viewportBounds(bounds: LngLatBounds): CameraOptions;
function focustOnFeature() {
const bounds: [LngLat, LngLat] = [{ lng: 3, lat: 4 }, [4, 5]];
// const zoom: number | undefined
// (property) center?: LngLat | undefined
// Property 'lat' does not exist on type 'LngLat | undefined'.(2339)
// Property 'lng' does not exist on type 'LngLat | undefined'.(2339)
const { center: { lat, lng }, zoom } = viewportBounds(bounds);
}
- 반환 타입을 좁히지 못해서 나오는 오류
- 반환 타입을 구체적으로 작성 해주자
함수의 사용자가 사용하기 편하게 다양한 타입의 변수를 매개변수로 사용 할 수 있도록 하였다.
하지만, viewportBounds
의 반환 값 또한 optional
로, 타입이 구체적이지 못하다.
이에 따라, 타입체커가 반환 값에 값이 실제로 있는지, undefined 인지 체크를 할 수 없기 때문에 에러가 발생 한다.
/** 사용하기는 쉽게, 생성할 때는 어렵게
* - 매개변수의 타입은 넓게 들어오도록
* - 반환 타입은 되도록 하나의 타입을 반환 하도록 설계하자
*/
type LngLat = { lng: number; lat: number; }
type LngLatLike = LngLat |
{
lon: number;
lat: number;
} |
[number, number]
type Camera = {
center: LngLat;
zoom: number;
bearing: number;
pitch: number;
}
type CameraOptions = Omit<Partial<Camera>, 'center'> & { center?: LngLatLike }
type LngLatBounds =
{
northeast: LngLatLike;
southwest: LngLatLike;
} |
[LngLatLike, LngLatLike] |
[number, number, number, number]
declare function setCamera(camera: CameraOptions): void;
declare function viewportBounds(bounds: LngLatBounds): Camera;
function focustOnFeature() {
const bounds: LngLatBounds = [{ lng: 3, lat: 4 }, [4, 5]];
// (property) center: LngLat
// const lat: number
// const lng: number
// const zoom: number
const { center: { lat, lng }, zoom } = viewportBounds(bounds);
}
위의 예시 처럼 매개 변수나, 사용하는 변수 들의 타입은 LngLatLike
, CameraOptions
등의 타입으로 넓게 정해주고
반환 값은 Camrea
, LngLat
으로 구체적으로 좁혀주었다.
이렇게 함으로써, 반환 값을 사용할때 어떠한 오류가 발생하지 않는 것을 파악 할 수 있다.
(object, array 등)
의 속성 값이 null
이 될 수 있으면/**
* 타입 주변에 null 값 배치하기
* - 값이 전부 null 이 되도록 설계 하거나
* - 값이 전부 null 아 이난 경우로 설계 해라
* - 변수가 null이 되는 것을 사람이든 타입체커든 확인하기 어렵기 때문에
*/
function getMinMax(numbers: number[]) {
// 값을 갖고 있던지, null 을 갖고 있던지 확실하게 해야함
let result: [number, number] | null = null
for (const num of numbers) {
if (result === null) {
result = [num, num]
} else {
result = [Math.min(num, result[0]), Math.max(num, result[1])]
}
}
return result
}
/**
* API 를 작성할 때에는, 반환 타입을 큰 객체로 만들고 확실하게 해줘야한다
* - 반환 타입 전체가 null (O)
* - 반환 타입 전체가 null (X)
*/
// const getUser: () => Promise<{
// id: number;
// name: string;
// } | null>
const getUser = async () => {
const response = await fetch('/getUer');
if (!response.ok) {
return null
}
const data: {
id: number;
name: string;
} = await response.json()
return data
}
태그된 유니온 패턴
사용하자/**
* - 유니온의 인터페이스 보다는 인터페이스의 유니온을 사용하기
* - 여러개의 선택적 필드 (optional property) 가 동시에 값이 있거나 동시에 undefined 인 경우에는 `태그된 유니온 패턴` 사용하기
*/
interface A {
status: "loading" | "success" | "error"
title?: string
errorMessage?: string
}
interface Loading {
status: "loading";
}
interface Success {
status: "suceess";
title: string
}
interface Error {
status: "error";
errorMessage: string;
}
type B = Loading | Success | Error
// function findSomethingA(something: A): string | undefined
function findSomethingA(something: A) {
if (something.status === 'error') {
// something.errorMessage 의 값이 있는지 undefined 인지 모름
// (property) A.errorMessage?: string | undefined
return something.errorMessage
}
if (something.status === 'loading') {
return `loading...`
}
// something.title 의 값이 있는지 undefined 인지 모름
// (property) A.title?: string | undefined
return something.title
}
// 타입에 따른 조건부 처리가 쉬움
// function findSomethingB(something: B): string
function findSomethingB(something: B) {
if (something.status === 'error') {
// (parameter) something: Error
return something.errorMessage
}
if (something.status === 'loading') {
// (parameter) something: Loading
return `loading...`
}
// (parameter) something: Success
return something.title
}
/**
* - 유니온의 인터페이스 보다는 인터페이스의 유니온을 사용하기
* - 여러개의 선택적 필드 (optional property) 가 동시에 값이 있거나 동시에 undefined 인 경우에는 `태그된 유니온 패턴` 사용하기
*/
interface Person {
name: string;
// 둘다 동시에 있거나 동시에 없다
placeOfBirth?: string;
dateofBirth?: Date;
}
interface Person2 {
name: string;
birth?: {
place: string;
date: string;
}
}
// 이렇게 할 경우 birth 속성 하나만 체크 하면, birth.place 와 birth.date 의 유무를 타입 체크를 할 수 있다.