리액트 + 타입스크립트 사용 시 Props로 전달할 때 객체 리터럴을 넘기게 되면 인터페이스에 정의 되지 않은 프로퍼티가 들어있을 때 에러가 나는 반면에,
변수에 담아서 넘기게 되면 인터페이스에 정의하지 않은 프로퍼티를 넣더라도 에러가 나지 않는 상황 발생.
이러한 현상이 왜 발생하는 것인지 알아보겠습니다. 또한 해결방법을 몇가지 공유 하겠습니다.
덕 타이핑이란
- 컴퓨터 프로그래밍 분야에서 덕 타이핑(duck typing)은 동적 타이핑의 한 종류로,
객체의 변수 및 메소드의 집합이 객체의 타입을 결정하는 것을 말합니다.
클래스 상속이나, 인터페이스 구현으로 타입을 구분하는 대신 덕 타이핑은 객체가 어떤 타입에 걸맞은 변수와 메소드를 지니면
객체를 해당 타입에 속하는 것으로 간주합니다.
만약 어떤 새가 오리처럼 걷고, 헤엄치고, 꽥꽥거리는 소리를 낸다면 저는 그 새를 오리라고 부를 것입니다.
코드로 보면 아래와 같습니다.
interface Person {
name: string;
introduce: () => void;
}
const farmer: Person = {
name: 'farmer',
introduce: () => { console.log('hello my name is farmer');}
}
const mark = { // mark 변수는 타입을 정의하지 않은 것을 확인해주세요.
name: 'mark',
introduce: () => { console.log('hello my name is mark');}
}
const getIntro = (person: Person):void => {
person.introduce();
}
// getIntro함수를 Call하기 위해서는 인자로 Person 인터페이스에 맞는 값을 보내줘야합니다.
getIntro(farmer); // Success
getIntro(mark); // Success
mark 변수는 Person 인터페이스 타입을 가지고 있지 않지만, 해당 타입에 걸맞는 변수와 메소드를 가지고 있기 때문에 에러가 나지 않습니다. 이러한 현상을 덕 타이핑 이라고 합니다.
초과 프로퍼티 검사
- 해당 인터페이스에 정의되지 않은 프로퍼티가 있다면 에러를 내뿜는 검사.
- 타입스크립트에서 객체 리터럴을 다른 변수에 할당할 때나 인수로 전달할 때는 초과 프로퍼티 검사를 받습니다.
- 맨 위에서 정의한 문제 상황도 이 검사와 관련이 있었습니다.
코드로 함께 보겠습니다.
interface Person {
name: string;
}
const a:Person = {
name: 'hello',
};
const b = { // b 변수에는 타입을 정의하지 않았다는 것을 확인해주세요.
name: 'hello',
age: 20,
};
const getPerson = (person: Person): string => {
return person.name;
};
getPerson(a); // Success.
getPerson(b); // Success.
getPerson({name: 'hello', age: 20}); // Error <- Person 인터페이스에 정의되지 않은 초과프로퍼티 검사가 일어납니다.
b 변수를 잘 보시면 인터페이스에 정의되지 않은 age라는 값을 가지고 있기 때문에 에러가 나야하는 것이 정상일 것 같습니다.
그런데 직접 실행해보시면 아시겠지만, 함수에 b 변수를 넘기더라도 잘 작동이 됩니다.
이러한 문제들을 해결하기 위해서 제가 생각한 2가지 방법이 있습니다. 물론 이 방법이 어느 상황에 따라서 좋을 수도 있고 안 좋을 수는 있으나 일단 2가지 공유 하겠습니다.
해결책
1. 값을 넘길 때 객체를 변수에 담아서 보내지 말고 객체 리터럴을 직접 넘긴다.
interface Person {
name: string;
}
const getName = (person:Person): string => {
return person.name;
}
getName({name: 'mark', age: 20}); // Error발생!
getName({name: 'mark'}); // Success
2. 객체를 변수에 담을 때 해당 변수에 관련 인터페이스 타입을 정의한다.
이전에 b 변수는 타입을 정의하지 않고 했었기 때문에 Person 인터페이스에 정의되지 않은 age라는 property를 가질 수 있었습니다.
하지만 처음 선언할 때 부터 변수에 타입을 지정했다면 이런 문제는 없었을 것 입니다.
interface Person {
name: string;
}
const getName = (person:Person): string => {
return person.name;
}
const b = { // 이전 방식 변수 선언 시 에러 x
name: 'hello',
age: 20,
}
const b:Person = { // 선언할 때 타입을 정의할 것.
name: 'hello',
age: 20, // Error.
}
감사합니다.