[스터디] Beginner's TypeScript 풀이하기(1)

정(JJeong)·2023년 5월 1일
0

스터디 기록

목록 보기
4/5
post-thumbnail

지난주부터 totaltypescript라는 사이트에서 제공하는 Beginner's TypeScript tutorial을 풀이하기로 했었다.

공교롭게 지난주에는 불참하게되어서 지난주 몫(?)까지 포함해서 이번주에 예정인 부분까지 한번 풀어보고 쭉 적어보자.

매우 분량이 짧고 어렵지 않으니 다른 분들에게도 추천하는 바입니다.


지난주 몫(?)

1. The Implicit 'Any' Type Error

문제

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메소드를 통한 수의 비교에서 통과될 수 없다.
    • why?) string의 합이 number가 아니니까.

2) Working with Object Params

문제

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를 이용해서 정할 수도 있다.
  1. 직접 지정
export const addTwoNumbers = (params: {first: number, second: number}) => {
 return params.first + params.second;
};
  1. type alias
type firstAndSecond = {
 first: number;
 second: number;
}

export const addTwoNumbers = (params: firstAndSecond) => {
 return params.first + params.second;
};

3) Set Properties as Optional

문제

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");
});
  • 객체의 속성을 optional하게 만들어보자.

풀이

type Params = {
  first: string;
  last?: string;
  // `?`키워드 추가
};

export const getName = (params: Params) => {
  if (params.last) {
    return `${params.first} ${params.last}`;
  }
  return params.first;
};
  • ?키워드를 사용해서 원하는 객체의 속성을 optional하게 바꿀 수 있다.


이번주 스터디

4) Optional Parameters

문제

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하게 바꿀 수 있다.

5) Assigning Types to Variables

문제

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);
});
  • 지정해둔 type형태에 맞춰 객체의 속성을 작성해보자.

풀이

const defaultUser: User = {
  id: 1,
  firstName: 'Lisa',
  lastName: 'Simpson',
  isAdmin: true
};
  • defaultUser의 속성값을 우리가 원하는 User interface에 맞춰 작성해주었다.

이때 보다 개발자의 작성 편의와 안정성을 높이기 위해선 defaultUser의 타입도 User로 지정해주는 것이 좋다.

-> 이렇게 하면 defaultUser에 User와 맞지 않는 속성이 들어오는 것을 막을 수 있을 뿐 아니라 IDE에 의해 자동완성 기능을 제공받을 수 있다.


6) Constraining Value Types

문제

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";	// 유니온 리터럴 타입
}
  • 유니온 타입을 이용해서 세가지 리터럴 타입만 되도록 설정

7) Working with Arrays

문제

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[];	// `[]`추가
}
  • type뒤에 []를 추가하므로써 배열임을 명시해줌

또는 제네릭을 이용할 수 있다.

interface User {
 id: number;
 firstName: string;
 lastName: string;
 role: "admin" | "user" | "super-admin";
 posts: Array<Post>;	// 제네릭 사용
}

8) Function Return Type Annotation

문제

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을 지정해 줄 수 있다.
  • 리턴 타입을 맞추기 위해 리턴값 안의 객체 속성은 임의 작성

9) Typing Promises and Async Requests

문제

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)을 끊고, 다음 포스팅에 이어적도록 하자.

profile
프론트엔드 개발자를 꿈꾸며 기록하는 블로그 💻

0개의 댓글