Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TypeScript & Module Typing] 호프(김문희) 미션 제출합니다. #2

Merged
merged 47 commits into from
Sep 28, 2022

Conversation

moonheekim0118
Copy link

@moonheekim0118 moonheekim0118 commented Sep 27, 2022

🔥 팀 미션 적용 결과

본 미션을 수행하며 경험, 영감, 학습한 내용을 팀 미션에 전파해주세요.

  • 실제 코드 반영 후 팀 프로젝트에서 타입 테스트 진행
  • 자주 사용되는 타입을 유틸로 출시하여 분리

분리/유틸 출시만 진행하고 팀프로젝트 코드에 적용은 아직 하지 않은 상태입니다.

  • 기존에는 리액트 컴포넌트에서 children 이 담긴 props 의 타입을 정의 할 때, 리액트 자체에서 제공해주는 propsWithChildren 을 사용했습니다. 하지만 해당 타입의 경우 children 인자가 옵셔널인 점이, 불안하다고 느꼈습니다. 반드시 children 을 받아서 연산을 해줘야 하는 경우도 있기 때문에요.
  • 또한, 몇몇 컴포넌트에서는 Function as Children 패턴을 사용하기 때문에, children 으로서 ReactNode 를 반환하는 함수를 받기도 했습니다. 이의 경우 매번 타이핑을 해줘야 하는게 번거로웠고, propsWithChildren을 사용하는 컴포넌트와 일관성이 없다고 느껴졌어요.

그래서 propsWithReactNodeChildren, propsWithFunctionChildren 그리고 FunctionChildren 이란 유틸 타입을 분리하여 npm 으로 출시하고 타입테스트를 진행하였습니다. : )

type FunctionChildren<T = any> = (...args: T[]) => ReactNode;

type PropsWithReactNodeChildren<P = unknown> = P & {
  children: ReactNode;
};

type PropsWithFunctionChildren<
  P = unknown,
  F extends FunctionChildren = (...args: any) => ReactNode
> = P & {
  children: F;
};

유틸 타입 레포지토리

배포 된 유틸 타입 npm 주소

✅ 요구사항 구현 확인

필히 테스트 & 타입이 포함

DOM 유틸

  • innerHTML()
  • show()
  • hidden()
  • addEvent()

유틸

  • fetch()
  • isNull()
  • isNil()
  • isNumber()
  • isFunction()
  • shuffle()
  • pick()
  • omit()
  • memoize()
  • debounce()
  • throttle()
  • clickOutside()

타입스크립트 자체 학습에 집중하고자 함수 구현의 경우 lodash 라이브러리를 아주아주 많~~이 참고했습니다 🙄

🧐 공유

생각

타입테스트의 효용성

  • 미션을 진행하면서 타입테스트를 작성 했을 때는 사실 별 효용성을 못느꼈습니다. 단순히 함수의 타입이 올바르게 반환되었는지 검사해주는데.. 함수 구현부에서 타입을 올바르지 않게 반환하거나, 올바르지 않은 타입을 매개변수로 넣으면 알아서 타입 에러가 뜨기 때문이었습니다. 단순히 구현된 함수의 타입에 대한 명세의 역할을 한다고만 느꼈어요.
  • 하지만, 유틸 타입을 출시하면서 타입테스트의 효용성을 느꼈습니다. 라이브러리 개발자가 구현된 유틸 타입을 수정 하며 의도치 않게 타입이 변경 될 경우가 있을 텐데, 이 때 테스트가 타입 변경사항을 알려줘서 안전하다고 느꼈습니다 : )

유틸타입이란..?

  • 사실 잘 모르겠는데 util 타입의 정의를 아직도 잘 모르겠습니다 😭 저 같은 경우 해당 미션에서 복잡한 타입이나, 중복되는 타입을 util 폴더에 분리하였는데, 이게 util 타입을 분리하는 올바른 기준인지 아직 잘 모르겠습니다.

학습한 것

1. 타입 검사 함수의 반환 타입

  export function isNull(value: any): boolean {
    return value === null;
  }

2. 함수 매개변수 타입

몰랐는데 Parameters<T> <- 이걸로 함수 매개변수 타입을 가져올 수 있더라구요!!
심지어 Parameters<T>[0] <- 이렇게하면 index 번째의 매개변수 타입만 가져올 수도 있었습니다.

3. d.ts

그러면 왜 굳이 타입스크립트로 애초에 만들지 않고 .d.ts 를 선언하여 사용할까? 하는 의문이 있었습니다.

라이브러리 개발자입장에서...라이브러리를 배포할 때 두가지 선택지가 있더라구요.

  1. 소스 타입스크립트 파일 (타스 사용자용) / 소스 자바스크립트 파일 (자스 사용자용) 을 둘 다 업로드 한다.
  2. 컴파일된 자바스크립트 파일 / 타입스크립트 사용자가 사용 할 수 있는 타입 선언을 포함하여 업로드.
    -> 두번째 선택이 파일 크기를 줄일 수 있으며, 무엇을 임포트 해야 하는지가 더 명확해집니다.

또한, 응용 프로그램을 컴파일 할 때 마다 해당 라이브러리를 다시 컴파일하지 않아도 되므로 TSC의 컴파일 시간을 줄일 수 도 있습니다.

따라서 d.ts 는 아래와 같은 역할을 가집니다.

  • 다른 사용자가 타입스크립트 응용 프로그램에서, 라이브러리로 배포된 컴파일한 타입스크립트를 사용한다면, 사용자의 tsc 인스턴스는 라이브러리의 타입스크립트로부터 생성된 자바스크립트 파일에 대응하는 .d.ts 를 검색합니다. 그 결과 타입스크립트가 라이브러리 프로젝트에 사용된 타입을 알 수 있게 됩니다.
  • 타입스크립트를 지원하는 ide 는 이 d.ts 파일을 읽어 해석한 다음 여러분이 코드를 작성할 때, 유용한 타입 힌트를 제공합니다.
  • 타입스크립트 코드의 불필요한 재컴파일을 막아주어 컴파일 시간을 크게 줄여줍니다.
  • 즉 타입선언은 타입스크립트에게 "자바스크립트에는 이런저런 정보가 정의되어 있어"라고 알려주는 수단입니다.

우리 팀은 타입스크립트를 어떻게 사용하는가

✅ 타입스크립트 컴파일 / 빌드 전략

  • 로컬환경과 배포환경에서 모두 ts loader 사용하여 타입스크립트를 컴파일하고 있습니다.
  • 현재 프로젝트 규모에서는 babel loader와 ts loader의 빌드 속도 차이가 크지 않기 때문에 로컬과 배포환경 모두 ts loader를 사용하고 있습니다. 하지만, 추후 프로젝트 규모가 커지고 빌드속도가 많이 느려저 로컬 환경에서 개발 속도가 지연된다고 느낄 시, 빌드 속도가 보다 빠른 babel loader 혹은 es build loader 사용하고, 배포환경에서는 최종 배포 전의 build 이기 때문에 타입 에러를 추가로 체크할 수 있는 ts loader를 사용해서 보다 안전하게 컴파일 된 정적 파일을 제공 할 수 있도록 할 것 같습니다.

✅ 스타일가이드 및 컨벤션

Type vs Interface

  • 객체 타입을 정의 할 때는 type 대신 interface를 씁니다. interface 는 객체만 사용 할 수 있고, props 는 항상 객체로 받기 때문에 type 보다는 interface 를 사용하는게 더 명시적이라고 생각했습니다. interface 로 타입을 선언해준 것 자체가 해당 props 의 타입이 ‘무조건 객체'임을 명시해주기 때문입니다.

일반 함수 vs 화살표 함수

  • 컴포넌트, hook에는 일반함수를 사용해 주고 있습니다. 일반함수는 type 재사용이 불가능한 것과, 함수 재선언이 가능하다는 단점을 가지고 있는데, 컴포넌트나 훅에서는 보편적으로 type 재사용 할 일이 없고 컴포넌트/훅 함수의 하나의 파일 내에서 중복된 함수명을 사용 할 일이 없기 때문에 함수 선언문을 사용해줘도 된다고 생각했습니다.
  • 그 외의 함수는 화살표 함수를 사용해 코드를 일관성 있게 작성해 읽기 좋은 코드를 만들고, 컴포넌트와 일반 함수를 명확하게 구분합니다. 또한 type 재사용이 가능하고, 중복된 함수명을 사용할 경우 (오버라이딩 불가) 에러가 발생하기 때문에 휴먼에러를 방지 할 수 있기 때문입니다.

- gitignore 파일을 추가한다.
- 의존성을 설치한다.
- jest-environment-json 설치
- ts-jest 설치
- jest.config.js 파일 추가
- fetch, clickOutside 제외하고 타이핑 완료
- debounce, throttle 구현 미완성
- isNull, isNill, isNumber, isFunction, shuffle, omit, pick 에 대한 타입테스트 추가
- 직접 정의한 타입을 util 타입으로 분리하여 관리한다.
@moonheekim0118 moonheekim0118 self-assigned this Sep 27, 2022
Copy link

@kkojae91 kkojae91 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

호프 작성해주신 타입들 보며 학습 많이 했습니다! 특히 mapped type 맛있었습니다.

역시 몬스터 디벨호프🍺

궁금한 사항들 코맨트 남겨뒀어요 확인하고 답변주세요~!

Comment on lines +21 to +36
export type FetchOptions = {
method?: HTTPMethod;
headers?: SoundObject;
body?: string;
credentials?: string;
};

export type FetchResponse<T> = {
status: number;
statusText: string;
ok: boolean;
headers: Headers;
url: string;
data: T;
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fetch type을 직접 구현해주셨는데 이유가 있을까요?
RequestInfo, RequestInit, Response라는 타입이 존재하는데 따로 구현하신 이유가 궁금해요

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는.. 그런타입이 존재하는지 몰랐어요 😧.....알려주셔서 감사합니다..!
그래도 fetch 함수도 직접 구현해주었으니까, 그에 맞는 type 들도 직접 구현하는게 더 좋을거라고는 생각합니다ㅎㅎ

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제공해주는 타입과 비교해보니 옵션 값들이 빠진게 있더라구요 한 번 체크해보시는 것도 좋을 것 같아요~!

Comment on lines +77 to +80
export function isNull(value: any): value is null {
return value === null;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

학습한 내용에 글을 읽어본 후 typescript playground에 호프가 작성해주신 코드를 작성해 테스트를 해보았는데요
is를 통해 타입을 정제해줬다고 하셨는데 어떤 부분을 정제해주셨다는 건지 이해가 되지 않아서요.
return type을 value is null로 해준 것과 boolean으로 해준 것에 똑같은 에러가 발생하던데 어떤 부분 때문에 타입이 정제되었다고 생각하시는지 다시 한 번 설명해주실 수 있을까요?

Copy link
Author

@moonheekim0118 moonheekim0118 Sep 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제가 잘못된 예시를 넣어놓았네요 ㅠㅠ
예시 코드 링크 드립니다!
https://www.typescriptlang.org/play?#code/FAMwrgdgxgLglgewgAjgZwMowE5wgcwAoA3AQwBswBTALmUgGsIEB3CASjoCMEFyrSKAN7BkybFRhhsKGAE8ADlQQhkZSlWQBeHcgBEaHHnx6A3MAC+wUJFiIU6LLgJO4sEhWp1GzNpzWemujIhs74yCJiElIyyPJKKgEa2roGRgRmltbg0PBIyFAAFlRQDACSEApgMIR4VTB0ocbIAD7IEGAAtlxU2OwRoqiqtZjpRHXV7P2RYgVIaHxUAHTkCOOV1UswCACqCkrYAMKkaFSEU+ZiVoNicMOOY67uEzBTA7NzEAv8K2u1GzAtrt9r1jqdzuxLsgrBYgA

function isString(value: unknown): boolean {
  return typeof value === "string";
}

function isStringStrict(value: unknown): value is string {
  return typeof value === "string";
}

function checkInput(input: string | number) {
  if (isString(input)) { // 그냥 boolean 일뿐, 타입 가드 역할을 해주지는 않음 
    console.log(input.toUpperCase()); // 에러 
  }

    if (isStringStrict(input)) {
    console.log(input.toUpperCase()); // 에러 안남 
  }
}

제가 학습한 바로는

  • 타입 정제(typeof ~ 분기문) 는 강력하지만 현재 영역(scope, 유효범위)에 속한 변수만을 처리할 수 있다고 합니다.
  • 한 영역에서 다른 영역으로 이동하면 기존의 정제 결과물은 유효하지 않다구 해요.
  • 따라서 타입 체커에 isString이 boolean을 반환할 뿐만 아니라 boolean이 true면 isString에 전달한 인수가 string임을 알려야 합니다. 이게 타입가드라구 해요..!

잘못된 예시임을 알려주셔서 감사합니닿...!! 다시한번 공부했네요!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 보내주신 링크타고 가서 이것 저것 많이 실험해봤습니다!
필요시에 사용자 정의 타입가드를 사용해 타입 가드를 해주면 좋을 것 같아요!


export type Nill = null | undefined;

export type SoundFunction<T = any> = (...args: any) => T;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

다른 type에도 Sound라는 prefix가 붙어있는데 Sound prefix를 붙이신 이유가 있을까요?

Copy link
Author

@moonheekim0118 moonheekim0118 Sep 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function , Object 는 타입이 너무 넓어서 더 좁혀주고자 해당 타입을 만들었는데요!
Sound 라는 프리픽스는 '건전한' 이라는 의미를 주고자해서 한번 적용해보았습니다 :)
더 좋은 네이밍이 있다면 추천받슴미다ㅎㅎ

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sound에 건전한이라는 뜻을 가지고 있군요 역시.. 990이시네요. 👍🏼

src/util.ts Outdated

export type SoundFunction<T = any> = (...args: any) => T;

export type SoundObject<T = any> = Record<string, T>;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Object key값으로는 string, number, symbol이 들어갈 수 있는데 string으로 설명해주신 이유는 무엇인가요?

Copy link
Author

@moonheekim0118 moonheekim0118 Sep 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

일단 자바스크립트 동작 방식 상, Object 의 key 값으로 들어갈 수 있는건 string, number, symbol 이 맞지만,
자바스크립트 엔진이 Object 의 key 를 저장하는건 결국 string 이라구 알고있어요!

const sym1 = Symbol('1');
const Obj = {
  NaN: 1,
  1: 0,
  sym1:2,
}

console.log(Object.keys(Obj));
// [ '1', 'NaN', 'sym1' ]

그래서 key 의 타입을 string 으로 잡아주긴 했습니다ㅎㅎ
그런데 꼬재 말들어보니 key 의 input 은 어쨋거나 string, number, symbol 다 들어올 수 있으니 더 확장하는게 좋을 것 같아요! 수정하겠습니다 ㅎㅎ
짚어주셔서 감사해요 !

type SoundObject<
  T = any,
  K extends string | number | symbol = string
> = Record<K, T>;

요롷게 수정해보았슴미당 ! 3a35b8b

[P in keyof Omit<T, K[number]>]: T[P];
};

export type DebounceThrottleOptions = Record<"leading" | "trailing", boolean>;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leading, trailing으로 타입을 좁혀주는 것 좋네요 👍🏼

src/util.ts Outdated
Comment on lines 37 to 40
export type GetArgumentsTypeByIndex<
T extends SoundFunction,
K extends number
> = Parameters<T>[K];

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

학습하신 내용에 작성되어 있듯 해당하는 인덱스의 타입도 가져오는게 있군요! 👍🏼
배워갑니다.

근데, 해당하는 타입은 사용처가 없는데 작성하신 이유는 어떤게 있을까요? (혹시 제가 찾지 못한것일까요..? 🥲)

Copy link
Author

@moonheekim0118 moonheekim0118 Sep 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맞아요..원래는 사용처가 있었는데요..없어졌어요.. 🥲..
근데 혼자서 처음 완성해본 나름 쓸만한 유틸타입이어서 아까워서 못지웠어요ㅋㅋㅋㅋㅋㅋㅋㅠㅠ
지우겠습니다 따흑

dc99edc

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아까운 유틸 타입은 저장을...!! 👍🏼

Comment on lines +9 to +11
export type PickResult<T extends SoundObject, K extends (keyof T)[]> = {
[P in K[number]]: T[P];
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맵드 타입... 배워가요! 역시 몬스터호프 👍🏼
이따 설명 한 번 더 들으러갈게요~!

Comment on lines +13 to +15
export type OmitResult<T extends SoundObject, K extends (keyof T)[]> = {
[P in keyof Omit<T, K[number]>]: T[P];
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

구우웃!! 👍🏼👍🏼👍🏼 몬스터디벨호프 👍🏼

@kkojae91 kkojae91 merged commit bb8fdb0 into woowacourse:moonheekim0118 Sep 28, 2022
@kkojae91
Copy link

재미있게 리뷰하고 갑니다 호프~! 👍🏼

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants