👩🏻‍💻 TIL

2022_02_09_TIL

ji-hyun 2022. 2. 10. 23:50

인터페이스는 타입들의 이름을 짓는 역할을 하고 코드 안의 계약을 정의하는 것 뿐만 아니라 프로젝트 외부에서 사용하는 코드의 계약을 정의하는 강력한 방법이다.

 

 

function printLabel(labeledObj: {label: string}){
	console.log(labeledObj.label);
    }
    
let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);

 

타입 검사는 printLabel 호출을 확인합니다. printLabel 함수는 string 타입 label을 갖는 객체를 하나의 매개변수로 가집니다. 이 객체가 실제로는 더 많은 프로퍼티를 갖고 있지만, 컴파일러는 최소한 필요한 프로퍼티가 있는지와 타입이 잘 맞는지만 검사합니다. TypeScript가 관대하지 않은 몇 가지 경우는 나중에 다루겠습니다.

 

 

 

이번엔 같은 예제를, 문자열 타입의 프로퍼티 label 을 가진 인터페이스로 다시 작성해보겠습니다.

 

interface LabeledValue {
	label: string;
}

function printLabel(labeledObj: LabeledValue){
	console.log(labeledObj.label);
}

let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);

 

 

 

 

선택적 프로퍼티

인터페이스의 모든 프로퍼티가 필요한 것은 아닙니다. 어떤 조건에서만 존재하거나 아예 없을 수도 있습니다. 선택적 프로퍼티들은 객체 안의 몇 개의 프로퍼티만 채워 함수에 전달하는 "option bags" 같은 패턴을 만들 때 유용합니다.

 

interface SquareConfig {
    color?: string;
    width?: number;
}

function createSquare(config: SquareConfig): {color: string; area: number} {
    let newSquare = {color: "white", area: 100};
    if (config.color) {
        newSquare.color = config.color;
    }
    if (config.width) {
        newSquare.area = config.width * config.width;
    }
    return newSquare;
}

let mySquare = createSquare({color: "black"});

 

선택적 프로퍼티를 가지는 인터페이스는 다른 인터페이스와 비슷하게 작성되고, 선택적 프로퍼티는 선언에서 프로퍼티 이름 끝에 ? 를 붙여 표시합니다.

 

선택적 프로퍼티의 이점인터페이스에 속하지 않는 프로퍼티의 사용을 방지하면서, 사용 가능한 속성을 기술하는 것입니다.

 

 

interface SquareConfig {
    color?: string;
    width?: number;
}

function createSquare(config: SquareConfig): { color: string; area: number } {
    let newSquare = {color: "white", area: 100};
    if (config.clor) {
        // Error: Property 'clor' does not exist on type 'SquareConfig'
        newSquare.color = config.clor;
    }
    if (config.width) {
        newSquare.area = config.width * config.width;
    }
    return newSquare;
}

let mySquare = createSquare({color: "black"});

 

 

 

 

읽기전용 프로퍼티 (Readonly properties)

일부 프로퍼티들은 객체가 처음 생성될 때만 수정 가능해야 합니다. 프로퍼티 이름 앞에 readonly 를 넣어서 이를 지정할 수 있습니다.

interface Point {
    readonly x: number;
    readonly y: number;
}

 

객체 리터럴을 할당하여 Point 를 생성합니다. 할당 후에는 x, y 를 수정할 수 없습니다.

 

let p1: Point = { x: 10, y: 20 };
p1.x = 5; // 오류!

 

"Cannot assign to 'x' because it is a read-only property."

 

 

 

 

 

타입스크립트에서는 모든 변경 메서드(Mutating Methods) 가 제거된 Array<T> 와 동일한 ReadonlyArray<T> 타입을 제공합니다. 그래서 생성 후에 배열을 변경하지 않음을 보장할 수 있습니다.

 

 

"Index signature in type 'readonly number[]' only permits reading"

"Property 'push' does not exist on type 'readonly number[]"

"The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'"

 

 

 

예제 마지막 줄에서 ReadonlyArray를 일반 배열에 재할당이 불가능한 것을 확인할 수 있습니다. 타입 단언(type assertion)으로 오버라이드하는 것은 가능합니다:

 

a = ro as number[];

 

 

1. 오버로딩이란

메소드 이름, 반환 타입과는 상관 없이 메소드 이름이 같다면 매개변수의 타입과 개수에 의해 호출되는 메소드가 결정되는 것입니다.

위의 같은 경우는 메소드 오버로딩이라 하는데 생성자 오버로딩도 존재합니다.

 

생성자 오버로딩은 메소드 오버로딩과 같습니다. 생성자도 하나의 메소드처럼 생각하면 되는데 객체를 생성하면서 초깃값으로 설정해주는 생성자에도 오버로딩이 가능합니다. 클래스 내에 생성자를 매개변수에 따라 각각 정의해준다면 객체를 생성할 때 넘겨주는 파라미터의 값에 따라 생성자가 호출되는 것입니다.

 

오버로딩 조건

1. 메소드 이름이 같아야 한다.

2. 매개변수의 개수 또는 타입이 달라야 한다. 당연히 매개변수로 전달되는 인자의 순서도 같아야 한다.

3. 매개변수는 같고 반환 타입이 다른 경우는 오버로딩이 성립되지 않는다.

 

 

대표적인 예로 우리가 자주 사용하는 printIn() 메소드가 있습니다. 우리는 메소드 이름은 printIn 하나만 사용하지만 int 형을 출력할 때와 String 형을 출력할 때 모두 같은 기능으로 출력됩니다. 이는 메소드 이름은 같으나 매개변수로 전달되는 인자 타입이 다르기 때문에 오버로딩을 통한 메소드 호출입니다.

 

 

2. 오버라이드란 

프로그래밍에서는 오버라이드를 덮어씌우는 것으로 생각하면 좋습니다.

예를 들어 부모 클래스와 자식 클래스 사이에서 부모 클래스의 메소드를 똑같이 가져와 사용하는 것입니다.

 

분명 상속 관계에서는 부모 클래스의 리소스를 자식 클래스가 사용할 수 있다 했는데 같은 리턴 타입, 메소드명, 매개변수를 가지고 재정의하여 사용했습니다. 이것이 바로 오버라이드라는 것인데 부모 클래스가 정의한 함수를 덮어씌워 다시 정의하여 사용하는 것이죠.

 

 

 

하지만 여기서 오버라이드를 한다고 부모 클래스의 기능이 사라지는 것이 아닙니다.

 

class Example {
    public static void main(String[] args) {
        Terran b = new Marine();
        System.out.println(b.tell());
        System.out.println(b.getResource());
    }
}

 

다음과 같이 Marine 객체를 생성하여 tell() 메소드를 호출하면 Marine 클래스에 정의된 메소드가 호출합니다.

그 말은 즉 Barrack 객체를 생성하여 tell() 메소드를 호출한다면 Barrack 클래스에 정의된 메소드가 호출된다는 것이죠.

 

 

이런 식으로 부모 클래스의 메소드를 사용할 수 있어도 자식 클래스에서 변경해야 할 상황이 발생한다면 오버라이드를 통해 자식 클래스에서만 새로운 기능으로 재정의할 수 있습니다....!

 

 

 

오버라이드 조건/장점

1. 조건

- 부모 클래스와 자식 클래스 사이에서만 성립됩니다.

2. static 메소드는 클래스에 속하는 메소드이기 때문에 상속되지 않고 오버라이드 되지도 않습니다.

: 이 부분은 static 이 객체가 생성될 때가 아니라 클래스가 생성되고 한번만 생성된다는 개념을 숙지하셨다면 금방 이해가 갈 것입니다.

3. private 의 접근제어자를 가진 메소드는 상속 자체가 되지 않아 오버라이드도 성립되지 않습니다.

4. interface 를 구현하여 오버라이드할 때는 반드시 public 접근 제어자를 사용해야 합니다.

5. 오버로드와 달리 리턴 타입, 메소드명, 매개변수 패턴이 모두 같아야 합니다!!!!!!

6. 부모 클래스의 메소드의 접근 제한자 범위보다 작아질 수 없고 확장은 가능합니다.

... 등등

 

 

장점은 오버로딩의 장점과 비슷하다. 

다만 오버라이드는 메소드 하나로 여러 객체를 다루고 객체마다 다른 기능을 사용할 수 있다는 장점이 있습니다.

 

 

 

다형성이란 하나의 클래스나 함수가 다양한 방식으로 동작하는 것을 의미합니다. 오버로딩에서는 하나의 메소드로 여러 동작을 할 수 있었고, 오버라이드에서는 여러 클래스의 다른 기능을 하나의 메소드로 제어할 수 있었습니다.

 

 

오버로딩

사전적 의미의 오버로딩은 과적이라는 뜻

무엇을 로딩할 때 기존 것을 덮어서 로딩한다고 해석

 

오버라이드

기각하다, 중단시키다

상속관계에서만 적용되는 특징

부모 클래스에 있는 메소드를 덮고 자식 클래스에서 정의한 것으로 대체한다.

 

 

출처

https://programmingnote.tistory.com/29

 

8. 오버로딩, 오버라이드 : Overloading, Override & 다형성

이번 포스팅에서는 저번 포스팅 마지막에 언급된 오버라이드와 유사한 단어로 헷갈릴 수 있는 오버로딩에 대해 이야기하겠습니다. ==================================================== Overloading vs Overrid..

programmingnote.tistory.com

 

 

초과 프로퍼티 검사

인터페이스의 첫번째 예제에서 타입스크립트가 label: string 을 기대해도 size:number; label: string 을 허용해주었습니다.

또한 선택적 프로퍼티를 배우고 유용하다는 것을 배웠습니다.

 

하지만 그냥 그 둘을 결합하면 에러가 발생할 수 있습니다. 예를 들어

 

interface SquareConfig {
    color?: string;
    width?: number;
}

function createSquare(config: SquareConfig): { color: string; area: number } {
    // ...
}

let mySquare = createSquare({ colour: "red", width: 100 });

 

 

 

width 프로퍼티는 적합하고, color 프로퍼티는 없고, 추가 colour 프로퍼티는 중요하지 않기 때문에, 이 프로그램이 올바르게 작성되었다고 생각할 수 있습니다.

 

 

타입스크립트는 이 코드에 버그가 있을 수 있다고 생각합니다

객체 리터럴은 다른 변수에 할당할 때나 인수로 전달할 때, 특별한 처리를 받고, 초과 프로퍼티 검사를 받습니다.

만약 객체 리터럴이 대상 타입이 갖고 있지 않은 프로퍼티를 갖고 있으면, 에러가 발생합니다.

 

 

// error: Object literal may only specify known properties, but 'colour' does not exist in type 'SquareConfig'. Did you mean to write 'color'?
let mySquare = createSquare({ colour: "red", width: 100 });

 

 

이 검사를 피하는 방법은 정말 간단합니다.

가장 간단한 방법은 타입 단언을 사용하는 것입니다.

 

let mySquare = createSquare({width: 100, opacity: 0.5} as SquareCoding);

 

하지만 특별한 경우에, 추가 프로퍼티가 있음을 확신한다면, 문자열 인덱스 서명(string index signatuer)을 추가하는 것이 더 나은 방법입니다. 만약 SquareConfig color width 프로퍼티를 위와 같은 타입으로 갖고 있고, 또한 다른 프로퍼티를 가질 수 있다면, 다음과 같이 정의할 수 있습니다.

 

더이상 이해하기가 어렵다. 일단 여기까지 공부

 

 

 

 

제네릭

잘 정의되고 일관된 API 뿐만 아닌 재사용 가능한 컴포넌트를 구축하는 것도 소프트웨어 엔지니어링에서 주요한 부분입니다.

현재의 데이터와 미래의 데이터 모두를 다룰 수 있는 컴포넌트는 거대한 소프트웨어 시스템을 구성하는데 있어 가장 유연한 능력을 제공할 것입니다.

 

C# 과 JAVA 같은 언어에서, 재사용 가능한 컴포넌트를 생성하는 도구상자의 주요 도구 중 하나는 제네릭입니다.

즉, 단일 타입이 아닌 다양한 타입에서 작동하는 컴포넌트를 작성할 수 있습니다.

사용자는 제네릭을 통해 여러 타입의 컴포넌트나 자신만의 타입을 사용할 수 있습니다.

 

function identity(arg: any): any {
    return arg;
}

 

any 를 쓰는 것은 함수의 arg 가 어떤 타입이든 받을 수 있다는 점에서 제네릭이지만, 실제로 함수가 반환할 때 어떤 타입인지에 대한 정보는 잃게 됩니다. 만약 number 타입을 넘긴다고 해도 any 타입이 반환된다는 정보만 얻을 뿐입니다.

 

 

대신에 우리는 무엇이 반환되는지 표시하기 위해 인수의 타입을 캡쳐할 방법이 필요합니다.

여기서는 값이 아닌 타입에 적용되는 타입 변수를 사용할 것입니다.

 

function identity<T>(arg: T): T {
    return arg;
}

 

identity 함수에 T 라는 타입 변수를 추가했습니다.

T 는  유저가 준 인수의 타입

 

 

'👩🏻‍💻 TIL' 카테고리의 다른 글

JavaScript / TypeScript fundamental  (0) 2022.03.16
프리즈마, 그래프큐엘의 yarn codegen  (0) 2022.02.21
기업협업에서 배운 것 정리  (0) 2022.01.17
GraphQL  (0) 2022.01.04
쿼리 루트 타입, mutation  (0) 2022.01.01