26.01.28 우타스 스터디 - 3장

lsjoon·2026년 1월 27일

TIL

목록 보기
56/57

cf. 우아한 타입스크립트 - 3장

목차

  1. 함수 컨벤션
  2. 기타

1. 함수 컨벤션


a. 선언문 vs 화살표 함수

functuon

  • 파일 최상위 레벨의 유틸리티 함수
  • 함수 오버로딩이 필요할 때
  • React 컴포넌트 선언(취향 차이).

const

  • 콜백 함수
  • this를 유지해야 하는 메서드
  • 간단한 한 줄 함수

ㄱ. 호이스팅(hosting) 차이

  • 선언문 : 컴파일 단계에서 코드의 맨 위로 끌어올려져, 함수를 정의하기 전에도 호출 가능
  • 화살표 함수 : 변수 선언 규칙에 따라, 선언 이후에만 호출 가능

ㄴ. this 바인딩 차이

  • 선언문 : 호출되는 위치에 따라 동적으로 결정
  • 화살표 함수 : 상위 스코프의 this를 따라감 (Lexical this)
class Button {
  name = "MyButton";

  handleClickV1 = function() {
    // 여기서의 this는 이 함수를 호출하는 주체(HTML 요소 등)가 될 수 있음
    console.log(this.name); // ⚠️ 에러 가능성 높음
  }

  handleClickV2 = () => {
    // 화살표 함수의 this는 무조건 Button 인스턴스를 가리킴
    console.log(this.name); // ✅ "MyButton" (안전)
  }
}

ㄷ. TypeScript 오버로딩

오버로딩

  • function: 중복 선언으로 오버로딩 가능
  • const: 타입 단언 혹은 복잡한 인터페이스 정의가 필요

b. default export vs named export

최근은 export 가 대세

default export

  • 하나의 파일에서 export default는 1개만 가능
  • 사용하는 곳에서 이름을 마음대로 별칭 사용 가능
  • 자동완성 지원이 비교적 느릴 수 있음

P.S. export default가 강제되는 경우

- Next.js의 페이지 파일(page.tsx)
- React.lazy 사용할 때

// user.ts
const User = { name: "A" };
export default User;

...

// import 시 중괄호 {} 없음, 'User'를 'MyUser'로 이름 바꿔서 가져옴
import MyUser from './user';

named export

  • 하나의 파일에서 여러 개 export 가능
  • 강제된 네이밍으로 일관성 유지 쉬움
  • 트리 쉐이킹에 유리
// user.ts
export const User = { name: "A" };
export const Role = "Admin";

...

// import 시 중괄호 {} 필수
import { User, Role } from './user';

generic example

https://www.notion.so/lsjoon/generic-refactoring-2f665bf2693d8063aac0c0c2eceb3e66?source=copy_link

// page.tsx
export async function generateMetadata({ searchParams }: {
  searchParams: Promise<{ [key: string]: string | undefined }>;
}): Promise<Metadata> {
  const searchParam = (await searchParams) as
    | string
    | string[][]
    | Record<string, string>
    | URLSearchParams
    | undefined;
  const queryString = new URLSearchParams(searchParam).toString();

  return getDefaultMetaData({
    title: "mock",
    openGraphUrl: `https://mock-url.com${queryString ? "?" + queryString : ""}`,
  });
}

/// 

type mockProps = {
  searchParams: Promise<{ [key: string]: string | undefined }>;
};


export default async function MockPage({ searchParams }: mockProps) {
  const searchParam = await searchParams;

  const serverId = !searchParam?.temporaryServerId
    ? Number(searchParam?.serverId || 1)
    : Number(searchParam?.temporaryServerId || 1);

  if (isNaN(serverId)) {
    return await getUserInfo(searchParam);
  }

  const [queryData, serverData] = await Promise.all([
    queryMock(
      serverId,
      searchParam?.keyword,
      searchParam?.nickname,
      { lastId: undefined, pageSize: 70 },
      header
    ),
    queryMockServer(),
  ]);

  return (
    <Mock
      mockData={queryData}
      mockServerData={serverData}
      serverId={serverId}
      nickname={searchParam?.nickname}
      keyword={searchParam?.keyword}
    />
  );
}
// type.ts
export type SearchParams = Record<string, string | undefined>;

export type SearchParamsWithServerId = {
  serverId?: string;
} & SearchParams;

export type PageProps<T extends SearchParams = SearchParams> = {
  params: Promise<T>;
  searchParams: Promise<T>;
};

/** generateMetadata 전용 제네릭 타입 **/
export type GenerateMetadata<T extends SearchParams> = (
  props: PageProps<T>
) => Promise<Metadata>;
// page.tsx
export async function generateMetadata({ searchParams }: PageProps<mockProps>) {
  const searchParam = await searchParams;

  return getDefaultMetaData({
    title: "mock",
    openGraphUrl: `https://mock-url.com${searchParam}`,
  });
}

///

type mockProps = {
  keyword: string;
  nickname: string;
} & SearchParamsWithServerId;


export default async function MockPage({ searchParams }: PageProps<mockProps>) {
  const { serverId, keyword, nickname } = await searchParams;

  const id = isNaN(Number(serverId)) ? 1 : Number(serverId);

  const [queryData, serverData] = await Promise.all([
    queryMock(
      id,
      keyword,
      nickname,
      { lastId: undefined, pageSize: 70 },
      header
    ),
    queryMockServer(),
  ]);

  return (
    <Mock
      mockData={queryData}
      mockServerData={serverData}
      serverId={id}
      nickname={nickname}
      keyword={keyword}
    />
  );
}

기타

생각할거리

상수 파일을 만드는 것보다, type으로 추론 가능하도록 구현하는게 타입 스크립트를 더 올바르게 사용하는 방향성이 아닐까?

Typesciprt Challenge

  1. https://typescript-exercises.github.io/
  2. https://github.com/type-challenges/type-challenges/blob/main/README.ko.md

AI 코드리뷰

코드 레빗 도입 고민

profile
중요한 것은 꺾여도 그냥 하는 마음

0개의 댓글