Jieunny의 블로그

[TS] 1. Fundamentals 본문

Study/TypeScript

[TS] 1. Fundamentals

Jieunny 2023. 1. 26. 15:42

✏️  목차

 

📣  TS에서 타입 표현하기

✔️ ':'를 이용하여 코드에 타입을 정의하는 방식을 타입 표기(Type Annotation) 이라고 한다.

➰ 타입 표기는 필수가 아닌 선택 사항, 가능하면 적게! 사용해라.

➰ 대부분의 경우 타입 표기는 필요하지 않다.

// 타입 표기가 필요하지 않다. -> string 타입으로 추론된다.
let myName = 'Alice';

 

📍 원시타입

✔️ string, number, boolean

let str: string = "hi";

let num: number = 10;

let isLoggedIn: boolean = false;

 

📍 Array

let arr: number[] = [1, 2, 3];

let arr: Array<number> = [1, 2, 3];	//제네릭 사용

 

📍 Tuple

✔️ 배열의 길이가 고정되고, 각 요소의 타입이 지정되어 있는 배열 형식

let arr: [string, number] = ['hi', 10];

arr[1].concat('!');	//Error, 'number' does not have 'concat'
arr[5] = 'hello'; //Error, Property '5' does not exist on type '[string, number]'.

 

📍 Enum

✔️ 특정 값(상수)들의 집합

enum Avengers { Capt, IronMan, Thor }
let hero: Avengers = Avengers.Capt;

let hero: Avengers = Avengers[0];

// 인덱스를 사용자 편의로 변경해서 사용 가능
enum Avengers { Capt = 2, IronMan, Thor }
let hero: Avengers = Avengers[2];	// Capt
let hero: Avengers = Avengers[4];	// Thor

➰ 숫자형 이넘에서, 선언할 때 만약 이넘 값에 다른 이넘 타입의 값을 사용하면 선언하는 이넘의 첫 번째 값에 초기화를 해줘야 한다.

➰ 문자형 이넘은 이넘 값 전부 다 특정 문자 또는 다른 이넘 값으로 초기화 해줘야 한다.

 

📍Any

✔️ 모든 타입에 대해서 허용한다는 의미

let str: any = 'hi';
let num: any = 10;
let arr: any = ['a', 2, true];
let obj: any = { x: 0 };
// 아래 코드들은 오류 없이 실행된다.
// any를 사용하면 추가적인 타입 검사가 비활성화 된다.

obj.foo();
obj();
obj.bar = 100;
obj = 'hello';
const n: number = obj;

➰ 특정 값으로 인하여 타입 검사 오류가 발생하는 것을 원하지 않을 때 사용

➰ TS는 타입이 지정되니 않은 값에 대하여 문맥으로부터 타입을 추론해낼 수 없다면, 컴파일러는 any타입을 부여한다.

    ➰ 컴파일러 플래그 noImplicitAny : 암묵적으로 any로 간주하는 모든 경우에 오류를 발생시킨다.

 

📍 Void

✔️ 변수에는 undefined, null만 할당하고, 함수에는 반환 값을 설정할 수 없는 타입

let unuseful: void = undefined;
function notuse(): void {
	console.log('sth');
}

 

📍 Never

✔️ 함수의 끝에 절대 도달하지 않는다는 의미를 지닌 타입(무한 루프)

function neverEnd(): never {
	while(true) {
    }
}

📍 함수

✔️ 함수의 매개변수 타입, 함수의 반환 타입, 함수의 구조 타입을 정의할 수 있다.

 

✔️ 함수의 매개변수 타입 표기

// 매개변수 타입 표기
function greet(name: string) {
	console.log("Hello, " + name.toUpperCase() + "!!");
}

greet(42); // 해당 함수에 대한 인자 검사가 이루어지고, 맞지 않으면 런타임 오류 발생
greet('jieun');
greet('jieun', 'cinnamon'); // Error, too many parameters

➰ 함수의 매개변수를 설정하면 undefined나 null 이라도 인자로 무조건 넘겨야 한다.

➰ 정의된 매개변수 값만 받을 수 있고 추가로 인자를 받을 수 없다.

 

➰ '?'를 이용하면 매개변수의 갯수보다 인자를 적게 넘겨도 에러가 나지 않는다.

function sum(a: number, b?: number): number {
	return a + b;
}

sum(10, 20); // 30
sum(10, 20, 30); // error, too many parameters
sum(10); // 타입 에러 없음

 

➰ Rest 문법이 적용된 매개변수

function sum(a: number, ...nums: number[]){
	const totalOfNums = 0;
    for (let key in nums) {
    	totalOfNums += nums[key];
    }
    return a + totalOfNums;
}

 

✔️ 함수의 반환 타입 표기

function getFavoriteNumber(): number {
	return 26;
}

➰ 변수의 타입 표기와 마찬가지로, 반환 타입은 표기하지 않아도 되는 것이 일반적이다.

 

✔️ 익명 함수 -> 문맥적 타입 부여

➰ 함수가 코드상에서 위치한 곳을 보고 해당 함수가 어떻게 호출될 지 알아낼 수 있다면, TS는 해당 함수의 매개변수에 자동으로 타입을 부여한다.

const names = ['Alice', 'Bob', 'Eve'];

names.forEach(function (s) {
	console.log(s.toUppercas());
    // Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
});

➰ 매개변수 s에 타입이 표기되지 않았음에도 TS는 배열의 타입과 forEach 함수의 타입을 활용해서 s의 타입을 추론해냈다.

 화살표 함수에도 적용된다.

 

✔️ this

➰ this가 가리키는 것을 명시할 때

interface Vue {
	el: string;
    count: number;
    init(this: Vue): () => {};
}

let vm: Vue = {
	el: '#app',
    count: 10,
    init: function(this: Vue) {
    	return () => {
        	return this.count;
        }
    }
}

let getCount = vm.init();
let count = getCount();
console.log(count);	//10

📍 객체

✔️ 프로퍼티들과 각 프로퍼티의 타입을 나열

 

function printCoord(pt: { x: number; y: number }) {
	console.log("The coordinate's x value is " + pt.x);
    console.log("The coordinate's y value is " + pt.y);
}
printCoord({x: 3, y: 7});

➰ 매개변수 pt는 x, y 두 개의 프로퍼티로 이루어진 타입을 표기하고 있고, 두 값은 모두 number 타입이다.

➰ 각 프로퍼티를 구분할 때 ',' 또는 ';'를 사용할 수 있다.

 

➰ 일부 또는 모든 프로퍼티의 타입을 옵셔널(? 키워드)로 지정할 수 있다.

function printName(obj: { first: string; last?: string })
	//...
}

printName({ first: 'Bob' }); //OK
printName({ first: 'Alice', last: 'Alisson' });	//OK


function printName(obj: { first: string; last?: string })
    if(obj.last !== undefined) {
        console.log(obj.last.toUpperCase());
    }
    // 이렇게 쓰거나

	console.log(obj.last?.toUpperCase());
    // 이렇게 써라
}

➰ 존재하지 않는 프로퍼티에 접근했을 때, 런타임 오류가 발생하지 않고 undefined 값을 얻게 된다.

➰ 옵셔널 프로퍼티를 읽었을 때, 해당 값을 사용하기에 앞서 undefined 인지 여부를 확인해야 한다.


📍 유니언 타입

✔️ 연산자를 사용하여 새로운 타입 만들기

 

function printId(id: number | string) {
	// 문자열 또는 숫자를 받을 수 있는 함수
	console.log("Your ID is: " + id);
}

printId(101);//OK
printId("202");	//OK
printId({ myId: 22342 });//error


function printId(id: number | string) {
	// 문자열 또는 숫자를 받을 수 있는 함수
	console.log(id.toUpperCase()); 
    // string 타입에만 유효한 메서드는 사용할 수 없다.
}


function printId(id: number | string) {
	// 문자열 또는 숫자를 받을 수 있는 함수    
    if(typeof id === 'string') {
		console.log(id.toUpperCase());
    }
    else {
    	console.log(id);
    }
}

 

코드상에서 유니언을 좁혀야 하는데, 조건을 나눠서 코드를 작성해준다.

유니언의 모든 멤버가 어떤 프로퍼티를 공통으로 가진다면, 좁히기 없이도 해당 프로퍼티를 사용할 수 있다.


📍 인터페이스

✔️ 인터페이스 선언은 객체 타입을 만드는 또 다른 방법

 

interface personAge {
  age: number;
}

function logAge(obj: personAge) {
  console.log(obj.age);	//28
}
let person = { name: 'Capt', age: 28 };
logAge(person);

➰ TS는 printCoord에 전달된 값의 구조에만 관심을 가진다 -> TS가 구조적 타입 시스템이라고 불리는 이유

➰ 인터페이스에 정의된 속성, 타입의 조건만 만족한다면 객체의 속성 갯수가 더 많아도 된다.

➰ 인터페이스에 선언된 속성 순서를 지키지 않아도 된다.

 

✔️ 옵셔널 속성

interface CraftBeer {
  name: string;
  hop?: number;  
}

let myBeer = {
  name: 'Saporo'
};
function brewBeer(beer: CraftBeer) {
  console.log(beer.name); // Saporo
  console.log(beer.brewery); // Error: Property 'brewery' does not exist on type 'Beer'
}
brewBeer(myBeer);

인터페이스에 정의되지 있지 않은 속성에 대해서 오류를 표시한다.

 

✔️ 읽기 전용 속성

let myBeer: CraftBeer = {
  brand: 'Belgian Monk'
};
myBeer.brand = 'Korean Carpenter'; // error!

➰ 읽기 전용 속성은 수정할 수 없다.

 

✔️ 읽기 전용 배열

let arr: ReadonlyArray<number> = [1,2,3];
arr.splice(0,1); // error
arr.push(4); // error
arr[0] = 100; // error

➰ 선언하는 시점에만 값을 정의할 수 있다.

 

✔️ 함수 타입

interface login {
  (username: string, password: string): boolean;
}

let loginUser: login;
loginUser = function(id: string, pw: string) {
  console.log('로그인 했습니다');
  return true;
}

➰ 함수의 인자의 타입과 반환 값의 타입을 정한다.

 

✔️ 클래스 타입

interface CraftBeer {
  beerName: string;
  nameBeer(beer: string): void;
}

class myBeer implements CraftBeer {
  beerName: string = 'Baby Guinness';
  nameBeer(b: string) {
    this.beerName = b;
  }
  constructor() {}
}

 

✔️ 인터페이스 확장

// 확장
interface Person {
  name: string;
}
interface Developer extends Person {
  skill: string;
}
let fe = {} as Developer;
fe.name = 'josh';
fe.skill = 'TypeScript';

// 상속
interface Person {
  name: string;
}
interface Drinker {
  drink: string;
}
interface Developer extends Person {
  skill: string;
}
let fe = {} as Developer;
fe.name = 'josh';
fe.skill = 'TypeScript';
fe.drink = 'Beer';

 

✔️ 하이브리드 타입

➰ 함수 타입이면서 객체 타입을 정의할 수 있다.

interface CraftBeer {
  (beer: string): string;
  brand: string;
  brew(): void;
}

function myBeer(): CraftBeer {
  let my = (function(beer: string) {}) as CraftBeer;
  my.brand = 'Beer Kitchen';
  my.brew = function() {};
  return my;
}

let brewedBeer = myBeer();
brewedBeer('My First Beer');
brewedBeer.brand = 'Pangyo Craft';
brewedBeer.brew();

📍 제네릭

✔️ 재사용성이 높은 컴포넌트를 만들 때 자주 활용된다.

✔️ 타입을 마치 함수의 파라미터처럼 사용하는 것

 

// 제네릭 기본 문법

function getText<T>(text: T): T {
  return text;
}

// 함수 호출 시 아래와 같이 함수에서 사용할 타입을 넘겨줄 수 있다.
getText<string>('hi');
getText<number>(10);
getText<boolean>(true);

➰ getText<string>('hi') 를 호출하면 T는 string이 되고, text는 string타입이 되서 string 타입인 hi를 반환한다.

 

✔️ 제네릭을 사용하는 이유

function logText(text: any): any {
  return text;
}

➰ 여러가지 타입을 허용하고 싶다면 any를 쓰면 되지만, any는 타입 검사를 하지 않기 때문에 함수의 인자로 어떤 타입이 들어갔고 어떤 타입이 반환되는지 알 수가 없다.

function logText<T>(text: T): T {
  return text;
}

// #1
const text = logText<string>("Hello Generic");
// #2
const text = logText("Hello Generic");

➰ 제네릭으르 쓰면 함수를 호출할 때 넘긴 타입에 대해 TS가 추정할 수 있다.

 

✔️ 제네릭 타입 변수

➰ 제네릭에 타입을 줌으로써 유연한 방식으로 함수의 타입을 정의해줄 수 있다.

// 제네릭에 어떤 타입이 들어올 지 모르니까 length를 허용하지 않는다.
function logText<T>(text: T): T {
  console.log(text.length); // Error: T doesn't have .length
  return text;
}


function logText<T>(text: T[]): T[] {
  console.log(text.length); // 제네릭 타입이 배열이기 때문에 `length`를 허용합니다.
  return text;
}


function logText<T>(text: Array<T>): Array<T> {
  console.log(text.length);
  return text;
}

📍 타입 추론

✔️ 타입스크립트가 코드를 해석해 나가는 동작

 

✔️ 타입 추론의 기본

let x = 3;

➰ x에 대한 타입을 따로 지정하지 않더라도, 일단 x는 number로 간주된다.

변수를 선언하거나 초기화 할 때 타입이 추론된다.

외에도 변수, 속성, 인자의 기본 값, 함수의 반환 값 등을 설정할 때 타입 추론이 일어난다.

 

✔️ 가장 적절한 타입(Best Common Type)

➰ 타입은 보통 몇 개의 코드를 바탕으로 추론하며, 그 코드를 이용하여 가장 근접한 타입을 추론하게 되는 것을 가장 근접한 타입 이라고 한다.

let arr = [0, 1, null];

➰ arr의 타입을 추론하기 위해서 배열의 각 아이템을 살펴본다.

➰ 배열의 아이템은 크게 number와 null로 구분되며, 이 때 Best Common Type알고리즘으로 다른 타입들과 가장 잘 호환되는 타입을 선정한다.

 

✔️ 문맥상의 타이핑(Contextual Typing)

➰ 타입스크립트에서 타입을 추론하는 또 하나의 방법

➰ 문맥상으로 타입을 결정하는 것 -> 코드의 위치(문맥)을 기준으로 일어난다.

window.onmousedown = function(mouseEvent) {
  console.log(mouseEvent.button);   //<- OK
  console.log(mouseEvent.kangaroo); //<- Error!
};

window.onmousedown에 할당되는 함수의 타입을 추론하기 위해서 window.onmousedown 타입을 검사한다.

타입 검사가 끝나고 함수의 타입이 마우스 이벤트와 연관이 있다고 추론하기 때문에 mouseEvent 인자에 button 속성은 있지만 kangaroo속성은 없다고 결론 내린다.

 

✔️ 타입스크립트의 타입 체킹

➰ Duck Typing : 객체의 변수 및 메서드의 집합이 객체의 타입을 결정하는 것을 의미한다.

➰ Structural Subtyping : 객체의 실제 구조나 정의에 따라 타입을 결정하는 것을 의미한다.


📍 타입 호환

✔️ 특정 타입이 다른 타입에 잘 맞는지를 의미한다.

interface Ironman {
  name: string;
}

class Avengers {
  name: string;
}

let i: Ironman;
i = new Avengers(); // OK, because of structural typing

➰ Avengers 클래스가 Ironman 인터페이스를 상속받지 않았는데도 에러가 나지 않는다 -> 구조적 타이핑 때문

 

interface Avengers {
  name: string;
}

let hero: Avengers;
// 타입스크립트가 추론한 y의 타입은 { name: string; location: string; } 입니다.
let capt = { name: "Captain", location: "Pangyo" };
hero = capt;

➰ capt가 hero 타입에 호환될 수 있는 이유는, capt의 속성 중에 name이 있기 때문이다.

 

function assemble(a: Avengers) {
  console.log("어벤져스 모여라", a.name);
}
// 위에서 정의한 capt 변수. 타입은 { name: string; location: string; }
assemble(capt);

➰ capt 변수에 이미 name 속서어 뿐만 아니라 location 속성도 있기 때문에 assemble 함수의 호출 인자로 넘길 수 있다.

 

 

✔️ Soundness란?

➰ 타입스크립트는 컴파일 시점에 타입을 추론할 수 없는 특정 타입에 대해서 일단 '안전하다' 고 본다.

➰ 이걸 '들리지 않는다' 라고 표현한다.


📍 타입 별칭

✔️ 똑같은 타입을 재사용하거나, 또 다른 이름으로 부르고 싶은 경우

✔️ 별도로 이름 붙인 타입을 새로 작성하는 것

✔️ 인터페이스와 비슷하지만, 인터페이스와 다르게 확장이 불가능하다.

 

➰ 객체 타입

type Point = {
	x: number;
    y: number;
};

function printCoord(pt: Point) {
	console.log('The coordinate's x value is " + pt.x);
    console.log('The coordinate's y value is " + pt.y);
}

printCoord({ x: 100, y: 100 });

 

➰ 유니언 타입

type ID = number | string;

 

'Study > TypeScript' 카테고리의 다른 글

[TS] 함수와 메서드  (0) 2023.02.01
[TS] 객체와 타입  (2) 2023.02.01
[TS] 타입스크립트 프로젝트 만들기  (0) 2023.01.30
[TS] 2. Usage  (0) 2023.01.27
[TS] 0. TypeScript란?  (0) 2023.01.26