≠ 명시적 선언이나 이름을 기반으로 하는 명목적 타입 시스템(Nominal Type System)과 달리
→ 런타임에 타입을 체크하는 동적 타이핑인 덕 타이핑과 달리 정적 타이핑과 관련 되어있다.
자바스크립트는 함수 표현식 및 객체 리터럴과 같은 익명 객체를 사용하며, 덕 타이핑(동적 타이핑의 종류)으로 객체의 변수 및 메소드의 집합이 객체의 타입을 결정한다.
따라서 타입스크립트는 명목적 타입 시스템 대신 구조적 타입 시스템을 바탕으로 동작한다.
// 구조적 타이핑
interface SomeInterface {
x: string;
y: string;
}
const anotherVariable = { x: 'x', y: 'y', z: 'z' };
// SomeInterface의 서브타입
// {x:string, y:string} <- { x:string; y: string; z: string } Ok
const someVariable:SomeInterface = anotherVariable; // 정상 실행
컴파일러 및 타입체커는 someVariable에 anotherVariable를 할당할 수 있는지 확인하는 과정에서,
명시적인 이름 및 선언을 확인하는 것이 아닌, SomeInterface와 anotherVariable의 속성을 비교한다.
anotherVariable의 속성 중 일부가 SomeInterface의 속성을 만족시킬 수 있다면(즉, anotherVariable의 타입이 SomeInterface의 서브타입이라면) 할당 가능한 타입으로 추론한다.
다만, 슈퍼타입와 서브타입의 순서가 반대가된, 역의 상황에서는 성립하지 않는다.
// 슈퍼타입과 서브타입의 순서가 바뀐 상황
interface SomeInterface {
x: string;
y: string;
z: string;
}
const anotherVariable = { x: 'x', y: 'y' };
// SomeInterface의 서브타입
// { x:string; y: string; z: string } <- {x:string, y:string} Error
const someVariable:SomeInterface = anotherVariable; // 에러 발생
// 에러 메시지
// Property 'z' is missing in type '{ x: string; y: string; }' but required in type 'SomeInterface'.
이때, 잉여 속성 체크의 에러와는 구분해야한다.
// 잉여 속성 체크(엄격한 객체 리터럴 체크)
interface SomeInterface {
x: string;
y: string;
}
// {x:string, y:string} <- { x:string; y: string; z: string } Ok
const someVariable:SomeInterface = { x: 'x', y: 'y', z: 'z' }; // 에러 발생(잉여 속성 체크)
// 에러 메시지
// Type '{ x: string; y: string; z: string; }' is not assignable to type 'SomeInterface'.
// Object literal may only specify known properties, and 'z' does not exist in type 'SomeInterface'.
함수에서 매개변수로 객체를 받는 상황에서도 동일한 상황이 발생한다.
매개변수의 타입에 인자가 들어오는 상황으로 해석하면, 위와 동일하게 인식하면 된다.
interface FunctionParameter {
x: number;
y: number;
}
function addTwo(parameterObject:FunctionParameter) {
return parameterObject.x + parameterObject.y;
}
// otherParameterObject Type: { x:string; y: string; z: string };
const otherParameterObject = {x: 1, y: 2, z: 3};
addTwo(otherParameterObject); // 정상 실행
위의 내용은, 객체의 타입 및 함수의 파라미터의 타입에 대한 내용이었다.
그렇다면 함수의 자체 타입을 정의하는 경우는 어떻게 될까?
여러개의 인자를 받는 상황을 살펴보면 다음과 같다.
const OneParamFunc:(a:number) => 0 = (a:number, b: number) => 0; // Error
const TwoParmsFunc:(a:number, b:number) => 0 = (a:number) => 0; // Ok
(a)라는 매개변수만 가지는 함수 타입에 (a, b) 라는 매개변수를 가지는 함수를 할당하려면 오류를 발생시키지만,
(a, b)라는 함수 타입에 (a) 라는 매개변수만 가지는 타입을 할당할 수 있다.
단순하게 a와 b로 동작하는 함수에 a로만 동작하는 함수를 대입한다는 것은 애초에 말이 안되기 때문이다.
이 문제를 매개변수로 가지는 상황에서도 똑같이 발생할텐데, strict(strictFunctionTypes) 모드에서는 해당 문제를 에러로 인지할 수 있게 해준다.
let objectOneProp = { a:1 };
let objectTwoProps = { a:1, b:2 };
objectOneProp = objectTwoProps; // Ok
objectTwoProps = objectOneProp; // Error
let funcOneParam = (objectOneProp: {a: number}) => 0;
let funcTwoParams = (objectTwoProps: {a: number, b: number}) => 0;
funcOneParam = funcTwoParams; // Error
funcTwoParams = funcOneParam; // Ok
a만 속성으로 가지고있는 objectOneProp은 a와 b를 가지고있는 objectTwoProps의 슈퍼타입으로써 앞서 얘기한 구조적 타이핑이 적용되지만, 함수의 경우는 정반대로 동작한다.
이는 반공변적으로 작동하는 것인데, 공변, 반공변에 대한 개념은 다음과 같다.
공변성(Covariant) : X -> Y가 가능할 때 C가 C -> C로 가능하다면 이는 공변이다.
반공변성(Contravariant) : X -> Y가 가능할 때 C가 C -> C로 사용 가능하다면 이는 반공변이다.
함수의 타입 추론이 반대로 동작한 것은 X→ Y, C → C 의 상황과 일치하기때문에 반공변적으로 동작을 했다고 하는 것이다.