2.18 ~ 2.22
목차
2.18 콜백 함수의 매개변수는 생략 가능하다
- 인수로 제공하는 콜백함수의 매개변수에는 타입을 표기하지 않아도 됨
- 문맥적 추론
- 콜백 함수의 매개변수는 함수를 호출할때 사용하지 않아도 됨
function example (callback: (error: Error, result: string)=>void){}
example((e,r)=>{}); //문맥적 추론
example(()=>{}); //매개변수 사용 X
example(()=>true);
- error와 result는 undefine이 아닌 각각의 타입을 가짐
- (실수) 옵셔널로 만들기
- error와 result 타입이 각각 Error|undefined, string|undefined가 됨
- 두 매개변수는 undefined가 아니기 때문에 의도와 달라짐
- (실수) 옵셔널로 만들기
- 콜백 함수의 반환값이 void면 어떤 값이 와도 상관 X, 단 해당 반환값을 다른 곳에 사용 X
forEach 예시
- forEach 메서드의 콜백함수는 callbackfn 타입
[1,2,3].forEach((item,index,array)=>{ //매개변수 타입 표기 X
console.log(item, index,array)
});
[1,2,3].forEach((item, index)=> {}); //반환값 없음
[1,2,3].forEach((item)=> item); //반환값 있음
- 함수 호출 시 매개변수를 모두 기입하지 않음
2.19 공변성과 반공변성을 알아야 함수끼리 대입할 수 있다
: 함수 간의 대입 가능성 알기
: 기본적으로 타입스크립트는 공변성을 갖고 있지만, 함수의 매개변수는 반공변성을 갖음
공변성 : A -> B일 때 T<A> -> T<B>인 경우
반공변성 : A -> B일 때 T<B> -> T<A>인 경우
이변성 : A -> B일 때 T<A> -> T<B>도 되고 T<B> -> T<A>도 되는 경우
무공변성 : A -> B일 때 T<A> -> T<B>도 안 되고 T<B> -> T<A>도 안 되는 경우
함수의 반환값은 공변성을 갖음
function a(x:string) : number {
return 0;
}
type B = (x:string) => number | string;
let b : B = a;
- a->b 관계
- a 함수의 반환값(number)을 b의 반환값(number|string)에 대입 가능 (두 타입의 차이는 반환값 뿐이며, 반환값 타입은 b가 a보다 넓음)
- T <> = 함수<반환값>라고 생각 (함수 자체 대입 확인하기)
- a -> b 일 때, T<a> -> T<b> (함수 a를 타입 b에 대입 가능)
function a(x:string) : number | string{
return 0;
}
type B = (x:string) => number;
let b : B = a;
- 반대로하면 에러 발생 -> 반환값에 대해서 항상 공변성을 가짐
매개변수는 반공변성을 가짐 (strict 옵션 활ㅅ)
function a(x:string | number) : number{
return 0;
}
type B = (x:string) => number;
let b : B = a;
- 매개변수 string -> string | number 이므로 b -> a
- 하지만, a를 b에 대입 가능
- b -> a 에서 T<a> -> T<b>이므로 매개변수는 반공변성 (반대는 불가, 에러 발생함)
- 반대는 에러
매개변수는 이변성을 가짐 (strict 옵션 X)
- 위의 에러가 strict 옵션을 해제하면 발생 안함 (잘 모르겠...)
- b -> a 에서 T<b> -> T<a>도 되고, T<a> -> T<b>도 됨
객체의 메서드 타이핑 방법에 따라 변성이 정해짐 (strict 옵션 활성화)
interface SayMethod {
say(a:string|number) : string;
}
interface SayFunction {
say : (a:string|number) => string;
}
interface SayCall{
say : {
(a:string | number) : string
}
}
const SayFunc = (a:string) => 'hello';
const MyAddingMethod : SayMethod = {
say : SayFunc //이변성
}
const MyAddingFunction : SayFunction = {
say : SayFunc //반공변성
}
const MyAddingCall : SayCall = {
say : SayFunc //반공변성
}
- 기본적으로 sayFunc 함수의 타입은 (a : string) => string임
- 반공변성에 의해 (a : string | number) => string에 대입 불가 (더 넓은 매개변수에 대입 X)
- 하지만, SayMethod 타입의 경우 에러 발생 X <- 선언 방법의 차이
- '함수(매개변수) : 반환값' // 이변성 가짐
- '함수 : (매개변수) => 반환값' // 반공변성
2.20 클래스는 값이면서 타입이다
: 클래스에 존재하는 특징 알아보기
타입스크립트에서의 클래스 코딩
class Person {
name : string;
age : number;
married : boolean;
constructor(name : string, age : number, married : boolean) {
this.name = name;
this.age = age;
this.married = married;
}
}
자바스트립트 코드
class Person {
constructor(name, age, married) {
this.name = name;
this.age = age;
this.married = married;
}
}
- 차이점 : 타입스크립트는 name, age, married 같은 멤버를 클래스 내부에 한 번 적어야 함
class Person {
name;
age ;
married;
constructor(name : string, age : number, married : boolean) {
this.name = name;
this.age = age;
this.married = married;
}
}
- 멤버의 타입은 생략 가능
- 생성자 함수를 통해 알아서 추론
클래스 표현식으로 선언하기
const Person = class{
name;
age ;
married;
constructor(name : string, age : number, married : boolean) {
this.name = name;
this.age = age;
this.married = married;
}
}
멤버는 항상 constructor 내부와 짝이 맞아야함
const Person = class{
name: string;
married : boolean;
constructor(name : string, age : number, married : boolean) {
this.name = name;
this.age = age;
}
}
- married : 멤버로만 선언하고 생성자 안에서 할당되지 않았다는 에러
- age : 멤버로 선언되지 ㅇ낳고, 생성자에서 만들어 age 속성이 클래스 안에 없다는 에러
interface와 implements 예약어를 사용해 더 엄격하게 멤버 검사하기
interface Human {
name: string;
age : number;
married : boolean;
sayName() : void;
}
class Person implements Human {
name;
age;
married;
constructor(name : string, age : number, married : boolean) {
this.name = name;
this.age = age;
this.married = married
}
}
- Human 인터페이스 생성
- Person 클래스가 Human 인터페이스를 implements함
- 인터페이스 안 satName 메소드를 구현하지 않아 에러
생성자 함수 방식으로 객체 만들기 불가
interface PersonInterface {
name: string;
age : number;
married : boolean;
}
function Person(this: PersonInterface,name: string, age : number, married : boolean) {
this.name = name;
this.age = age;
this.married = married
}
new Person('zero', 28, false)
- 클래스가 new를 붙여 호출할 수 있는 유일한 객체
클래스 자체의 타입이 필요하면 'typeof 클래스이름'
const person1 : Person = new Person ('zero', 28, false)
const P:typeof Person = Person
const person2= new P('nero', 32, true)
클래스 멤버로 다양한 수식어 추가하기
class Parent {
name? : string; //옵셔널 수식어
readonly age : number; //readonly 수식어
protected married: boolean; //protected 수식어
private value : number; //private 수식어
constructor(name: string, age: number, married: boolean) {
this.name = name;
this.age = age;
this.married = married
this.value = 0;
}
changeAge (age: number) {
this.age = age; //age는 readonly -> 변경불가
}
}
class Child extends Parent {
constructor(name:string, age: number, married: boolean){
super(name, age, married)
}
SayName(){
console.log(this.name)
}
SayMarried(){
console.log(this.married)
}
SayValue(){
console.log(this.value) //private 멤버인 value는 Parent 클래스 외 사용 불가
}
}
const child = new Child ('zero', 28, false)
child.name;
child.married; //protected 속성은 인스턴스에선 사용 불가
child.value; //private 멤버인 value는 Parent 클래스 외 사용 불가
- public 속성
- 선언한 자신의 클래스, 자손 클래스, new 호출로 만들어낸 인스턴스에서 속성 사용 가능, 위 예의 name
- protected 속성
- 자신의 클래스와 자손 클래스에서는 속성을 사용할 수 있음
- 인스턴스에서는 사용 불가 (married 속성)
- privated 속성
- 자신의 클래스에서만 속성 사용 가능 (value 속성)
privated 속성을 선언하는 두가지 방법
- private
- # : private field
class PrivateMember {
private priv : string = 'priv';
}
class ChildPrivateMember extends PrivateMember {
private priv : string = 'priv';
}
class PrivateField {
#priv : string = 'priv';
sayPriv(){
console.log(this.#priv)
}
}
class ChildPrivateField extends PrivateField {
#priv : string = 'priv'
}
- 차이점
- private 수식어로 선언한 속성은 자손 클래스에서 같은 이름으로 선언 불가 -> childPrivateMember 오류
- #priv는 에러 X
- (저자) #priv 선호
- 자바스크립트의 원래 기능가 더 가까움
implements하는 속성은 전부 public이어야 함
interface Human {
name: string;
age : number;
married : boolean;
}
class Person implements Human {
name;
protected age;
married;
constructor(name : string, age : number, married : boolean) {
this.name = name;
this.age = age;
this.married = married
}
}
- 애초에 인터페이스 속성은 protected, private 불가
- implements한 클래스에서도 인터페이스 속성들은 모두 public
override 수식어 활용
class Human {
eat() {
console.log("냠냠");
}
sleep() {
console.log("쿨쿨");
}
}
class Employee extends Human {
work() {
console.log("끙차");
}
sleep() {
console.log("에고고");
}
}
- Employee의 sleep 메서드는 Human의 sleep 메서드를 오버라이드 시도
- 오류 이유 : 명시적으로 오버라이드할 경우 앞에 override 수식어 붙이기
(해결)
class Human {
eat() {
console.log("냠냠");
}
sleap() {
console.log("쿨쿨");
}
}
class Employee extends Human {
work() {
console.log("끙차");
}
override sleap() {
console.log("에고고");
}
}
override 수식어 장점
- 부모 클래스의 메서드가 바뀔 때, 확인 가능
- 아래 코드 : 부모클래스 sleap을 sleep으로 바꿈
class Human {
eat() {
console.log("냠냠");
}
sleep() {
console.log("쿨쿨");
}
}
class Employee extends Human {
work() {
console.log("끙차");
}
override sleap() {
console.log("에고고");
}
}
- 없는 메서드를 오버라이드 했다는 오류
오버로딩
클래스의 생성자 함수에 오버로딩 적용하기
class Person {
name? : string;
age? : number;
married? : boolean;
constructor();
constructor(name: string, married: boolean);
constructor(name: string, age:number ,married: boolean);
constructor(name?: string, age?: boolean|number ,married?: boolean){
if (name){
this.name = name;
}
if (typeof age === 'boolean'){
this.married = age;
} else {
this.age = age;
}
if (married) {
this.married = married
}
}
}
const person1 = new Person();
const person2 = new Person('nero', true);
const person3 = new Person('zero', 28, false);
- 타입 선언 여러 번
- 함수의 구현은 한 번만
- 구현에서 여러 번 타입 선언한 것드에 대해 모두 대응
클래스의 속성에 인덱스 시그니처 사용
class Signature {
[propName : string] : string | number | undefined;
static [propName : string] : boolean;
}
const sig = new Signature();
sig.hello = 'world';
Signature.isGood = true;
- static 속성에서도 인덱스 시그니처 가능, 속성 자유롭게 추가 가능
클래스나 인터페이스 메서드에서는 this를 타입으로 사용 가능
class Person{
age: number;
married : boolean;
constructor(age: number, married: boolean) {
this.age= age;
this.married = married;
}
sayAge() {
console.log(this.age); //this: this
}
sayMarried(this: Person) {
console.log(this.married); //this: Person
}
sayCallback(callback: (this:this) => void) {
callback.call(this)
}
}
- 기본적으로 this는 클래스 자신
- sayMarried : 명시적으로 this 타이핑
- sayCallback
- 매개변수로 콜백함수
- 콜백함수의 this : this -> 콜백 함수의 this 타입이 Person 인스턴스가 됨
class A{
callbackWithThis (cb : (this:this) => void){
cb.call(this);
}
callbackWithoutThis (cb : () => void){
cb();
}
}
new A().callbackWithThis(function() {
this; //this: A
})
new A().callbackWithoutThis(function() {
this;
})
- 클래스 메서드의 콜백함수에 this 타이핑 유무에 따라 메서드 호출 시 this의 타입이 달라짐
- 콜백함수에서 this을 사용하고 싶다면 this 타이핑 필요
- this가 클래스 자신이면 this:this 로 타이핑
인터페이스로 클래스 생성자 타이핑 가능
- 앞에 new 연산자 추가
interface PersonConstructor {
new (name : string, age : number) : {
name: string;
age : number;
}
}
class Person {
name: string;
age: number;
constructor (name: string, age: number) {
this.name = name;
this.age = age;
}
}
function createPerson (ctor: PersonConstructor, name: string, age: number){
return new ctor(name, age); //인터페이스 앞에 new 붙여 호출
}
createPerson(Person, 'zero', 28)
위를 활용해 타입스크립트에서도 생성자 함수 사용 가능 -> 클래스가 있는데 굳이..?
2.20.1 추상 클래스
: implements보다 조금 더 구체적으로 클래스 모양 정의하는 방법
abstract class AbstractPerson {
name : string;
age : number;
married : boolean = false;
abstract value : number;
constructor(name: string, age:number ,married: boolean){
this.name = name;
this.married = married;
this.age = age;
}
sayName() {
console.log(this.name);
}
abstract sayAge():void
abstract sayMarried():void
}
class RealPerson extends AbstractPerson {
sayAge(){
console.log(this.age)
}
}
- abstract class로 선언
- abstract 클래스의 속성과 메서드는 abstract일 수 있음
- 이 경우 실제 값은 없고 타입 선언만 되어 있음
- RealPerson 클래스는 AbstractPerson 클래스를 상속
- 반드시 abstract 속성이나 메소드 구현
- sayAge만 구현해 나머지도 구현하라는 오류
- abstract 클래스의 속성과 메서드는 abstract일 수 있음
(해결) RealPerson 수정
abstract class AbstractPerson {
name : string;
age : number;
married : boolean = false;
abstract value : number;
constructor(name: string, age:number ,married: boolean){
this.name = name;
this.married = married;
this.age = age;
}
sayName() {
console.log(this.name);
}
abstract sayAge():void
abstract sayMarried():void
}
class RealPerson extends AbstractPerson {
value : number = 0;
sayAge(){
console.log(this.age)
}
sayMarried() {
console.log(this.married)
}
}
2.21 enum은 자바스크립트에서도 사용할 수 있다.
: erum(열거형) 타입
: 자바스크립트엔 없는 타입이지만, 자바스크립트의 값으로 사용 가능한 특이한 타입
: 목적 - 여러 상수 나열
방법 - enum 예약어로 선언
enum Level1 {
NOVICE,
INTERMEDIATE,
ADANCED,
MASTER
}
- 멤버(member) : NOVICE와 같은 enum 안에 존재하는 이름
enum은 자바스크립트 코드로 남음
var Level1;
(function (Level1) {
Level1[Level1["NOVICE"] = 0] = "NOVICE";
Level1[Level1["INTERMEDIATE"] = 1] = "INTERMEDIATE";
Level1[Level1["ADANCED"] = 2] = "ADANCED";
Level1[Level1["MASTER"] = 3] = "MASTER";
})(Level1 || (Level1 = {}));
Level1[Level1["NOVICE"] = 0] = "NOVICE";
=
Level1[0]="NOVICE" 와 Level1["NOVICE"]=0
즉 위 코드는
var Level1 = {
0: "NOVICE",
1: "INTERMEDIATE",
2: "ADANCED",
3: "MASTER",
NOVICE: 0,
INTERMEDIATE: 1,
ADANCED: 2,
MASTER: 3,
};
- enum은 멤버의 순서대로 0부터 숫자 할당
- 0 대신 다른 숫자 할당을 원할 경우 = 연산자 사용
enum Level1 {
NOVICE = 3,
INTERMEDIATE, //4
ADANCED = 7,
MASTER, //8
}
- INTERMEDIATE와 MASTER 같이 할당하지 않은 경우
- 이전 값에 1을 더한 값이 할당
문자열 할당도 가능
enum Level1 {
NOVICE , //0
INTERMEDIATE="hello",
ADANCED = "oh",
MASTER, //8
}
- 한 멤버를 문자열로 할당한 다음부턴 전부 직접 값 할당 필요 / 안하면 오류
enum 타입의 속성은 값으로 활용 가능
enum Level {
NOVICE ,
INTERMEDIATE,
ADANCED,
MASTER,
}
const a = Level.NOVICE //0
const b = Level[Level.NOVICE] //NOVICE
- enum[enum_멤버] : enum의 멤버 이름을 가져오는 방법
enum은 값보단 타입으로 더 많이 사용
enum Level {
NOVICE ,
INTERMEDIATE,
ADANCED,
MASTER,
}
function whatYourlevel(level : Level) {
console.log(Level[level]); //ADANCED
}
const myLevel = Level.ADANCED;
whatYourlevel(myLevel)
- 매개변수의 타입으로 enum 사용
- 멤버의 유니언과 비슷한 역할
- Level.NOVICE | Level.INTERMEDIATE | Level.ADANCED | Level.MASTER
- enum의 멤버를 사용해 함수 호출
enum의 불완전함
enum Role {
USER,
GUEST,
ADMIN
}
enum Role2 {
USER = "USER",
GUEST ="GUEST",
ADMIN = "ADMIN"
}
function changeUserRol (rol : Role) {}
function changeUserRol2 (rol : Role2) {}
changeUserRol(2)
changeUserRol(4) // 옳은 오류
changeUserRol2(Role2.USER)
changeUserRol2("USER") // 오류 : 불완전한 예
- enum의 멤버인 USER을 넣었음에도 오류 -> 불완전함
enum의 사용 - 브랜딩
enum Money {
WON,
DOLLAR,
}
interface WON {
type : Money.WON,
}
interface DOLLAR {
type : Money.DOLLAR,
}
function moneyOrDollar(param : WON | DOLLAR) {
if (param.type === Money.WON) {
param; //(parameter) param : Won
}
else {
param; //(parameter) param : DOLLAR
}
}
- 브랜드 속성으로 enum 멤버 사용
- 같은 enum의 멤버여야 서로 구분 가능
enum Money {
WON,
}
enum Water {
LITER,
}
interface M {
type : Money.WON,
}
interface N {
type : Water.LITER,
}
function moneyOrLiter(param : M | N) {
if (param.type === Money.WON) {
param;
}
else {
param;
}
}
moneyOrLiter({type: Money.WON}) //Money
moneyOrLiter({type: Water.LITER}) //Water
console.log(Money.WON) //0
console.log(Water.LITER) //0
- 다른 enum을 사용해 비교할 경우 위와 같이 둘 다 0으로 else 문으로 가지 않음
enum을 사용하면서 자바스크립트 코드는 생성되지 않게 하기
- const enum 사용
const enum Money {
WON,
DOLLAR
}
Money.WON; //1
Money[Money.WON] //Money라는 자바스크립트 객체가 없어 에러
2.22 infer로 타입스크립트의 추론을 직접 활용하자.
: infer 예약어 - 타입스크립트의 추론 기능을 극한까지 활용하는 기능 / 컨디셔널 타입과 함께 사용
: Element<number> extends Element<infer U>와 같은 타입을 작성하면, U타입은 number타입으로 추론(infer)
type El<T> = T extends (infer E)[] ? E : never;
type Str = El<string[]>; //string
type NumOrBool = El<(number | boolean)[]>; //number | boolean
- El 타입에서 infer 활용
- 추론 맡기고 싶은 부분 : 'infer 타입_변수'
- E가 타입 변수
- 타입 변수는 참 부분에만 사용
infer의 다양한 예시
type MyParameters<T> = T extends (...args: infer P) => any ? P : never;
type MyConstructorParameters<T> = T extends abstract new (...args: infer P) => any? P : never;
type MyReturnType<T> = T extends (...args: any) => infer R ? R : any;
type MyInstanceType<T> = T extends abstract new (...args : any) => infer R ? R : any;
type P = MyParameters<(a:string, b: number)=> string> //함수의 매개변수 추론 / 결과 : type P = [a : string, b : number]
type R = MyReturnType<(a:string, b: number)=> string> //함수의 반환값 추론 / string
type CP = MyConstructorParameters<new (a:string, b:number) => {}> //[a : string, b : number]
type I = MyInstanceType<new (a:string, b:number) => {}> //{}
- [a : string, b: number]은 [string, number]로 보면 됨
서로 다른 타입 변수들을 여러개 동시에 사용하기
type MyPAndR<T> = T extends (...args: infer P) => infer R ? [P,R] : never;
type PR = MyPAndR<(a:string, b:number) => string>;
- 매개변수는 P, 반환값은 R로 추론
같은 타입 변수를 여러 곳에 사용하기
type Union<T> = T extends {a: infer U, b: infer U} ? U : never;
type Result1 = Union<{ a:1|2 , b: 2|3}> // 1|2|3
type Intersection<T> = T extends {
a: (pa:infer U) => void,
b: (pb:infer U) => void
} ? U : never;
type Result2 = Intersection<{a(pa: 1|2):void, b(pb: 2|3) : void}> //2
- Union<T>
- 1|2|3 : 같은 이름의 속성 타입 변수는 유니온
- Intersection<T>
- a와 b가 메소드
- 매개변수의 반공변성 특성으로 같은 이름의 타입 변수는 인터섹션
(볼 일 별로 X) 같은 타입 변수 중 하나는 매개변수, 하나는 반환값인 경우
type ReturnAndParam<T> = T extends {
a:() => infer U,
b:(pb: infer U) => void,
} ? U : never;
type Result3 = ReturnAndParam<{a:() => 1|2, b(pb:1|2|3):void }> //1|2
type Result4 = ReturnAndParam<{a:() => 1|2, b(pb:2|3):void }> //never
- Result3
- 반환값 타입(a)이 매개변수의 타입(b)의 부분집합인 경우 -> 교집합
- Result4 : 그 외의 경우 모두 never
유니언을 인터섹션으로 만드는 타입 작성
type UnionToIntersection<U>
= (U extends any ? (p:U) => void : never) extends (p:infer I) => void
? I
: never;
type Result5 = UnionToIntersection<{a:number}|{b:string}>; //{a:number;} & {b:string;}
type Result6 = UnionToIntersection<boolean | true>; //never
- U: 제네릭이자 유니온 -> 분배법칙 실행
- Result5
- UnionToIntersection<{a:number}> | UnionToIntersection<{b:string}>
- I 는 각각 추론에 따라 {a:number}, {b:string}이 됨
- I는 매개변수이므로 인터섹션이 실해돼 {a:number}&{b:string}
- Result6
- <boolean| true> -> true | false | true 임
- 매개변수이므로 true & false & true가 되므로 never 됨