타입스크립트(ZeroCho) 기본문법
타입 에일리어스와 인터페이스의 상속(extends)
type Animal = {breath:true}
type Poyouryu = {breed:true}
type Human = {think:true}
이렇게 되어있는걸
type Animal = {breath:true}
type Poyouryu = Animal & {breed:true}
type Human = Poyouryu & {think:true}
상속으로 코드를 짤 수 있다.
type Animal = {breath:true}
type Poyouryu = Animal & {breed:true}
type Human = Poyouryu & {think:true}
const uri : Human = {breath:true, breed: true, think: true}
// uri는 사람이기 떄문에 숨도쉬고 생각도하고 등등 해야한다.
interface A {
breath:true
}
interface B extends A {
breed:true
}
const b: B = {breath:true, breed:true}
타입을 &로 상속하는것보단 extends로 상속받을 수 있다.
여기서 중요한건 interface표현성과 타입의 표현성의 차이일 뿐이다 라고 생각하면 된다.
interface의 특징
interface A {
talk: ()=> void;
}
interface A {
eat: ()=> void;
}
interface A {
shit: ()=> void;
}
const a : A = {talk(){},eat(){},shit(){}}
중복으로 사용할 수 있다. 선언할때마다 합쳐진다.
여기에서
interface A {
sleep: ()=> void;
}
sleep을 추가하게되면
const a : A = {talk(){},eat(){},shit(){},sleep(){}}
여기에 자동으로 sleep(){}이 추가가 된다.
네이밍 룰
interface IProps{}
type TType = string | number;
enum EHello {
Left,
Right
}
I , T, E 를 붙히는 방식을 했었는데 요새는 안붙이는 방법으로 한다.
interface Props{}
type Type = string | number;
enum Hello {
Left,
Right
}
타입을 집합으로 생각하자(좁은 타입과 넓은 타입)
type A = string | number; //넓은 타입
type B = string //좁은 타입
좁은타입에서 넓은타입으로 대입이 가능하지만 넓은타입에서 좁은타입은 대입이 불가능하다.
// 넓은타입
type A = { name : string }
type B = { age : number}
//좁은타입
type C = { name : string, age :number}
객체는 속성이 좁은것일 수록 넓은 타입이다. 객체는 상세(구체)할수록 좁다고 생각해야 한다.
// 넓은타입
type A = { name : string }
type B = { age : number}
type AB = A | B
//좁은타입
type C = A & B
const ab: AB = {name :"uri"}
const c: C = {name : 'uri', age: 29}
여기에서 C가 좁은 타입인데 ab인 넓은 타입을 c에 대입해보면
이렇게 대입할 수 없다고 나온다.
좁은 타입을 넓은 타입에 대입해보면
에러없이 잘 되는걸 볼 수 있다.
{name : 'uri', age: 29, married:false};
이건 타입 C보다 좁은 타입이여서 C에 대입이 가능한걸로 알지만 에러가 생긴다.
이건 특수한 사항으로 객체 리터럴 검사로 잉여속성 검사로 추가적인 검사를 더 하게 된다.
const obj = {name : 'uri', age: 29, married:false};
const c: C = obj
이런건 중간에 데이터를 빼주면 에러가 사라진다.
void의 두 가지 사용법
A속성에는 a타입밖에 없는데 b를 넣었더니 b가 없다고 에러가 뜬다.
interface A { a: string }
const obj = { a: 'hello', b: 'world' }
const obj1 : A = obj
이렇게 obj로 한번빼고 하면 에러가 발생하지 않는다.
타입스크립트에서는 객체 리터럴을 바로 대입할때는 잉여속성 검사가 추가로 들어간다.
함수값은 void라는 타입으로 추론된다.
void란?
리턴값이 있으면 에러가 난다. 함수의 리턴값이 void라면 리턴값을 넣으면 안되는 함수가.
다만 undefined는 가능하다. 또란 retrun; 만으로 끝나면 가능하다.
function a (callback:()=>void): void{
}
interface Human {
talk: () => void;
}
void의 존재
1. 리턴값이 void인 것
2. 매개변수가 void인 것
3. 매서드가 void 인 것
매개변수, 메서드가 void인것은 retrun값이 있어도 되고 리턴값이 void인것은 retrun값이 있으면 에러가 난다.
이유는 매개변수, 메서드의 void의 의미는 리턴값을 사용하지 않겠다는 의미고 리턴값에 void는 리턴값을 없어야 한다는 의미여서 그렇다.
function forEach<T>(arr: T[], callback: (el: T) => undefined): void;
⬇️
declare function forEach<T>(arr: T[], callback: (el: T) => undefined): void;
// declare를 사용하면 함수를 만들어주지 않아도 에러가 나지않고 자바스크립트로 변환을 하면 사라진다.
declare function forEach(arr: number[], callback: (el: number) => undefined): void;
// declare function forEach<T>(arr: T[], callback: (el: T) => void): void;
let target: number[] = [];
forEach([1, 2, 3], el => target.push(el));
target.push의 타입은 number인데 forEach에 리턴문이 undefined로 타입을 지정해놔서 에러가 발생한다.
타입이 undefined를 void로 지정해줘도 에러는 발생하지 않는다.
declare function forEach(arr: number[], callback: (el: number) => void): void;
// declare function forEach<T>(arr: T[], callback: (el: T) => void): void;
let target: number[] = [];
forEach([1, 2, 3], el => target.push(el));
매개변수에서 쓰이는 void는 실제 리턴값이 무엇이든 상관하지 않겠다는 의미이기 때문에 에러가 발생하지 않는거다.
void는 undefined랑 다르다.
interface A {
talk: () => void;
}
const a: A = {
talk() { return 3; }
}
const b = a.talk();
여기에서 b는 a.talk의 리턴값인 3이어야 할 것 같지만 결과는 b는 void이다.
이유는 메서드의 void는 retrun값을 무시하는거기 때문에 타입이 void인것이다. void는 원칙적으로 리턴을 안넣는게 맞다.
declare은 스크립트 파일이 여러개 있을때 해당 타입스크립트 파일에는 선언이 되어있지 않지만 다른 파일에 선언을 했다는 걸 보증할때 사용한다. 즉, 외부에서 만들어진걸 사용할때 쓴다.
unknown과 any(그리고 타입 대입가능표)
any를 쓸바에는 unknown을 쓴다!
any의 문제점은 타입검사를 포기해버린다.고로 타입스크립트를 쓰는 의미가 없어진다.
unknown을 사용하면 지금당장은 타입을 정확히 모르겠고 나중에 쓸때 타입정의를 해줄때 사용한다.
타입 포기한 any랑은 다르다.
타입간 대입 가능 표
초록색 표시도 x라고 보면된다.
ex ) undefined는 void에 대입 가능하다
function a(): void {
return undefined
}
타입 좁히기(타입 가드)
function numOrStr(a: number | string) {
a.toFixed(1);// 에러발생 'string' 형식에 'toFixed' 속성이 없습니다.
}
numOrStr('123');
numOrStr(1);
이렇게 되면 string이올 수도 있기 떄문에 에러가 발생한다.
function numOrStr(a: number | string) {
if(typeof a === 'number'){
a.toFixed(1);
} else {
a.charAt(3);
}
}
numOrStr('123');
numOrStr(1);
조건문으로 a가 number일 경우로 넣어주면 에러발생을 방지한다.
function numOrNumArr(a: number | number[]) {
// 배열인지 아닌지 구별하는 방법
if (Array.isArray(a)) {
a.slice(1);
} else {
a.toFixed(1);
}
}
numOrNumArr(123);
numOrNumArr([1,2,3]);
배열 타입인지 아닌지도 구분이 가능하다.
객체 구분
속성 값으로 구분
type B = { type: 'b', bbb: string };
type C = { type: 'c', ccc: string };
type D = { type: 'd', ddd: string };
type A = B | C | D;
function typeCheck(a: A) {
if (a.type === 'b') {
a.bbb;
} else if (a.type === 'c') {
a.ccc;
} else {
a.ddd;
}
}
객체는 속성이 다른것과 속성의 값이 다른걸로 구분이 가능하다.
속성으로 구분
type B = { type: 'b', bbb: string };
type C = { type: 'c', ccc: string };
type D = { type: 'd', ddd: string };
type A = B | C | D;
function typeCheck(a: A) {
if ('bbb' in a) {
a.bbb;
} else if ('ccc' in a) {
a.ccc;
} else {
a.ddd;
}
}
커스텀 타입 가드(is, 형식 조건자)
interface Cat { meow: number }
interface Dog { bow: number }
function catOrDog(a: Cat | Dog): a is Dog {
// 타입판별을 직접 만들어야 한다.
if ((a as Cat).meow) { return false }
// 강아지려면 meow가 false여야 한다.
return true;
}
//타입을 구분해주는 커스텀 함수를 직접 만들 수 있다.
const cat: Cat | Dog = { meow: 3 }
if (catOrDog(cat)) {
console.log(cat.meow);
}
if ('meow' in cat) {
console.log(cat.meow);
}
리턴에 is가 들어가있으면 커스텀 타입 가드 함수다.
if 문안에 써서 타입스크립트에게 정확한 타입이 뭔지 알려주는거고 타입판별을 직접 만들어야 한다.
const isRejected = (input: PromiseSettledResult<unknown>): input is PromiseRejectedResult => {
input.status === 'rejected'}
const isFulfilled = <T>(input: PromiseSettledResult<T>): input is PromiseFulfilledResult<T> => {
input.status === 'fulfilled'}
// Promise -> pending -> Settled(Resolved, rejected)
// 실패 성공 합쳐서 Settled이긴 하다.
const promises = await Promise.allSettled([Promise.resolve('a'), Promise.resolve('b')]);
const errors = promises.filter(isRejected);