////
Search

Typescript

Type inference(타입 추론)

let a = "hello" // implicit let b : boolean = false // explicit //if you do like this, compiler tells it has error b = "false"; //another example let c = [1,2,3] c.push("1") // error let c : number[] = [] // when you want to create empty array c.push(1) // no error
TypeScript
복사

Types

규칙

let a = [1,2]; let b : string[] = ["i1","1"]; let c : boolean[] = [true];
TypeScript
복사

optional

// 어떤 애들음 name이 있고 어떤 애들은 age가 있을 때 const player : { name: string, age?: number } = { name: "nico" } //이렇게 생각할 수 있다. const player : object = { name: "nico" } player.name; // error /* 왜 그러냐 : 뒤에는 타입이 들어간다. 정확하게 얘기하면 어떤 property를 가지는 object인지가 명시되는 듯 따라서 그냥 object라고 써버리면, object는 아무 프로퍼티도 가지지 않으므로 에러가 뜨는 듯하다. 따라서 어떤 타입을 만든다면 맨 처음 처럼 해줘야함. */ //드는 의문, 클래스 어떻게 정의하냐? //자바랑 비슷하네, 클래스를 그냥 정의하면 되는구나
TypeScript
복사

type alias

type Player = { name: string, age?: number } const nico : Player = { name: "nico" } const lynn : Player = { name:"nico", age: 12 }
TypeScript
복사
→ type alias 대신에 클래스를 쓰면 되는거 아닌가?
보니까 세 가지가 있네. interface, type alias, class
OOP의 원칙에 좀 더 맞게 사용하려면 사실 interface는 그냥 타입이고 class는 생성되는 객체이다.
아직 잘 모르겠지만 class에 컨스트럭터가 필수적으로 있어야하는 것처럼 보인다.
따라서 대부분의 경우 일반 자바처럼 하면 될 듯.
다만 조금 다른 점을 꼽자면, duck typing이라고 하는게 있네
애초에 타입 검사를 할 때 어떻게 하냐에서 출발한 문제인거 같은데, typeScript는 같은 시그니처의 메서드나 프로퍼티가 있다면 구현한 것으로 보는 듯.
뿐만 아니라 아래처럼 프로퍼티가 요구하는 사항보다 넘쳐도 마찬가지인듯
interface IPerson { name: string; } function sayHello(person: IPerson): void { console.log(`Hello ${person.name}`); } const me = { name: 'Lee', age: 18 }; sayHello(me); // Hello Lee
TypeScript
복사

function rerturn type

type Player = { name: string, age?: number } const nico : Player = { name: "nico" } const lynn : Player = { name:"nico", age: 12 } function playerMaker(name:string) : Player{ return { name:name } } const playerMaker = (name:string) : Player => ({name}); // arrow function const larry = playerMaker("larry"); larry.age
TypeScript
복사

readonly property to type

type Player = { readonly name: string, age?: number } const lynn : Player = { name:"nico", age: 12 } const playerMaker = (name:string) : Player => ({name}); const nico = playerMaker("nico"); nico.age = "las"; //another example const numbers : readonly number[] = [1,2,3,4,5]; numbers.push(6);
TypeScript
복사

tuple

// 정해진 순서에 맞는 배열이 탄생~ const player: [string,number,boolean] = ["nico",1,true]; player[0] = "larry"; const player: readonly [string,number,boolean] = ["nico",1,true];
TypeScript
복사
javascript에서 const는 재선언과 재할당이 안 되는거지, 프로퍼티의 값은 변경할 수 있는 거다.
let은 재할당도 가능함.

any

any
TypeScript
복사
타입 스크립트의 보호장치에서 벗어나고 싶으면 쓰는거다. 그냥 쓰지 마셈.
참고로 null과 undefined의 차이는 null은 값이 할당되었지만, 빈 값일 때이다. 즉 빈 오브젝트이고
undefined는 primitivie 타입으로서, 아직 값이 할당이 되지 않은 상태를 얘기함.

unknown & void & never

let a:unknown; //api로부터 응답이 왔는데, 어떤 타입일지 모를때 if(typeof a === 'number'){ let b = a+1; } if(typeof a === 'string'){ let b = a.toUpperCase(); } //return nothing function hello(){ console.log('x'); } // 함수가 절대 return하지 않을 때, never 타입 function foo2(x: string | number): boolean { if (typeof x === "string") { return true; } else if (typeof x === "number") { return false; } return fail("Unexhaustive!"); } function fail(message: string): never { throw new Error(message); } let tmp: any = {id:"123"} console.log(foo2(tmp))
TypeScript
복사

Functions

function type Expressions

function greeter(fn:{a:string} => void){ fn("hello world"); } function prinToConsole(s: string){ console.log(s); } greeter(printToConsole); type GreetFunctions = (a:string) => void; function greeter(fn:GreetFunctions) { }
TypeScript
복사
위와 같은 방식으로 function에 대한 type expression을 할 수 있다.
타입 스크립트 독스에 보면, 타입 스크립트에서 type은 그저 set of Data라고 생각하면 편하단다. 따라서 함수 또한 일급 객체인 자바스크립트에서, 각 함수별로 타입을 표현할 수 있을텐데, 위의 예시처럼 표현해준다.

call signature

type DescribaleFunction = { descripton: string; (someArg: number): boolean; }; function doSomething(fn:DescribableFunction){ console.log(fn.description + " returned " + fn(6)); }
TypeScript
복사
함수 타입을 정의할 수 있다. 이것을 call signature라고 한다.s
자바스크립트에서 함수는 callable한 특성 외에 properties를 가질 수 있다.
하지만 function type expression은 이것을 허용하지 않기에, object type으로 call signature를 써준다.

Construct Sginatures

type SomeConstructor = { new (s:string): SomeObject; } function fn(ctor: SomeConstructor){ return new ctor("hello"); }
TypeScript
복사
객체를 생성하는 특수한 함수인 constructor가 있다.
이 constructor 또한 함수이기에 signature를 생성해줄 수 있고, 이것을 new를 통해 호출할 수 있다.

Generic Functions

// type SuperPrint = { // <TypePlaceHolder>(arr:TypePlaceHolder[]):void, // } type SuperPrint = <T>(a: T[]) => T; const superPrint:SuperPrint = (arr) => { arr.forEach(i=>console.log(i)); return arr[0]; } superPrint([1,2,3,4]); superPrint([true,false,true]); superPrint(["a","b","c"]); type GamePlayer<E> = { name:string extraInfo:E } type NicoExtra = { favFood:string } type NicoPlayer = GamePlayer<NicoExtra> const nnico:NicoPlayer = { name:"nico", extraInfo:{ favFood:"kimchi" } } const llynn: GamePlayer<null> = { name:"lynn", extraInfo:null }
TypeScript
복사
기본적으로 사용할 때는 typeScript가 자동으로 타입을 inference한다.
뿐만 아니라 자바처럼 Generic에 대한 constraint를 줄 수 있는 듯 하다.
function longest<Type Extends {length:number}>(a: Type, b: Type){ if(a.length >= b.length){ return a; }else{ return b; } } //longerArray is of type 'number[]' const longerArray = longest([1,2], [1,2,3]); //longerString is of type 'alice' | 'bob' const longerString = longest("alice", "bob"); //Error! Numbers don't have a 'length' property const notOK = longest(10,100);
TypeScript
복사

generic에 대해서 explicit하게 inference하도록 알려줄 수 있다.

function combine<Type>(arr1: Type[]. arr2: Type[]): Type[]{ return arr1.concat(arr2); } const arr = combine([1,2,3],["hello"]); // error // string is not assignable to type 'number' //근데 이건 또 안됨 function combine<Type1,Type2>(arr1:Type1[], arr2: Type2[]){ return arr1.concat(arr2); } //array에 대해서 concat할 수 있는 거는 같은 타입이여야 한다. //해결 방법 const arr = combine<string | number>([1,2,3],["hello"]); //이거는 type이 string | number인 union 타입이라서 가능한건데, 대체 union type이 뭐냐? /* type은 그냥 데이터의 set이기 때문에, 그 set을 union 시켜놓은 타입이다. union type은 따라서, union의 모든 멤버에 대해서 valid한 연산만 허용한다. 따라서 이 set들을 함수 내부에서 분리시켜주는 작업이 필요한데, 이게 바로 narrow라고 하는 거다. narrow는 기본적으로 set을 분리시키는 걸 의미하기에, 어떤 방식으로든 잘 분리하면 그만이다. 1. if-else 2. equality 3. truthiness 4. in operator 5. instanceof 쓰는 방법 정도가 있겠네. */
TypeScript
복사

Function Overloads

//방법1: 다양한 function expression을 type으로 일단 정의해놓고, 구현하고 구현할 때 narrowing 한다. type Add = { (a:number, b:number) : number, (a:number, b:string) : number, (a:number, b:number, c:number): number } const add:Add = (a,b,c?) => { if(typeof b === "string") return a; else{ return a+b; } } //방법2: overloading을 정의하는 파트와 구현부를 분리해서 생각한다. //Overload signatures function greet(person: string): string; function greet(person: string[]): string[]; //implementation function greet(person: unknown) : unknown { if(typeof person == 'string'){ return `Hello. ${person}!`; }else if(Array.isArray(person)){ return person.map(name => `Hello, ${name}!`); } throw new Error('Unable to greet'); } //여기서 중요한 것은 마지막 구현부는 overload되는 모든 함수 signature를 포함할 수 있어야한다는 것!!
TypeScript
복사
말 그대로 다양한 argument counts나 type에 대해서 호출하고 싶을 때 사용하는 거다.
위에 처럼 두 가지 방법이 있는 듯하다.
Function overload를 할 때는 몇 가지 가이드라인이 있는 듯 하다.
1.
specific overload타입을 무조건 먼저 써라.
a.
타입 체킹을 위해서부터 하는 듯. 따라서 허접한게 먼저 나오면 무조건 거기서 다 매치된다.
2.
Optional Parameter를 써라
a.
따라오는 파라미터가 있는 경우에는 오버로딩을 하지 마라. 대신 optional이 가능하다면 최대한 그렇게 하기를...
b.
왜냐?
i.
일단 signature compatibility, 즉 signature가 넘겨도 되는건지 검사를 할 때 어떤 방식으로 하냐
1.
source의 argument들을 넘길 때, 불러내는 메소드가 invoke 가능하면 합당하다고 판단한다.
interface Example{ diff(one: string): number; diff(one: string, two: string): number; diff(one: string, two: string, three: boolean): number; } interface Example { diff(one:string, two?: string, three?: boolean): number; } function fn(x: (a: string, b: number, c: number) => number){ let result: number = x("1", 2,3); console.log(result); } let x: Example = { diff(one:string, two?:string, three?:boolean): number{ if(three){ console.log(three); console.log(typeof three); return 3; }else if(two){ console.log(two); return 2; }else{ console.log(one); return 1; } } } } fn(x.diff)
TypeScript
복사
이 예시에서 x.diff에 해당하는 함수를 fn이 요구하는 타입
즉, (a: string, b: number, c: number) ⇒ number에 맞출 수 있을 것이냐가 관건인데, 원래는 가능했던 이유가 fn 내부에서 x를 호출했을 때, 어떤 타입을 오버로딩할 수 있냐를 봤을 때, 첫 번째 형식의 함수, diff(one: string): number에 해당하는 형식에 매칭이 된다. 즉 첫 번째 parameter의 타입이 일치하고, 해당 오버로딩을 invoke할 수 있기 때문에, 합당하다고 판단하고, 뒤에 것들은 extraneous argument로 판단하고 그냥 넘겨버리는 것.
optional parameter를 사용했을 때만 방어가 가능한 버그다.
ii.
하나의 파라미터만 있다면, union 타입을 써라
interface Moment{ utcOffset(): number; utcOffset(b: number): Moment; utcOffset(b: string): Moment; } interface Moment{ utcOffset(): number; utcOffset(b: number | string): Moment; }
TypeScript
복사

Object Types

index Signature

interface StringArray{ [index: number]: string; } const myArray: StringArray = getStringArray(); const secondItem = myArray[1];
TypeScript
복사
이 때 index signature의 프로퍼티는 number이거나 string이여야한다.
index signature는 ‘dictionary’를 짜게하는 좋은 방법이지만, 모든 프로퍼티의 return type을 통일시킬 것을 강요한다. 왜냐하면 obj.property로 접근할 수도 있지만은 obj[”property”]로도 접근할 수 있어야하기 때문이다. 따라서 리턴 타입이 통일되어야함.
다만 union type으로 해놓은 경우에는 달라도 됨.

Extending Types

interface BasicAddress{ name?: string; street: string; city: string; country: string; postalCode: string; } interface AddressWithUnit extends BasicAddress{ unit: string; }
TypeScript
복사
extends를 쓰면 원래 타입을 그대로 복사해온다.

Intersection Types

interface Colorful{ color: string; } interface Circle{ radius: number; } type ColorfulCircle = Colorful & Circle;
TypeScript
복사
Colorful과 Circle에 있는 모든 멤버들을 가진 새로운 타입을 생성한다.

Type Manipulation

keyof

object type을 받아서 string이나 numberic literal union of its keys를 반환한다. type Point = { x: number; y: number{; type P = keyof Point; type Arrayish = {[n:number]: unknown}; type A = keyof Arrayish; // type A = number type Mapish = {[key:string]: boolean}; type M = keyof Mapish; // type B = string | number
TypeScript
복사

Classes & Interfaces

class Point{ x: number; y: number; constructor(x = 0, y = 0){ this.x = x; this.y = y; } } class Point{ constructor(x: number, y: string); constructor(s: string); constructor(xs: any, y?: any){ //구현 } }
TypeScript
복사

Implements

interface Checkable{ check(name: string): boolean; } class NameChecker implements Checkable{ check(s){ return s.toLowercse() === "ok"; } }
TypeScript
복사
implements는 해당 클래스가 해당 interface로 취급될 수 있는지를 나타내는 거지. 바꾸는게 아니다.
따라서 s의 타입은 any이다. 명시해주지 않았기 때문에.
비슷하게 implementing interface는 optional property를 생성해주는 것은 아니다.
interface A{ x: number; y?: number; } class C implements A{ x = 0; } const c = new C(); c.y = 10;
TypeScript
복사

Overriding

class Base{ greet(){ console.log("Hello, world!"); } } class Derived extends Base{ greet(name?: string){ if(name === undefined){ super.greet(); }else{ console.log(`Hello, ${name.toUpperCase()}`); } } }
TypeScript
복사
오버라이딩을 할꺼면 base 클래스에 해당하는 규약을 좀 맞출 필요가 있다.
그니까 이런 경우는 안됨.
class Base { greet() { console.log("Hello, world!"); } } class Derived extends Base { // Make this parameter required greet(name: string) { /* Property 'greet' in type 'Derived' is not assignable to the same property in base type 'Base'. Type '(name: string) => void' is not assignable to type '() => void'. Property 'greet' in type 'Derived' is not assignable to the same property in base type 'Base'. Type '(name: string) => void' is not assignable to type '() => void'. */ console.log(`Hello, ${name.toUpperCase()}`); } }
TypeScript
복사
다른건 일반적인 클래스랑 비슷한듯?