본문 바로가기
Frontend/Javascript

[Typescript] Typescript Essentials3

by 디스코비스킷 2023. 11. 1.
반응형

10. 호출 시그니처, 인덱스 시그니처

호출 시그니처(call signature)

interface getLikeNumber { // 계속 사용하고싶다면 interface생성
  (like: number): number; // call signature
}

interface Post {
  id: number;
  title: string;
  getLikeNumber: getLikeNumber;
}
const post1: Post = {
  id: 1,
  title: "post 1",
  getLikeNumber(like: number) {
    return like
  }
}

post1.getLikeNumber(1);

인덱스 시그니처(index signature)

속성의 모든 이름을 미리 알지 못하는 경우가 있지만 값의 형태는 알고 있을때 index signature를 사용하여 가능한 값의 Type을 지정할 수 있다.

객체 인덱스 시그니처

계속 속성이 더해져서 post1객체에 모든 속성의 이름을 알지 못할때에는 인덱스 시그니처를 사용할 수 있다.

// - 객체 인덱스 시그니처
interface Post {
  [key: string]: unknown; // key 부분은 다른이름으로 사용가능 props나.. 
  id: number;
  title: string;
}
const post1: Post = {
  id: 1,
  title: "post 1",
}

post1['description'] = 'description1';
post1['pages'] = 300;

배열 인덱스 시그니처

// - 배열 인덱스 시그니처
interface Names {
  [item: number]: string;
}
const userNames: Names= ['John', 'Kim', 'Joe'];

11. 함수 오버로딩

👉두 함수를 하나로 만들어주려면
기본적인 구조는 같지만 매개변수가 다를때 오버로딩을 이용해서 두 함수를 하나로 만들어 줄 수 있다.

// 1. overloading1
function add(a: string, b: string): string;
function add(a: number, b: number): number;
function add(a: any, b: any): any {
    return a + b;
}

add(1,2); // number
add("hello", "world"); // string
// add(1, "world"); // error

// 2. overloading2
function saySomething(word: string): string;
function saySomething(words: string[]): string;
function saySomething(word: any): string {
  if (typeof word === "string") {
    return word;
  } else if (Array.isArray(word)) {
    return word.join(" "); // string
  }
  throw new Error("unable to say something");
  // string으로 return하는데 undefined될 수 있어서 error throw함
}
saySomething('hello');
saySomething(['hello', 'world']);

12. 접근 제어자

생성자를 이용해서 매개변수 2개를 가져오는데,
가져오는 매개변수를 this.id와 this.title에 할당하는데
이 둘다 타입에러가 나오고 있다.

// type error
class Post {
  constructor(id: number, title: string) {
    this.id = id;
    this.title = title;
  }

  getPost() {
    return `postId: ${this.id}, postTitle: ${this.title}.`;
  }
}

let post: Post = new Post(1, "title 1");

타입스크립트에서는 this로 접근하는 속성들을 위한 타입이 class의 body에 지정되어 있어야한다.

class Post2 {
  id: number; // 속성 type 지정. public default
  title: string;
  constructor(id: number, title: string) {
    this.id = id;
    this.title = title;
  }

  getPost() {
    return `postId: ${this.id}, postTitle: ${this.title}.`;
  }
}

let post2: Post2 = new Post2(1, "title 1");

class PostB extends Post2 {
    getPost() {
        return this.title;
    }
}
종류 설명
public default값이며 어디서나 접근가능
protected 클래스 내, 상속받은 자식 클래스에서 접근가능
private 클래스 내에서만 접근 가능

생성자에 접근제어자 넣어 수정

class Post {
  constructor(
    public id: number = 0, // public은 생략할 수없고 default값 넣을수있다.
    protected title: string
  ) {}

  getPost() {
    return `postId: ${this.id}, postTitle: ${this.title}.`;
  }
}

13. Generic 기본

  • Generic을 사용하면 재사용성이 높은 함수와 클래스를 생성할 수 있다.
  • any처럼 타입을 직접 지정하지 않지만, 타입을 체크해 컴파일러 오류를 찾을 수 있게 된다.

*👉함수에 다른 타입의 인수가 계속해서 들어온다면? *
배열의 경우

// function getArrayLength(arr: number[] | string[] | boolean[] ): number {
//   return arr.length;
// }
function getArrayLength<T>(arr: T[]): number {
  return arr.length;
}

const array1 = [1, 2, 3];
const array2 = ['a', 'b', 'c'];
const array3 = [true, false, true];

getArrayLength<number>(array1);
getArrayLength<string>(array2);
getArrayLength<boolean>(array3);

any를 사용하지 않고 generic쓰기

Before

interface Vehicle {
  name: string;
  color: string;
  option: any; // any 를 쓰지않고 타입처리하는방법? 
}

const car: Vehicle = {
  name: 'Car',
  color: 'red',
  option: {
    price: 1000
  }
}
const bike: Vehicle = {
  name: 'Bike',
  color: 'green',
  option: true
}

After

interface Vehicle<T> {
  name: string;
  color: string;
  option: T;
}
const car: Vehicle<{ price: number }> = {
  name: 'Car',
  color: 'red',
  option: {
    price: 1000
  }
}
const bike: Vehicle<boolean> = {
  name: "Bike",
  color: "green",
  option: true,
};

14. 좀 더 다양한 상황에서 Generics 를 사용해보기

👉함수에 매개변수가 두개라면?

const makeArr = <X, Y>(x: X, y: Y): [X, Y] => {
  return [x, y];
}

const array = makeArr<number, number>(4, 5);
const array2 = makeArr("a", "b");
const array3 = makeArr<string, number>("a", 3);

// default
const makeArr1 = <X, Y = string>(x: X, y: Y): [X, Y] => {
  return [x, y];
};

extends를 사용한 generics

꼭 넣어야하는 속성을 넣을 수 있음

// before
const makeFullName = (obj: {firstName: string, lastName: string}) => {
  return {
    ...obj,
    fullName: obj.firstName + " " + obj.lastName,
  };
}

makeFullName({firstName: "John", lastName: "Doe", location: "Seoul"});

// after
const makeFullName2 = <T extends {firstName: string, lastName: string}>(obj: T) => {
  return {
    ...obj,
    fullName: obj.firstName + " " + obj.lastName,
  };
};

makeFullName2({ firstName: "John", lastName: "Doe", location: "Seoul" });
makeFullName2({ lastName: "Doe", location: "Seoul" }); // error: firstName은 필수

리액트에서 Generic

// 컴포넌트에서 props로 뭐가 올지 모를때 generic사용
interface Props {
  name: string;
}
const ReactComponent: React.FC<Props> = ({ name }) => { // Functional Component
  const [state] = useState<{name: string | null}>({name: ""}); // useState에서도 generic사용
  state.name
  return <div>{name}</div>
}

15. Utility Type

Partial

파셔 타입은 특정 타입의 부분집합을 만족하는 타입을 정의할 수 있다.

interface Address {
  email: string;
  address: string;
}

const me: Partial<Address> = {};
const you: Partial<Address> = { email: "john@gmail.com" };
const youAndMe: Address = { email: "john@gmail.com", address: "john" };

Pick

특정 타입에서 몇개의 속성을 선택하여 타입을 정의한다.

interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

type TodoPreview = Pick<Todo, "title" | "completed">;

const todo: TodoPreview = {
  title: "Clean Room",
  completed: false,
};

Omit

특정 속성만 제거한 타입을 정의한다. Pick의 반대.

interface Todo2 {
  title: string;
  description: string;
  completed: boolean;
  createdAt: number;
}

type TodoPreview2 = Omit<Todo2, "description">;

const todo2: TodoPreview2 = {
  title: "clean room",
  completed: false,
  createdAt: 123123,
};

Exclude

일반 Union 유형을 전달한 다음 두번째 인수에서 제거할 멤버를 지정한다.

type myUnionType = "🍈" | "🍉" | "🍊" | "🍋";
let lemon: myUnionType = "🍋";
let noLemonsPlease: Exclude<myUnionType, "🍋"> = "🍈";
let noLemonsOrMelons: Exclude<myUnionType, "🍋" | "🍈"> = "🍉";

Required

원래 유형이 일부 속성을 선택 사항으로 정의한 경우에도 객체에 Required 속성이 있는지 확인해야 하는 경우가 있다.

type User = {
  firstName: string,
  lastName?: string,
}

let firstUser: User = {
  firstName: "John"
}

let secondUser: Required<User> = {
  firstName: "John"
  // lastName 있어어한다.
}

Record<Keys, Type>

속성 키가 Keys이고 속성 값이 Type인 객체 type을 구성한다.
이 유틸리티는 type의 속성을 다른 type에 매핑하는데 사용할 수 있다.

interface CatInfo {
  age: number;
  breed: string;
}

type CatName = "miffy" | "boris" | "mordred";

const cats: Record<CatName, CatInfo> = {
  miffy: { age: 10, breed: "Persian" },
  boris: { age: 5, breed: "Maine Coon" },
  mordred: { age: 16, breed: "British ShortHair" },
};

cats.boris;

ReturnType<T>

함수 T의 반환타입으로 구성된 타입을 만든다.

type T0 = ReturnType<() => string>; 
type T1 = ReturnType<(s: string) => string>;

function fn(str: string) {
  return str;
}

const a: ReturnType<typeof fn> = 'Hello';
const b: ReturnType<typeof fn> = true; // error

16. Implements vs Extends

Extends

Extends 키워드는 자바스크립트에서도 사용할 수 있으며
부모클래스에 있는 프로퍼티나 메소드를 상속해서 사용할 수 있게 만들어준다.

Implements

Implements 키워드는 새로운 클래스의 타입체크를 위해 사용되며
그 클래스 모양을 정의할때 사용한다.(부모클래스의 프로퍼티와 메소드를 상속받아 사용하는게 아니다.)

class Car {
  mileage = 0;
  price = 1000;
  color = 'white';

  drive() {
    return 'drive!';
  }

  brake() {
    return 'stop!';
  }
}

// extends로는 모두 상속받기에 가능
class Ford extends Car {

}
const myFordCar = new Ford();


// implements
interface Part {
  seats: number;
  tire: number;
}
class Ford2 implements Car, Part { // 지정한 속성을 넣어야한다.
  mileage = 0;
  price = 2000;
  color = "red";
  seats = 2;
  tire = 4;

  drive() {
    return "drive!";
  }

  brake() {
    return "stop!";
  }
}
const myFordCar2 = new Ford2();

17. Keyof operator

keyof연산자는 제공된 타입의 키를 추출하여 새로운 Union 유형으로 반환한다.

interface IUser {
    name: string;
    age: number;
    address: string;
}

type UserKeys = keyof IUser; // 새로운 Union유형 반환
// "name" | "age" | "address"

typeof keyof

typeof연산자와 함께 사용하면 keyof연산자는 객체의 모든 키에 대한 union형식을 반환한다.

const user = {
    name: "John",
    age: 20, 
    address: 'seoul'
}

// type으로 변환후 keyof로 사용
type UserKeys2 = keyof typeof user; 

enum의 key 추출

enum UserRole {
    admin,
    manager
}

type UserRoleKey = keyof typeof UserRole;

18. Mapped Types

맵드 타입을 이용하면 중복을 피하기 위해서 다른 타입을 바탕으로 새로운 타입을 생성할 수 있다.

Mapped type을 사용하는 것은 type이 다른 type에서 파생되고 동기화상ㅇ태를 유지하는 경우에 특히 유용하다.

Array.prototype.map()의 대표적인 예이다.

[1,2,3].map(value => value.toString());

여기에서 배열의 각 숫자를 가져와서 문자열로 변환했다.
이것처럼 Typescript에서 Mapped Type은 하나의 Type을 가져와 각 속성에 변환을 적용하여 다른 type으로 변환한다는 의미이다.

Mapped Type 사용해보기

type DeviceFormatter<T> = {
    [K in keyof T]: T[K];
}

type Device = {
    manufacturer: string;
    price: number;
};

const iphone: DeviceFormatter<Device> = {manufacturer: 'apple', price: 100};

// 만약 객체에서 가격이나 제조사만 가지거나
// 둘다 가지지 않는게 있을 경우에는? 

// Before
const priceOnly: DeviceFormatter<Device> = { price: 23 };
const manufacturerOnly: DeviceFormatter<Device> = { manufacturer: 'apple' };
const empty: DeviceFormatter<Device> = {};

// After
type DeviceFormatter2<T> = {
    [K in keyof T]? : T[K]; // optional하게 만들어준다.
}

const priceOnly2: DeviceFormatter2<Device> = { price: 23 };
const manufacturerOnly2: DeviceFormatter2<Device> = { manufacturer: "apple" };
const empty2: DeviceFormatter2<Device> = {};

19. tsconfig.json 파일 더 자세히 알아보기

디렉터리에 tsconfig.json파일이 있으면 해당 디렉터리가 typescript프로젝트의 루트임을 나타낸다.
tsconfig.json파일은 프로젝트를 컴파일하는데 필요한 루트파일과 컴파일러 옵션을 지정한다.

전체구성

{
  "compilerOptions": { },
  // 컴파일할 개별 파일 목록
  "files": [
    "node_modules/library/index.ts"
  ],
  // 컴파일러를 이용해서 변환할 폴더 경로를 지정
  "include": [
    "./src/**/*.ts"
  ],
  // 컴파일러를 이용해서 변환하지 않을 폴더 경로를 지정
  "exclude": [ 
      "node_modules",
      "dist"
  ],
  // 상속해서 사용할 다른 TS 구성파일 지정
  "extends": "main_config.json"
}

compilerOption

  "compilerOptions": {
    "rootDir": "./src", // 소스 파일 안에서 root폴더 명시
    "outDir": "./build/js", // ts 컴파일 후에 어느 경로로 들어가는지 명시
    "target": "ES2015", // 타입스크립트를 ES2015버전의 자바스크립트로 변환
    "noEmitOnError": false, // 파일에 에러가 있을 때에는 컴파일 하지 않는 옵션
    "module": "ESNext", // 컴파일을 마친 후 자바스크립트가 사용한은 모듈 시스템
    "esModuleInterop": true, // ESM(ES Module), CommonJS 호환해서 사용 가능
    "moduleResolution": "Node",
    "lib": ["ESNext", "DOM"], // 컴파일 과정에서 사용하는 라이브러리 지정
    "declaration": true, // d.ts파일 생성 타입들만 따로 관리할 수 있다.
    "baseUrl": "./", // tsconfig.json과 동일한 폴더에서 시작하는 파일을 먼저 찾는다
    "path": {
      // 임포트 경로 설정
      "@src/*": ["src/*"]
    },
    "isolatedModules": true, // 프로젝트 내 각 소스파일을 모듈로 만들기 강제
    "removeComments": true, // 컴파일시 타입스크립트 소스의 주석을 모두 제거
    "allowJs": true, // js파일과 ts파일을 같이 사용
    "checkJs": true, // js파일에서도 type check
    "strict": true, // 타입스크립트 파일에 타입을 엄격하게 사용한다고 명시
    "noImplicitAny": true, // 명시적이지 않은 any유형으로 표현식 및 선언 사용시 오류 발생
    "strictNullChecks": true, // 엄격한 null검사 사용
    "strictFunctionTypes": true, // 엄격한 함수 유형 검사 사용
    "strictBindCallApply": true, // 엄격한 bind, call, apply 함수 메서드 사용
    "strictPropertyInitialization": true, // 클래스에서 속성 초기화 엄격 검사 사용
    "noImplicitThis": true, // 명시적이지 않은 any유형으로 this표현식 사용시 오류발생
    "alwaysStrict": true // 엄격모드에서 구문 분석후, 각 소스파일에 "use strict"코드 출력
  },

lib

target에 어떤 값을 지정하느냐에 따라 다른 기본값을 가진다.
target es3 -> 기본값 lib.d.ts
target es5 -> 기본값 dom, es6, scripthost
만약 lib를 직접 지젛아면 배열안에 있는 라이브러리만 사용한다.
"lib": ["ESNext", "DOM"]

moduleResolution

타입스크립트 컴파일러가 모듈을 찾는 방법

  1. 먼저 모듈 import가 상대인지 비상대 import인지 구분한다.
  2. Classic또는 Node 전략 중 하나를 이용해서 컴파일러에서 moduleA를 찾을 위치를 알려준다.
  • Classic : Typescript 1.6이전에 기본값
  • Node : 현재 기본값이며 modern code에서는 이 전략만 사용

baseUrl

이 프로퍼티가 지정되어 있다면 비상대적 import의 모듈 해석 과정에 하나의 과정을 추가합니다.
"baseUrl" : "./"이라고 사용하면 tsconfig.json와 동일한 폴더에서 시작하는 파일을 먼저 찾는다.

path

src/foo/a/b/index.ts에서
src/bar/index.ts에 있는 함수를 import해오려면?
path 설정 전:import { bar } from '../../../../bar'

path 설정 후:

"path": {
    "@src/*": [
        "src/*"
    ]
}

import { bar } from '@src/bar'라고 사용할 수 있다.

isolationModules

true로 설정하면 프로젝트 내에서 모든 각각의 소스코드 파일을 모듈로 만들기를 강제한다. 소스코드 파일에서 import 또는 export를 사용하면 그 파일은 모듈이 되지만 만약 import / export를 하지 않으면 그 파일은 전역 공간으로 정의되고 모듈이 아니기에 에러가 나게 된다.
이때는 export{}라고 적어야 에러가 없다.

removeComments

컴파일시 타입스크립트 소스의 주석을 모두 제거하는 설정

allowJS

js파일을 같이 사용한다.

checkJs

allowJS와 함께 작동한다.
checkJs가 활성화되면 Javascript파일에 오류가 보고된다.
이는 프로젝트에 포함된 모든 Javascript파일의 맨 위에 //@ts-check를 포함하는 것과 같다.
js파일에서도 타입을 체크하게된다.
"checkJs": true,

forceConsistentCasingInFileNames

파일의 이름을 대문자로 판별하게 하는 옵션이다.(대소문자구별)
import {B} from './Test'

declaration

이 옵션을 true로 하면 TS파일을 JS로 컴파일하는 과정에서 JS파일과 함께 d.ts선언 파일이 생성된다.
선언파일에서 타입들만 따로 관리할 수 있다.

strict

    "strict": true, // 타입스크립트 파일에 타입을 엄격하게 사용한다고 명시
    "noImplicitAny": true, // 명시적이지 않은 any유형으로 표현식 및 선언 사용시 오류 발생
    "strictNullChecks": true, // 엄격한 null검사 사용
    "strictFunctionTypes": true, // 엄격한 함수 유형 검사 사용
    "strictBindCallApply": true, // 엄격한 bind, call, apply 함수 메서드 사용
    "strictPropertyInitialization": true, // 클래스에서 속성 초기화 엄격 검사 사용
    "noImplicitThis": true, // 명시적이지 않은 any유형으로 this표현식 사용시 오류발생
    "alwaysStrict": true // 엄격모드에서 구문 분석후, 각 소스파일에 "use strict"코드 출력
반응형

최근댓글

최근글

© Copyright 2023 jngmnj