2020년 7월 25일 토요일

TypeScript Essence - Access Modifier

TypeScript Essence - Access Modifier

TypeScript Essence - Access Modifier 

 

Access Modifier

타입스크립트는 3가지 종류의 접근제어자를 제공합니다. 접근제어자를 명시하지 않으면 모두 public으로 간주합니다.

  • public
    누구나 접근할 수 있습니다. class 내부/외부에서 자유롭게 접근이 가능합니다.
  • protected
    class 내부와 상속받은 하위 class 내부에서 접근이 가능합니다.
  • private
    class 내부에서만 접근이 가능합니다.

constructor에 파라미터를 명시할 때, 접근제어자를 같이 명시하면 클래스 밑에 멤버변수가 선언된 것으로 간주합니다. readonly 키워드도 비슷한 역할을 수행합니다.

Readonly Properties

readonly 키워드를 이용해 객체가 처음 생성되는 시점에만 프로퍼티들을 수정가능하도록 설정할 수 있습니다. 한번 값이 세팅되면 그 후에는 수정할 수 없게됩니다.

if (true) {
  interface Point {
    readonly x: number;
    y: number;
  }

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

if (true) {
  let arr: number[] = [1, 2, 3, 4];
  let roArray: ReadonlyArray<number> = arr;

  roArray[0] = 10; // 코드 에러
  roArray.push(10); // 코드 에러

  arr = roArray; // 코드 에러
  arr = roArray as number[]; // 가능
}

TypeScript Essence - Class

TypeScript Essence - Class 

 

Class

타입스크립트는 ES6에서 도입한 클래스 문법을 그대로 사용하지 않고 일부를 변경하거나 조금 더 기능을 추가해서 사용합니다. 가장 큰 기능 확장은 이름에 맞게 추가적으로 타입을 정의해 놓고 사용하는 것이라 할 수 있습니다. ES6에서는 static 키워드를 함수에만 적용할 수 있지만 타입스크립트는 변수에도 적용할 수 있습니다.

타입스크립트는 클래스를 인터페이스처럼 사용하기도 합니다. 그렇기 때문에 interface가 클래스를 확장할 수도 있습니다.

ES6 Class Syntax

class Sedan {
  constructor(color = 'Red', doors = 4) {
    this.color = color;
    this.doors = doors;
  }
  show() {
    console.log(this.color, this.doors);
  }
}

let s = new Sedan('Blue', 2);
console.log(s);
s.show();

위 코드는 ECMAScript 표준문법에 맞게 작성된 제대로 작동하는 코드입니다. 그러나 .js 확장자를 .ts로 바꾸는 순간 여러 곳에서 에러가 있다가 불만을 표시할 것입니다.

Property 'color' does not exist on type 'Sedan'.
Property 'doors' does not exist on type 'Sedan'.

ES 스크립트 관점에서 이는 거짓입니다. 심지어 그대로 방치해도 트랜스파일링이 잘 진행됩니다. 코드의 수행도 문제가 없습니다. 그러나, 타입스크립트 관점에서는 이는 문법 에러입니다. 타입스크립트는 보다 강력한 코드 작성규칙을 원합니다. 협업이 중시되는 현대에서는 코드작성 시 강력한 작성규칙을 부여하는 것이 버그를 잡는 것보다 유리하다고 판단하는 것 입니다. 타입스크립트가 원하는 대로 코드를 작성해 봅시다.

TS Class Syntax

class Sedan {
  color; // 추가
  doors; // 추가
  constructor(color = 'Red', doors = 4) {
    this.color = color;
    this.doors = doors;
  }
  show() {
    console.log(this.color, this.doors);
  }
}

let s = new Sedan('Blue', 2);
console.log(s);
s.show();

this 연산자로 새 객체에 멤버 프로퍼티로 추가하는 자원은 클래스 바로 밑에 미리 선언해야 합니다. 이렇게 사용하면 전통적인 클래스 문법과 비슷해 집니다. 타입스크립트 코드도 결국 자바스크립트 코드로 바뀐 후 실행되기에 자바스크립트의 관점에서 이 방식을 보면 트랜스파일링 후 사라지는 쓸데없는 부가작업이지만 새 객체의 멤버 프로퍼티가 무엇인지 파악하는 점에서는 도움이 된다고 할 수 있습니다. 중요한 것은 클래스 밑에 선언된 변수의 값을 어디에서도 할당하지 않는다면 새 객체에 프로퍼티에 추가되지 않는다는 부분입니다.

class Sedan {
  color;
  doors;
  wheels; // 초기 값을 할당하지 않았다.
  constructor(color = 'Red', doors = 4) {
    this.color = color;
    this.doors = doors;
  }
  show() {
    console.log(this.color, this.doors);
  }
}

let s = new Sedan('Blue', 2);
console.log(s); // wheels 프로퍼티는 존재하지 않는다.
s.show();

좀 더 엄격하게 TS 방식으로 코드를 개선하면 다음과 같습니다.

class Sedan {
  private color: string;
  private doors: number;
  constructor(color: string = 'Red', doors: number = 4) {
    this.color = color;
    this.doors = doors;
  }
  public show(): void {
    console.log(this.color, this.doors);
  }
}

let s = new Sedan('Blue', 2);
console.log(s);
s.show();

전통적인 클래스 지원 언어의 관점에서 보면 colordoors 멤버변수가 먼저 처리되고 그 다음 생성자 코드가 처리되는 것처럼 보일 수 있습니다. 아닙니다. 타입스크립트 문법을 지키기 위해서 선언한 멤버변수는 타입스크립트의 허상일 뿐입니다. 왜 자꾸 강조하냐면 다음처럼 변형해서 사용할 수도 있기 때문입니다.

class Sedan {
  constructor(private color: string = 'Red', private doors: number = 4) {
    this.color = color;
    this.doors = doors;
  }
  public show(): void {
    console.log(this.color, this.doors);
  }
}

let s = new Sedan('Blue', 2);
console.log(s);
s.show();

constructor 파라미터 변수 앞에 접근제어자 중 하나를 추가하면 굳이 클래스 밑에 멤버변수를 선언하지 않아도 됩니다.

TypeScript Essence - Interface

Interface

타입스크립트에서 인터페이스는 새로운 데이터 타입을 만드는 추상 데이터 타입으로 사용이 되며 일반 변수, 함수, 클래스의 타입 체크를 위해서 사용됩니다. 인터페이스를 이용하여 타입을 선언하면 인터페이스 안에 명시된 프로퍼티의 선언과 메소드의 구현이 강제되기 때문에 프로그래밍의 일관성을 확보할 수 있습니다.

참고로 ES6는 interface를 지원하지 않습니다. 타입스크립트만 지원합니다. 그렇기 때문에 인터페이스를 컴파일 한 결과물을 보면 인터페이스의 내용은 사라지게 됩니다.

Parameter Type Checking

인터페이스는 객체의 프로퍼티 구성 상태를 체크하는 목적으로 사용할 수 있습니다. 함수가 받는 파라미터는 객체이고 그 객체가 가진 프로퍼티는 어떠한 것들이 있는지 확신할 수 있게 됩니다.

interface User {
  id: number;
  name: string;
  show(): void;
}

let user: User = {
  id: 1,
  name: 'Tom',
  show(): void {
    console.log(`Id is ${this.id} and Name is ${this.name}`);
  }
};

function proceed(user: User): void {
  user.show();
}

proceed(user);

Class Types

클래스가 인터페이스를 구현하여 인터페이스가 제안하는 변수나 함수를 소유하도록 강제할 수 있습니다.

interface User {
  id: number;
  name: string;
  show(): void;
}

class Member implements User {
  id;
  name;
  constructor(id: number, name: string) { 
    this.id = id;
    this.name = name;
  }
  show(): void {
    console.log(`Id is ${this.id} and Name is ${this.name}`);
  }
}

let member: Member = new Member(1, 'Tom');

function proceed(user: User): void {
  user.show();
}

proceed(member);

Function Types

인터페이스는 함수의 파라미터 정의와 리턴 자료형을 지정하는데 사용할 수 있습니다.

interface MyFunctionParamsType {
  (name: string, age: number): void;
}

let show: MyFunctionParamsType = function(name: string, age: number): void {
  console.log(`Name: ${name}, Age: ${age}`);
};

show("Tom", 56);

Constructor Interface

사용하는 생성자 함수의 자료형을 명시하면 오히려 에러가 나는 경우가 있습니다. 다음 예제에서 getInstance 메소드의 파라미터 construct의 자료형을 any로 설정하면 해결이 되지만 User 자료형의 객체를 만들어 주는 UserFactory라는 이름에 걸 맞지 않는 것이 마음에 들지 않습니다.

const UserFactory = {
  getInstance: function(construct: User, name: string, age: number) {
    // Cannot use 'new' with an expression 
    // whose type lacks a call or construct signature.
    return new construct(name, age);
  }
};

class User {
  constructor(private name: string, private age: number) {
    this.name = name;
    this.age = age;
  }
  show() {
    console.log(`Name: ${this.name}, Age: ${this.age}`);
  }
}

let user = UserFactory.getInstance(User, "Tom", 56);
user.show();

이 때, 인터페이스를 사용하여 constructor의 타입을 지정하면 해결할 수 있습니다.

interface UserConstructor {
  // construct signature
  new(name: string, age: number): User;
}

const UserFactory = {
  getInstance: function(construct: UserConstructor, name: string, age: number) {
    return new construct(name, age);
  }
};

class User {
  constructor(private name: string, private age: number) {
    this.name = name;
    this.age = age;
  }
  show() {
    console.log(`Name: ${this.name}, Age: ${this.age}`);
  }
}

let user = UserFactory.getInstance(User, "Tom", 56);
user.show();

(C#교육동영상)C# ADO.NET 실습 ODP.NET/ODAC 설치 오라클 함수 호출 실습, C#학원, WPF학원, 닷넷학원, 자바학원

  (C#교육동영상)C# ADO.NET 실습  ODP.NET/ODAC 설치  오라클 함수 호출 실습, C#학원, WPF학원, 닷넷학원, 자바학원 https://www.youtube.com/watch?v=qIPU85yAlzc&list=PLxU-i...