Today we’re excited to announce and get some early feedback with TypeScript 2.9’s Release Candidate. To get started with the RC, you can access it through NuGet, or use npm with the following command:
npm install -g typescript@rc
You can also get editor support by
- Downloading for Visual Studio 2015 (with Update 3)
- Downloading for Visual Studio 2017 (for version 15.2 or later)
- Following directions for Visual Studio Code and Sublime Text.
Let’s jump into some highlights of the Release Candidate!
Support for symbols and numeric literals in keyof
and mapped object types
TypeScript’s keyof
operator is a useful way to query the property names of an existing type.
interface Person { name: string; age: number; } // Equivalent to the type // "name" | "age" type PersonPropertiesNames = keyof Person;
Unfortunately, because keyof
predates TypeScript’s ability to reason about unique symbol
types, keyof
never recognized symbolic keys.
const baz = Symbol("baz"); interface Thing { foo: string; bar: number; [baz]: boolean; // this is a computed property type } // Error in TypeScript 2.8 and earlier! // `typeof baz` isn't assignable to `"foo" | "bar"` let x: keyof Thing = baz;
TypeScript 2.9 changes the behavior of keyof
to factor in both unique symbols as well as numeric literal types. As such, the above example now compiles as expected. keyof Thing
now boils down to the type "foo" | "bar" | typeof baz
.
With this functionality, mapped object types like Partial
, Required
, or Readonly
also recognize symbolic and numeric property keys, and no longer drop properties named by symbols:
type Partial<T> = { [K in keyof T]: T[K] } interface Thing { foo: string; bar: number; [baz]: boolean; } type PartialThing = Partial<Thing>; // This now works correctly and is equivalent to // // interface PartialThing { // foo?: string; // bar?: number; // [baz]?: boolean; // }
Unfortunately this is a breaking change for any usage where users believed that for any type T
, keyof T
would always be assignable to a string
. Because symbol- and numeric-named properties invalidate this assumption, we expect some minor breaks which we believe to be easy to catch. In such cases, there are several possible workarounds.
If you have code that’s really meant to only operate on string properties, you can use Extract<keyof T, string>
to restrict symbol
and number
inputs:
function useKey<T, K extends Extract<keyof T, string>>(obj: T, k: K) { let propName: string = k; // ... }
If you have code that’s more broadly applicable and can handle more than just string
s, you should be able to substitute string
with string | number | symbol
, or use the built-in type alias PropertyKey
.
function useKey<T, K extends keyof T>(obj: T, k: K) { let propName: string | number | symbol = k; // ... }
Alternatively, users can revert to the old behavior under the --keyofStringsOnly
compiler flag, but this is meant to be used as a transitionary flag.
import()
types
One long-running pain-point in TypeScript has been the inability to reference a type in another module, or the type of the module itself, without including an import at the top of the file.
In some cases, this is just a matter of convenience – you might not want to add an import at the top of your file just to describe a single type’s usage. For example, to reference the type of a module at an arbitrary location, here’s what you’d have to write before TypeScript 2.9:
import * as _foo from "foo"; export async function bar() { let foo: typeof _foo = await import("foo"); }
In other cases, there are simply things that users can’t achieve today – for example, referencing a type within a module in the global scope is impossible today. This is because a file with any imports or exports is considered a module, so adding an import for a type in a global script file will automatically turn that file into a module, which drastically changes things like scoping rules and strict module within that file.
That’s why TypeScript 2.9 is introducing the new import(...)
type syntax. Much like ECMAScript’s proposed import(...)
expressions, import types use the same syntax, and provide a convenient way to reference the type of a module, or the types which a module contains.
// foo.ts export interface Person { name: string; age: number; } // bar.ts export function greet(p: import("./foo").Person) { return ` Hello, I'm ${p.name}, and I'm ${p.age} years old. `; }
Notice we didn’t need to add a top-level import specify the type of p
. We could also rewrite our example from above where we awkwardly needed to reference the type of a module:
export async function bar() { let foo: typeof import("./foo") = await import("./foo"); }
Of course, in this specific example, foo
could have been inferred, this might be more useful with something like the TypeScript language server plugin API.
Breaking changes
keyof
types include symbolic/numeric properties
As mentioned above, key queries/keyof
types now include names that are symbol
s and number
s, which can break some code that assumes keyof T
is assignable to string
. Users can avoid this by using the --keyofStringsOnly
compiler option:
// tsconfig.json { "compilerOptions": { "keyofStringsOnly": true } }
Trailing commas not allowed on rest parameters
#22262
This break was added for conformance with ECMAScript, as trailing commas are not allowed to follow rest parameters in the specification.
Unconstrained type parameters are no longer assignable to object
in strictNullChecks
#24013
The following code now errors:
function f<T>(x: T) { const y: object | null | undefined = x; }
Since generic type parameters can be substituted with any primitive type, this is a precaution TypeScript has added under strictNullChecks
. To fix this, you can add a constraint on object
:
// We can add an upper-bound constraint here. // vvvvvvvvvvvvvvv function f<T extends object>(x: T) { const y: object | null | undefined = x; }
never
can no longer be iterated over
Values of type never
can no longer be iterated over, which may catch a good class of bugs. Users can avoid this behavior by using a type assertion to cast to the type any
(i.e. foo as any
).
What’s next?
We try to keep our plans easily discoverable on the TypeScript roadmap for everything else that’s coming in 2.9 and beyond. TypeScript 2.9 proper should arrive towards the end of the month, but to make that successful, we need all the help we can get, so download the RC today and let us know what you think!
Feel free to drop us a line on GitHub if you run into any problems, and let others know how you feel about this RC on Twitter and in the comments below!