Was this page helpful?

TypeScript 2.9

Supportnumber andsymbol named properties withkeyof and mapped types

TypeScript 2.9 adds support fornumber andsymbol named properties in index types and mapped types.Previously, thekeyof operator and mapped types only supportedstring named properties.

Changes include:

  • An index typekeyof T for some typeT is a subtype ofstring | number | symbol.
  • A mapped type{ [P in K]: XXX } permits anyK assignable tostring | number | symbol.
  • In afor...in statement for an object of a generic typeT, the inferred type of the iteration variable was previouslykeyof T but is nowExtract<keyof T, string>. (In other words, the subset ofkeyof T that includes only string-like values.)

Given an object typeX,keyof X is resolved as follows:

  • IfX contains a string index signature,keyof X is a union ofstring,number, and the literal types representing symbol-like properties, otherwise
  • IfX contains a numeric index signature,keyof X is a union ofnumber and the literal types representing string-like and symbol-like properties, otherwise
  • keyof X is a union of the literal types representing string-like, number-like, and symbol-like properties.

Where:

  • String-like properties of an object type are those declared using an identifier, a string literal, or a computed property name of a string literal type.
  • Number-like properties of an object type are those declared using a numeric literal or computed property name of a numeric literal type.
  • Symbol-like properties of an object type are those declared using a computed property name of a unique symbol type.

In a mapped type{ [P in K]: XXX }, each string literal type inK introduces a property with a string name, each numeric literal type inK introduces a property with a numeric name, and each unique symbol type inK introduces a property with a unique symbol name.Furthermore, ifK includes typestring, a string index signature is introduced, and ifK includes typenumber, a numeric index signature is introduced.

Example
ts
constc ="c";
constd =10;
conste =Symbol();
constenumE1 {
A,
B,
C,
}
constenumE2 {
A ="A",
B ="B",
C ="C",
}
typeFoo = {
a:string;// String-like name
5:string;// Number-like name
[c]:string;// String-like name
[d]:string;// Number-like name
[e]:string;// Symbol-like name
[E1.A]:string;// Number-like name
[E2.A]:string;// String-like name
};
typeK1 =keyofFoo;// "a" | 5 | "c" | 10 | typeof e | E1.A | E2.A
typeK2 =Extract<keyofFoo,string>;// "a" | "c" | E2.A
typeK3 =Extract<keyofFoo,number>;// 5 | 10 | E1.A
typeK4 =Extract<keyofFoo,symbol>;// typeof e

Sincekeyof now reflects the presence of a numeric index signature by including typenumber in the key type, mapped types such asPartial<T> andReadonly<T> work correctly when applied to object types with numeric index signatures:

ts
typeArrayish<T> = {
length:number;
[x:number]:T;
};
typeReadonlyArrayish<T> =Readonly<Arrayish<T>>;
declareconstmap:ReadonlyArrayish<string>;
letn =map.length;
letx =map[123];// Previously of type any (or an error with --noImplicitAny)

Furthermore, with thekeyof operator’s support fornumber andsymbol named keys, it is now possible to abstract over access to properties of objects that are indexed by numeric literals (such as numeric enum types) and unique symbols.

ts
constenumEnum {
A,
B,
C,
}
constenumToStringMap = {
[Enum.A]:"Name A",
[Enum.B]:"Name B",
[Enum.C]:"Name C",
};
constsym1 =Symbol();
constsym2 =Symbol();
constsym3 =Symbol();
constsymbolToNumberMap = {
[sym1]:1,
[sym2]:2,
[sym3]:3,
};
typeKE =keyoftypeofenumToStringMap;// Enum (i.e. Enum.A | Enum.B | Enum.C)
typeKS =keyoftypeofsymbolToNumberMap;// typeof sym1 | typeof sym2 | typeof sym3
functiongetValue<T,KextendskeyofT>(obj:T,key:K):T[K] {
returnobj[key];
}
letx1 =getValue(enumToStringMap,Enum.C);// Returns "Name C"
letx2 =getValue(symbolToNumberMap,sym3);// Returns 3

This is a breaking change; previously, thekeyof operator and mapped types only supportedstring named properties.Code that assumed values typed withkeyof T were alwaysstrings, will now be flagged as error.

Example
ts
functionuseKey<T,KextendskeyofT>(o:T,k:K) {
varname:string =k;// Error: keyof T is not assignable to string
}

Recommendations

  • If your functions are only able to handle string named property keys, useExtract<keyof T, string> in the declaration:

    ts
    functionuseKey<T,KextendsExtract<keyofT,string>>(o:T,k:K) {
    varname:string =k;// OK
    }
  • If your functions are open to handling all property keys, then the changes should be done down-stream:

    ts
    functionuseKey<T,KextendskeyofT>(o:T,k:K) {
    varname:string |number |symbol =k;
    }
  • Otherwise usekeyofStringsOnly compiler option to disable the new behavior.

Generic type arguments in JSX elements

JSX elements now allow passing type arguments to generic components.

Example
ts
classGenericComponent<P>extendsReact.Component<P> {
internalProp:P;
}
typeProps = {a:number;b:string };
constx = <GenericComponent<Props>a={10}b="hi" />;// OK
consty = <GenericComponent<Props>a={10}b={20} />;// Error

Generic type arguments in generic tagged templates

Tagged templates are a form of invocation introduced in ECMAScript 2015.Like call expressions, generic functions may be used in a tagged template and TypeScript will infer the type arguments utilized.

TypeScript 2.9 allows passing generic type arguments to tagged template strings.

Example
ts
declarefunctionstyledComponent<Props>(
strs:TemplateStringsArray
):Component<Props>;
interfaceMyProps {
name:string;
age:number;
}
styledComponent<MyProps>`
font-size: 1.5em;
text-align: center;
color: palevioletred;
`;
declarefunctiontag<T>(strs:TemplateStringsArray, ...args:T[]):T;
// inference fails because 'number' and 'string' are both candidates that conflict
leta =tag<string|number>`${100}${"hello"}`;

import types

Modules can import types declared in other modules. But non-module global scripts cannot access types declared in modules. Enterimport types.

Usingimport("mod") in a type annotation allows for reaching in a module and accessing its exported declaration without importing it.

Example

Given a declaration of a classPet in a module file:

ts
// module.d.ts
exportdeclareclassPet {
name:string;
}

Can be used in a non-module fileglobal-script.ts:

ts
// global-script.ts
functionadopt(p:import("./module").Pet) {
console.log(`Adopting${p.name}...`);
}

This also works in JSDoc comments to refer to types from other modules in.js:

js
// a.js
/**
*@paramp { import("./module").Pet }
*/
functionwalk(p) {
console.log(`Walking${p.name}...`);
}

Relaxing declaration emit visibility rules

Withimport types available, many of the visibility errors reported during declaration file generation can be handled by the compiler without the need to change the input.

For instance:

ts
import {createHash }from"crypto";
exportconsthash =createHash("sha256");
// ^^^^
// Exported variable 'hash' has or is using name 'Hash' from external module "crypto" but cannot be named.

With TypeScript 2.9, no errors are reported, and now the generated file looks like:

ts
exportdeclareconsthash:import("crypto").Hash;

Support forimport.meta

TypeScript 2.9 introduces support forimport.meta, a new meta-property as described by the currentTC39 proposal.

The type ofimport.meta is the globalImportMeta type which is defined inlib.es5.d.ts.This interface is extremely limited.Adding well-known properties for Node or browsers requires interface merging and possibly a global augmentation depending on the context.

Example

Assuming that__dirname is always available onimport.meta, the declaration would be done through reopeningImportMeta interface:

ts
// node.d.ts
interfaceImportMeta {
__dirname:string;
}

And usage would be:

ts
import.meta.__dirname;// Has type 'string'

import.meta is only allowed when targetingESNext modules and ECMAScript targets.

New--resolveJsonModule

Often in Node.js applications a.json is needed. With TypeScript 2.9,resolveJsonModule allows for importing, extracting types from and generating.json files.

Example
ts
// settings.json
{
"repo":"TypeScript",
"dry":false,
"debug":false
}
ts
// a.ts
importsettingsfrom"./settings.json";
settings.debug ===true;// OK
settings.dry ===2;// Error: Operator '===' cannot be applied boolean and number
// tsconfig.json
{
"":"commonjs",
}
}

--pretty output by default

Starting TypeScript 2.9 errors are displayed underpretty by default if the output device is applicable for colorful text.TypeScript will check if the output stream hasisTty property set.

Use--pretty false on the command line or set"pretty": false in yourtsconfig.json to disablepretty output.

New--declarationMap

EnablingdeclarationMap alongsidedeclaration causes the compiler to emit.d.ts.map files alongside the output.d.ts files.Language Services can also now understand these map files, and uses them to map declaration-file based definition locations to their original source, when available.

In other words, hitting go-to-definition on a declaration from a.d.ts file generated withdeclarationMap will take you to the source file (.ts) location where that declaration was defined, and not to the.d.ts.

The TypeScript docs are an open source project. Help us improve these pagesby sending a Pull Request

Contributors to this page:
MHMohamed Hegazy  (51)
OTOrta Therox  (13)
NSNick Schonning  (1)
JBJack Bates  (1)
BHBjørnar Hvidsten  (1)
2+

Last updated: Dec 16, 2025