이 글을 쓰는 시점으로 확인해 보니 최근에 TypeScript 공식 문서 Handbook 내용 구성이 변경된 것 같습니다. 그래서 본 글의 내용 구성과 약간 상이할 수 있음을 미리 밝힙니다.
1. TypeScript for JS Programmers
클래스에서는 멤버 변수의 타입 선언이 필수이다. 클래스에서 멤버 변수의 타입 선언은 {멤버 변수 이름}: {타입}의 형태로 클래스의 최상단에 작성해준다. 그래야 this.{멤버 변수 이름}이 어떤 타입인지 TypeScript가 알 수 있고 이를 통해 타입 검사가 가능해진다.
기본적으로는 interface 문법이 type 문법보다 우선이다. type 문법은 필요한 상황에만 사용하자.
JavaScript의 런타임에서 특정 값의 타입을 검사하는 방법
typeof 연산자: "string", "number", "boolean", "undefined", "object", 또는 "function"을 반환
Array.isArray() 함수: 주어진 값이 배열 객체인지 여부를 반환
declare 키워드: 해당 심볼의 선언이 다른 어딘가에 존재하고 있음을 TypeScript에게 알려주는 역할을 수행한다. 즉, TypeScript 입장에선 그 자리에 실제로 해당 심볼이 선언되어 있는 것으로 생각하게 된다는 것이다. 물론 이것이 JavaScript의 런타임에서도 실제로 에러를 만들지 않으려면, 전역으로 해당 심볼이 선언되어 있거나 해당 심볼의 선언을 다른 모듈로부터 import 해와야 할 것이다. 참고로 여기서 말하는 심볼이란 변수/상수/함수/클래스 중 하나를 말한다.
Structural Typing(= Duck Typing): 값의 모양만 같으면 같은 타입으로 취급되는 타입 시스템을 말한다. 이때 객체의 모양 비교는 해당 객체의 프로퍼티들 중 필요한 몇 개만 검사하게 된다.
2. Basic Types
Number 타입: JavaScript와 마찬가지로 floating-point 숫자 값을 나타내는 number 타입과 큰 숫자 값을 나타내는 bigint 타입을 모두 지원한다. 그리고 10진수와 16진수뿐 아니라 ES6에서 도입된 2진수와 8진수까지도 함께 지원한다.
Tuple 타입: 길이가 고정이고 각 요소의 타입이 알려진 배열을 표현할 때 사용한다.
Enum 타입: 여러 개의 숫자 값들에 가독성이 좋은 이름을 붙여주기 위해 사용한다. JavaScript에는 존재하지 않고 TypeScript에만 존재하는 타입이다.
Unknown 타입: 코드를 작성하는 시점에 타입을 알 수 없는 변수의 타입을 표현할 때 사용한다. 유저로부터의 입력 값이거나, 모든 값을 받아들일 수 있는 API가 대표적인 예시이다. 즉 이 타입의 변수나 상수에는 어떠한 타입의 값도 대입할 수 있다. 또 이 타입은 typeof 검사 등의 타입 가드를 통해 더 구체적인 타입으로 좁히는 것이 가능하다(그렇게 사용해야 한다).
Any 타입: 타입 정보가 충분하지 않거나 타입 선언을 위한 노력이 많이 요구되는 경우 타입 검사를 건너뛰도록 하기 위해 사용하는 타입이다. 이러한 상황은 TypeScript 없이 작성된 코드나 서드 파티 라이브러리에서 흔히 볼 수 있다. 이 타입의 값은 unknown 타입과 다르게 임의의 프로퍼티에 접근할 수 있다. 이 타입은 기존의 JavaScript 프로젝트를 TypeScript로 점진적으로 바꾸려 할 때 매우 유용하다.
Void 타입: 일반적으로 값을 반환하지 않는 함수의 반환 타입으로 사용하는 타입이다. 그런데 이 타입의 변수나 상수는 그리 유용하지 않다. 왜냐하면 이 타입의 변수나 상수에는 같은 타입(void)의 값이나 undefined만 대입할 수 있기 때문이다(물론 --strictNullChecks 플래그가 false이면 null도 대입이 가능함).
Null, Undefined 타입: TypeScript에서는 undefined와 null이 각각 undefined 타입과 null 타입을 가진다. 그리고 이것도 void 타입과 마찬가지로 변수나 상수의 타입으로 사용하기에는 그리 유용하지 않다. 왜냐하면 undefined 타입에는 undefined만, null 타입에는 null만 대입할 수 있기 때문이다(물론 --strictNullChecks 플래그가 false이면 undefined와 null은 모든 타입의 변수나 상수에 대입이 가능함). 만약 undefined 혹은 null도 대입할 수 있는 타입을 사용하고 싶다면 Union 타입을 사용하도록 하자.
Never 타입: 절대 발생할 수 없는 값의 타입을 표현할 때 사용한다. 예를 들어, 항상 예외를 발생시키거나 무한 루프로 인해 값을 반환하지 않는 함수의 반환 타입을 표현한다. 또한 타입 가드를 통해 모든 타입을 커버한 뒤에 남은 케이스에 존재하는 변수나 상수도 이 타입을 획득한다. 그리고 never 타입은 모든 타입의 하위 타입이기 때문에 모든 타입의 변수나 상수에 대입할 수 있다. 하지만 never 타입의 하위 타입은 존재하지 않기 때문에 어떠한 타입의 값도 never 타입의 변수나 상수에는 대입할 수 없다.
Object 타입: non-primitive 타입(number, string, boolean, bignint, symbol, null, undefined 타입이 아닌 타입)을 표현한다.
Type Assertion
때로는 프로그래머가 특정 값의 타입에 대해 TypeScript보다 잘 아는 경우가 있다.
일반적으로, 이는 특정 값의 타입이 현재의 타입보다 훨씬 구체적일 수 있음을 알 때 그렇다.
Type Assertion은 TypeScript 컴파일러에게 "나를 믿어, 내가 맞아"라고 전달하는 방법이다.
다른 언어에서의 타입 캐스팅과 유사하지만, 이는 특별한 검사나 데이터의 재구성 등과 같은 런타임 효과는 전혀 없다. 즉 오로지 컴파일러에 의해서만 사용되는 문법이다.
이것을 사용하면 TypeScript는 프로그래머가 필요한 검사를 이미 수행했다고 가정한다.
as 문법 혹은 꺽쇠 문법으로 표현할 수 있다. 무엇을 사용하든 상관은 없지만, TypeScript를 JSX와 함께 사용하는 경우라면 as 문법만 허용이 된다.
About Number, String, Boolean, Symbol and Object: 소문자로 쓰는 number, string, boolean, symbol, object와 완전히 다른 것이다. 혼동하지 말도록 하자.
3. Interfaces
ReadonlyArray<T> 타입: 모든 프로퍼티들이 readonly이고 수정과 관련된 메소드들이 전부 제거된 Array<T> 타입이다. 즉, 수정할 수 없는 배열 객체를 표현한다. 참고로 Array<T> 타입의 값은ReadonlyArray<T> 타입의 변수나 상수에 대입할 수 있다. 다만 그 반대는 불가능하다.
Optional Properties: 모든 프로퍼티들이 선택적 프로퍼티인 타입 A가 있다고 하자. 이때 A 타입의 변수나 상수에 타입 A의 프로퍼티를 하나도 가지지 않는 객체를 대입하게 되면 에러가 발생한다. 기본적으로 타겟 타입과 겹치는 프로퍼티가 하나는 꼭 있어야 하는 원칙 때문이다.
Excess Property Checks: 객체 리터럴을 변수나 상수에 대입할 때 수행하는 특별한 종류의 타입 검사로, 객체 리터럴이 타겟 타입에 없는 프로퍼티를 가지면 에러를 발생시키는 것을 말한다. 물론 이 검사를 회피하기 위한 몇몇 수단들이 있긴 하지만, 이 검사에서 에러가 발생한다면 버그일 확률이 상당히 높다. 따라서 이 검사를 회피하려고 하는 것보다는 타입 정의를 조금 더 섬세하게 하는 것(EX. Index Signature 추가)을 권장한다.
Function Types
인터페이스에 Call Signature를 추가하면 함수의 타입도 표현할 수 있다.
Call Signature: 파라미터 목록과 반환 타입만을 가진 함수 선언문과 동일한 생김새이다.
이렇게 정의된 함수 타입의 변수나 상수에 함수를 대입하여 사용할 수 있다. 이때 대입하는 함수의 파라미터들에는 타입을 명시해도 되고 안 해도 된다. 안 하는 경우에는 Contextual Typing에 의해 타겟 타입의 파라미터 타입으로 자동 추론되기 때문이다.
Indexable Types
인터페이스에 Index Signature를 추가하면 인덱싱이 가능한 값의 타입도 표현할 수 있다.
Index Signature: 인덱싱 시에 사용할 타입과 그렇게 인덱싱 할 때의 반환 타입을 표현한다.
인덱싱 시에 사용할 수 있는 타입은 string과 number이다. 둘 다 사용해도 문제는 없으나, 이 경우에 number 타입으로 인덱싱 할 때의 반환 타입은 string 타입으로 인덱싱 할 때의 반환 타입의 하위 타입이어야 한다. 왜냐하면 JavaScript에서 number 타입으로 인덱싱 할 때는 이를 자동으로 string 타입으로 변환하여 인덱싱 하기 때문이다. 따라서 number 타입으로 인덱싱 하는 것은 string 타입으로 인덱싱 할 때의 조건을 모두 만족시켜야 한다.
Index Signature가 딕셔너리 패턴을 표현하는 데는 유용하지만, 앞서 말한 원리 때문에 모든 프로퍼티들의 타입은 string 타입으로 인덱싱 할 때의 반환 타입의 하위 타입이어야만 한다.
클래스의 인터페이스 구현 (implements): 해당 클래스로부터 만들어질 인스턴스가 만족해야 하는 타입 조건을 명시한다. 단, 인터페이스는 클래스의 Public Side와 Instance Side가 만족해야 하는 타입 조건만을 명시한다. 즉, 인터페이스는 Private Side 또는 Static Side와 무관하다.
클래스의 Static Side와 Instance Side
클래스가 인터페이스를 구현할 때는 해당 클래스의 Instance Side만 검사한다. 따라서 다음 코드는 에러를 발생시킨다. 생성자는 Static Side에 속하기 때문이다.
따라서 생성자를 위한 인터페이스를 다음과 같이 별도로 정의해서 사용해야 한다. 이때 new 연산자를 붙인 부분이 Constructor Signature로, 생성자 타입을 정의할 때 사용한다.
인터페이스 상속 (extends): 클래스와 마찬가지로 인터페이스도 또 다른 인터페이스를 상속할 수 있다. 이 경우, 상속하는 인터페이스의 프로퍼티들을 복사해서 그대로 가져온다. 여러 개의 인터페이스들을 상속하는 것도 가능한데, 이때 상속하는 인터페이스들은 서로 호환이 되어야 한다. 예를 들어, prop이라는 동일한 프로퍼티를 정의하고 있는 두 인터페이스를 상속한다면, 두 prop의 타입은 서로 호환이 되어야 한다(같거나, 하나가 하나의 하위 타입이어야 함).
Hybrid Types: 앞서 설명한 것처럼 인터페이스는 여러 형태의 객체 타입을 표현할 수 있다. 그런데 JavaScript의 문법은 매우 동적이고 유연하기 때문에, 앞서 설명한 여러 형태를 동시에 가지고 있는 객체도 존재할 수 있다. 예를 들면 함수이면서 동시에 프로퍼티를 가지는 객체가 존재할 수 있다는 것이다. 이러한 객체의 타입도 인터페이스를 통해 다음과 같이 표현할 수 있다.
클래스를 선언하는 것은 두 가지 의미를 가진다. 생성자의 선언과 타입의 선언이다. 즉, 클래스는 TypeScript의 입장에서 타입의 역할도 수행한다. 따라서 인터페이스는 클래스를 상속하는 것도 가능하다. 이 경우, 해당 클래스의 멤버들을 그대로 복사해서 가져온다. 그런데 이때 Private 멤버와 Protected 멤버까지 모두 가져온다. 따라서 Private 멤버 혹은 Protected 멤버를 가진 클래스를 상속하는 인터페이스를 선언할 경우, 그 인터페이스는 해당 클래스 혹은 해당 클래스의 하위 클래스에 의해서만 구현(implements)될 수 있다.
4. Functions
Contextual Typing: 변수나 상수에 함수를 대입할 때 한 쪽에만 타입 정보가 있어도 다른 한 쪽의 타입 정보를 추론할 수 있게 해주는 타입 추론 방식의 일종이다.
함수 타입의 파라미터 목록
JavaScript에서는 모든 파라미터들이 기본적으로 선택적 파라미터이다. 그러나 TypeScript에서 함수 타입에 명시된 파라미터들은 기본적으로 전부 필수 파라미터이며, 넘겨주는 파라미터들의 개수도 함수 타입에 명시된 파라미터들의 개수와 완전히 같아야 한다.
선택적 파라미터: 파라미터의 이름 뒤에 ? 기호를 붙인다. 이때 선택적 파라미터들은 반드시 필수 파라미터들의 뒤에만 올 수 있다.
디폴트 파라미터: 값을 넘겨주지 않거나 undefined를 넘겨줄 때 기본값으로 원하는 값이 설정되도록 하는 파라미터를 말한다. 디폴트 파라미터는 반드시 필수 파라미터들의 뒤에만 올 필요는 없다. 필수 파라미터들의 뒤에 오는 디폴트 파라미터는 선택적 파라미터로 취급된다.
필수 파라미터들의 뒤에 오는 디폴트 파라미터는 선택적 파라미터로 취급되기 때문에, 타입 자체도 선택적 파라미터일 때랑 완전히 똑같다. 다음 예시를 참고하자.
Rest 파라미터: 여러 개의 파라미터들을 한 그룹으로 다루고 싶을 때, 혹은 몇 개의 파라미터가 전달될지 모르는 함수일 때 사용한다. 이는 0개 이상의 선택적 파라미터들의 목록으로 취급이 된다.
this 키워드
JavaScript에서 this 키워드는 함수가 호출되는 순간에 동적으로 바인딩 된다. 그러나 ES6의 화살표 함수를 사용하면 함수가 생성되는 순간에 this 키워드가 정적으로 바인딩 된다. 바인딩 되는 대상은 함수가 생성되는 위치(스코프)의 this 키워드이다.
--noImplicitThis 플래그: this 키워드가 any 타입으로 추론되는 경우 에러를 발생시킨다.
함수의 파라미터 목록에서 this 파라미터의 타입을 명시적으로 지정해주면, 어떤 맥락에서 그 함수가 호출되어야 하는지 강제할 수 있다. 예를 들어, 객체의 메소드로 실행되어야 하는 함수는 this 파라미터의 타입을 해당 객체의 타입으로 지정해주면 된다. 그러면 반드시 메소드로만 호출할 수 있고, 그 자체적으로 호출하려 할 때는 에러가 발생하게 된다.
콜백 함수에서의 this: 콜백 함수를 요구하는 라이브러리는 전달받은 콜백 함수를 그 자체적으로 호출하기 때문에, this 키워드는 undefined로 바인딩 된다. 따라서 this 키워드를 사용하는 함수를 콜백 함수로 넘겨주면, 런타임 시에 에러가 발생할 것이다. 이 문제를 해결하기 위해서는 다음과 같이 두 가지가 필요하다.
해당 라이브러리의 타입 선언 시, 요구하는 콜백 함수의 타입에서 this 파라미터의 타입을 void로 명시한다. 이는 해당 콜백 함수가 this 키워드를 사용하지 않음을 의미한다.
콜백 함수로 넘겨줄 함수의 선언 시, this 파라미터의 타입을 명시한다. 만약 this 키워드를 사용한다면 특정 타입으로 명시할 것이고, 아니라면 void로 명시할 것이다. 이를 통해 해당 콜백 함수가 라이브러리에 전달되어도 괜찮은지 타입 검사를 진행할 수 있게 된다.
클래스에서 메소드를 일반 함수로 선언하는 것과 화살표 함수로 선언하는 것은 this 키워드 뿐 아니라 메모리 할당 방식에서도 차이가 있다. 일반 함수로 선언하면 해당 메소드가 클래스의 프로토타입 객체에 프로퍼티로 붙지만, 화살표 함수로 선언하면 그 클래스로부터 만들어지는 모든 인스턴스들 각각에 프로퍼티로 붙는다.
함수 오버로딩: 파라미터 타입에 따라 반환 타입이 다른 함수의 경우, 다음과 같이 구현부가 있는 함수 선언문 앞에 구현부가 없는 함수 선언문들을 작성해주면 된다. 그러면 컴파일러는 구현부가 없는 그 함수 선언문들을 위에서부터 탐색하며 해당 함수의 호출문을 검사하게 된다. 매칭되는 함수 선언문을 통해 호출하려는 그 함수의 파라미터 타입과 반환 타입을 파악하는 것이다. 이때 구현부가 있는 함수 선언문은 오버로딩 목록에 포함되지 않는다. 따라서 다음 예시의 함수에서 인자로 number 타입이나 string 타입이 아닌 값을 넘겨주면 에러가 발생한다.
5. Classes
TypeScript에서 클래스의 각 멤버는 기본적으로 Public이다. 그렇지만 명시적으로 public 키워드를 붙여서 Public으로 만들 수도 있다. 그리고 클래스 외부에서 접근할 수 없도록 하려면 private 키워드를 붙여주면 된다. 혹은 ES2019에서 도입한 것처럼 심볼 앞에 # 기호를 붙여줘도 된다.
두 타입을 비교할 때 한 타입에 Private 멤버(혹은 Protected 멤버)가 있다면 다른 한 타입에도 그 멤버가 Private(혹은 Protected)이어야 하며 그 선언의 출처까지 같아야 호환이 된다고 판단한다.
Protected 멤버: Private 멤버와 동일하지만, 하위 클래스에서도 접근이 가능하다는 점만 다르다. 만약 생성자에 protected 키워드를 붙이면 해당 클래스 외부에서 인스턴스화가 불가능하고 하위 클래스에서 호출하는 것만 가능하다.
Readonly 프로퍼티: readonly 키워드를 붙여주면 읽기 전용 프로퍼티가 된다. 이러한 프로퍼티는 반드시 선언 시에 혹은 생성자 안에서 초기화가 이뤄져야 한다.
파라미터 프로퍼티: 생성자의 파라미터에 readonly, public, private, 혹은 protected 키워드를 붙여주면 해당 이름의 프로퍼티의 선언과 초기화를 한 자리에서 간결하게 수행할 수 있다.
Accessors (getter, setter): ES5부터 도입된 JavaScript 문법으로, TypeScript에서도 사용 가능하다. 단, 타겟 버전이 ES5 이상일 때만 사용 가능하다. 참고로 setter 없이 getter만 있는 경우에는 기본적으로 readonly 프로퍼티인 것처럼 취급이 된다.
추상 클래스와 추상 메소드: 추상 클래스와 추상 메소드는 각각 abstract 키워드를 붙여서 정의한 클래스와 메소드를 의미한다. 추상 클래스는 인스턴스화가 불가능하며, 추상 메소드를 가질 수 있다. 추상 메소드에는 구현부가 없기 때문에, 인터페이스의 메소드와 문법적으로 형태가 비슷하다. 다만 abstract 키워드가 필요하고 앞에 접근 제어 지시자(public, private, protected)가 붙을 수 있다는 점이 다르다.
클래스의 선언이 가지는 의미
클래스 인스턴스의 타입 선언: 컴파일러에 의해서만 사용되는 타입 정보로, 컴파일 시에는 삭제된다. 즉 클래스는 하나의 타입이기도 하기 때문에 인터페이스를 사용하는 것처럼 사용할 수 있다. 예를 들어, 앞서 설명한 것처럼 인터페이스는 클래스도 상속(extends)할 수 있다.
생성자 선언: 클래스의 선언은 컴파일 시 함수의 선언으로 변환된다. 이때 Static 멤버는 생성자 자체에 프로퍼티로서 저장된다. 생성자의 타입은 typeof 클래스명을 통해 파악 가능하다.
6. Generics
제네릭 (Generic): 유저가 제공한 타입 정보를 나중에도 사용할 수 있도록 하는 핵심 메커니즘이다. 예를 들어, 유저가 제공한 타입을 반환 타입에도 사용할 수 있다.
Type Argument Inference: 제네릭 함수를 호출할 때 넘겨준 인자 값의 타입에 근거하여 컴파일러가 타입 변수의 값을 자동으로 설정해주는 것을 말한다. 만약 컴파일러가 이러한 타입 추론에 실패하는 경우에는 프로그래머가 직접 타입 변수의 값을 지정해줘야 한다.
제네릭 타입
타입 자체에 타입 변수가 존재하는 경우를 말한다.
type GenericType<T> = (arg: T) ⇒ T;
interface GenericType<T> { (arg: T): T; }
제네릭 함수
함수 자체에 타입 변수가 존재하는 경우를 말한다.
function genericFunction<T>(arg: T): T { ... }
type GenericFunctionType = <T>(arg: T) ⇒ T;
interface GenericFunctionType { <T>(arg: T): T; }
제네릭 클래스
클래스 자체에 타입 변수가 존재하는 경우를 말한다.
단, 제네릭은 Instance Side에만 적용된다. 즉, Static Side에선 타입 변수 사용이 불가능하다.
class GeneriClass<T> { ... }
제네릭 열거형/네임스페이스: 존재하지 않는다.
제네릭 제약 조건
몇몇 제약 조건을 통해 타입 변수의 범위를 좁히는 것을 말한다.
또 다른 타입 변수에 의해 제약되는 타입 변수를 선언하는 것도 가능하다.
제네릭을 이용한 팩토리 함수 정의: 다음과 같이 클래스 타입을 참조해야 한다.
댓글 0개
좋아요 0개
아직 작성된 댓글이 없어요.
enum Color {
Red,
Green,
Blue
}
let c: Color = Color.Green // 1
let colorName: string = Color[2]; // 'Blue'
declare const maybe: unknown;
// 'maybe' could be a string, object, boolean, undefined, or other types.
const aNumber: number = maybe; // Error: Type 'unknown' is not assignable to type 'number'.
if (maybe === true) {
const aBoolean: boolean = maybe;
const aString: string = maybe; // Error: Type 'boolean' is not assignable to type 'string'.
}
if (typeof maybe === "string") {
const aString: string = maybe;
const aBoolean: boolean = maybe; // Error: Type 'string' is not assignable to type 'boolean'.
}
let looselyTyped: any = 4;
// OK, 'ifItExists' might exist at runtime.
looselyTyped.ifItExists();
// OK, 'toFixed' exists. (But the compiler doesn't check.)
looselyTyped.toFixed();
// OK, 'd' has 'any' type due to propagation.
let d = looselyTyped.a.b.c.d;
let strictlyTyped: unknown = 4;
strictlyTyped.toFixed(); // Error: Object is of type 'unknown'.
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
interface NotOkay {
[x: string]: Dog;
[x: number]: Animal; // Error: Numeric index type 'Animal' is not assignable to string index type 'Dog'.
}
interface NumberDictionary {
[index: string]: number;
length: number;
name: string; // Error: Property 'name' of type 'string' is not assignable to string index type 'number'.
}
interface NumberOrStringDictionary {
[index: string]: number | string;
length: number;
name: string;
}
interface ClockConstructor {
new (hour: number, minute: number): void;
}
// Error: Class 'Clock' incorrectly implements interface 'ClockConstructor'.
// Error: Type 'Clock' provides no match for the signature 'new (hour: number, minute: number): void'
class Clock implements ClockConstructor {
currentTime: Date;
constructor(h: number, m: number) {}
}
class Control {
private state: any;
}
interface SelectableControl extends Control {
select(): void;
}
class Button extends Control implements SelectableControl {
select() {}
}
class TextBox extends Control {
select() {}
}
// Error: Class 'ImageControl' incorrectly implements interface 'SelectableControl'.
// Error: Types have separate declarations of a private property 'state'.
class ImageControl implements SelectableControl {
private state: any;
select() {}
}
function overloadedFunction(x: number): string; // only for TypeScript
function overloadedFunction(x: string): number; // only for TypeScript
function overloadedFunction(x: any): any {
if (typeof x == "number") return String(x);
else return Number(x);
}
// overloadedFunction의 타입 = {
// (x: number): string;
// (x: string): number;
// }
let s = overloadedFunction(3);
let n = overloadedFunction('3');
class Animal {
a: string; // public by default
public b: string;
private c: string;
#d: string;
constructor(theName: string) { // public by default
this.name = theName;
}
e() { // public by default
console.log('e');
}
public f() {
console.log('f');
}
private g() {
console.log('g');
}
#h() {
console.log('h');
}
}
class Base {
private name: string;
constructor(theName: string) {
this.name = theName;
}
}
class Derived extends Base {
constructor(theName: string) { super(theName); }
}
class Similar {
private name: string;
constructor(theName: string) { this.name = theName; }
}
let base = new Base("base");
let derived = new Derived("derived");
let similar = new Similar("similar");
base = derived;
base = similar; // Error: Types have separate declarations of a private property 'name'.
class Octopus {
readonly numberOfLegs: number = 8;
constructor(readonly name: string) {}
}
let dad = new Octopus("Man with the 8 strong legs");
dad.name;
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, "a");
getProperty(x, "m"); // Error: Argument of type '"m"' is not assignable to parameter of type '"a" | "b" | "c" | "d"'.
function create<T>(c: { new (): T }): T {
return new c();
}