지난주부터 totaltypescript라는 사이트에서 제공하는 Beginner's TypeScript tutorial을 풀이하기로 했었다.
공교롭게 지난주에는 불참하게되어서 지난주 몫(?)까지 포함해서 이번주에 예정인 부분까지 한번 풀어보고 쭉 적어보자.
매우 분량이 짧고 어렵지 않으니 다른 분들에게도 추천하는 바입니다.
import { expect, it } from 'vitest';
export const addTwoNumbers = (a, b) => {
// ~ ~
return a + b;
};
it('Should add the two numbers together', () => {
expect(addTwoNumbers(2, 4)).toEqual(6);
expect(addTwoNumbers(10, 10)).toEqual(20);
});
addTwoNumbers
의 타입에러를 해결할 것toEqual
메소드를 통과시키도록 할 것export const addTwoNumbers = (a: number, b: number) => {
return a + b;
};
string
으로 바꿔도 +
에 대한 연산은 통과시킬 수 있지만 toEqual
메소드를 통한 수의 비교에서 통과될 수 없다.import { expect, it } from 'vitest';
export const addTwoNumbers = (params) => {
return params.first + params.second;
};
it('Should add the two numbers together', () => {
expect(
addTwoNumbers({
first: 2,
second: 4,
})
).toEqual(6);
expect(
addTwoNumbers({
first: 10,
second: 20,
})
).toEqual(30);
});
객체
로써 parameter로 보내고자 할 경우를 해결해보자interface firstAndSecond {
first: number;
second: number;
}
export const addTwoNumbers = (params: firstAndSecond) => {
return params.first + params.second;
};
interface
를 활용하여 객체의 속성에 대한 타입지정
- 직접 파라미터에 적거나,
type alias
를 이용해서 정할 수도 있다.
- 직접 지정
export const addTwoNumbers = (params: {first: number, second: number}) => { return params.first + params.second; };
- type alias
type firstAndSecond = { first: number; second: number; } export const addTwoNumbers = (params: firstAndSecond) => { return params.first + params.second; };
import { expect, it } from "vitest";
type Params = {
first: string;
last?: string;
};
export const getName = (params: Params) => {
if (params.last) {
return `${params.first} ${params.last}`;
}
return params.first;
};
it("Should work with just the first name", () => {
const name = getName({
first: "Matt",
// ~~~~~~~~~~~~~~ Error
});
expect(name).toEqual("Matt");
});
it("Should work with the first and last name", () => {
const name = getName({
first: "Matt",
last: "Pocock",
});
expect(name).toEqual("Matt Pocock");
});
type Params = {
first: string;
last?: string;
// `?`키워드 추가
};
export const getName = (params: Params) => {
if (params.last) {
return `${params.first} ${params.last}`;
}
return params.first;
};
?
키워드를 사용해서 원하는 객체의 속성을 optional하게 바꿀 수 있다.import { expect, it } from "vitest";
export const getName = (first: string, last: string) => {
if (last) {
return `${first} ${last}`;
}
return first;
};
it("Should work with just the first name", () => {
const name = getName("Matt");
// ~~~~~~~~~~~~~~~~
expect(name).toEqual("Matt");
});
it("Should work with the first and last name", () => {
const name = getName("Matt", "Pocock");
expect(name).toEqual("Matt Pocock");
});
last
매개변수는 optional하게 받을 수 있도록 하자.export const getName = (first: string, last?: string) => {
// `?`키워드 추가
if (last) {
return `${first} ${last}`;
}
return first;
};
?
키워드를 사용해서 원하는 매개변수를 optional하게 바꿀 수 있다.import { expect, it } from "vitest";
interface User {
id: number;
firstName: string;
lastName: string;
isAdmin: boolean;
}
/**
* How do we ensure that defaultUser is of type User
* at THIS LINE - not further down in the code?
*/
const defaultUser = {};
const getUserId = (user: User) => {
return user.id;
};
it("Should get the user id", () => {
expect(getUserId(defaultUser)).toEqual(1);
});
const defaultUser: User = {
id: 1,
firstName: 'Lisa',
lastName: 'Simpson',
isAdmin: true
};
defaultUser
의 속성값을 우리가 원하는 User
interface에 맞춰 작성해주었다.이때 보다 개발자의 작성 편의와 안정성을 높이기 위해선 defaultUser의 타입도 User로 지정해주는 것이 좋다.
-> 이렇게 하면 defaultUser에 User와 맞지 않는 속성이 들어오는 것을 막을 수 있을 뿐 아니라 IDE에 의해 자동완성 기능을 제공받을 수 있다.
interface User {
id: number;
firstName: string;
lastName: string;
/**
* How do we ensure that role is only one of:
* - 'admin'
* - 'user'
* - 'super-admin'
*/
role: string;
}
export const defaultUser: User = {
id: 1,
firstName: "Matt",
lastName: "Pocock",
// @ts-expect-error
role: "I_SHOULD_NOT_BE_ALLOWED",
};
role
속성을 단순 string이 아니라 'admin', 'user', 'super-admin'중 하나만 될 수 있도록 설정하자.// @ts-expect-error의 역할을 다음 줄의 type체크 역할을 한다.
interface User {
id: number;
firstName: string;
lastName: string;
role: "adimn" | "user" | "super-admin"; // 유니온 리터럴 타입
}
interface User {
id: number;
firstName: string;
lastName: string;
role: "admin" | "user" | "super-admin";
posts: Post;
}
interface Post {
id: number;
title: string;
}
export const defaultUser: User = {
id: 1,
firstName: "Matt",
lastName: "Pocock",
role: "admin",
posts: [
//~~~~~
{
id: 1,
title: "How I eat so much cheese",
},
{
id: 2,
title: "Why I don't eat more vegetables",
},
],
};
posts
속성을 배열로 만들고자 한다. 이를 해결하자.interface User {
id: number;
firstName: string;
lastName: string;
role: "admin" | "user" | "super-admin";
posts: Post[]; // `[]`추가
}
[]
를 추가하므로써 배열임을 명시해줌또는
제네릭
을 이용할 수 있다.interface User { id: number; firstName: string; lastName: string; role: "admin" | "user" | "super-admin"; posts: Array<Post>; // 제네릭 사용 }
import { expect, it } from "vitest";
interface User {
id: number;
firstName: string;
lastName: string;
role: "admin" | "user" | "super-admin";
posts: Array<Post>;
}
interface Post {
id: number;
title: string;
}
/**
* How do we ensure that makeUser ALWAYS
* returns a user?
*/
const makeUser = () => {
return {};
};
it("Should return a valid user", () => {
const user = makeUser();
expect(user.id).toBeTypeOf("number");
//~~
expect(user.firstName).toBeTypeOf("string");
//~~~~~~~~~
expect(user.lastName).toBeTypeOf("string");
//~~~~~~~~
expect(user.role).to.be.oneOf(["super-admin", "admin", "user"]);
//~~~~
expect(user.posts[0].id).toBeTypeOf("number");
//~~~~~
expect(user.posts[0].title).toBeTypeOf("string");
//~~~~~
});
makeUser
함수는 빈 객체를 반환하기 때문에 아래 User
타입으로 지정된 매개변수를 사용하는 로직에 에러를 발생시키고 있다.const makeUser = (): User => {
return {
id: 1,
firstName: 'first',
lastName: 'last',
role: 'admin',
posts: [
{
id: 1,
title: 'title'
}
]
};
};
()
뒤에 annotation을 작성하여 return값의 type을 지정해 줄 수 있다.interface LukeSkywalker {
name: string;
height: string;
mass: string;
hair_color: string;
skin_color: string;
eye_color: string;
birth_year: string;
gender: string;
}
export const fetchLukeSkywalker = async (): LukeSkywalker => {
// ~~~~~~~~~~~~~
const data = await fetch("https://swapi.dev/api/people/1").then((res) => {
return res.json();
});
return data;
};
async
함수에서 표시되는 에러를 해결해보자.export const fetchLukeSkywalker = async (): Promise<LukeSkywalker> => {
const data = await fetch("https://swapi.dev/api/people/1").then((res) => {
return res.json();
});
return data;
};
async
함수는 항상 Promise
를 반환한다. 그러니 반환 값을 Promise객체로 지정해주고, 제네릭을 통해 Promise객체의 상세 타입이 LukeSkywalker
가 되도록 설정하였다.혹은 async로 받아오는 값을 담는 변수 혹은 리턴하는 변의 타입을 지정해줘도 된다.
1) 담아 주는 변수 지정
export const fetchLukeSkywalker = async () => { const data: LukeSkywalker = await fetch("https://swapi.dev/api/people/1").then((res) => { // data의 타입을 지정해줌 return res.json(); }); return data; };
2) 리턴 하는 변수 지정:
as
키워드 사용export const fetchLukeSkywalker = async () => { const data = await fetch("https://swapi.dev/api/people/1").then((res) => { // data의 현태 타입은 any return res.json(); }); return data as LukeSkywalker; // 리턴 단계에서 구체화 };
오호 에러가 발생해 있는 예제를 해결하면서 공부하니까 이전에 공부했던 것들이 새록새록 떠올라서 좋고, 가물하던 것들은 다시 복기해볼 수 있어서 좋다.
총 18번까지 tutorial이 있는데 문제가 많으므로 절반으로 chapter(1)을 끊고, 다음 포스팅에 이어적도록 하자.