이 글은 PC 버전 TISTORY에 최적화 되어있습니다.
Javascript Index Signatures
Javascript(및 Typescript)의 Object는 다른 Object의 참조를 유지하기 위해 문자열로 접근할 수 있습니다.
다음은 간단한 예입니다.
let foo:any = {};
foo['Hello'] = 'World';
console.log(foo['Hello']); // World
우리는 "Hello"라는 Key로 "World"라는 문자열을 저장했습니다. 기억하세요, 우리는 Javascript의 어떤 객체든 저장할 수 있습니다. 그럼 이제 이번 장의 컨셉을 이해하기 위해 클래스의 인스턴스를 아래와 같이 저장해보도록 하죠.
class Foo {
constructor(public message: string){};
log(){
console.log(this.message)
}
}
let foo:any = {};
foo['Hello'] = new Foo('World');
foo['Hello'].log(); // World
우리는 문자열로 접근할 수 있다는 것을 알고 있습니다. 만약 당신이 index signature([]을 통해 인덱싱하는 것을 말함)에 object를 넘긴다면 Javascript는 runtime에 결과를 얻기 전에 실제로 .toString을 호출합니다. 이것에 대한 실험은 아래에 있습니다.
let obj = {
toString(){
console.log('toString called')
return 'Hello'
}
}
let foo:any = {};
foo[obj] = 'World'; // toString called
console.log(foo[obj]); // toString called, World
console.log(foo['Hello']); // World
Note: toString은 object가 index 위치에서 실행될 때마다 호출됩니다.
배열은 살짝 다릅니다. number 인덱싱을 위해 JavaScript VM은 최적화(그것이 실제로 배열이고, 아이템 구조를 저장하고 있는지에 따라 다릅니다.)를 시도합니다. 그래서 number는 그 자체로 유효한(string과는 다르게) 객체 접근자가 됩니다. 아래의 간단한 배열 예가 있습니다.
let foo = ['World'];
console.log(foo[0]); // World
지금까지가 Javascript였습니다. 이제 아래에서 Typescript가 이 개념을 어떻게 능숙하게 다루는지 보도록 하죠.
TypeScript Index Signature
우선, Javascript가 어떤 object index signature건 간에 암묵적으로 toString을 호출하기 때문에, Typescirpt는 초보자들이 자기 발에 총을 쏘는 것을 막기 위해 친절하게도 index signature는 무조건 string이거나 number여야한다고 에러를 내줍니다. (저자는 stackoverflow에서 유저들이 자신의 발에 총 쏘는 것을 많이 봤다고 합니다. 재미는 없군요.)
let obj = {
toString(){
return 'Hello'
}
}
let foo:any = {};
// ERROR: the index signature must be string, number ...
foo[obj] = 'World';
// FIX: TypeScript forces you to be explicit
foo[obj.toString()] = 'World';
위 처럼 유저가 toString 을 명시적으로 강제하게 하는 이유는 뭘까요? 기본 Object가 실행하는 toString은 굉장히 별로기 때문입니다. 예를들어 크롬 V8에서는 무조건 [object Object]를 반환하여 정보가 모호하게 표현되죠.
let obj = {message:'Hello'}
let foo:any = {};
// ERROR: the index signature must be string, number ...
foo[obj] = 'World';
// Here is what you actually stored!
console.log(foo["[object Object]"]); // World
물론 number도 다음과 같은 이유로 지원됩니다.
- 훌륭한 Array / Tuple에 필요합니다.
- Object에 쓰더라도 default toString의 실행이 꽤 멋집니다. ([object Object]를 보여주지 않음)
2번째 포인트에 대한 예시가 아래에 있음:
console.log((1).toString()); // 1
console.log((2).toString()); // 2
자 lesson 1:
TypeScript index signature는 무조건
string
또는number
Note: symbol도 Typescript에서 지원하지만 아직은 사용하지 맙시다.
index signature 선언하기
우리는 Typescript에게 우리가 원하는 무엇이든 사용할 수 있게 any를 사용해 왔죠. 이제는 실제로 index signature를 사용하여 명시적으로 말해줄 수 있게 되었습니다. 예를들어, 당신이 객체에 string 구조를 넣고 싶으면 {message: string}의 구조로 명시적으로 알려줄 수 있습니다. 이것은 다음과 같이 할 수도 있죠 { [index:string] : {message: string} }
. 아래의 예시를 보시죠:
let foo:{ [index:string] : {message: string} } = {};
/**
* Must store stuff that conforms the structure
*/
/** Ok */
foo['a'] = { message: 'some message' };
/** Error: must contain a `message` or type string. You have a typo in `message` */
foo['a'] = { messages: 'some message' };
/**
* Stuff that is read is also type checked
*/
/** Ok */
foo['a'].message;
/** Error: messages does not exist. You have a typo in `message` */
foo['a'].messages;
TIP: index signature의 이름 예를들어 위의 코드에서 {[index:string] : {message:string}} 의 index는 Typescript에서 중요하지 않습니다. 단지 읽기 쉽게 만들어 줄 뿐. 예를들어 다음 사람이 보기에 user의 name으로 접근한다면 다음과 같이 할 수 있습니다. {[username:stirng]: {message:strign}}
물론 number 타입의 index도 가능하죠. 예를들어, { [count: number] : SomeOtherTypeYouWantToStoreEgRebate }
모든 멤버는 string index signature를 따라야함
당신이 string 타입 index signature이 있는 즉시, 모든 명시적 멤버들은 해당 index signature 구조를 따라야 합니다. 다음의 예를 보시죠.
/** Okay */
interface Foo {
[key:string]: number
x: number;
y: number;
}
/** Error */
interface Bar {
[key:string]: number
x: number;
y: string; // Property `y` must of of type number
}
이는 모든 문자열 액세스가 동일한 결과를 제공하도록 안전을 제공하기 위한 것입니다.
var bar: Bar; bar ["y"] |
로 접근하게 되면, inexable type인 [key:string]: number에 의하면 number를 넣어야하고 y: string에 의하면 string을 반환하기 때문에 반환하는 값의 정보가 모호해지기 때문에 명시적으로 선언하는 멤버들은 indexable type의 구조를 따라야합니다.
interface Foo {
[key:string]: number
x: number;
}
let foo: Foo = {x:1,y:2};
// Directly
foo['x']; // number
// Indirectly
let x = 'x'
foo[x]; // number
interface Foo {
[key:string]: number
x: number;
}
let foo: Foo = {x:1,y:2};
// Directly
foo['x']; // number
// Indirectly
let x = 'x'
foo[x]; // number
제한된 문자열 세트 사용
index signature은 다음과 같이 매핑 된 유형을 사용하여 index signature이 문자열 조합의 구성원이어야 사용할 수 있도록 제약 할 수 있습니다.
type Index = 'a' | 'b' | 'c'
type FromIndex = { [k in Index]?: number } // a, b, c 모두 있어야되는 것은 아님
const good: FromIndex = {b:1, c:2}
// Error:
// Type '{ b: number; c: number; d: number; }' is not assignable to type 'FromIndex'.
// Object literal may only specify known properties, and 'd' does not exist in type 'FromIndex'.
const bad: FromIndex = {b:1, c:2, d:3};
string과 number 모두 보유하는 인덱서
이건 흔한 케이스는 아니지만, Typescript 컴파일러는 이것을 지원해주고 있습니다.
interface ArrStr {
[key: string]: string | number; // 모든 멤버를 포용해야함
[index: number]: string; // 문자열 인덱스의 하위셋일 수 있음
// Just an example member
length: number;
}
Design Pattern: 중첩된 index signature
index signature를 추가할 때의 API 고려사항
JS 커뮤니티에서는 문자열 인덱스를 남용하는 API를 흔히 볼 수 있다. 예를들어 JS 라이브러리 안의 CSS의 흔한 패턴은 다음과 같다.interface NestedCSS {
color?: string;
[selector: string]: string | NestedCSS;
}
const example: NestedCSS = {
color: 'red',
'.subclass': {
color: 'blue'
}
}
index signature를 추가할 때의 API 고려사항
interface NestedCSS {
color?: string;
[selector: string]: string | NestedCSS;
}
const example: NestedCSS = {
color: 'red',
'.subclass': {
color: 'blue'
}
}
문자열 인덱서를 다음과 같이 유효한 값과 섞지 마세요. 예를들어 아래의 오타는 그대로 남을 것 입니다.
const failsSilently: NestedCSS = {
colour: 'red', // No error as `colour` is a valid string selector
}
대신 nest와 같은 이름으로 (또는 childeren, subnodes 등등) 프로퍼티를 만든 후 그 안에 내장시키세요.
interface NestedCSS {
color?: string;
nest?: {
[selector: string]: NestedCSS;
}
}
const example: NestedCSS = {
color: 'red',
nest: {
'.subclass': {
color: 'blue'
}
}
}
const failsSilently: NestedCSS = {
colour: 'red', // TS Error: unknown property `colour`
}
'Basic > Typescript' 카테고리의 다른 글
[Typescript] Decorator (0) | 2018.01.03 |
---|---|
[Typescript] Class를 통한 구조적 추상화 (0) | 2017.12.22 |
[Typescirpt] Class 알아보기 (0) | 2017.12.22 |
[Typescript] 미래의 자바스크립트 (0) | 2017.12.22 |