인터페이스는 extend 키워드를, type alias는 intersection연산자(&)를 통해 상속을 구현한다.
타입별칭이 인터페이스를 상속할 수도 있고, 인터페이스가 타입별칭을 상속 할 수도 있다.
type Dog을 interface Cow로 확장했다. 하지만, Dog이 union일경우 확장이 불가하다.
상속할때 부모의 type을 override 가능하다. (완전히 다른 타입이 아니라, 넓은범위에서 좁은범위의 대입이 가능한것임)
{
interface Animal{
name :string;
}
type Dog = Animal & {
bark : string
}
//bark는 string type, Dog type은 interface를 이용해 intersetion 함.
type Cat = Animal | {
meow : () =>void;
}
type Name = Cat['name']; // Animal에는 존재하지만, meow에는 존재x
interface Cow extends Dog{
bark : "hello"
} // Dog의 bark 속성을 override => string to "hello"
const ex : Cow = {
bark : "hello",
}
}
넓은타입부터 좁은타입으로 타입을 좁혀가며 대입하는게 원칙임
배열이나 튜플에는 readonly 수식어를 붙일 수 있음 => readonly 수식어가 붙은게 더 넓은 타입임.
타입이 넓다 좁다의 기준? => 경우의수로 판단하면.. readonly가 붙은게 더 좁은타입이 되는것이 아닌가?
이 부분만 좀 예외적으로 생각하는 방식이 다른듯함.
let a: readonly string[] = ["hi","readonly"];
let b: string[] = ["hi","readonly"];
a = b; // readonly string[] 타입에 string[] 대입 가능.
b = a; //error => string[]타입에 readonly string[] 대입 불가능.
// 일반 배열
const arr1 = [1, 2, 3];
arr1.push(4); // 유효, 배열 수정 가능
// 읽기 전용 배열
const arr2: readonly number[] = [1, 2, 3];
arr2.push(4); // 에러, 읽기 전용 배열은 수정 불가능
프로퍼티가 optional인 객체가 optional이지 않은 객체보다 넓은타입임
배열과 다르게 객체에서는 속성에 readonly가 붙어도 대입가능함.
TS에서는 모든 속성이 동일하면 객체의 이름이 다르더라도 동일한 타입으로 취급함.
B인터페이스는 A인터페이스가 되기 위한 모든 조건을 충족함 => 구조적 타이핑관점에서 A인터페이스라고 할 수 있음. A인터페이스는 안됨.
CopyArr는 객체타입인데도 숫자배열을 대입 할 수 있음. CopyArr 타입에 존재하는 모든 속성을 숫자배열이 갖고 있음.
서로 구분하게 하려면 => 브랜드 속성같이 type을 구분 짓는 속성을 따로 추가하는 방법으로 방지가능.
interface A{
name: string;
}
interface B{
name:string;
age:number;
}
type Arr = number[];
type CopyArr = {
[key in keyof Arr] : Arr[key];
}
const copyArr:CopyArr = [1,3,4];
// 브랜드속성 사용 예제
interface IA{
_type : "A" // 브랜드속성
name: string;
}
interface IB{
_type : "B"
name:string;
}
{
interface Zero{
type : "human",
race : "yellow",
name : "zero",
age : 28,
}
interface Nero{
type:"human",
race : "yellow",
name : "nero",
age : 32,
}
interface Person<N,A>{
type :"human",
race : "yellow",
name : N,
age : A,
}
interface Zero extends Person<"zero", 28>{}
interface Nero extends Person<"nero", 32>{}
}
interface Array<T>{
[key: number] :T,
length : number,
}
const personFac = <N,A>(name:N, age:A)=>({
type:"human",
race: "yellow",
name,
age,
})
function personFactory<N,A>(name:N, age:A){
return ({
type:"human",
race: "yellow",
name,
age,
})
}
interface IPerson<N= string,A=number>{
method : <B>(param:B) => void;
}
첫번째함수 (value23)에서 매개변수로 들어오는 type은 T[]임 => 그래서 hasValue의 value는 T타입으로 추론됨.
string[]가 파라미터로 들어왔다면, hasValue는 string type모두가 올 수 있음.
상수형 타입으로 사용하려면(유니언처럼) => const 키워드를 제너릭에 붙혀주면 됨.
T값이 유니언타입으로 "a"|"b" 이렇게 선언됨.
function values23<T>(initial :T[]){
return {
hasValue(value:T){
return initial.includes(value),
};
}
}
const savedValues1 = values23(["a","b"]);
savedValues1.hasValue("x"); // type string not error
function values<const T>(initial :T[]){
return {
hasValue(value:T){
return initial.includes(value),
};
}
}
const savedValues = values(["a","b"]);
savedValues.hasValue("x"); // type => "a"|"b" error
제너릭은 extends 키워드로 제약조건을 줄 수 있음
제약이 걸리면 제약에 어긋나는 타입은 입력 할 수 없지만, 구체적인 타입은 입력 할 수 있음
하나의 타입 매개변수가 다른 타입 매개변수의 제약이 될 수도 있음
interface Example<A, B extends A>{
a:A,
b:B,
}
type Usecase1 = Example<string, number>; // error
type Usecase2 = Example<string, "hi">; // ok
많이 쓰이는 제약조건 (객체, 배열 , 함수, 생성자, 속성의 키)
<T extends object> //모든객체
<T extends any[]> //모든배열
<T extends (...args : any) => any>//모든 함수
<T extends abstract new (...args :any) => any> //생성자 타입
<T extends keyof any> // string|number|symbol
타입매개변수야 제약조건의 차이를 잘 구분해야함.
아래코드의 의미 T type은 V0에 올 수 있는 모든 타입을 의미함. (즉 반례가 존재함)
즉, 파라미터로 넘어오는 type이 정해져있는경우에는 제너릭을 사용하지않고 특정해서 type을 정해놓는게 좋음 + 원시값 타입을 사용할 때 대부분 제약을 걸지 않아도 되는 경우가 많음
{
interface V0{
value: any;
}
const returnV0 = <T extends V0>():T => {
return {value :'test'};
} // {value:string , another : string}도 T가 될수있음.
function onlyBoolean<T extends boolean>(arg:T = false):T{
return arg;
} //error T는 never일수도 있으므로 오류발생
function onlyBoolean(arg: true |false = true){
return arg;
} //제너릭을 사용하지 않으면 오류해결
const f = ():V0 =>{
return {value:"test"}
}
}```