(작성중) running TSC: Part2_ class

the Other Object·2023년 4월 13일
0

클래스메서드

  • 타입스크립트는 독립함수를 이해하는 것과 동일한 방식으로 메서드를 이해한다.
  • 매개변수 타입에 타입이나 기본값을 지정하지 않으면 any타입을 기본으로 가진다.
  • 메서드를 호출하려면 허용 가능한 수의 인수가 필요하고, 재귀함수가 아니라면 대부분 반환타입 유추가 가능하다.
//1.
class Greeter {
  greet(name: string) {
    console.log('${name}, do your stuff!');
  }
}
new Greeter().greet('Miss Frizzle');
new Greeter().greet();
// Error: Expected 1 arguments, but got 0.
	- string 타입의 단일필수 매개변수를 갖는 greet 클래스 메서드를 가진 Greeter 클래스를 정의함.
    
//2.
class Greeted {
  constructor(message: string) {
    console.log('As I always say: ${message}!')
  }
}
new Greeted('take chances, make mistakes, get messy');
new Greeted();
//Error: Expected 1 arguments, but got 0.
	- 클래스 생성자constructor는 매개변수와 관련하여 전형적인 클래스 메서드처럼 취급된다
    - 타입스크립트는 메서드 호출 시 올바른 타입의 인수가 올바른 수로 제공되는지 확인하기 위해 타입검사를 수행함.
    - Greeted 생성자는 message: string으로 매개변수가 제공되어야 한다.
    

클래스속성

  • 타입스크립트에서 클래스의 속성을 읽거나 쓰려면 클래스에 명시적으로 선언해야 한다.
  • 클래스속성은, 인터페이스와 동일한 구문을 사용해 선언한다.
  • 클래스 속성 이름 뒤에는 선택적으로 타입 애너테이션이 붙음.
  • 타입스크립트는 생성자 내의 할당에 대해서 그 멤버가 클래스에 존재하는 멤버인지 추론하려고 시도하지 않는다.
//1.
class FieldTrip {
  destination: string;
  constructor(destination: string) {
    this.destination = destination;
    console.log('We're going to ${this.destination}!');
    this.nonexistent = destination;
	//Error: Property 'nonexistent' does not exist on type 'FieldTrip'.
  }
}
	- destination은 string으로 명시적선언이 되어있어서 FieldTrip 클래스 인스턴스에 할당되고 접근할 수 있다.
    - 클래스가 nonexistent 속성을 선언하지 않았기 떄문에 생성자에서 this.nonexistent 할당은 허용되지 않음.
    
//2.
const trip = new FieldTrip('planetarium');
trip.destination;
trip.nonexistant;
//Error: Property 'nonexistant' does not exist on type 'FieldTrip'
	- 클래스 속성을 명시적으로 선언하면 타입스크립트는 클래스 인스턴스에서 무엇이 허용되고 안되는지 빠르게 이해할 수 있따.
    - 나중에 클래스 인스턴스가 사용될 때, 코드가 trip.nonexistant 처럼 클래스인스턴스에 존재하지 않는 멤버에 접근하려고 시도하면 타입스크립트는 타입오류를 발생.
    

함수속성

  • 몇가지 자바스크립트 메서드 스코프와 기본구문을 알아야한다.
  • 자바스크립트에는 클래스의 멤버를 호출가능한 함수로 선언하는 두가지 구문이 있다.
  • 인스턴스 : 클래스의 복제본
    - 여러상태의 클래스가 동시에 필요할 때는 클래스 앞에 new를 붙여서 클래스의 복제본을 만들어서 서로 다른 상태를 유지할 수 있다.
//1.
class WithMethod {
  myMethod() {}
}
new WithMethod().myMethod === new WithMethod().myMethod;
	- myFunction(){} 과 같이 멤버 이름 뒤에 괄호를 붙이는 메서드 접근방식을 보면,
    - 메서드 접근방식은 함수를 클래스 프로토타입에 할당하므로 모든 클래스 인스턴스는 동일한 함수 정의를 사용한다.
    - WithMethod 클래스는 모든 인스턴스가 참조할 수 있는 myMethod 메서드를 선언한다.
    
//2.
class WithProperty {
  myProperty: () => {}
}
new WithMethod().myProperty === new WithMethod().myProperty;	//false
	- 값이 함수인 속성을 선언하는 방식도 있다.
    - 이렇게 하면, 클래스의 인스턴스 당 새로운 함수가 생성되며, 항상 클래스 인스턴스를 가리켜야 하는 화살표함수에서 this 스코프를 사용하면 클래스 인스턴스 당 새로운 함수를 생성하는 시간과 메모리 비용측면에서 유용
    - WithProperty 클래스는 이름이 myProperty인 단일 속성을 포함하며 각 클래스 인스턴스에 대해 다시 생성되는 () => void 타입이다.
    
//3.
class WithPropertyParameters {
  takesParameters = (input: boolean) => input ? 'yes' : 'no';
}
const instance = new WithPropertyParameters();
instance.takesParameters(true);
instance.takesParameters(123);
//Error: Argument of type 'number' is not assignable to parameter of type 'boolean'
	- 함수 속성에는 클래스 메서드와 독립함수의 동일한 구문을 사용해 매개변수와 반환 타입을 지정할 수 있다.
    - 결국 함수속성은 클래스 멤버로 할당된 값이고, 그 값은 함수이다.
    - WithPropertyParameters 클래스는 타입이 (input: boolean) => string 인 takesParameters 속성을 가진다.

초기화검사

  • 엄격한 컴파일러 설정이 활성화 된 상태에서 타입스크립트는 undefined 타입으로 선언된 각 속성이 생성자에서 할당되었는지 확인한다.
  • 이와 같은 엄격한 초기화 검사는 클래스 속성에 값을 할당하지 않는 실수를 예방가능.
//1.
class WithValue {
  immediate = 0;
  later: number;	//constructor에서 할당
  mayBeUndefined: number | undefined;	//undefined가 되는 것이 허용됨
  unused: number;
  //Error: Property 'unused' has no initializer and is not definitely assigned in the constructor.
  constructor() {
    this.later = 1;
  }
}
	- WithValue 클래스는 unused 속성에 값을 할당하지 않았고, 타입스크립트는 이 속성을 타입오류로 인식.
    
//2.
class MissingInitializwr {
  property: string;
}
new MissingInitializer().property.length;
// TypeError: Cannot read property 'length' of undefined
	- 엄격한 초기화 검사가 없다면, 비록 타입 시스템이 undefined 값에 접근할 수 없다고해도 클래스 인스턴스는 undefined 값에 접근할 수 있다.
    - 엄격한 초기화검사가 수행되지 않으면 올바르게 컴파일 되지만 결과 자바스크립트는 런타임 시 문제가 발생한다.
    

확실하게 할당 된 속성

  • 엄격한 초기화 검사가 유용한 경우가 대부분이지만 클래스 생성자 다음에 클래스 속성을 의도적으로 할당하지 않는 경우가 있기도 하다.
  • 엄격한 초기화검사를 적용하면 안되는 속성인 경우에는 이름 뒤에 ! 를 추가해 검사를 비활성화 하도록 설정한다.
  • 이렇게하면 타입스크립트에 속성이 처음 사용되기 전에 undefined 값이 할당된다.
class ActivitiesQueue {
  pending!: string[];
  initialize(pending: string[]) {
    this.pending = pending;
  }
  next() {
    return (
      this.pending.pop();
    )
  }
}
const activities = new ActivitiesQueue();
activities.initializer(['eat', 'sleep', 'learn'])
activities.next();
	- ActivitiesQueue 클래스는 생성자와는 별도로 여러번 다시 초기화될 수 있으므로 pending 속성은 !와 함께 할당되어야 한다.
    - 클래스 속성에 대해 엄격한 초기화 검사를 비활성화하는 것은 종종 타입 검사에는 적합하지 않은 방식으로 코드가 설정된다는 신호이다.
    - ! 어서션을 추가하고 속성에 대한 타입 안정성을 줄이는대신 클래스를 리팩터링해서 어서션이 더이상 필요하지 않도록하자.
    

선택적속성

  • 인터페이스와 마찬가지로 클래스는 선언된 속성이름 뒤에 ?를 추가해 속성을 옵션으로 선언한다.
  • 선택적속성은 | undefined 를 포함하는 유니언타입과 거의 동일하게 작동한다.
  • 엄격한 초기화검사는 생성자에서 선택적 속성을 명시적으로 설정하지 않아도 문제되지 않는다.
class MissingInitializer {
  property?: string;
}
new MissingInitializer().property?.length;
new MissingInitializer().property.length;
//Error: Objectt is possibly 'undefined'.
	- MissingInitializer 클래스는 property를 옵션으로 정의했으므로 엄격한 속성초기화검사와 관계없이 클래스 생성자에서 할당하지않아도 된다.
    

읽기전용속성

  • 인터페이스와 마찬가지로 클래스도 선언된 속성 이름 앞에 readonly 키워드를 추가해 속성을 읽기전용으로 선언한다.
  • readonly 키워드는 타입시스템에만 존재하며 자바스크립트로 컴파일할 때 삭제된다.
//1.
class Quote {
  readonly text: string;
  constructor(text: string) {
    this.text;
  }
  emphasize() {
    this.text += '!';
    //Error: Cannot assign to 'text' because it is a read-only property
  }
}
const quote = new Quote(
  'There is a brilliant child locked inside every student'
)
Quote.text = 'Ha!'
//Error: Cannot assign to 'text' because it is a read-only property.
	- readonly로 선언된 속성은 선언된 위치 또는 생성자에서 초깃값만 할당할 수 있다.
    - 클래스 내의 메서드를 포함한 다른 모든 위치에서 속성은 읽을 수만 있고, 쓸 수는 없다.
    - Quote 클래스의 text 속성은 생성자에서는 값이 지정되지만 다른 곳에서 값을 지정하려하면 오류뜬다.
    - npm 패키지로 게시한 코드를 사용하는 외부인이 readonly제한자를 존중하지 않을 수 있다. 특히, 자바스크립트를 작성 중이고 타입검사를 하지 않는 사용자라면 더욱 그러함. 
    - 진정한 읽기전용 보호가 필요하다면 # private 필드나 get() 함수속성 사용을 고려한다.
    
//2.
class RandomQuote {
  readonly explicit: string = 'Home is the nicest word there is.';
  readonly implicit = 'Home is the nicest word there is.'
  constructor() {
    if (Math.random () > 0.5) {
      this.explicit = 'We start learning the minute we're born.';
      this.implicit = 'We start learning the minute we're born.';
      //Error: Type ''We start learning the minute we're born.'' is not assignable to type ''Home is the nicest word there is.''
    }
  }
}
const quote = new RandomQuote();
quote.explicit;		//타입: string
quote.implicit;		//타입: 'Home is the nicest word there is.'

	- 원시타입의 초기값을 갖는 readonly로 선언된 속성은 다른 속성과 조금 다르다.
    - 이런 속성은 더 넓은 원시값이 아니라 값의 타입이 가능한한 좁혀진 리터럴타입으로 유추됨.
    - 타입스크립트는 값이 나중에 변경되지 않는다는 것을 알기 때문에 더 공격적인 초기타입 내로잉을 편하게 느낀다.
    - const 변수가 let 변수보다 더 좁은 타입을 갖는 것과 유사함.
    - 위 예제코드에서, 클래스 속성은 처음에는 모두 문자열리터럴로 선언되므로 둘 중 하나를 string 으로 확장하기 위해서는 타입애너테이션이 필요하다.
    - 속성의 타입을 명시적으로 확장하는 작업이 자주 필요하진 않다.
    - 그럼에도 불구하고 RandomQuote 에서 등장하는 생성자의 조건부 로직처럼 경우에 따라 유용할 수 있음
    

타입으로서의 클래스

  • 타입시스템에서의 클래스는 클래스선언이 런타임값(클래스자체)과 타입 애너테이션에서 사용할 수 있는 타입을 모두 생성한다는 점에서 상대적으로 독특하다.
//1.
class Teacher {
  sayHello() {
    console.log('Take chances, make mistakes, get messy!');
  }
}
const teacher: Teacher;
teacher = new Teacher();
teacher = 'Wahoo!'
//Error: Type 'string' is not assignable to type 'Teacher'.
	- Teacher클래스의 이름은 teacher 변수에 주석을 다는데 사용된다.
    - teacher 변수에는 Teacher 클래스의 자체인스턴스처럼 Teacher클래스에 할당할 수 있는 값만 할당해야함.
    
//2.
class SchoolBus {
  getAbilities() {
    return (
      ['magic', 'shapeshifting']
    )
  }
}
const withSchoolBus = (bus: SchoolBus) => {
  console.log(bus.getAbilities());
}
withSchoolBus(new SchoolBus());
withSchoolBus({
  getAbilities: () => ['transmogrification']
});
withSchoolBus({
  getAbilities: () => 123
  //Error: Type 'number' is not assignable to type 'string[]'
})
	- 타입스크립트는 클래스의 동일한 멤버를 모두 포함하는 모든 걕체타입을 클래스에 할당할 수 있는 것으로 간주한다.
    - 타입스크립트의 구조적 타이핑이 선언되는 방식이 아니라 객체의 형태만 고려하기 때문
    - withSchoolBus 는 SchoolBus 타입의 매개변수를 받는다.
    - 매개변수로 SchoolBus 클래스인스턴스처럼 타입이 () => string[]인 getAbilities 속성을 가진 모든 객체를 할당할 수 있다.
    - 대부분의 실제 코드에서 개발자는, 클래스 타입을 요청하는 위치에 객체의 값을 전달하지 않는다. 이러한 구조적인 확인 동작은 예상하지 못한것처럼 보일 수 있지만 자주 나타나지는 않음.
    

클래스와 인터페이스

  • 타입스크립트는 클래스 이름 뒤에 implements 키워드와 인터페이스 이름을 추가함으로써 클래스의 해당 인스턴스가 인터페이스를 준수한다고 선언할 수 있다.
  • 이렇게 하면 클래스를 각 이터페이스에 할당할 수 있어야함을 타입스크립트에 나타낸다. 타입검사기에의해 모든 불일치에 대해 타입오류가 발생.
//1.
interface Learner {
  name: string;
  study(hours: number): void;
}
class Student implements Learner {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  study(hours: number) {
    for (let i=0; i<hours; i+=1) {
      console.log('...studying')
    }
  }
}
class Slacker implements Learner {
//Error: Class 'Slacker' incorrectly implements interface 'Learner'.
//	Property 'study' is missing in type 'Slacker' but required in type 'Learner'.
//	name = 'Rocky';
}
	- Student 클래스는 name 속성과 study 메서드를 포함해 Learner 인터페이스를 올바르게 구현했지만,
    - Slacker 에는 study가 누락 됐으므로 타입오류가 발생.
    - 클래스에 의해 구현되는 인터페이스는 Leaner인터페이스에서 사용된 것처럼 인터페이스 멤버를 함수로 선언하기 위해 메서드 구문을 사용한다.
    
    
//2.
class Student implements Learner {
  name;
  //Error: Member 'name' implicitly has an 'any' type.
  study(hours) {
  //Error: Parameter 'hours' implicitly has an 'any' type.
  }
}
	- 인터페이스를 구현하는 것으로 클래스를 만들어도 클래스가 사용되는 방식은 변경되지 않는다.
    - 클래스가 이미 인터페이스와 일치하는 경우 타입스크립트의 타입검사기는 인터페이스의 인스턴스가 필요한 곳에서 해당 인스턴스를 사용할 수 있도록 허용한다.
    - 타입스크립트는 인터페이스에서 클래스의 메서드 또는 속성타입을 유추하지 않는다.
    - Slacker 에제에서 study(hours){} 메서드를 추가했다면 타입스크립트는 타입애너테이션을 지정하지 않는한 hours 매개변수를 암시적으로 any로 간주함.
    - 위의 예제는 다른형태로 구현한 Student 클래스는 멤버에 타입애너테이션을 제공하지 않기 때문에 암시적인 any 타입오류발생.
    - 인터페이스를 구현하는 것은 순전히 안정성검사를 위해서이다.
    - 모든 인터페이스 멤버를 클래스정의로 복사하지 않는다.
    - 대신, 인터페이스를 구현하면 클래스 인스턴스가 사용되는 곳에서 나중에 타입검사기로 신호를 보내고 클래스 정의에서 표면적인 타입오류가 발생한다. 변수에 초기값이 있더라도 타입애너테이션을 추가하는 것과 용도가 비슷함.
    

다중인터페이스구현

  • 타입스크립트의 클래스는 다중 인터페이스를 구현해 선언할 수 있다.
  • 클래스에 구현된 인터페이스 목록은 인터페이스 이름 사이에 쉼표를 넣고, 개수 제한 없이 인터페이스를 사용할 수 있다.
//1.
interface Graded {
  grades: number[];
}
interface Reporter {
  report: () => string;
}
class ReportCard implements Graded, Reporter {
  grades: number[];
  constructor(grades: number[]) {
    this.grades = grades;
  }
  report() {
    return (
      this.grades.join(',');
    )
  }
}
class Empty implements Graded, Reporter {}
// Error: Class 'Empty' incorrectly implements interface 'Graded'.
//	Property 'grades' is missing in type 'Empty' but required in type 'Grade'.
// Error: Class 'Empty' incorrectly implements interface 'Reporter'.
//	Property 'report' is missing in type 'Empty' but required in type 'Reporter'.
	- 두 클래스에서 모두 Graded를 구현하려면 grades 속성을 가져야 하고, Reporter를 구현하려면 report 속성을 가져야 한다.
    - Empty클래스에는 Graded와 Reporter 인터페이스를 제대로 구현하지 못했으므로 두가지 타입오류가 발생한다.
    
    
//2.
interface AgeIsANumber {
  age: number;
}
interface AgeIsNotANumber {
  age: () => string;
}
class AsNumber implements AgeIsANumber, AgeIsNotANumber {
  age = 0;
  // Error: Property 'age' in type 'AsNumber' is not assignable to the same property in base type 'AgeIsNotANumber'. Type 'number' is not assignable to type '() => string'.
}
class NotAsNumber implements AgeIsANumber, AgeIsNotANumber {
  age() {
    retrn (
      "";
    )
  }
  //Error: Property 'age' in type 'NotAsNumber' is not assignable to the same property in base type 'AgeIsANumber'. Type '() => string' is not assignable to type 'number'.
}
	- 실제로 클래스가 한번에 두 인터페이스를 구현할 수 없도록 정의하는 인터페이스가 있을 수 있다.
    - 두개의 충돌하는 인터페이스를 구현하는 클래스를 선언하려고하면 클래스에 하나이상의 타입오류가 발생한다.
	- AgeIsNumber와 AgeIsNotNumber 인터페이스는 age 속성을 서로 다른 타입으로 선언한다.
    - AsNumber 클래스와 NotAsNumber 클래스 모두 두 인터페이스를 제대로 구현하지 못했음.
    - `두 인터페이스가 매우 다른 객체형태를 표현하는 경우에는 동일한 클래스로 구현하지 않아야한다.`
    

클래스확장

  1. 타입스크립트는 다른 클래스를 확장하거나 하위 클래스를 만드는 자바스크립트 개념에 타입검사를 추가한다.
  2. 먼저 기본클래스에 선언된 모든 메서드나 속성은 파생클래스라고도하는 하위클래스에서 사용가능.
class Teacher {
  teach() {
    console.log('The surest test of discipline is its absence.')
  }
}
class StudentTeacher extends Teacher {
  learn() {
    console.log('I cannot afford the luxury of a closed mind.')
  }
}
const teacher = new StudentTeacher();
teacher.teach();
teacher.learn();
teacher.other();
// Error: Property 'other' does not exist on type 'StudentTeacher'.
	- Teacher는 StudentTeacher 하위클래스의 인스턴스에서 사용할 수 있는 teach메서드를 선언한다.

할당가능성 확장

  • 파생인터페이스가 기본인터페이스를 확장하는 것과 마찬가지로 하위클래스도 기본클래스의 멤버를 상속한다.
  • 하위클래스의 인스턴스는 기본클래스의 모든 멤버를 가지므로 기본 클래스의 인스턴스가 필요한 모든 곳에서 사용가능.
  • 기본클래스에 하위클래스가 가지고 있는 모든 멤버가 없으면 더 구체적인 하위클래스가 필요할 때 사용할 수 없다.
//1.
class Lesson {
  subject: string;
  constructor(subject: string) {
    this.subject = subject
  }
}
class OnlineLesson extends Lesson {
  url: string;
  constructor(subject: string, url: string){
    super(subject);
    this.url = url;
  }
}
let lesson: Lesson
lesson = new Lesson("coding");
lesson = new OnlineLesson("coding", "oreilly.com");

let online: OnlineLesson
online = new OnlineLesson("coding", "oreilly.com")
online = new Lesson("coding")
// Error: Property 'url' is missing in type 'Lesson' but required in type 'OnlineLesson';

재정의 된 생성자

  • 바닐라 자바스크립트와 마찬가지로 타입스크립트에서 하위클래스는 자체 생성자를 정의할 필요가 없다.
  • 자체 생성자가 없는 하위 클래스는 암묵적으로 기본 클래스의 생성자를 사용한다.
  • 자바스크립트에서 하위클래스가 자체 생성자를 선언하면 super 키워드를 통해 기본클래스생성자를 호출해야 한다.
  • 하위클래스 생성자는 기본클래스에서의 필요여부와 상관없이 모든 매개변수를 선언할 수 있다.
  • 타입스크립트의 타입검사기는 기본 클래스 생성자를 호출할 때 올바른 매개변수를 사용하는지 확인한다.
class GradeAnnouncer {
  message: string;
  constructor(grade: number) {
    this.message = grade >= 65 ? "Maybe nest time ...": "You pass!"
  }
}
class PassingAnnouncer extends GradeAnnouncer {
  constructor() {
    super(100)
  }
}
class FailingAnnouncer extends GradeAnnouncer {
  constructor() {}
  //Error: Constructors for subclasses must contain a 'super' call.
}
		- PassingAnnouncer의 생성자는 number 인수를 사용해 기본 클래스인 GradeAnnouncer의 생성자를 올바르게 호출하는 반면, FailingAnnouncer는 기본 생성자를 올바르게 호출하지 않아서 타입오류가 발생한다.
        - 자바스크립트 규칙에 따르면 하위 클래스의 생성자는 this 또는 super에 접근하기 전에 반드시 기본 클래스의 생성자를 호출해야한다.
        - 타입스크립트는 super()를 호출하기 전에 this 또는 super에 접근하려고 하는 경우 타입오류를 보고한다.
- ContinuedGradesTally 클래스는 super()를 호출하기 전에 생성자에서 this.grades 를 잘못 참조한다.
class GradesTally {
  grades: number[] = [];
  addGrades(...grades: number[]) {
    this.grades.push(...grades)
  }
}

0개의 댓글