Project References

TypeScript 3.0 introduces a new concept of project references. Project references allow TypeScript projects to depend on other TypeScript projects - specifically, allowing tsconfig.json files to reference other tsconfig.json files. Specifying these dependencies makes it easier to split your code into smaller projects, since it gives TypeScript (and tools around it) a way to understand build ordering and output structure.

TypeScript 3.0 also introduces a new mode for tsc, the --build flag, that works hand-in-hand with project references to enable faster TypeScript builds.

See Project References handbook page for more documentation.

Tuples in rest parameters and spread expressions

TypeScript 3.0 adds support to multiple new capabilities to interact with function parameter lists as tuple types. TypeScript 3.0 adds support for:

With these features it becomes possible to strongly type a number of higher-order functions that transform functions and their parameter lists.

Rest parameters with tuple types

When a rest parameter has a tuple type, the tuple type is expanded into a sequence of discrete parameters. For example the following two declarations are equivalent:

  1. ts
    declare function foo(...args: [number, string, boolean]): void;
  1. ts
    declare function foo(args_0: number, args_1: string, args_2: boolean): void;

Spread expressions with tuple types

When a function call includes a spread expression of a tuple type as the last argument, the spread expression corresponds to a sequence of discrete arguments of the tuple element types.

Thus, the following calls are equivalent:

  1. ts
    const args: [number, string, boolean] = [42, "hello", true];
  2. foo(42, "hello", true);
  3. foo(args[0], args[1], args[2]);
  4. foo(...args);

Generic rest parameters

A rest parameter is permitted to have a generic type that is constrained to an array type, and type inference can infer tuple types for such generic rest parameters. This enables higher-order capturing and spreading of partial parameter lists:

Example
  1. ts
    declare function bind<T, U extends any[], V>(
  2. f: (x: T, ...args: U) => V,
  3. x: T
  4. ): (...args: U) => V;
  5. declare function f3(x: number, y: string, z: boolean): void;
  6. const f2 = bind(f3, 42); // (y: string, z: boolean) => void
  7. const f1 = bind(f2, "hello"); // (z: boolean) => void
  8. const f0 = bind(f1, true); // () => void
  9. f3(42, "hello", true);
  10. f2("hello", true);
  11. f1(true);
  12. f0();

In the declaration of f2 above, type inference infers types number, [string, boolean] and void for T, U and V respectively.

Note that when a tuple type is inferred from a sequence of parameters and later expanded into a parameter list, as is the case for U, the original parameter names are used in the expansion (however, the names have no semantic meaning and are not otherwise observable).

Optional elements in tuple types

Tuple types now permit a ? postfix on element types to indicate that the element is optional:

Example
  1. ts
    let t: [number, string?, boolean?];
  2. t = [42, "hello", true];
  3. t = [42, "hello"];
  4. t = [42];

In --strictNullChecks mode, a ? modifier automatically includes undefined in the element type, similar to optional parameters.

A tuple type permits an element to be omitted if it has a postfix ? modifier on its type and all elements to the right of it also have ? modifiers.

When tuple types are inferred for rest parameters, optional parameters in the source become optional tuple elements in the inferred type.

The length property of a tuple type with optional elements is a union of numeric literal types representing the possible lengths. For example, the type of the length property in the tuple type [number, string?, boolean?] is 1 | 2 | 3.

Rest elements in tuple types

The last element of a tuple type can be a rest element of the form ...X, where X is an array type. A rest element indicates that the tuple type is open-ended and may have zero or more additional elements of the array element type. For example, [number, ...string[]] means tuples with a number element followed by any number of string elements.

Example
  1. ts
    function tuple<T extends any[]>(...args: T): T {
  2. return args;
  3. }
  4. const numbers: number[] = getArrayOfNumbers();
  5. const t1 = tuple("foo", 1, true); // [string, number, boolean]
  6. const t2 = tuple("bar", ...numbers); // [string, ...number[]]

The type of the length property of a tuple type with a rest element is number.

New unknown top type

TypeScript 3.0 introduces a new top type unknown. unknown is the type-safe counterpart of any. Anything is assignable to unknown, but unknown isn’t assignable to anything but itself and any without a type assertion or a control flow based narrowing. Likewise, no operations are permitted on an unknown without first asserting or narrowing to a more specific type.

Example
  1. ts
    // In an intersection everything absorbs unknown
  2. type T00 = unknown & null; // null
  3. type T01 = unknown & undefined; // undefined
  4. type T02 = unknown & null & undefined; // null & undefined (which becomes never)
  5. type T03 = unknown & string; // string
  6. type T04 = unknown & string[]; // string[]
  7. type T05 = unknown & unknown; // unknown
  8. type T06 = unknown & any; // any
  9. // In a union an unknown absorbs everything
  10. type T10 = unknown | null; // unknown
  11. type T11 = unknown | undefined; // unknown
  12. type T12 = unknown | null | undefined; // unknown
  13. type T13 = unknown | string; // unknown
  14. type T14 = unknown | string[]; // unknown
  15. type T15 = unknown | unknown; // unknown
  16. type T16 = unknown | any; // any
  17. // Type variable and unknown in union and intersection
  18. type T20<T> = T & {}; // T & {}
  19. type T21<T> = T | {}; // T | {}
  20. type T22<T> = T & unknown; // T
  21. type T23<T> = T | unknown; // unknown
  22. // unknown in conditional types
  23. type T30<T> = unknown extends T ? true : false; // Deferred
  24. type T31<T> = T extends unknown ? true : false; // Deferred (so it distributes)
  25. type T32<T> = never extends T ? true : false; // true
  26. type T33<T> = T extends never ? true : false; // Deferred
  27. // keyof unknown
  28. type T40 = keyof any; // string | number | symbol
  29. type T41 = keyof unknown; // never
  30. // Only equality operators are allowed with unknown
  31. function f10(x: unknown) {
  32. x == 5;
  33. x !== 10;
  34. x >= 0; // Error
  35. x + 1; // Error
  36. x * 2; // Error
  37. -x; // Error
  38. +x; // Error
  39. }
  40. // No property accesses, element accesses, or function calls
  41. function f11(x: unknown) {
  42. x.foo; // Error
  43. x[5]; // Error
  44. x(); // Error
  45. new x(); // Error
  46. }
  47. // typeof, instanceof, and user defined type predicates
  48. declare function isFunction(x: unknown): x is Function;
  49. function f20(x: unknown) {
  50. if (typeof x === "string" || typeof x === "number") {
  51. x; // string | number
  52. }
  53. if (x instanceof Error) {
  54. x; // Error
  55. }
  56. if (isFunction(x)) {
  57. x; // Function
  58. }
  59. }
  60. // Homomorphic mapped type over unknown
  61. type T50<T> = { [P in keyof T]: number };
  62. type T51 = T50<any>; // { [x: string]: number }
  63. type T52 = T50<unknown>; // {}
  64. // Anything is assignable to unknown
  65. function f21<T>(pAny: any, pNever: never, pT: T) {
  66. let x: unknown;
  67. x = 123;
  68. x = "hello";
  69. x = [1, 2, 3];
  70. x = new Error();
  71. x = x;
  72. x = pAny;
  73. x = pNever;
  74. x = pT;
  75. }
  76. // unknown assignable only to itself and any
  77. function f22(x: unknown) {
  78. let v1: any = x;
  79. let v2: unknown = x;
  80. let v3: object = x; // Error
  81. let v4: string = x; // Error
  82. let v5: string[] = x; // Error
  83. let v6: {} = x; // Error
  84. let v7: {} | null | undefined = x; // Error
  85. }
  86. // Type parameter 'T extends unknown' not related to object
  87. function f23<T extends unknown>(x: T) {
  88. let y: object = x; // Error
  89. }
  90. // Anything but primitive assignable to { [x: string]: unknown }
  91. function f24(x: { [x: string]: unknown }) {
  92. x = {};
  93. x = { a: 5 };
  94. x = [1, 2, 3];
  95. x = 123; // Error
  96. }
  97. // Locals of type unknown always considered initialized
  98. function f25() {
  99. let x: unknown;
  100. let y = x;
  101. }
  102. // Spread of unknown causes result to be unknown
  103. function f26(x: {}, y: unknown, z: any) {
  104. let o1 = { a: 42, ...x }; // { a: number }
  105. let o2 = { a: 42, ...x, ...y }; // unknown
  106. let o3 = { a: 42, ...x, ...y, ...z }; // any
  107. }
  108. // Functions with unknown return type don't need return expressions
  109. function f27(): unknown {}
  110. // Rest type cannot be created from unknown
  111. function f28(x: unknown) {
  112. let { ...a } = x; // Error
  113. }
  114. // Class properties of type unknown don't need definite assignment
  115. class C1 {
  116. a: string; // Error
  117. b: unknown;
  118. c: any;
  119. }

Support for defaultProps in JSX

TypeScript 2.9 and earlier didn’t leverage React defaultProps declarations inside JSX components. Users would often have to declare properties optional and use non-null assertions inside of render, or they’d use type-assertions to fix up the type of the component before exporting it.

TypeScript 3.0 adds support for a new type alias in the JSX namespace called LibraryManagedAttributes. This helper type defines a transformation on the component’s Props type, before using to check a JSX expression targeting it; thus allowing customization like: how conflicts between provided props and inferred props are handled, how inferences are mapped, how optionality is handled, and how inferences from differing places should be combined.

In short using this general type, we can model React’s specific behavior for things like defaultProps and, to some extent, propTypes.

  1. tsx
    export interface Props {
  2. name: string;
  3. }
  4. export class Greet extends React.Component<Props> {
  5. render() {
  6. const { name } = this.props;
  7. return <div>Hello {name.toUpperCase()}!</div>;
  8. }
  9. static defaultProps = { name: "world" };
  10. }
  11. // Type-checks! No type assertions needed!
  12. let el = <Greet />;

Caveats

Explicit types on defaultProps

The default-ed properties are inferred from the defaultProps property type. If an explicit type annotation is added, e.g. static defaultProps: Partial<Props>; the compiler will not be able to identify which properties have defaults (since the type of defaultProps include all properties of Props).

Use static defaultProps: Pick<Props, "name">; as an explicit type annotation instead, or do not add a type annotation as done in the example above.

For function components (formerly known as SFCs) use ES2015 default initializers:

  1. tsx
    function Greet({ name = "world" }: Props) {
  2. return <div>Hello {name.toUpperCase()}!</div>;
  3. }

Changes to @types/React

Corresponding changes to add LibraryManagedAttributes definition to the JSX namespace in @types/React are still needed. Keep in mind that there are some limitations.

/// reference directives

TypeScript adds a new triple-slash-reference directive (/// <reference lib="name" />), allowing a file to explicitly include an existing built-in lib file.

Built-in lib files are referenced in the same fashion as the "lib" compiler option in tsconfig.json (e.g. use lib="es2015" and not lib="lib.es2015.d.ts", etc.).

For declaration file authors who relay on built-in types, e.g. DOM APIs or built-in JS run-time constructors like Symbol or Iterable, triple-slash-reference lib directives are the recommended. Previously these .d.ts files had to add forward/duplicate declarations of such types.

Example

Using /// <reference lib="es2017.string" /> to one of the files in a compilation is equivalent to compiling with --lib es2017.string.

  1. ts
    /// <reference lib="es2017.string" />
  2. "foo".padStart(4);