typescript

2.18 ~ 2.22

케케_ 2024. 10. 6. 22:26

목차

 

2.18 콜백 함수의 매개변수는 생략 가능하다

  • 인수로 제공하는 콜백함수의 매개변수에는 타입을 표기하지 않아도 됨
    • 문맥적 추론
  • 콜백 함수의 매개변수는 함수를 호출할때 사용하지 않아도 됨

 

 

function example (callback: (error: Error, result: string)=>void){}
example((e,r)=>{});		//문맥적 추론
example(()=>{});		//매개변수 사용 X
example(()=>true);
  • error와 result는 undefine이 아닌 각각의 타입을 가짐
    • (실수) 옵셔널로 만들기
      • error와 result 타입이 각각 Error|undefined, string|undefined가 됨
      • 두 매개변수는 undefined가 아니기 때문에 의도와 달라짐
  • 콜백 함수의 반환값이 void면 어떤 값이 와도 상관 X, 단 해당 반환값을 다른 곳에 사용 X

 

forEach 예시

  • forEach 메서드의 콜백함수는 callbackfn 타입
[1,2,3].forEach((item,index,array)=>{	//매개변수 타입 표기 X
    console.log(item, index,array)
});

[1,2,3].forEach((item, index)=> {});    //반환값 없음
[1,2,3].forEach((item)=> item);         //반환값 있음
  • 함수 호출 시 매개변수를 모두 기입하지 않음

 

 

2.19 공변성과 반공변성을 알아야 함수끼리 대입할 수 있다

: 함수 간의 대입 가능성 알기

: 기본적으로 타입스크립트는 공변성을 갖고 있지만, 함수의 매개변수는 반공변성을 갖음

 

공변성 : A -> B일 때 T<A> -> T<B>인 경우
반공변성 : A -> B일 때 T<B> -> T<A>인 경우
이변성 : A -> B일 때 T<A> -> T<B>도 되고 T<B> -> T<A>도 되는 경우
무공변성 : A -> B일 때 T<A> -> T<B>도 안 되고 T<B> -> T<A>도 안 되는 경우

 

 

함수의 반환값공변성을 갖음

function a(x:string) : number {
    return 0;
}
type B = (x:string) => number | string;
let b : B = a;
  •  a->b 관계
    • a 함수의 반환값(number)을 b의 반환값(number|string)에 대입 가능 (두 타입의 차이는 반환값 뿐이며, 반환값 타입은 b가 a보다 넓음)
  • T <> = 함수<반환값>라고 생각 (함수 자체 대입 확인하기)
    • a -> b 일 때, T<a> -> T<b> (함수 a를 타입 b에 대입 가능)
function a(x:string) : number | string{
    return 0;
}
type B = (x:string) => number;
let b : B = a;

  • 반대로하면 에러 발생 -> 반환값에 대해서 항상 공변성을 가짐

 

매개변수반공변성을 가짐 (strict 옵션 활ㅅ)

function a(x:string | number) : number{
    return 0;
}
type B = (x:string) => number;
let b : B = a;
  • 매개변수 string -> string | number 이므로 b -> a
  • 하지만, a를 b에 대입 가능
  • b -> a 에서 T<a> -> T<b>이므로 매개변수는 반공변성 (반대는 불가, 에러 발생함)

  • 반대는 에러

 

 

 

매개변수 이변성을 가짐 (strict 옵션 X)

  • 위의 에러가 strict 옵션을 해제하면 발생 안함 (잘 모르겠...)
  • b -> a 에서 T<b> -> T<a>도 되고, T<a> -> T<b>도 됨

 

 

객체의 메서드 타이핑 방법에 따라 변성이 정해짐 (strict 옵션 활성화)

interface SayMethod {
    say(a:string|number) : string;
}

interface SayFunction {
    say : (a:string|number) => string;
}

interface SayCall{
    say : {
        (a:string | number) : string
    }
}
const SayFunc = (a:string) => 'hello';
const MyAddingMethod : SayMethod = {
    say : SayFunc                           //이변성
}
const MyAddingFunction : SayFunction = {
    say : SayFunc                           //반공변성
}
const MyAddingCall : SayCall = {
    say : SayFunc                           //반공변성
}

  • 기본적으로 sayFunc 함수의 타입은 (a : string) => string임 
    • 반공변성에 의해 (a : string | number) => string에 대입 불가 (더 넓은 매개변수에 대입 X)
  • 하지만, SayMethod 타입의 경우 에러 발생 X <- 선언 방법의 차이
    • '함수(매개변수) : 반환값'                   // 이변성 가짐
    • '함수 : (매개변수) => 반환값'            // 반공변성

 

 

 

2.20 클래스는 값이면서 타입이다

: 클래스에 존재하는 특징 알아보기

 

타입스크립트에서의 클래스 코딩

class Person {
    name : string;
    age : number;
    married : boolean;
    constructor(name : string, age : number, married : boolean) {
        this.name = name;
        this.age = age;
        this.married = married;
    }
}

 

자바스트립트 코드

class Person {
    constructor(name, age, married) {
        this.name = name;
        this.age = age;
        this.married = married;
    }
}
  • 차이점 : 타입스크립트는 name, age, married 같은 멤버를 클래스 내부에 한 번 적어야 함

 

class Person {
    name;
    age ;
    married;
    constructor(name : string, age : number, married : boolean) {
        this.name = name;
        this.age = age;
        this.married = married;
    }
}
  • 멤버의 타입은 생략 가능
    • 생성자 함수를 통해 알아서 추론

 

클래스 표현식으로 선언하기

const Person = class{
    name;
    age ;
    married;
    constructor(name : string, age : number, married : boolean) {
        this.name = name;
        this.age = age;
        this.married = married;
    }
}

 

 

멤버는 항상 constructor 내부와 짝이 맞아야함

const Person = class{
    name: string;
    married : boolean;
    constructor(name : string, age : number, married : boolean) {
        this.name = name;
        this.age = age;
    }
}

  • married : 멤버로만 선언하고 생성자 안에서 할당되지 않았다는 에러
  • age : 멤버로 선언되지 ㅇ낳고, 생성자에서 만들어 age 속성이 클래스 안에 없다는 에러

 

 

interfaceimplements 예약어를 사용해 더 엄격하게 멤버 검사하기

interface Human {
    name: string;
    age : number;
    married : boolean;
    sayName() : void;
}

class Person implements Human {
    name;
    age;
    married;
    constructor(name : string, age : number, married : boolean) {
        this.name = name;
        this.age = age;
        this.married = married
    }
}

  • Human 인터페이스 생성
  • Person 클래스가 Human 인터페이스를 implements함
    • 인터페이스 안 satName 메소드를 구현하지 않아 에러

 

생성자 함수 방식으로 객체 만들기 불가

interface PersonInterface {
    name: string;
    age : number;
    married : boolean;
}
function Person(this: PersonInterface,name: string, age : number, married : boolean) {
    this.name = name;
    this.age = age;
    this.married = married
}

new Person('zero', 28, false)

  • 클래스가 new를 붙여 호출할 수 있는 유일한 객체

 

클래스 자체의 타입이 필요하면 'typeof 클래스이름'

const person1 : Person = new Person ('zero', 28, false)
const P:typeof Person = Person
const person2= new P('nero', 32, true)

 

 

클래스 멤버로 다양한 수식어 추가하기

class Parent {
    name? : string;                         //옵셔널 수식어
    readonly age : number;                  //readonly 수식어
    protected married: boolean;             //protected 수식어
    private value : number;                 //private 수식어
    constructor(name: string, age: number, married: boolean) {
        this.name = name;
        this.age = age;
        this.married = married
        this.value = 0;
    }
    changeAge (age: number) {
        this.age = age;                     //age는 readonly -> 변경불가
    }
}

class Child extends Parent {
    constructor(name:string, age: number, married: boolean){
        super(name, age, married)
    }
    SayName(){
        console.log(this.name)
    }
    SayMarried(){
        console.log(this.married)
    }
    SayValue(){
        console.log(this.value)            //private 멤버인 value는 Parent 클래스 외 사용 불가
    }
}

const child = new Child ('zero', 28, false)
child.name;
child.married;                  //protected 속성은 인스턴스에선 사용 불가
child.value;                    //private 멤버인 value는 Parent 클래스 외 사용 불가

  • public 속성
    • 선언한 자신의 클래스, 자손 클래스, new 호출로 만들어낸 인스턴스에서 속성 사용 가능, 위 예의 name
  • protected 속성
    • 자신의 클래스와 자손 클래스에서는 속성을 사용할 수 있음
    • 인스턴스에서는 사용 불가 (married 속성)
  • privated 속성
    • 자신의 클래스에서만 속성 사용 가능 (value 속성)

 

 

privated 속성을 선언하는 두가지 방법

  • private
  • # : private field
class PrivateMember {
    private priv : string = 'priv';
}

class ChildPrivateMember extends PrivateMember {
    private priv : string = 'priv';
}

class PrivateField {
    #priv : string = 'priv';
    sayPriv(){
        console.log(this.#priv)
    }
}

class ChildPrivateField extends PrivateField {
    #priv : string = 'priv'
}

  • 차이점
    • private 수식어로 선언한 속성은 자손 클래스에서 같은 이름으로 선언 불가 -> childPrivateMember 오류
    • #priv는 에러 X
  • (저자) #priv 선호
    • 자바스크립트의 원래 기능가 더 가까움

 

 

implements하는 속성은 전부 public이어야 함

interface Human {
    name: string;
    age : number;
    married : boolean;
}

class Person implements Human {
    name;
    protected age;
    married;
    constructor(name : string, age : number, married : boolean) {
        this.name = name;
        this.age = age;
        this.married = married
    }
}

  • 애초에 인터페이스 속성은 protected, private 불가
  • implements한 클래스에서도 인터페이스 속성들은 모두 public

 

override 수식어 활용

class Human {
    eat() {
        console.log("냠냠");
    }
    sleep() {
        console.log("쿨쿨");
    }
}

class Employee extends Human {
    work() {
         console.log("끙차");
    }
    sleep() {
         console.log("에고고");
    }
}

  • Employee의 sleep 메서드는 Human의 sleep 메서드를 오버라이드 시도
  • 오류 이유 : 명시적으로 오버라이드할 경우 앞에 override 수식어 붙이기

 

(해결)

class Human {
    eat() {
        console.log("냠냠");
    }
    sleap() {
        console.log("쿨쿨");
    }
}

class Employee extends Human {
    work() {
         console.log("끙차");
    }
    override sleap() {
         console.log("에고고");
    }
}

 

 

override 수식어 장점

  • 부모 클래스의 메서드가 바뀔 때, 확인 가능
  • 아래 코드 : 부모클래스 sleap을 sleep으로 바꿈
class Human {
    eat() {
        console.log("냠냠");
    }
    sleep() {
        console.log("쿨쿨");
    }
}

class Employee extends Human {
    work() {
         console.log("끙차");
    }
    override sleap() {
         console.log("에고고");
    }
}

  • 없는 메서드를 오버라이드 했다는 오류

 

오버로딩

클래스의 생성자 함수에 오버로딩 적용하기

class Person {
    name? : string;
    age? : number;
    married? : boolean;
    constructor();
    constructor(name: string, married: boolean);
    constructor(name: string, age:number ,married: boolean);
    constructor(name?: string, age?: boolean|number ,married?: boolean){
        if (name){
            this.name = name;
        }
        if (typeof age === 'boolean'){
            this.married = age;
        } else {
            this.age = age;
        }
        if (married) {
            this.married = married
        }
    }
}

const person1 = new Person();
const person2 = new Person('nero', true);
const person3 = new Person('zero', 28, false);
  • 타입 선언 여러 번
  • 함수의 구현은 한 번만
    • 구현에서 여러 번 타입 선언한 것드에 대해 모두 대응

 

클래스의 속성에 인덱스 시그니처 사용

class Signature {
    [propName : string] : string | number | undefined;
    static [propName : string] : boolean;
}

const sig = new Signature();
sig.hello = 'world';
Signature.isGood = true;
  • static 속성에서도 인덱스 시그니처 가능, 속성 자유롭게 추가 가능

 

클래스나 인터페이스 메서드에서는 this를 타입으로 사용 가능

class Person{
    age: number;
    married : boolean;
    constructor(age: number, married: boolean) {
        this.age= age;
        this.married = married;
    }
    sayAge() {
        console.log(this.age);		//this: this
    }
    sayMarried(this: Person) {
        console.log(this.married);	//this: Person
    }
    sayCallback(callback: (this:this) => void) {
        callback.call(this)
    }
}
  • 기본적으로 this는 클래스 자신
  • sayMarried : 명시적으로 this 타이핑
  • sayCallback 
    • 매개변수로 콜백함수
    • 콜백함수의 this : this -> 콜백 함수의 this 타입이 Person 인스턴스가 됨
class A{
    callbackWithThis (cb : (this:this) => void){
        cb.call(this);
    }
    callbackWithoutThis (cb : () => void){
        cb();
    }
}

new A().callbackWithThis(function() {
    this;       //this: A
})

new A().callbackWithoutThis(function() {
    this; 
})

  • 클래스 메서드의 콜백함수에 this 타이핑 유무에 따라 메서드 호출 시 this의 타입이 달라짐
  • 콜백함수에서 this을 사용하고 싶다면 this 타이핑 필요
  • this가 클래스 자신이면 this:this 로 타이핑

 

인터페이스로 클래스 생성자 타이핑 가능

  • 앞에 new 연산자 추가
interface PersonConstructor {
    new (name : string, age : number) : {
        name: string;
        age : number;
    }
}

class Person {
    name: string;
    age: number;
    constructor (name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}

function createPerson (ctor: PersonConstructor, name: string, age: number){
    return new ctor(name, age);			//인터페이스 앞에 new 붙여 호출
}

createPerson(Person, 'zero', 28)

 

 

위를 활용해 타입스크립트에서도 생성자 함수 사용 가능 -> 클래스가 있는데 굳이..?

 

 

 

2.20.1 추상 클래스

: implements보다 조금 더 구체적으로 클래스 모양 정의하는 방법

 

abstract class AbstractPerson {
    name : string;
    age : number;
    married : boolean = false;
    abstract value : number;

    constructor(name: string, age:number ,married: boolean){
        this.name = name;
        this.married = married;
        this.age = age;
    }
    sayName() {
        console.log(this.name);
    }
    abstract sayAge():void
    abstract sayMarried():void
}

class RealPerson extends AbstractPerson {
    sayAge(){
        console.log(this.age)
    }
}

  • abstract class로 선언
    • abstract 클래스의 속성과 메서드는 abstract일 수 있음
      • 이 경우 실제 값은 없고 타입 선언만 되어 있음
    • RealPerson 클래스는 AbstractPerson 클래스를 상속
      • 반드시 abstract 속성이나 메소드 구현
      • sayAge만 구현해 나머지도 구현하라는 오류

(해결) RealPerson 수정

abstract class AbstractPerson {
    name : string;
    age : number;
    married : boolean = false;
    abstract value : number;

    constructor(name: string, age:number ,married: boolean){
        this.name = name;
        this.married = married;
        this.age = age;
    }
    sayName() {
        console.log(this.name);
    }
    abstract sayAge():void
    abstract sayMarried():void
}

class RealPerson extends AbstractPerson {
    value : number = 0;
    sayAge(){
        console.log(this.age)
    }
    sayMarried() {
        console.log(this.married)
    }
}

 

 

 

 

2.21 enum은 자바스크립트에서도 사용할 수 있다.

: erum(열거형) 타입

: 자바스크립트엔 없는 타입이지만, 자바스크립트의 값으로 사용 가능한 특이한 타입

: 목적 - 여러 상수 나열

 

방법 - enum 예약어로 선언

enum Level1 {
    NOVICE,
    INTERMEDIATE,
    ADANCED,
    MASTER
}
  • 멤버(member) : NOVICE와 같은 enum 안에 존재하는 이름

 

enum은 자바스크립트 코드로 남음

var Level1;
(function (Level1) {
    Level1[Level1["NOVICE"] = 0] = "NOVICE";
    Level1[Level1["INTERMEDIATE"] = 1] = "INTERMEDIATE";
    Level1[Level1["ADANCED"] = 2] = "ADANCED";
    Level1[Level1["MASTER"] = 3] = "MASTER";
})(Level1 || (Level1 = {}));

 

Level1[Level1["NOVICE"] = 0] = "NOVICE";

=

Level1[0]="NOVICE" 와 Level1["NOVICE"]=0

 

즉 위 코드는

var Level1 = {
    0: "NOVICE",
    1: "INTERMEDIATE",
    2: "ADANCED",
    3: "MASTER",
    NOVICE: 0,
    INTERMEDIATE: 1,
    ADANCED: 2,
    MASTER: 3,
};
  • enum은 멤버의 순서대로 0부터 숫자 할당
  • 0 대신 다른 숫자 할당을 원할 경우 = 연산자 사용
enum Level1 {
	NOVICE = 3,
    INTERMEDIATE,       //4
    ADANCED = 7,
    MASTER,            //8
}
  • INTERMEDIATE와 MASTER 같이 할당하지 않은 경우
    • 이전 값에 1을 더한 값이 할당

 

문자열 할당도 가능

enum Level1 {
	NOVICE ,            //0
    INTERMEDIATE="hello",
    ADANCED = "oh",
    MASTER,            //8
}

  • 한 멤버를 문자열로 할당한 다음부턴 전부 직접 값 할당 필요 / 안하면 오류

 

enum 타입의 속성은 값으로 활용 가능

enum Level {
	NOVICE ,  
    INTERMEDIATE,
    ADANCED,
    MASTER,   
}

const a = Level.NOVICE          //0
const b = Level[Level.NOVICE]   //NOVICE
  • enum[enum_멤버] : enum의 멤버 이름을 가져오는 방법

 

enum은 값보단 타입으로 더 많이 사용

enum Level {
	NOVICE ,  
    INTERMEDIATE,
    ADANCED,
    MASTER,   
}

function whatYourlevel(level : Level) {
    console.log(Level[level]);				//ADANCED
}

const myLevel = Level.ADANCED;
whatYourlevel(myLevel)
  • 매개변수의 타입으로 enum 사용
    • 멤버의 유니언과 비슷한 역할
    • Level.NOVICE | Level.INTERMEDIATE | Level.ADANCED | Level.MASTER
  • enum의 멤버를 사용해 함수 호출

 

enum의 불완전함

enum Role {
	USER,
    GUEST,
    ADMIN
}

enum Role2 {
	USER = "USER",
    GUEST ="GUEST",
    ADMIN = "ADMIN"
}

function changeUserRol (rol : Role) {}
function changeUserRol2 (rol : Role2) {}

changeUserRol(2)
changeUserRol(4)            //  옳은 오류
changeUserRol2(Role2.USER)
changeUserRol2("USER")      // 오류 : 불완전한 예
  • enum의 멤버인 USER을 넣었음에도 오류 -> 불완전함

 

enum의 사용 - 브랜딩

enum Money {
    WON,
    DOLLAR,
}

interface WON {
    type : Money.WON,
}

interface DOLLAR {
    type : Money.DOLLAR,
}

function moneyOrDollar(param : WON | DOLLAR) {
    if (param.type ===  Money.WON) {
        param;              //(parameter) param : Won
    }
    else {
        param;              //(parameter) param : DOLLAR
    }
}
  • 브랜드 속성으로 enum 멤버 사용
  • 같은 enum의 멤버여야 서로 구분 가능

 

enum Money {
    WON,
}

enum Water {
    LITER,
}

interface M {
    type : Money.WON,
}

interface N {
    type : Water.LITER,
}

function moneyOrLiter(param : M | N) {
    if (param.type ===  Money.WON) {
        param;             
    }
    else {
        param;             
    }
}

moneyOrLiter({type: Money.WON})     //Money
moneyOrLiter({type: Water.LITER})   //Water
console.log(Money.WON)              //0
console.log(Water.LITER)            //0
  • 다른 enum을 사용해 비교할 경우 위와 같이 둘 다 0으로 else 문으로 가지 않음

 

enum을 사용하면서 자바스크립트 코드는 생성되지 않게 하기

  • const enum 사용
const enum Money {
    WON,
    DOLLAR
}

Money.WON;          //1
Money[Money.WON]    //Money라는 자바스크립트 객체가 없어 에러

 

 

 

 

2.22 infer로 타입스크립트의 추론을 직접 활용하자.

: infer 예약어 - 타입스크립트의 추론 기능을 극한까지 활용하는 기능 / 컨디셔널 타입과 함께 사용

: Element<number> extends Element<infer U>와 같은 타입을 작성하면, U타입은 number타입으로 추론(infer)

 

type El<T> = T extends (infer E)[] ? E : never;
type Str = El<string[]>;                    //string
type NumOrBool = El<(number | boolean)[]>;  //number | boolean
  • El 타입에서 infer 활용
  • 추론 맡기고 싶은 부분 : 'infer 타입_변수'
    • E가 타입 변수
    • 타입 변수는 참 부분에만 사용

 

infer의 다양한 예시

type MyParameters<T> = T extends (...args: infer P) => any ? P : never;
type MyConstructorParameters<T> = T extends abstract new (...args: infer P) => any? P : never;
type MyReturnType<T> = T extends (...args: any) => infer R ? R : any;
type MyInstanceType<T> = T extends abstract new (...args : any) => infer R ? R : any;

type P = MyParameters<(a:string, b: number)=> string>               //함수의 매개변수 추론 / 결과 : type P = [a : string, b : number]
type R = MyReturnType<(a:string, b: number)=> string>               //함수의 반환값 추론 / string
type CP = MyConstructorParameters<new (a:string, b:number) => {}>   //[a : string, b : number]
type I = MyInstanceType<new (a:string, b:number) => {}>             //{}
  • [a : string, b: number]은 [string, number]로 보면 됨

 

 

서로 다른 타입 변수들을 여러개 동시에 사용하기

type MyPAndR<T> = T extends (...args: infer P) => infer R ? [P,R] : never;
type PR = MyPAndR<(a:string, b:number) => string>;
  • 매개변수는  P, 반환값은 R로 추론

 

 

같은 타입 변수를 여러 곳에 사용하기

type Union<T> = T extends {a: infer U, b: infer U} ? U : never;
type Result1 = Union<{ a:1|2 , b: 2|3}>                                  // 1|2|3

type Intersection<T> = T extends {
    a: (pa:infer U) => void,
    b: (pb:infer U) => void
} ? U : never;

type Result2 = Intersection<{a(pa: 1|2):void, b(pb: 2|3) : void}>       //2
  • Union<T>
    • 1|2|3 : 같은 이름의 속성 타입 변수는 유니온
  • Intersection<T>
    • a와 b가 메소드
    • 매개변수의 반공변성 특성으로 같은 이름의 타입 변수는 인터섹션

 

 

(볼 일 별로 X) 같은 타입 변수 중 하나는 매개변수, 하나는 반환값인 경우

type ReturnAndParam<T> = T extends {
    a:() => infer U,
    b:(pb: infer U) => void,
} ? U : never;

type Result3 = ReturnAndParam<{a:() => 1|2, b(pb:1|2|3):void }>     //1|2
type Result4 = ReturnAndParam<{a:() => 1|2, b(pb:2|3):void }>       //never
  • Result3
    • 반환값 타입(a)이 매개변수의 타입(b)의 부분집합인 경우 -> 교집합
  • Result4 : 그 외의 경우 모두 never

 

유니언을 인터섹션으로 만드는 타입 작성

type UnionToIntersection<U> 
= (U extends any ? (p:U) => void : never) extends (p:infer I) => void
    ? I
    : never;

type Result5 = UnionToIntersection<{a:number}|{b:string}>;  //{a:number;} & {b:string;}
type Result6 = UnionToIntersection<boolean | true>;         //never
  • U: 제네릭이자 유니온 -> 분배법칙 실행
  • Result5 
    • UnionToIntersection<{a:number}> | UnionToIntersection<{b:string}>
    • I 는 각각 추론에 따라 {a:number}, {b:string}이 됨
    • I는 매개변수이므로 인터섹션이 실해돼 {a:number}&{b:string}
  • Result6
    • <boolean| true> -> true | false | true 임
    • 매개변수이므로 true & false & true가 되므로 never 됨