class Animal {
private name: string;
}
class Dog extends Animal {}
class Person {
private name: string;
}
let animal = new Animal();
let dog = new Dog();
let person = new Person();
animal = dog;
animal = person; /* Type 'Person' is not assignable to type 'Animal'.
Types have separate declarations of a private property 'name'.ts(2322) */
declare const loggedInUsername: string;
const users = [
{ name: "Oby", age: 12 },
{ name: "Heera", age: 32 },
];
const loggedInUser = users.find((u) => u.name === loggedInUsername);
console.log(loggedInUser.age);
위 코드에는 오류가 있습니다. loggedInUsername 식별자는 const키워드와 함께 string 타입으로 annotation 됐습니다. const를 정의할 때는 반드시 초기화가 필요하다는 에러가 발생합니다. 하지만 declare 키워드를 사용했기 때문에 이미 해당 변수가 존재한다고 인식되어 에러가 발생하지 않습니다. 한 파일 내에서 다시 변수에 할당이 불가능하기 때문에 명백한 오류입니다.
위의 오류 속에서 loggedInUsername은 반드시 undefined이며, users.find 메서드도 undefined를 반환할 것입니다. 즉 loggedInUser는 반드시 undefined 이지만 맨 아랫줄의 loggedInUser.age가 실행됩니다.
undefined.age를 실행하면 반드시 type error가 발생합니다. 이를 typescript가 컴파일 단계에서 인지하지 못합니다.
이럴때 strictNullChecks를 true로 설정하면 Null과 undefined타입이 할당돼었을 때 Object is possibly 'undefined'.ts(2532) 이라는 에러 메세지를 출력해 주며 오류를 사전에 방지할 수 있습니다.
// strictNullChecks가 false(default)인 경우 Runtime 단계에서 오류가 발생한다.
declare let a: { hello: number } | undefined;
a = undefined;
console.log(a.hello); // TypeError: Cannot read properties of undefined (reading 'hello')
------------------
// strictNullChecks가 true인 경우 Compile 단계에서 오류가 발생한다.
declare let a: { hello: number } | undefined;
a = undefined;
console.log(a.hello); // Object is possibly 'undefined'.ts(2532)
class Animal {
private name: string;
}
1번 예제를 그대로 가져왔다. Animal 클래스에 name 필드 타입을 annotation했는데 constructor에서 초기화를 하지 않고 있다. 그래도 실행이 된다. 이를 방지하기 위해서 strictPropertyInitialization을 true로 설정해보자. ts(6133) name이 선언되었지만 read.ts에 없다라는 에러가 발생한다. 이어서 ts(2564) 에러가 발생하였고 할당이 되지 않았다고 한다.
에러 메세지: 'name' is declared but its value is never read.ts(6133)
Property 'name' has no initializer and is not definitely assigned in the constructor.ts(2564)
class Animal {
private name: string;
constructor(name: string) {
this.name = name;
}
}
위와 같이 반드시 초기화를 해주면 에러 메세지가 사라진다.
constructor에 protected를 선언하면 이 함수 또한 자신 클래스 혹은 확장 클래스 내에서만 호출할 수 있습니다. 아래 예시에서 new Person()을 호출하면 외부에서 constructor를 호출하기 때문에 오류가 발생하게 됩니다. 따라서 확장된 클래스 내에서 super 키워드를 통해 호출하도록 유도해야 합니다.
class Person {
protected name: string;
protected constructor(name: string) {
this.name = name;
}
}
class Employee extends Person {
private department: string;
constructor(name: string, department: string) {
super(name);
this.department = department;
}
public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}
const p = new Person('John'); /* Constructor of class
'Person' is protected and only accessible within
the class declaration.ts(2674) */
const e = new Employee('John', 'Sales');
접근 지정자를 생성자 매개변수 앞에 사용하면, 멤버 타입 선언과 동시에 매개변수를 타입도 지정할 수 있다.
class Animal {
protected name: string;
constructor(name: string) {
this.name = name;
}
}
----------------------------------------
// 짧아진 코드량
class Animal {
constructor(protected name: string) {
this.name = name;
}
}
추상 클래스는 interface와 비슷한 면모를 가지고 있다. 차이점은 interface는 타입만 지정할 수 있는 반면, 추상 클래스는 멤버에 대한 구현 세부 정보를 포함할 수 있다. 즉, 일부는 구현을 하고 일부는 타입만 정의할 수 있다는 의미이다.
// interface는 멤버 구현이 불가능하다.
interface Animal {
makeSound(): void;
move(): void;
}
// move 멤버 함수를 구현하고 싶은 경우 가능하다.
abstract class Animal {
abstract makeSound(): void; // 반드시 파생 클래스에서 구현되어야 함.
move(): void {
conole.log('roaming the earth...');
}
}
new Animal() // Cannot create an instance of an abstract class.ts(2511) - 추상 클래스는 스스로 실행될 수 없다.
class Dog extends Animal {
makeSound() {
console.log("bow bow");
}
}
중요한 것은, 추상 클래스에서 abstract 키워드를 사용하여 정의한 추상 메서드는 반드시 파상 클래스에서 구현해주어야 한다.