24/08/12 - 이든/ FE test + TS

TEST

테스트 용의성 : https://www.epicweb.dev/good-code-testable-code

단위 테스트란?

앱에서 테스트 가능한 가장 작은 소프트웨어를 실행해 예상대로 동작하는지 확인하는 테스트

그럼 FE에서 실행가능한 작은 SW는?

  • 단일 함수의 결과값
  • 단일 컴포넌트 상태, 행위

단위 테스트는 상호작용이 아닌 단일(각각의 행위)을 독립적으로 검사한다.

단위 테스트의 검증 요소

  1. 공통 컴포넌트(버튼, 텍스트 인풋, 캐러샐, … )

    ⇒ 공통 컴포넌트는 안전하게 테스트 모두 작성

  2. hook

FE UI컴포넌트들은 내부 prop나 state값을 검증하는게 아니라 렌더링 되는 DOM 구조가 올바르게 변경 되었는지를 확인해야 한다

(← 내부 구현에 종속되지 않기 위해서 + 최종 상태가 반영된 결과물은 사용자가 보는 DOM이기 때문이다)

정리

Untitled

테스트를 작성하는 패턴

AAA(Arrange-Act-Assert) 패턴

  1. Arrange : 테스트를 위한 환경을 만들고

    ex) 컴포넌트 렌더링 (await render)

  2. Act : 테스트를 할 동작을 발생시키고

    ex) 컴포넌트 클릭(), 키 입력 등의 액션

    Untitled

  3. Assert : 올바른 동작이 실행 되었는지   변경사항을 검사한다

    ex) 기대 결과가 맞는지 검증


용어정리

match : 기대 결과를 검증하기 위해 사용되는 일종의 API 집합

setup : 테스트를 실행하기 전 수행해야 하는 작업 (ex. beforeAll, beforeEach)

teardown: 테스트를 실행한 뒤 수행해야 하는 작업 (ex. afterAll, afterEach)

테스트 원칙

모든 테스트는 독립적으로 실행되어야 한다(테스트끼리 의존성x)

테스트 환경과 매처

vitest 기준으로 사용할 거임(jest와 호환가능함)

jsdom

node.js에는 DOM이 없어서 node환경에서도 브라우저를 구동하는 것처럼 DOM을 검사하기 위해서 JS로 작성된 jsdom을 통해 검증하게 도와주는 모듈

문법

toHaveClass : 해당 DOM에 해당 class가 있는지 여부 검사 toBeInTheDocument : 해당 DOM이 존재하는지 여부 검사

정리

Untitled


tip)

  1. *screen*.debug(); 를 통해 DOM 구조를 확인 할 수 있음
  2. describe가 스코프의 기준이 된다. → 원하는 테스트끼리 그룹핑 할 수 있다.
  3. 원하는 매처 외에 필요한게 있으면 만들어서 쓰면된다.

setup과 teardown

테스트의 원칙

모든 테스트는 독립적으로 실행되어야 한다

이를 위해 setup과 teardown을 사용할 수 있다.

  • 공통으로 처리해야하는 부분 정의(효율성 향상)

setup

테스트를 실행하기 전 수행해야 하는 작업 (테스트하기 위한 사전작업)

ex) beforeAll, beforeEach

→ 주로 스코프 내에서 전역으로 공유할 환경이나 상태 설정할 때 사용

teardown

테스트를 실행한 뒤 수행해야 하는 작업 (다른 곳에 영향 안 미치기 초기화작업)

ex) afterAll, afterEach

→ 테스트에 의해 생성된 상태를 초기화하는 경우에 사용

같은 스코프내에 실행 순서 : beforeAll → beforeEach → test실행 → afterEach → afterAll

test파일단위가 아닌 전역으로사용하고 싶다면? setup.js파일을 작성해서 전역으로 사용할 수 있음

Untitled

React Testing Libaray와 컴포넌트 테스트

이벤트 핸들러를 사용해서 테스트를 작성하는게 좋다

Testing Library의 핵심철학

UI 컴포넌트를 사용자가 사용하는 방식으로 테스트하자

⇒ DOM노드를 조회하고 이벤트를 발생시켜 테스트 한다

← 내부구현여부와 상관없음

← 올바른 테스트를 위해서는 이벤트 인터페이스 기반으로 검증해야한다

// render.jsx

import { render } from "@testing-library/react";
import userEvent from "@testing-library/user-event";

export default async (component) => {
  const user = userEvent.setup(); // 클릭, 키보드 이벤트 등 유저동작과 유사하게 시뮬레이션 할 수 있는 라이브러리임

  return {
    user,
    ...render(component),
  };
};

Testing Liabary의 요소 조회방법

getByPlaceholderText : placeholder를 기준으로 요소를 찾음

getByText : text를 기준으로 요소를 찾음

getByRole : role을 기준으로 요소를 찾음

getByLabelText : label text를 기준으로 요소를 찾음

… 사용자 관점에서 요소를 찾을 수 있음 + 특정 못짓겠으면 직접 요소를 넣어서 찾을 수 있는 getByTestId도 사용할 수 있음

우선순위 : 실제 사용자의 상호작용 방식과 가장 유사한 쿼리가 우선순위가 높음

  1. getByRole, getByLabelText, getByPlaceholderText, getByText, getByDisplayValue
  2. getByAltText, getByTitle
  3. getByTestId

출처 : https://testing-library.com/docs/queries/about

spy힘수 : 테스크 코드에서 특정 함수가 호출되었는지, 함수의 인자로 어떤 값이 넘어왔는지, 어떤 값을 반환하는지 등 다양한 값을 저장하고 있음

⇒ 보통 콜백함수나 이벤트 핸들러가 올바르게 호출되었는지 검증하고 싶을 때 spy함수를 사용한다

ex)

onChange 이벤트

it("텍스트를 입력하면 onChange prop으로 등록한 함수가 호출된다.", async () => {
  const spy = vi.fn(); // spy힘수 : 테스크 코드에서 특정 함수가 호출되었는지, 함수의 인자로 어떤 값이 넘어왔는지, 어떤 값을 반환하는지 등 다양한 값을 저장하고 있음
  // 보통 콜백함수나 이벤트 핸들러가 올바르게 호출되었는지 검증하고 싶을 때 spy함수를 사용한다
  const { user } = await render(<TextField onChange={spy} />);

  const textInput = screen.getByPlaceholderText("텍스트를 입력해 주세요.");

  await user.type(textInput, "test"); // type == keydown 이벤트

  expect(spy).toHaveBeenCalledWith("test"); // 내가 원하는 test라는 문자와 함께 spy함수가 호출되었는가 검증
});

onEnter 이벤트

it("텍스트를 입력하면 onEnter prop으로 등록한 함수가 호출된다.", async () => {
  const spy = vi.fn();

  const { user } = await render(<TextField onEnter={spy} />);

  const textInput = screen.getByPlaceholderText("텍스트를 입력해 주세요.");

  await user.type(textInput, "test{Enter}"); // type == keydown + Enter 이벤트

  expect(spy).toHaveBeenCalledWith("test"); // 내가 원하는 test라는 문자와 함께 spy함수가 호출되었는가 검증
});

onFocus 이벤트

포커스가 되는 여러방법들을 테스트 할 수 있음

it("포커스가 활성화 onEnter prop으로 등록한 함수가 호출된다.", async () => {
  const spy = vi.fn();

  const { user } = await render(<TextField onFocus={spy} />);

  const textInput = screen.getByPlaceholderText("텍스트를 입력해 주세요.");

  await user.click(textInput); // click 이벤트

  expect(spy).toHaveBeenCalled(); // spy함수가 호출되었는가 검증
});
  • 포커스, 마우스다운, 마우스업 등 이벤트 고려가능

스타일 검증

it("포커스가 활성화 border 스타일이 추가된다.", async () => {
  const spy = vi.fn();

  const { user } = await render(<TextField onFocus={spy} />);

  const textInput = screen.getByPlaceholderText("텍스트를 입력해 주세요.");

  await user.click(textInput); // click 이벤트

  expect(textInput).toHaveStyle({
    borderWidth: 2,
    borderColor: "rgb(25, 118, 210)",
  }); // CSS검증
});

정리

Untitled

단위 테스트 대상 선정하기

모든 컴포넌트에 대해 단위 테스트를 하는것은 작성 및 유지보수 비용만 늘어난다

→ 테스트 할 컴포넌트를 잘 선택해야한다

→ JSdom은 실제로 레이아웃이 페인팅 되는게 아니기 때문에 스타일이나 레이아웃이 제대로 나오는지는 확인할 수 없다

→ 이거는 스토리 북에서 확인해야한다

⇒ 즉, 별도의 상태변경이나 비즈니스 로직이 없는 UI를 검증하는 것은 별 의미가 없다

  • 비즈니스 로직이 있긴한데 복잡하지 않는 컴포넌트는 통합테스트로 한 큐에 끝내는게 이득이다

단위 테스트의 후보

  1. 상호작용이 없는 공통 컴포넌트
  2. React hook
  3. utils 함수

정리

Untitled

모듈 모킹

독립적인 컴포넌트 단위 테스트에서 의존성이 있는 경우 테스트 하는 방법 → 의존성을 모킹한다

규모있는 라이브러리는 대부분 단위 테스트로 내부 검증이 끝난 상태이다 (예를들면 react, next, react-router-dom, …) 따라서 모듈에 대한 검증이 아닌 모듈의 특정 기능을 제데로 호출하는지만 검증하면된다.

⇒ 이걸 가능하게 해주는게 모킹이다.

장점

외부 모듈과 의존성 제외하고 필요한 부분만 검증 가능

단점

남용하면 테스트의 신뢰성이 낮아짐

import 된 모듈을 가져오기 전에 vi.mock(’’, mockFn)를 하면 모킹한 함수를 사용할 수있다.

이때 모듈의 일부분만 모킹하고 싶다면

const navigateFn = vi.fn(); // spy 함수

vi.mock("react-router-dom", async () => {
  const original = await vi.importActual("react-router-dom");

  return { ...original, useNavigate: () => navigateFn };
});

it('"홈으로 가기" 링크를 클릭할경우 "/"경로로 navigate함수가 호출된다', async () => {
  const { user } = await render(<EmptyNotice />);

  await user.click(screen.getByText("홈으로 가기"));

  expect(navigateFn).toHaveBeenNthCalledWith(1, "/"); // 한번만 호출되는지 검증
});

이렇게 쓸 수 있다.

이렇게 모킹한 값으로 테스트가 끝나면 다른 테스트에 영향이 가지 않도록 초기화 해줘야 한다

afterEach(() => {
  server.resetHandlers();
  vi.clearAllMocks(); // 모킹된 모의 객체 호출에 대한 히스토리 초기화 (히스토리를 없애는 것이지 모킹된 객체를 없애는게 아님)
});

afterAll(() => {
  vi.resetAllMocks(); // 모든 모킹 모듈에 대한 구현을 초기화함(이제 필요없으니깐)
  server.close();
});

Untitled

리액트 훅 테스트 (feat. act 함수)

react hook을 사용해서 비즈니스 로직을 분리하고 컴포넌트와의 결합도를 낮출 수 있다 ⇒ 독립적으로 단위 테스트를 검증 가능하다

useState, useEffect같은 훅은 원래 react안에서만 사용할 수 있다

→ 하지만, testing-libaray/reactrenderHook사용해서 훅을 검증할 수 있도록 API를 제공해준다.

const { result, rerender } = renderHook(() => customHook());
// result : 훅을 호출해서 얻은 결과값으로 result.current값에 최신 상태가 담겨있다.
// rerender : 훅에 특정 인자를 호출했을 때 새로운 상태가 변경되는지 확인하고 싶을 때 사용할 수 있다.

act()

act함수는 상호작용(렌더링, 이펙트 등,,,)을 함께 그룹화하고 실행해 렌더링과 업데이트가 실제 앱이 동작하는 것과 유사한 방식으로 동작함

→ 테스트 환경에서 act를 사용하면 가상돔에 제대로 반영되었다는 가정하에 테스트가 가능해짐 (react공식문서 가이드)

⇒ 컴포넌트를 렌더링한 뒤 업데이트 하는 코드의 결과를 검증하고 싶을 때 사용

?

근데 왜 지금까지는 잘 작동했나?

@testing-library/react, @testing-libaray/user-event, 컴포넌트 렌더링, 이벤트 핸들러 실행에는 사실 내부적으로 act 함수를 호출해서 상태를 반영했기 때문

⇒ 주어진 API를 사용하는게 아니라 직접 상태를 변경하는 코드가 있다면 act함수를 통해서 감싸야한다

// custom hook
const useConfirmModal = (initialValue = false) => {
  const [isModalOpened, setIsModalOpened] = useState(initialValue);

  const toggleIsModalOpened = () => {
    setIsModalOpened(!isModalOpened);
  };

  return {
    toggleIsModalOpened,
    isModalOpened,
  };
};

export default useConfirmModal;

//test
it("훅의 toggleIsModalOpened()를 호출하면 isModalOpened 상태가 toggle된다.", () => {
  const { result } = renderHook(useConfirmModal);

  result.current.toggleIsModalOpened(); // 이렇게 하면 반영이 안됨

  // 상태를 직접 수정하려면 act함수로 감싸줘야함
  act(() => {
    result.current.toggleIsModalOpened();
  });

  expect(result.current.isModalOpened).toBe(true);
});

Untitled

타이머 테스트(비동기 테스트)

테스트는 비동기 타이머와 무관하게 동기적으로 실행된다

→ 비동기 함수가 실행되기 전에 단언이 실행된다

→ 이를 해결하기 위해 딜레이를 걸어줘야한다

→ 근데 이미 타이머 모킹되어있다 → 가져다 쓰면됨

it("특정 시간이 지난 후 함수가 호출된다.", () => {
  vi.useFakeTimers(); // 모킹된 타이머 함수

  const spy = vi.fn();

  const debouncedFn = debounce(spy, 300);

  debouncedFn();
  vi.advanceTimersByTime(300); // 타이머 시간 300ms 지나가게 만듬

  expect(spy).toHaveBeenCalled();

  vi.useRealTimers(); // 모킹된 타이머 함수 초기화
});

매번 테스트마다 모킹하고 초기화해주는 것은 비효율적이므로 setup, teardown에서 설정해준다

describe("debounce", () => {
  beforeEach(() => {
    vi.useFakeTimers();

    // 시간은 흐르므로 일관된 시간을 지정하고 싶으면 이렇게 할 수 있음
    vi.setSystemTime(new Date("2024-07-30"));
  });

  afterEach(() => {
    vi.useRealTimers();
  });

  // test
});

userEvent를 사용한 사용자 상호작용 테스트

fireEvent

userEvent같이 react testing library에서 DOM 이벤트를 시뮬레이션 하기 위해 제공한 API임

단, userEvent와 다르게 react testing library에 내장 되어있으서 import해서 사용가능함

userEvent vs fireEvent

fireEvent는 특정 이벤트에 대한 dispatch이다. 즉, 다른 연쇄반응은 신경쓰지 않는다

ex) user.click ⇒ pointerdown, mousedown, pointerup, mouseup, click, focus 등 연쇄반응 (실제 상황과 유사)

fireEvent.click ⇒ click 끝.

테스트는 유저의 행동을 시뮬레이션하는 것이다.

fireEvent는 실제 사용자가 사용하는 것과 다르다 (userEvent는 유사하다)

⇒ userEvent쓰셈

단, scroll event처럼 userEvent에서 제공해주지 않는 이벤트를 테스트해야한다면 fireEvent를 사용할 수 있다.

Untitled

단위 테스트의 한계

단위테스트는

  1. 공통 컴포넌트
  2. 커스텀 훅
  3. 공통 유틸

처럼 모듈에 대한 의존성이 거의 없을 때(독립적일때) 유용하다

하지만, 여러 모듈이 조합되었을 때 발생하는 이슈는 찾을 수 없다

⇒ 단위 테스트에서 검증하지 못하는 부분을 통합, E2E, 시각적 테스트 등을 사용해 보강해야 한다

통합 테스트란?

두 개 이상의 모듈이 상화 작용하여 발생하는 상태를 검증. 실제 앱의 비즈니스 로직과 가깝게 기능을 검증할 수 있다.

통합테스트를 한다면?

⇒ 상호작용을 테스트하기 때문에 모킹 비중이 적다

⇒ 모듈간의 상호작용에서 발생하는 에러를 검증할 수 있다.

⇒ 특정 상태를 기준으로 동작하는 컴포넌트 조합 + API와 함께 상호작용하는 컴포넌트 조합 등이 제데로 렌더링 되는지 검증할 수 있다(비즈니스 로직에 가깝게 기능 검증)

⇒ 하위 모듈이 단위 테스트에서 검증할 수 있는 부분까지 한번에 검증가능

단위와 통합테스트 별 테스트 할 수 있는 케이스 차이

ProductCard(단위)

  1. prop 기준으로 가격, 상품명이 제데로 렌더링 되는지
  2. 상품 클릭 시 navigate 모킹을 통해 상세화면으로 이동하는지
  3. 장바구니, 구매 버튼을 눌렀을 때 spy 함수를 통해 각 헨들러가 호출되는지

즉,

  1. 호출은 되는데 올바르게 동작하는지
  2. API에서 주는 데이터 기준으로 올바르게 렌더링 되는지

를 검증할 수 없다

ProductList(통합)

  1. 상품 리스트 조회 API에 맞게 가격, 상품명이 제대로 렌더링 되는지
  2. 상품을 클릭 했을 때 navigate 모킹을 통해 상세화면으로 이동하는지
  3. 장바구니, 구매 버튼을 눌렀을 때
    • 로그인 : 상품 추가 후 장바구니로 이동
    • 비로그인 : 로그인 페이지로 이동
  4. 상품 리스트가 더 있는 경우 show more 버튼이 노출되며, 이를 통해 데이터를 더 가져올 수 있는지

즉,

실제 앱에서 사용자와 상호 작용할 로직을 검증할 수 있다.

1, 2번과 같이 단위, 통합에서 동일하게 검증할 수 있는 것들이 있다.

이런 것들은 통합 테스트에서 한번에 작성하는게 효율적이다.

FE에서의 통합테스트

상태나 데이터를 관리하는 특정 컴포넌트를 기준으로 하위 컴포넌트가 제대로 렌더링 되는지 검증하는 테스트

따라서 효율적인 통합테스트를 위해서는 상태 및 데이터를 어디서 어떻게 관리할지 설계를 잘 해야 통합 테스트 작성이 수월해짐

Untitled

통합 테스트 대상 선정하기

통합 테스트가 검증하는 것은?

API, 상태관리 스토어, react context 등, 다양한 요소들이 결합된 컴포넌트가 특정 비지니스 로직을 올바르게 수행하는지 검증

⇒ 주로 컴포넌트간의 상호작용, API 호출 및 상태 변경에 따른 UI 변경 사항을 검증한다.

어느정도까지 통합 테스트를 할 것인지 사이즈를 정해야 한다

한 테스트에서 너무 많은 비즈니스 로직을 검증하려면 많은 모킹이 필요하다 → 신뢰도가 낮아진다

일부 컴포넌트를 수정해도 테스트가 깨질 수 있음 → 유지보수 어려워짐

그럼 어떻게 나누는게 좋을까?

비즈니스 로직별로 나눠서! ← 그러기 위해서 비즈니스 로직의 도메인을 적절한 사이즈로 분리해야한다

ex) 쇼핑몰 사이트로 예시

Untitled

이렇게 나누면 페이지 내 전체 컴포넌트 간의 상호작용은 검증 못해서 비즈니스 로직 별로 분리해서 검증할 수 있다

tip)

  1. 모킹을 최소로! 최대한 앱의 실제 기능과 유사하게 검증
  2. 비즈니스 로직을 처리하는 상태관리나 API 로직은 상위 컴포넌트로 응집해서 관리!
    1. 상태관리나 api로직이 한곳에 몰려있으므로 로직 관리가 편해짐
    2. 이렇게 응집된 상위 컴포넌트를 대상으로 통합테스트를 작성할 수 있는 기준이 생김
  3. 변경 가능성을 고려해 여러 도메인 기능이 조합된 로직은 비즈니스 로직 별로 나눠서 작성
  4. 단순 UI렌더링하는 것들은 테스트 작성하지말고 상태나 로직이 담겨있는 것들이 렌더링 되는 것을 확인

Untitled

상태 관리 모킹하기 (zustand)

통합 테스트를 하다보면 전역 상태가 들어가있는 컴포넌트를 테스트 해야하고

store의 상태에 따라 검증이 필요한 경우가 생김 → store 모킹해서 사용

(참고 : https://docs.pmnd.rs/zustand/guides/testing)

  1. mocks/zustand.js안에 세팅해주면 jest가 알아서 모킹함 (이미 거스가 다 해주심ㅎ)
  2. global test setup에서 zustand를 모킹하면 위의 모듈을 사용할 수 있음 (이렇게 하면 테스타마다 독립적으로 스토어 검증 가능)
  3. mockZustandStore에서 유틸 함수를 만들어서 store를 변경 할 수 있음 (간편하게 사용)
  • 현재 세팅은 js기준 참고

https://docs.pmnd.rs/zustand/guides/testing

https://jestjs.io/blog/2019/01/25/jest-24-refreshing-polished-typescript-friendly#typescript-support

Untitled

TS

기초 타입 추론

TS가 타입 추론 어케함?

타입 :

  • 어떤 심볼(Symbol-변수명에 엮인)
  • 메모리 공간에 존재할 수 있는
  • 값의 집합과 그 값들이 가질 수 있는 성질

ex) 3.141592는 타입 number에 속한다

Untitled

Untitled

⇒ 타입은 부분순서집합이다.

원시 타입(Primitive Type)

TS가 구분하는 타입 :boolean, number, string, symbol, null, undefined

⇒ 공리적으로 정의

⇒ 이들 간에는 어떤 관계도 없음(서로 대입 안됨)

⇒ null 제외하곤, 값에 typeof를 수행하면 해당 타입 이름이 나옴

리터럴 타입(Listeral Type)

  • 어떤 타입에 속한 값 하나만으로 구성하는 서브타입
  • 본래 타입의 서브타입으로 가능 ex) 6, {INSULIN : ‘INSULIN’} as const로 특정 값을 리터럴 타입으로 선언할 수 있음
    const num1 = 6; // num1은 number 타입
    const num2 = 6 as const; // num2 는 6 타입
    

객체 타입(Object Type)

공변성에 의해 A ≥ B 이냐를 판단할때는 A의 속성으로 판단을 한다

Untitled

배열 / 튜플 타입(Arry/Tuple Type)

  • 배열을 number를 키로 값는점이 다르다
  • 튜플은 length가 상수 리터럴 타입으로 고정된다.

나머지는 객체와 동일하다

따라서 공변성을 가지고 있고

A ≤ B 일경우 A[] ≤ B[] 가 된다.

키 타입(keyof)

  • 객체 타입의 속성의 합집합으로 이루어진 타입
    • 명시적인 타입이 주어질 경우 속성의 리터럴 union타입이 생성
      type k = keyof { x: string; y?: string }; // type k = "x" | "y"
      
    • keyof x ≤ number string symbol이다 .

함수 타입(funciton Type)

  • 반환형과 인자형의 대입 조건을 모두 만족해야 한다

    • 반환형 : 공변적
      • A ≤ B : X → A ≤ X → B (A가 B의 서브타입이면 A를 반환하는 함수는 B를 반환하는 함수의 서브타입이다.) ex) A: string , B: string | number
    • 인자형 : 반변적
      • A ≤ B : A → X ≥ B → X
      • (A가 B의 서브타입이면 A를 인자로 받는 함수는 B를 인자로 받는 서브타입이다.) ex) A: string , B: string | number (ㅇㅖ..?) 인자형 예시1
    const precessNumber = (x: number) => {}; // A -> X
    const precessNumberOrString = (x: number | string) => {}; // B -> X
    
    let wide: (x: number) => void; // A
    wide = precessNumber;
    wide = precessNumberOrString;
    
    let narrow: (x: number | string) => void; // B
    narrow = precessNumber; // Type string | number is not assignable to type number
    narrow = precessNumberOrString;
    
    // + 더 좁은 타입이라면?
    const ZERO = 0 as const;
    const precessZero = (x: typeof ZERO) => {};
    wide = precessZero; // Type number is not assignable to type 0
    
    const precessNumber = (x: number) => {}; // C -> X
    
    let testwide: (arg: never) => any; // C
    let testnarrow: (arg: number) => any;
    
    testwide = precessNumber; // 얘 되나요?
    testnarrow = precessNumber; //가능 한 애잖아
    

    인자형 예시2 (인자가 적은 함수를 인자가 많은 함수 타입에 대입가능하다)

    // 더 많이 받아야 되는데 덜 받는 경우 -> success
    const consumeOneArg = (x: any) => {};
    let wide: (x: any, y: any) => void;
    wide = consumeOneArg;
    wide(0, 1);
    
    // 적게받는 친구에게 많이 넣어버리면 -> error
    const consumeTwoArg = (x: any, y: any) => {};
    let narrow: (x: any) => void;
    narrow = consumeTwoArg; // Type (x: any, y: any) => void is not assignable to type (x: any) => void
    narrow(0);
    

⇒ 쉽게 생각하면 좁은 타입으로 입력 받을수록 + 넓은 타입으로 반환 할수록 넓은 타입이된다.

특수 타입

  • never, unknown, any, void
  • 어떤 타입에 대해서도 never ≤ T ≤ unknown
    • never는 가장 좁은 타입(like 공집합)
    • unknown은 가장 넓은 타입 (any, unkown외에 어디에도 대입 못함 - 슈퍼슈퍼타입)
  • 어떤 타입에 대해서도 T ≤ any, any ≤ T (단, never는 never ≤ any만 가능)
  • void는 함수의 반환에만 유의미하며 undefined의 슈퍼타입임

  • 퀴즈 : 다음 중 이론 상 가장 넓은 함수의 타입은?
    let a: (...args: unknown[]) => unknown;
    let b: (...args: never[]) => unknown;
    let c: (...args: any[]) => any;
    let d: (...args: void[]) => never;
    
  • 정답 b 반환형은 공변성을 가지기 때문에 반환형이 클수록 넓은 함수다 (4번 제낌) 인자형은 반변성을 가지기 때문에 인자형이 작을수록 넓은 함수다 (1,2,3중 가장 좁은 타입은 never이다. because 배열은 공변성 가짐) 함수가 가질 수 있는 가장 넓은 타입이기 때문에 TS에서 종종 보인다.

고급 타입 추론

타입검사

타입검사란 대입, 연산, 참고가 가능한지 확인하는 과정

  • 모든 심볼이 제약 조건(=타입)을 만족하는가?
  • 어떤 코드 맥락에서, 심볼이 가질 수 있는 타입은 어떤 것인가?( ex 타입 가드가 된if문 블록)
  • 완벽히 추론에는 이론상 지수시간복잡도가 발생함(CSP-constrained satisfaction problem랑 동치래요)
    • 따라서 그리드 알고리즘을 사용하는 것으로 추정됨

제네릭(Gemeric)

  • 타입에 대한 함수이자 관계 그 자체
  • 1차 논리만 서술 가능 (고차함수는 안됨 F<T> = T<number> 같은거)

참고

naver D2영상 : https://d2.naver.com/helloworld/7472830

⇒ 다국어 라이브러리: https://github.com/naver/airport : 제가 만드려는 것 + Next-i18next 라이브러리

Untitled

기초 타입 이론 : https://d2.naver.com/helloworld/9283310

고급 타입 추론 : https://d2.naver.com/helloworld/3713986

응용 문제 : https://d2.naver.com/helloworld/5088940

알쓸신잡

새로운 JS스펙 : https://exploringjs.com/js/book/ch_new-javascript-features.html#ch_new-javascript-features

태그: ,

카테고리:

업데이트:

댓글남기기