설모의 기록

TypeScript Index Signature 본문

언어/TypeScript

TypeScript Index Signature

HA_Kwon 2017. 12. 28. 18:53

Index Signatures

An Object in JavaScript (and hence TypeScript) can be accessed with a string to hold a reference to any other JavaScript object.
자바스크립트나 타입스크립트에서 객체는 다른 자바스크립트 객체에 대한 참조를 가지고 있는 string 으로 접근 가능하다.
Here is a quick example:
let foo:any = {};
foo['Hello'] = 'World';
console.log(foo['Hello']); // World
We store a string "World" under the key "Hello". Remember we said it can store any JavaScript object, so lets store a class instance just to show the concept:
우리는 Hello 키 밑에 World 텍스트를 저장한다. 어떠한 자바스크립트 객체도 저장할 수 있다고 했던 것을 기억하고 개념을 보기 위해 클래스 인스턴스를 저장해보자.
class Foo {
  constructor(public message: string){};
  log(){
    console.log(this.message)
  }
}

let foo:any = {};
foo['Hello'] = new Foo('World');
foo['Hello'].log(); // World
Also remember that we said that it can be accessed with a string. If you pass some any other object to the index signature the JavaScript runtime actually calls .toString on it before getting the result. This is demonstrated below:
string 으로 접근가능하다고 말했던 것 또한 기억해라. 만약 index signature 에 다른 객체를 넣는다면 실제로 결과를 얻기 전에 .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 that toString will get called whenever the obj is used in an index position.
toString 은 obj 가 인덱스로 쓰일 때 마다 호출될 것이다.
Arrays are slightly different. For number indexing JavaScript VMs will try to optimise (depending on things like is it actually an array and do the structures of items stored match etc.). So number should be considered as a valid object accessor in its own right (distinct from string). Here is a simple array example:
배열은 약간 다르다. 숫자로 인덱싱 하는 것은 자바스크립트 가상머신은 최적화를 시도할 것이다 (배열 같은 것에 따라 최적화를 하고 저장된 항목의 구조를 일치시킨다). 아래의 간단한 예제를 보자. 
let foo = ['World'];
console.log(foo[0]); // World
So that's JavaScript. Now let's look at TypeScript graceful handling of this concept.
이것은 자바스크립트이다. 지금부터는 타입스크립트에서 이 개념을 우아하게 처리하는 것을 살펴보자.

TypeScript Index Signature

First off, because JavaScript implicitly calls toString on any object index signature, TypeScript will give you an error to prevent beginners from shooting themselves in the foot (I see users shooting themselves in their feet when using JavaScript all the time on stackoverflow):
우선, 자바스크립트는 객체인 인덱스 시그니처에서 toString 을 묵시적으로 호출하기 때문이다. 타입스크립트는 발에 총 쏘는 것 같은 위험으로부터 초보 개발자들을 예방하기 위해 에러를 줄 것이다.
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';
The reason for forcing the user to be explicit is because the default toString implementation on an object is pretty awful, e.g. on v8 it always returns [object Object]:
사용자에게 "객체..toString()" 명시적으로 강제하는 이유는 객체에 기본 toString 실행이 끔찍하기 때문이다. 그것은 언제나 [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
Of course number is supported because
  1. its needed for excellent Array / Tuple support.
  2. even if you use it for an obj its default toString implementation is nice (not [object Object]).
Point 2 is shown below:
아래의 이유 떄문에 당연히 number 는 지원된다.
1. 배열이나 튜플의 완벽한 지원에 필요하다.
2. 심지어 만약 객체에 사용해도 그것의 기본 toString 실행은 아주 잘 된다.
2번 이유는 아래의 예제와 같다.
console.log((1).toString()); // 1
console.log((2).toString()); // 2
So lesson 1:
TypeScript index signatures must be either string or number
따라서 타입스크립트의 index signature 은 string 이나 number 여야 한다.
Quick note: symbols are also valid and supported by TypeScript. But let's not go there just yet. Baby steps.
symbol 타입 또한 유효하며 타입스크립트에서 지원하고 있다. 하지만 거기까지 하려면 멀었다. 

Declaring an index signature

So we've been using any to tell TypeScript to let us do whatever we want. We can actually specify an index signature explicitly. E.g. say you want to make sure than anything that is stored in an object using a string conforms to the structure {message: string}. This can be done with the declaration { [index:string] : {message: string} }. This is demonstrated below:
따라서 any 를 쓰는 것은 타입스크립트에게 우리가 원하는 무엇이든 할 수 있게 해달라고 말하는 것과 같다. 우리는 사실 index signature 를 명시적으로 구체화할 수 있다. 예를 들어 구조체 {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: the name of the index signature e.g. index in { [index:string] : {message: string} }has no significance for TypeScript and really for readability. e.g. if its user names you can do { [username:string] : {message: string} } to help the next dev who looks at the code (which just might happen to be you).
index signature 의 이름(예제에서의 index) 은  타입스크립트에서 중요하지 않고 사실 가독성을 위한 것이다. 다음에 이 코드를 볼 개발자를 위해 이름을 지어야한다.
Of course number indexes are also supported e.g. { [count: number] : SomeOtherTypeYouWantToStoreEgRebate }
number 인덱스 또한 지원된다.

All members must conform to the string index signature

As soon as you have a string index signature, all explicit members must also conform to that index signature. This is shown below:
string 인덱스 시그니처를 가지자마자 모든 명시적인 멤버들은 인덱스 시그니처에 확인해야 한다.
/** 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
}
This is to provide safety so that any string access gives the same result:
이것은 안정성을 제공하기 위해서이고 어떠한 string 접근도 같은 결과를 초래한다.
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

Using a limited set of string literals

An index signature can require that index strings be members of a union of literal strings by using Mapped Types e.g.:
인덱스 시그니처는 매핑된 타입을 사용해  string 인덱스를 literal string 의 string 인덱스들의 union 멤버로 요구할 수 있다.
=> 리터럴안에 들어있는 스트링 인덱스들은 매핑된 타입들에 모두 존재해야 한다.
type Index = 'a' | 'b' | 'c'
type FromIndex = { [k in Index]?: number }

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};
This is often used together with keyof typeof to capture vocabulary types, described on the next page.
이것은 keyof 나 typeof 와 함께 쓰이기도 하는데 이것은 다음 페이지에 설명되어 있다.
The specification of the vocabulary can be deferred generically:
type FromSomeIndex<K extends string> = { [key in K]: number }

Having both string and number indexers

This is not a common use case, but TypeScript compiler supports it nonetheless.
However it has the restriction that the string indexer is more strict than the number indexer. This is intentional e.g. to allow typing stuff like:
이것은 일반적인 예제는 아니지만 그럼에도 타입스크립트 컴파일러가 지원한다.
그러나 이것은 number 인덱서보다 string 인덱서가 더 엄격하다는 제한을 갖고 있다. 이것은 아래와 같은 타이핑 작업을 허락하는 의도적인 예제이다.
interface ArrStr {
  [key: string]: string | number; // Must accomodate all members

  [index: number]: string; // Can be a subset of string indexer

  // Just an example member
  length: number;
}

Design Pattern: Nested index signature

API consideration when adding index signatures
Quite commonly in the JS community you will see APIs that abuse string indexers. e.g. a common pattern among CSS in JS libraries:
일반적으로 JS 커뮤니티에서 string 인덱서를 남용하는 API 들을 보게 될 것이다. 아래 예제는 JS 라이브러리들의 CSS에서 사용하는 일반적인 패턴이다.
interface NestedCSS {
  color?: string;
  [selector: string]: string | NestedCSS;
}

const example: NestedCSS = {
  color: 'red',
  '.subclass': {
    color: 'blue'
  }
}
Try not to mix string indexers with valid values this way. E.g. a typo in the padding will remain uncaught:
string 인덱서와 유효한 값들을 섞어 쓰지 않도록 노력해라.
const failsSilently: NestedCSS = {
  colour: 'red', // No error as `colour` is a valid string selector
}
Instead seperate out the nesting into its own property e.g. in a name like nest (or children or subnodes etc.):

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`
}



글 출처 : https://basarat.gitbooks.io/typescript/content/docs/types/index-signatures.html

'언어 > TypeScript' 카테고리의 다른 글

Symbol  (0) 2018.01.03
Promise  (0) 2018.01.02
Classes Emit  (0) 2017.12.28
Classes Super  (0) 2017.12.28
TypeScript Class  (0) 2017.12.28
Comments