엘리스 25일차 금요일 온라인강의 Generic

치즈말랑이·2022년 5월 8일
0

엘리스

목록 보기
26/47
post-thumbnail

01

interface

  • 일반적으로 변수, 함수, 클래스에 타입 체크를 위해 사용된다.

  • 직접 인스턴스를 생성할 수 없고 모든 메소드가 추상 메소드이다.

  • 추상 클래스의 추상 메소드와는 달리 abstract키워드는 사용할 수 없다.

  • ES6는 인터페이스를 지원하지 않지만, TypeScript는 인터페이스를 지원한다.

Interface를 사용하는 이유

  • 타입의 이름을 짓고 코드 안의 계약을 정의하나다.
  • 프로젝트 외부에서 사용하는 코드의 계약을 정의하는 강력한 방법이다.
  • 다음과 같은 범주에 대해 계약을 정의할 수 있다.
    • 객체의 스펙 (속성과 속성의 타입)
    • 함수의 파라미터
    • 함수의 스펙(파라미터, 반환 타입 등)
    • 배열과 객체에 접근하는 방식
    • 클래스

Interface 사용 X

function hello(obj: {name: string}) {
	console.log(obj.name);
}

let person = {name: "ho"};

hello(person);

Interface 사용 O

interface Person {
	name: string
}
function hello(obj: Person) {
	console.log(obj.name);
}

let person = {name: "ho"};

hello(person);

Interface를 추가하여 함수 매개변수 프로퍼티를 정의할 수 있다.
정의한 프로퍼티 값을 누락하거나 정의하지 않는 값을 인수로 전달 시 컴파일 에러가 발생한다.

Properties

  • 컴파일러는 프로퍼티의 두가지 요소를 검사한다.
    • 필수요소 프로퍼티의 유무
    • 프로퍼티 타입
  • 다음 예약어로 프로퍼티를 세밀하게 컨트롤할 수 있다.
    • ? (Optional Properties)
    • readonly (Readonly properties)

Optional Properties

  • 프로퍼티 선언 시 이름 끝에 ?를 붙여서 표시한다.
  • 인터페이스에 속하지 않는 프로퍼티의 사용을 방지하면서, 사용 가능한 프로퍼티를 기술할 때 사용한다.
  • 객체 안의 몇 개의 프로퍼티만 채워 함수에 전달하는 "option bags" 같은 패턴에 유용하다.
interface person {
	name: string
  	phone?: number
  	email?: string
}

function createUser(user:person): {name: string, phone:number, email:string} {
	let newUser = {name: "newFace", phone:00000000000, email: ok@ok.com};
  	if (user.phone) {
    	newUser.phone = user.phone;
    }
  	if (user.email) {
    	newUser.email = user.email;
    }
  	return newUser;
}

let myUser = createUser({name: "faker"});

Readonly properties

  • 객체가 처음 생성될때만 값 설정이 가능하고, 이후 수정이 불가능하다.
  • 프로퍼티 이름 앞에 readonly를 붙여 사용한다.
interface Point {
	readonly x: number
  	readonly y: number
}

let point: Point = {x: 10, y: 20};
point.x = 5; // 에러

readonly vs const

  • 둘다 생성 후에 배열을 변경하지 않음을 보장한다.
  • 변수 - const , 프로퍼티 - readonly

interface types

  • TypeScript에서 인터페이스는 함수, 클래스에서 사용할 수 있다.
  • 함수
    • JavaScript객체가 가질 수 있는 넓은 범위의 형태를 기술한다.
    • 프로퍼티로 객체를 기술하는 것 외에도, 인터페이스는 함수 타입을 설명한다.
  • 클래스
    • 클래스가 특정 통신 프로토콜을 충족하도록 명시적으로 강제한다.
    • C#과 Java와 같은 언어에서 일반적으로 인터페이스를 사용하는 방법과 동일하다.

function type

  • 함수의 인자 타입과 반환 값 타입을 정의한다
  • 함수의 타입을 정의할 때도 사용
interface SearchFunc {
	(source: string, substring: string): boolean
}

// 변수로 직접 함수 값이 할당되기때문에 인수 타입 생략 가능
// TypeScript의 문맨상 contextual typing
let mySearch: SearchFunc
mySearch = function (src, sub) {
	let result = src.search(sub);
  	return result > -1;
  	// return "string" // 에러
}

class type

  • 클래스가 특정 계약을 충족하도록 명시적으로 강제한다.
interface Animal {
	makeSound(): void
}

class Dog implements Animal {
	makeSound(): void {
    	console.log("멍멍");
    }
}

interface 확장

interface Animal {
	makeSound(): void
}

interface Dog extends Animal {
	speed: number
}

class Bulldog implements Dog {
	makeSound(): void {
    	console.log("멍멍");
    }
}

hybrid type

interface Counter {
	(start: number): string
  	interval: number
  	reset(): void
}

function getCounter(): Counter{
	let counter = function(start: number) {} as Counter
    counter.interval = 123;
  	counter.reset = function () {}
  	return counter;
}

let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;

디자인 패턴 (Strategy pattern)

02 Generic

Generic

  • 정적 type 언어는 클래스나 함수를 정의할 때 type을 선언해야 한다.
    ex) c언어는 int type변수를 선언하면 정수형 값만 할당할 수 있다.
  • Generic은 코드를 작성할 때가 아니라 코드가 수행될 때 타입을 명시한다.
  • 코드를 작성할 때 식별자를 써서 아직 정해지지 않은 타입을 표시한다.
    일반적으로 식별자는 T, U, V를 사용한다.
    필드 이름의 첫 글자를 사용하기도 한다.

Generic을 사용하는 이유

  • 재사용성이 높은 함수와 클래스를 생성할 수 있다.
    여러 타입에서 동작이 가능하다. (한번의 선언으로 다양한 타입에 재사용할 수 있다.)
    코드의 가독성이 향상된다.
  • 오류를 쉽게 포착할 수 있다.
    • any 타입을 사용하면 컴파일 시 타입을 체크하지 않는다.
      타입을 체크하지 않아 관련 메소드의 힌트를 사용할 수 없다.
      컴파일 시에 컴파일러가 오류를 찾지 못한다.
  • Generic도 any처럼 미리 타입을 지정하지는 않지만 타입을 체크해 컴파일러가 오류를 찾을 수 있다.

Generic으로 함수와 클래스 만들기

  • function
function sort<T>(items: T[]): T[] {
	return items.sort();
}

const nums: number[] = [1, 2, 3, 4];
const chars: string[] = ["a", "b", "c", "d"];

sort<number>(nums);
sort<string>(chars);
  • class
class Queue<T> {
	protected data: Array<T> = [];
  	push(item: T) {
    	this.data.pupsh(item);
    }
  	pop(): T|undefined {
    	return this.data.shift();
    }
}

const numberQueue = new Queue<number>();

numberQueue.push(0);
numberQueue.push("1"); // 사전 검출 가능
numberQueue.push(+"1"); // 사전 검출 가능

Union type

  • Union type
    "|" 를 사용해 두개 이상의 타입을 선언하는 방식
  • Union과 Generic 모두 여러 타입을 다룰 수 있다.
    Union은 선언한 공통된 메소드만 사용할 수 있다.
    리턴 값이 하나의 타입이 아닌 선언된 union타입으로 지정된다.
1. Union type
const printMessage = (message: string|number) => {
	return message;
}

const msg1 = printMessage(1234);
const msg2 = printMessage("hello");

msg1.length; // 에러 -> string과 number type의 공통된 메소드만 사용 가능

제약조건 (constraints / keyof)

  • 원하지 않는 속성에 접근하는 것을 막기 위해 Generic에 제약조건을 사용한다.
  1. Constraints: 특정 타입들로만 동작하는 Generic함수를 만들때 사용한다.
  2. Keyof: 두 객체를 비교할때 사용한다.

Constraints

  • Generic T에 제약조건을 설정한다. (문자열 or 숫자)
  • 제약조건에 벗어나는 타입을 선언하면 에러가 발생한다.
const msg = <T extends string|number> (message:T):T => {
	return message;
}

msg<string>("1");
msg<number>(1);
msg<boolean>(false); // 에러

keyof

const pro = <T extends object, U extends keyof T> (obj:T, key: U) => {
	return obj[key]
}

pro({a:1, b:2, c:3}, "a");
pro({a:1, b:2, c:3}, "z"); // 에러
  • 두번째 함수에서 오류 발생
  • Generic T는 키 값이 a, b, c만 존재하는 object이다
  • U의 값인 z가 Generic T의 키 값 중 존재하지 않기 때문에 오류가 발생한다.

디자인패턴 (Factory Pattern with Generics)

  • 객체를 생성하는 인터페이스만 미리 정의하고, 인스턴스를 만들 클래스의 결정은 서브클래스가 내리는 패턴
  • 여러개의 서브 클래스를 가진 슈퍼클래스가 있을 때 입력에 따라 하나의 서브 클래스의 인스턴스를 반환한다.
interface Car {
	drive(): void
  	park(): void
}

class Bus implements Car {
	drive(): void{}
  	park(): void{}
}
class Taxi implements Car {
	drive(): void {}
  	park(): void {}
}
class Suv implements Car {
	drive(): void{}
  	park(): void{}
}

export class Car Factory{
	static getInstance<T extends Car> (type: {new(): T}): T {
    	return new type();
    }
}

const bus = CarFactory.getInstance(Bus);
const taxi = CarFactory.getInstance(Taxi);
profile
공부일기

0개의 댓글