TS

总结

  • 类型别名的体验优化
  • 新增具有可选项的元组[string, string?]、具有可读性标签的元组[first: string, second?: string]、具有不固定项的元素[string, string, ...boolean[]](非 4.2 新增)
    • 一个元组只允许有一个...rest元组项,可以出现在任意位置,元组项后不允许有任何可选项(4.2 新增)
  • js 中操作符 in 右边是个非对象时会在运行时报错,4.2 支持了该报错的提醒。
  • 提供了标志--noPropertyAccessFromIndexSignature来开启索引签名通过.访问的报错

原文地址 www.typescriptlang.org

Smarter Type Alias Preservation

export type BasicPrimitive = number | string | boolean;
export function doStuff(value: BasicPrimitive) {
  if (Math.random() < 0.5) {
    return undefined;
  }
  return value;
}

We can see what happens in the TypeScript 4.1 playground. While we might want TypeScript to display the return type of doStuff as BasicPrimitive | undefined, it instead displays string | number | boolean | undefined! What gives?

翻译:在 4.1 中,doStuff 函数的返回值会被推断为 string | number | boolean | undefined 而不是我们期望的 BasicPrimitive | undefined

Well this has to do with how TypeScript represents types internally. When creating a union type out of one or more union types, it will always normalize those types into a new flattened union type - but doing that loses information. The type-checker would have to find every combination of types from string | number | boolean | undefined to see what type aliases could have been used, and even then, there might be multiple type aliases to string | number | boolean.

翻译:这是因为从联合类型创建联合类型时 4.1 会把这些联合转为新的扁平化的联合类型

In TypeScript 4.2, our internals are a little smarter. We keep track of how types were constructed by keeping around parts of how they were originally written and constructed over time. We also keep track of, and differentiate, type aliases to instances of other aliases!

For more information, check out the first pull request that improves various cases around preserving union type aliases, along with a second pull request that preserves indirect aliases.

Leading/Middle Rest Elements in Tuple Types

Over time, TypeScript’s tuple types have become more and more sophisticated, since they’re also used to model things like parameter lists in JavaScript. As a result, they can have optional elements and rest elements, and can even have labels for tooling and readability.

翻译:新增具有可选项的元组、具有可读性标签的元组、具有不固定项的元素

// A tuple that has either one or two strings.
let c: [string, string?] = ["hello"];
c = ["hello", "world"];

// A labeled tuple that has either one or two strings.
let d: [first: string, second?: string] = ["hello"];
d = ["hello", "world"];

// A tuple with a *rest element* - holds at least 2 strings at the front,
// and any number of booleans at the back.
let e: [string, string, ...boolean[]];
e = ["hello", "world"];
e = ["hello", "world", false];
e = ["hello", "world", true, false, true];

In TypeScript 4.2, rest elements specifically been expanded in how they can be used. In prior versions, TypeScript only allowed ...rest elements at the very last position of a tuple type.

翻译:4.2 中不定长元组项...rest可出现在元组的任意位置

However, now rest elements can occur anywhere within a tuple - with only a few restrictions.

let foo: [...string[], number];

foo = [123];
foo = ["hello", 123];
foo = ["hello!", "hello!", "hello!", 123];

let bar: [boolean, ...string[], boolean];
bar = [true, false];
bar = [true, "some text", false];
bar = [true, "some", "separated", "text", false];

The only restriction is that a rest element can be placed anywhere in a tuple, so long as it’s not followed by another optional element or rest element. In other words, only one rest element per tuple, and no optional elements after rest elements.

翻译:一个元组只允许有一个...rest元组项,并且这个元组项后不允许有任何可选项

For more details, see the original pull request.

Stricter Checks For The in Operator

In JavaScript, it is a runtime error to use a non-object type on the right side of the in operator. TypeScript 4.2 ensures this can be caught at design-time.

翻译:js 中操作符 in 右边是个非对象时会在运行时报错,4.2 支持了该报错的提醒。

"foo" in 42;
The right-hand side of an 'in' expression must not be a primitive.2361The right-hand side of an 'in' expression must not be a primitive.

A big thanks to our external contributor Jonas Hübotter for their pull request!

--noPropertyAccessFromIndexSignature

To make these types of situations easier, a while back, TypeScript made it possible to use “dotted” property access syntax like person.name when a type had a string index signature. This also made it easier to transition existing JavaScript code over to TypeScript.

翻译:具有索引签名的接口允许使用 . 的方式访问未明确声明 key 名的属性

function processOptions(opts: Options) {
  // Notice we're *accidentally* accessing `excludes` this time.
  // Oops! Totally valid.
  for (const excludePattern of opts.excludes) {
    // ...
  }
}

In some cases, users would prefer to explicitly opt into the index signature - they would prefer to get an error message when a dotted property access doesn’t correspond to a specific property declaration.

That’s why TypeScript introduces a new flag called --noPropertyAccessFromIndexSignature. Under this mode, you’ll be opted in to TypeScript’s older behavior that issues an error. This new setting is not under the strict family of flags, since we believe users will find it more useful on certain codebases than others.

翻译:4.2 提供了名为--noPropertyAccessFromIndexSignature的标志,在通过.访问未明确声明属性时进行报错(不确定是否这个含义)

You can understand this feature in more detail by reading up on the corresponding pull request. We’d also like to extend a big thanks to Wenlu Wang who sent us this pull request!

abstract Construct Signatures

That’s why TypeScript 4.2 allows you to specify an abstract modifier on constructor signatures.

interface HasArea {
    getArea(): number;
}

// Works!
let Ctor: abstract new () => HasArea = Shape;

Adding the abstract modifier to a construct signature signals that you can pass in abstract constructors. It doesn’t stop you from passing in other classes/constructor functions that are “concrete” - it really just signals that there’s no intent to run the constructor directly, so it’s safe to pass in either class type.

翻译:4.2 提供了修饰符abstract允许接受抽象类或继承于它的具体类

This feature allows us to write mixin factories in a way that supports abstract classes. For example, in the following code snippet, we’re able to use the mixin function withStyles with the abstract class SuperClass.

abstract class SuperClass {
    abstract someMethod(): void;
    badda() {}
}



type AbstractConstructor<T> = abstract new (...args: any[]) => T




function withStyles<T extends AbstractConstructor<object>>(Ctor: T) {

    abstract class StyledClass extends Ctor {

        getStyles() {

            // ...

        }

    }

    return StyledClass;

}




class SubClass extends withStyles(SuperClass) {

    someMethod() {

        this.someMethod()

    }

}

Note that withStyles is demonstrating a specific rule, where a class (like StyledClass) that extends a value that’s generic and bounded by an abstract constructor (like Ctor) has to also be declared abstract. This is because there’s no way to know if a class with more abstract members was passed in, and so it’s impossible to know whether the subclass implements all the abstract members.

You can read up more on abstract construct signatures on its pull request.

Understanding Your Project Structure With --explainFiles

A surprisingly common scenario for TypeScript users is to ask “why is TypeScript including this file?“. Inferring the files of your program turns out to be a complicated process, and so there are lots of reasons why a specific combination of lib.d.ts got used, why certain files in node_modules are getting included, and why certain files are being included even though we thought specifying exclude would keep them out.

That’s why TypeScript now provides an --explainFiles flag.

tsc --explainFiles

When using this option, the TypeScript compiler will give some very verbose output about why a file ended up in your program. To read it more easily, you can forward the output to a file, or pipe it to a program that can easily view it.

# Forward output to a text file
tsc --explainFiles > expanation.txt
# Pipe output to a utility program like `less`, or an editor like VS Code
tsc --explainFiles | less
tsc --explainFiles | code -

Typically, the output will start out by listing out reasons for including lib.d.ts files, then for local files, and then node_modules files.

TS_Compiler_Directory/4.2.2/lib/lib.es5.d.ts
  Library referenced via 'es5' from file 'TS_Compiler_Directory/4.2.2/lib/lib.es2015.d.ts'
TS_Compiler_Directory/4.2.2/lib/lib.es2015.d.ts
  Library referenced via 'es2015' from file 'TS_Compiler_Directory/4.2.2/lib/lib.es2016.d.ts'
TS_Compiler_Directory/4.2.2/lib/lib.es2016.d.ts
  Library referenced via 'es2016' from file 'TS_Compiler_Directory/4.2.2/lib/lib.es2017.d.ts'
TS_Compiler_Directory/4.2.2/lib/lib.es2017.d.ts
  Library referenced via 'es2017' from file 'TS_Compiler_Directory/4.2.2/lib/lib.es2018.d.ts'
TS_Compiler_Directory/4.2.2/lib/lib.es2018.d.ts
  Library referenced via 'es2018' from file 'TS_Compiler_Directory/4.2.2/lib/lib.es2019.d.ts'
TS_Compiler_Directory/4.2.2/lib/lib.es2019.d.ts
  Library referenced via 'es2019' from file 'TS_Compiler_Directory/4.2.2/lib/lib.es2020.d.ts'
TS_Compiler_Directory/4.2.2/lib/lib.es2020.d.ts
  Library referenced via 'es2020' from file 'TS_Compiler_Directory/4.2.2/lib/lib.esnext.d.ts'
TS_Compiler_Directory/4.2.2/lib/lib.esnext.d.ts
  Library 'lib.esnext.d.ts' specified in compilerOptions

... More Library References...

foo.ts
  Matched by include pattern '**/*' in 'tsconfig.json'

Right now, we make no guarantees about the output format - it might change over time. On that note, we’re interested in improving this format if you have any suggestions!

For more information, check out the original pull request!

Improved Uncalled Function Checks in Logical Expressions

Thanks to further improvements from Alex Tarasyuk, TypeScript’s uncalled function checks now apply within && and || expressions.

Under --strictNullChecks, the following code will now error.

function shouldDisplayElement(element: Element) {
  // ...
  return true;
}
function getVisibleItems(elements: Element[]) {
  return elements.filter((e) => shouldDisplayElement && e.children.length);
  //                          ~~~~~~~~~~~~~~~~~~~~
  // This condition will always return true since the function is always defined.
  // Did you mean to call it instead.
}

For more details, check out the pull request here.

Destructured Variables Can Be Explicitly Marked as Unused

Thanks to another pull request from Alex Tarasyuk, you can now mark destructured variables as unused by prefixing them with an underscore (the _ character).

let [_first, second] = getValues();

Previously, if _first was never used later on, TypeScript would issue an error under noUnusedLocals. Now, TypeScript will recognize that _first was intentionally named with an underscore because there was no intent to use it.

For more details, take a look at the full change.

Relaxed Rules Between Optional Properties and String Index Signatures

String index signatures are a way of typing dictionary-like objects, where you want to allow access with arbitrary keys:

const movieWatchCount: { [key: string]: number } = {};



function watchMovie(title: string) {

  movieWatchCount[title] = (movieWatchCount[title] ?? 0) + 1;

}

Of course, for any movie title not yet in the dictionary, movieWatchCount[title] will be undefined (TypeScript 4.1 added the option --noUncheckedIndexedAccess to include undefined when reading from an index signature like this). Even though it’s clear that there must be some strings not present in movieWatchCount, previous versions of TypeScript treated optional object properties as unassignable to otherwise compatible index signatures, due to the presence of undefined.

type WesAndersonWatchCount = {
  "Fantastic Mr. Fox"?: number;
  "The Royal Tenenbaums"?: number;
  "Moonrise Kingdom"?: number;
  "The Grand Budapest Hotel"?: number;
};



declare const wesAndersonWatchCount: WesAndersonWatchCount;

const movieWatchCount: { [key: string]: number } = wesAndersonWatchCount;

//    ~~~~~~~~~~~~~~~ error!

// Type 'WesAndersonWatchCount' is not assignable to type '{ [key: string]: number; }'.

//    Property '"Fantastic Mr. Fox"' is incompatible with index signature.

//      Type 'number | undefined' is not assignable to type 'number'.

//        Type 'undefined' is not assignable to type 'number'. (2322)

TypeScript 4.2 allows this assignment. However, it does not allow the assignment of non-optional properties with undefined in their types, nor does it allow writing undefined to a specific key:

type BatmanWatchCount = {
  "Batman Begins": number | undefined;
  "The Dark Knight": number | undefined;
  "The Dark Knight Rises": number | undefined;
};



declare const batmanWatchCount: BatmanWatchCount;




// Still an error in TypeScript 4.2.

const movieWatchCount: { [key: string]: number } = batmanWatchCount;

Type 'BatmanWatchCount' is not assignable to type '{ [key: string]: number; }'.
  Property '"Batman Begins"' is incompatible with index signature.
    Type 'number | undefined' is not assignable to type 'number'.
      Type 'undefined' is not assignable to type 'number'.2322Type 'BatmanWatchCount' is not assignable to type '{ [key: string]: number; }'.
  Property '"Batman Begins"' is incompatible with index signature.
    Type 'number | undefined' is not assignable to type 'number'.
      Type 'undefined' is not assignable to type 'number'.


// Still an error in TypeScript 4.2.

// Index signatures don't implicitly allow explicit `undefined`.

movieWatchCount["It's the Great Pumpkin, Charlie Brown"] = undefined;

Type 'undefined' is not assignable to type 'number'.2322Type 'undefined' is not assignable to type 'number'.

The new rule also does not apply to number index signatures, since they are assumed to be array-like and dense:

declare let sortOfArrayish: { [key: number]: string };
declare let numberKeys: { 42?: string };



sortOfArrayish = numberKeys;

Type '{ 42?: string | undefined; }' is not assignable to type '{ [key: number]: string; }'.
  Property '42' is incompatible with index signature.
    Type 'string | undefined' is not assignable to type 'string'.
      Type 'undefined' is not assignable to type 'string'.2322Type '{ 42?: string | undefined; }' is not assignable to type '{ [key: number]: string; }'.
  Property '42' is incompatible with index signature.
    Type 'string | undefined' is not assignable to type 'string'.
      Type 'undefined' is not assignable to type 'string'.

You can get a better sense of this change by reading up on the original PR.

Declare Missing Helper Function

Thanks to a community pull request from Alexander Tarasyuk, we now have a quick fix for declaring new functions and methods based on the call-site!

Breaking Changes

We always strive to minimize breaking changes in a release. TypeScript 4.2 contains some breaking changes, but we believe they should be manageable in an upgrade.

lib.d.ts Updates

As with every TypeScript version, declarations for lib.d.ts (especially the declarations generated for web contexts), have changed. There are various changes, though Intl and ResizeObserver’s may end up being the most disruptive.

noImplicitAny Errors Apply to Loose yield Expressions

When the value of a yield expression is captured, but TypeScript can’t immediately figure out what type you intend for it to receive (i.e. the yield expression isn’t contextually typed), TypeScript will now issue an implicit any error.

function* g1() {
  const value = yield 1;
'yield' expression implicitly results in an 'any' type because its containing generator lacks a return-type annotation.7057'yield' expression implicitly results in an 'any' type because its containing generator lacks a return-type annotation.

}




function* g2() {

  // No error.

  // The result of `yield 1` is unused.

  yield 1;

}




function* g3() {

  // No error.

  // `yield 1` is contextually typed by 'string'.

  const value: string = yield 1;

}




function* g4(): Generator<number, void, string> {

  // No error.

  // TypeScript can figure out the type of `yield 1`

  // from the explicit return type of `g4`.

  const value = yield 1;

}

See more details in the corresponding changes.

Expanded Uncalled Function Checks

As described above, uncalled function checks will now operate consistently within && and || expressions when using --strictNullChecks. This can be a source of new breaks, but is typically an indication of a logic error in existing code.

Type Arguments in JavaScript Are Not Parsed as Type Arguments

Type arguments were already not allowed in JavaScript, but in TypeScript 4.2, the parser will parse them in a more spec-compliant way. So when writing the following code in a JavaScript file:

f<T>(100);

TypeScript will parse it as the following JavaScript:

f < T > 100;

This may impact you if you were leveraging TypeScript’s API to parse type constructs in JavaScript files, which may have occurred when trying to parse Flow files.

See the pull request for more details on what’s checked.

Tuple size limits for spreads

Tuple types can be made by using any sort of spread syntax (...) in TypeScript.

// Tuple types with spread elements
type NumStr = [number, string];
type NumStrNumStr = [...NumStr, ...NumStr];
// Array spread expressions
const numStr = [123, "hello"] as const;
const numStrNumStr = [...numStr, ...numStr] as const;

Sometimes these tuple types can accidentally grow to be huge, and that can make type-checking take a long time. Instead of letting the type-checking process hang (which is especially bad in editor scenarios), TypeScript has a limiter in place to avoid doing all that work.

You can see this pull request for more details.

.d.ts Extensions Cannot Be Used In Import Paths

In TypeScript 4.2, it is now an error for your import paths to contain .d.ts in the extension.

// must be changed something like
//   - "./foo"
//   - "./foo.js"
import { Foo } from "./foo.d.ts";

Instead, your import paths should reflect whatever your loader will do at runtime. Any of the following imports might be usable instead.

import { Foo } from "./foo";
import { Foo } from "./foo.js";
import { Foo } from "./foo/index.js";

Reverting Template Literal Inference

This change removed a feature from TypeScript 4.2 beta. If you haven’t yet upgraded past our last stable release, you won’t be affected, but you may still be interested in the change.

The beta version of TypeScript 4.2 included a change in inference to template strings. In this change, template string literals would either be given template string types or simplify to multiple string literal types. These types would then widen to string when assigning to mutable variables.

declare const yourName: string;
// 'bar' is constant.
// It has type '`hello ${string}`'.
const bar = `hello ${yourName}`;
// 'baz' is mutable.
// It has type 'string'.
let baz = `hello ${yourName}`;

This is similar to how string literal inference works.

// 'bar' has type '"hello"'.
const bar = "hello";
// 'baz' has type 'string'.
let baz = "hello";

For that reason, we believed that making template string expressions have template string types would be “consistent”; however, from what we’ve seen and heard, that isn’t always desirable.

In response, we’ve reverted this feature (and potential breaking change). If you do want a template string expression to be given a literal-like type, you can always add as const to the end of it.

declare const yourName: string;
// 'bar' has type '`hello ${string}`'.
const bar = `hello ${yourName}` as const;
//                              ^^^^^^^^
// 'baz' has type 'string'.
const baz = `hello ${yourName}`;

TypeScript’s lift Callback in visitNode Uses a Different Type

TypeScript has a visitNode function that takes a lift function. lift now expects a readonly Node[] instead of a NodeArray<Node>. This is technically an API breaking change which you can read more on here.

原文地址:https://www.cnblogs.com/qq3279338858/p/14578190.html