May 07, 2021 TypeScript
2. Type analysis based on the control flow
5. Read-only properties and index signatures
6. Specifies the this the function
7. tsconfig.json file wildcards
8. Module resolution increases: BaseUrl, path mapping, rootDirs, and tracking
9. Quick external module declaration
10.. A wildcard in the module name
11.. UMD module definitions are supported
12.. Optional class properties
13.. Private and protected constructors
14.. Abstract properties and accessors
16.. Compiling --lib contains built-in type declarations
17.. Use --noUnusedParameters --noUnusedLocals claims
18.. The module name .js to use the extension
19.. Supports the compilation target : es5 module: es6 at the same time
20.. Commas are supported at the end of the function parameter and argument list
21.. New compilation parameter --skipLibCheck
TypeScript now has two special types: Null and Undefined, whose
null
and
undefined
Previously it was impossible to explicitly name these types,
null
undefined
be used as type names regardless of type check mode.
Previous type inspectors thought
null
and
undefined
were assigned to everything.
In fact,
null
and
undefined
for each type and cannot be explicitly excluded (so errors cannot be detected).
--strictNullChecks
--strictNullChecks
be switched to the new strict air check mode.
In strictly empty check mode,
null
and
undefined
any type of value, only values of their own type
any
type (with one exception,
undefined
assigned to
void
Therefore, although
T
and
T | undefined
in general type check mode (because
undefined
sub-type of any
T
they are different in strict type check
T | undefined
are
undefined
as is the relationship between
T
T | null
// 使用--strictNullChecks参数进行编译的
let x: number;
let y: number | undefined;
let z: number | null | undefined;
x = 1; // 正确
y = 1; // 正确
z = 1; // 正确
x = undefined; // 错误
y = undefined; // 正确
z = undefined; // 正确
x = null; // 错误
y = null; // 错误
z = null; // 正确
x = y; // 错误
x = z; // 错误
y = x; // 正确
y = z; // 错误
z = x; // 正确
z = y; // 正确
In strictly empty check mode, the compiler requires that local variables that do not contain the
undefined
must be assigned before they can be used.
// 使用--strictNullChecks参数进行编译
let x: number;
let y: number | null;
let z: number | undefined;
x; // 错误,使用前未赋值
y; // 错误,使用前未赋值
z; // 正确
x = 1;
y = null;
x; // 正确
y; // 正确
The compiler checks that variables are explicitly over-valued by performing type analysis based on the control flow. Further details will be provided later in this article.
Optional parameters and properties
undefined
their type, even if their type annotations explicitly do not
undefined
For example, the following two types are identical:
// 使用--strictNullChecks参数进行编译
type T1 = (x?: number) => string; // x的类型是 number | undefined
type T2 = (x?: number | undefined) => string; // x的类型是 number | undefined
If the object or function type
null
undefined
compilation error occurs when the property is accessed or the function is called.
As a result, type protection has been extended to support non-null and non-underfined checks.
// 使用--strictNullChecks参数进行编译
declare function f(x: number): string;
let x: number | null | undefined;
if (x) {
f(x); // 正确,这里的x类型是number
}
else {
f(x); // 错误,这里的x类型是number?
}
let a = x != null ? f(x) : ""; // a的类型是string
let b = x && f(x); // b的类型是 string | 0 | null | undefined
Non-null and non-undefined type
===
protection can
!==
be compared with the operator and
null
or
undefined
such as
x != null
or
==
x === undefined
!=
The effect on the type of variable being tested accurately reflects the semantics of JavaScript (for example, the double equal operator checks two values whether you specify null or underfined, wherein yet the three-equal operator checks only that value specified).
Type protection previously supported only the examination of local variables and parameters. Type protection now supports checking for "point names" consisting of variable or parameter names followed by one or more access properties.
interface Options {
location?: {
x?: number;
y?: number;
};
}
function foo(options?: Options) {
if (options && options.location && options.location.x) {
const x = options.location.x; // x的类型是number
}
}
Point name type protection and user-defined type protection functions,
typeof
instanceof
and do not rely on --
--strictNullChecks
parameters.
Assigning any part of the point name after type protection results in invalid type protection.
For example,
x.y.z
x
x.y
to
x.y.z
protection results in
x.y.z
type protection.
The expression operator allows the type of the operator
null
and/or
undefined
but always produces result values that are not null and non-underfined types.
// 使用--strictNullChecks参数进行编译
function sum(a: number | null, b: number | null) {
return a + b; // 计算的结果值类型是number
}
&&
The operator
null
undefined
operating object depending on the type of the current left-hand operating object,
||
The operator removes both null and
undefined
from
null
the operation object of the union type on the left.
// 使用--strictNullChecks参数进行编译
interface Entity {
name: string;
}
let x: Entity | null;
let s = x && x.name; // s的类型是string | null
let y = x || { name: "test" }; // y的类型是Entity
In strictly empty check mode,
null
and
undefined
are
not extended
any
type.
let z = null; // z的类型是null
In general type check mode, because of the extension,
z
is
any
in strict empty check
z
null type
null
null
the unique
z
there are no type comments).
In the context, a new suffix expression
!
! C
an be used to assert that the action object is non-null and non-underfined types. S
pecifically, operation
x!
P
roduces a value of x that does not contain
null
and
undefined
x
T
he form of
<T>x
x as T
!
Non-empty assertion operators are removed from compiled JavaScript code.
// 使用--strictNullChecks参数进行编译
function validateEntity(e?: Entity) {
// 如果e是null或者无效的实体,就会抛出异常
}
function processEntity(e?: Entity) {
validateEntity(e);
let s = e!.name; // 断言e是非空并访问name属性
}
These new features are designed to be available in both the strict air check mode and the regular type check mode. E
specially in general type check mode,
null
undefined
automatically removed from the union type (because they are sub-types of all other types),
!
N
on-empty assertion expression operators are also allowed to be used but have no effect.
Therefore, after the claims file is updated with null and underfined sensitive types, it is still backward compatible in regular type mode.
In practice, strict air check mode requires that all files compiled are null and undefined sensitive types.
TypeScript 2.0 enables a flow of control flow type analysis of local variables and parameters. P
reviously, type analysis of type protection was limited
if
?:
and did not include the effects of assignments and control
return
and break
break
With TypeScript 2.0, the type inspector analyzes all possible control flows of statements and expressions, producing the most likely specific type (the narrowed type) of local variables or parameters declared as union types at any given location.
function foo(x: string | number | boolean) {
if (typeof x === "string") {
x; // 这里x的类型是string
x = 1;
x; // 这里x的类型是number
}
x; // 这里x的类型是number | boolean
}
function bar(x: string | number) {
if (typeof x === "number") {
return;
}
x; // 这里x的类型是string
}
Type analysis based on the control flow is particularly important in the
--strictNullChecks
pattern because empty types are represented by union types:
function test(x: string | null) {
if (x === null) {
return;
}
x; // 在函数的剩余部分中,x类型是string
}
Also, in
--strictNullChecks
on the control flow includes analysis that does not allow the type to have explicit assignments for local
variables
that are not allowed to be
undefined
function mumble(check: boolean) {
let x: number; // 类型不允许为undefined
x; // 错误,x是undefined
if (check) {
x = 1;
x; // 正确
}
x; // 错误,x可能是undefi
x = 2;
x; // 正确
}
TypeScript 2.0 implements tag (or differentiated) union types.
Specifically, the TS compiler now supports type protection, narrows the scope of the federation type based on the checks of the
switch
statement also supports this attribute.
interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
interface Circle {
kind: "circle";
radius: number;
}
type Shape = Square | Rectangle | Circle;
function area(s: Shape) {
// 在下面的switch语句中,s的类型在每一个case中都被缩小
// 根据判别属性的值,变量的其它属性不使用类型断言就可以被访问
switch (s.kind) {
case "square": return s.size * s.size;
case "rectangle": return s.width * s.height;
case "circle": return Math.PI * s.radius * s.radius;
}
}
function test1(s: Shape) {
if (s.kind === "square") {
s; // Square
}
else {
s; // Rectangle | Circle
}
}
function test2(s: Shape) {
if (s.kind === "square" || s.kind === "rectangle") {
return;
}
s; // Circle
}
The judged property type
protection is an expression of
x.p == v
x.p === v
x.p != v
x.p !== v
in which
p
and
v
are an expression of a literal type of property and string or a literal union of strings.
The judged property type protects the
x
shrinks x to one of the
p
v
the judged properties p and v.
Note that we currently only support the character properties of string literal types. We intend to add support for Boolean values and digital literal types later.
never
type
TypeScript 2.0 introduces a new original
never
never
type indicates that the type of the value never appears.
Specifically,
never
the return type that never returns the function, and it is also the type of variable that never true in type protection.
never
type has the following characteristics:
never
is a sub-type of all types and can be assigned to all types.
never
of never or can be assigned to
never
(except for
never
type itself).
return
statement, or if there is only a return statement for the
never
return
and if the function is not executable to the end (for example, determined by
never
the return type of the function is never.
never
return type annotations, all
return
any, must have an expression of the
never
type and the end point of the function must be unexe executable.
Because
never
is a sub-type of each type, it is always omitted in the union type, and the type inference ignores the never type as long as other types
never
function.
Some examples that return the
never
function:
// 函数返回never必须无法执行到终点
function error(message: string): never {
throw new Error(message);
}
// 推断返回类型是never
function fail() {
return error("Something failed");
}
// 函数返回never必须无法执行到终点
function infiniteLoop(): never {
while (true) {
}
}
Some functions return examples of the use of
never
// 推断返回类型是number
function move1(direction: "up" | "down") {
switch (direction) {
case "up":
return 1;
case "down":
return -1;
}
return error("Should never get here");
}
// 推断返回类型是number
function move2(direction: "up" | "down") {
return direction === "up" ? 1 :
direction === "down" ? -1 :
error("Should never get here");
}
// 推断返回类型是T
function check<T>(x: T | undefined) {
return x || error("Undefined value");
}
Because
never
can be assigned to each type, when a callback function is required to return a more specific type, the
never
can be used to detect that the return type is correct:
function test(cb: () => string) {
let s = cb();
return s;
}
test(() => "hello");
test(() => fail());
test(() => { throw new Error(); })
Property or index signatures can now be declared read-only using
readonly
modifiers.
Read-only properties can be initialized and assigned in constructors of the same class, but in other cases assignments to read-only properties are not allowed.
In addition, there are several cases where entities are implicitly read-only:
get
read-only using
set
accessors and not set accessors.
const
read-only properties.
import
in the import statement are considered read-only.
foo
example,
foo.x
is read-only when declared as
import * as foo from "foo"
interface Point {
readonly x: number;
readonly y: number;
}
var p1: Point = { x: 10, y: 20 };
p1.x = 5; // 错误,p1.x是只读的
var p2 = { x: 1, y: 1 };
var p3: Point = p2; // 正确,p2的只读别名
p3.x = 5; // 错误,p3.x是只读的
p2.x = 5; // 正确,但是因为别名使用,同时也改变了p3.x
class Foo {
readonly a = 1;
readonly b: string;
constructor() {
this.b = "hello"; // 在构造函数中允许赋值
}
}
let a: Array<number> = [0, 1, 2, 3, 4];
let b: ReadonlyArray<number> = a;
b[5] = 5; // 错误,元素是只读的
b.push(5); // 错误,没有push方法(因为这会修改数组)
b.length = 3; // 错误,length是只读的
a = b; // 错误,缺少修改数组的方法
this
the function
Immediately following classes and interfaces, functions and methods can
this
type of this.
The default
this
in the function is
any
S
tarting with TypeScript 2.0, you can provide a clear
this
parameter.
this
is a pseudo-argument, which is at the first place in the list of function parameters:
function f(this: void) {
// 确保`this`在这个独立的函数中无法使用
}
this
function
The library can also
this
parameter to declare how callback functions are called.
interface UIElement {
addClickListener(onclick: (this: void, e: Event) => void): void;
}
this:void
addClickListener
onclick
be a function of this argument
this
does not require a type.
Now if you typed this in
this
calling code:
class Handler {
info: string;
onClickBad(this: Handler, e: Event) {
// 哎哟,在这里使用this.在运行中使用这个回调函数将会崩溃。
this.info = e.message;
};
}
let h = new Handler();
uiElement.addClickListener(h.onClickBad); // 错误!
--noImplicitThis
TypeScript 2.0 also adds a new compilation option to mark the use of all this in functions that do not have explicit
this
tsconfig.json
file wildcards
File wildcards are coming!! Supporting file wildcards has always been one of the most needed features.
File patterns like file wildcards support two
"include"
"exclude"
{
"compilerOptions": {
"module": "commonjs",
"noImplicitAny": true,
"removeComments": true,
"preserveConstEnums": true,
"outFile": "../../built/local/tsc.js",
"sourceMap": true
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"**/*.spec.ts"
]
}
The symbols that support file wildcards are:
*
Match zero or more characters (excluding directory)
?
Match any one character (excluding directory)
**/
Recursively matches all subdirections
If the file wildcard mode
*
statement contains only
.*
then only files with
.ts
and
.d.ts
by default) are matched, and .js and
.jsx
are also default if
.tsx
allowJs
is set to
true
.js
If
"files"
"include"
the compiler defaults to including TypeScript files
.ts
.d.ts
and
.tsx
in all directories, except those
exclude
property.
If
allowJs
set to true, JS
.js
.jsx
are also included.
If
"files"
"include"
are specified, the compiler will contain the two properties that specify the set of files.
Directory
ourDir
compilation option are always
"exclude"
property are
files
by the file property are not excluded.
"exclude"
property
"include"
by the "include" property. H
owever, it has no effect on the files specified by
"files"
When not explicitly specified,
"exclude"
property excludes
node_modules
bower_components
jspm_packages
default.
TypeScript 2.0 provides a series of additional module resolution properties that tell the compiler where to find the declaration of a given module.
For more information, see the module resolution documentation.
It is common practice to use the AMD module loader and for modules to
baseUrl
applications at runtime.
All module imports with non-relative names are
baseUrl
{
"compilerOptions": {
"baseUrl": "./modules"
}
}
Importing
moduleA
./modules/moduleA
import A from "moduleA";
Sometimes the module is not directly in baseUrl. The loader uses a mapping configuration to map module names and files at runtime, see the RequireJs and SystemJS documentation.
The TypeScript compiler
tsconfig
files that
"paths"
property map.
For example,
"jquery"
module is converted
"node_modules/jquery/dist/jquery.slim.min.js"
{
"compilerOptions": {
"baseUrl": "./node_modules",
"paths": {
"jquery": ["jquery/dist/jquery.slim.min"]
}
}
Using
"paths"
allows for more complex mapping, including multiple regressing locations.
Consider that one module with only one place is available, and the others are configured in another place for the project.
rootDirs
and virtual directories
With
rootDirs
you can tell the compiler's
root to
combine these "virtual" directories.
So the compiler parses relative import modules in these "virtual" directories as if they were merged into one directory.
The given project structure
src
└── views
└── view1.ts (imports './template1')
└── view2.ts
generated
└── templates
└── views
└── template1.ts (imports './view2')
The build step
/src/views
/generated/templates/views
directories to the same directory.
At runtime, the view expects its template to exist in the same directory as it does, so it should be
"./template"
The contents of a set of root directories specified by
"rootDir"
will be merged at runtime.
So in our case,
tsconfig.json
should look like this:
{
"compilerOptions": {
"rootDirs": [
"src/views",
"generated/templates/views"
]
}
}
--traceResolution
a convenient way to understand how modules are parsed by the compiler.
tsc --traceResolution
When you use a new module, if you don't want to spend time writing a declaration, you can now use a shortcut declaration to get started quickly.
declare module "hot-new-module";
All imports from shortcut modules have any type.
import x, {y} from "hot-new-module";
x(y);
Previously, it was not easy to import code-free resources using module loaders such as AMD and SystemJS. Previously, an external module declaration must be defined for each resource.
TypeScript 2.0 supports the use of wildcard
*
to define a class of module names.
In this way, a claim requires only one extension, not every resource.
declare module "*!text" {
const content: string;
export default content;
}
// Some do it the other way around.
declare module "json!*" {
const value: any;
export default value;
}
Now you can import
"*!text"
or
"json!*"
import fileContent from "./xyz.txt!text";
import data from "json!http://example.com/data.json";
console.log(data, fileContent);
The name of a wildcard module may be more useful when migrating from a non-typed code.
Combined with a shortcut external module declaration, a set of modules can be easily declared
any
declare module "myLibrary/*";
Imports of
myLibrary
under the myLibrary directory are considered
any
so any type checks for these modules are turned off.
import { readFile } from "myLibrary/fileSystem/readFile`;
readFile(); // readFile是'any'类型
Some libraries are designed to be used using multiple module loaders or not with module loaders (global variables), which are called UMD or omogeneous modules. These libraries can be accessed through imports or global variables.
Example:
export const isPrime(x: number): boolean;
export as namespace mathLib;
The library can then be used as a module import:
import { isPrime } from "math-lib";
isPrime(2);
mathLib.isPrime(2); // 错误:无法在模块内部使用全局定义
It can also be used as a global variable, limited to files
import
export
scripts.
mathLib.isPrime(2);
You can now declare optional properties and methods in a class, similar to interfaces.
class Bar {
a: number;
b?: number;
f() {
return 1;
}
g?(): number; // 可选方法的方法体可以省略
h?() {
return 2;
}
}
When
--strictNullChecks
optional properties and methods
undefined
to their type. T
herefore,
b
number | undefined
and the type of
g
method above is
(()=> number) | undefined
Use type protection to remove
undefined
The constructor of the class can be marked
private
protected
A
class of a private constructor cannot be instantiated outside the class and cannot be inherited.
The class of a protected constructor can no longer be instantiated externally by the class, but can be inherited.
class Singleton {
private static instance: Singleton;
private constructor() { }
static getInstance() {
if (!Singleton.instance) {
Singleton.instance = new Singleton();
}
return Singleton.instance;
}
}
let e = new Singleton(); // 错误:Singleton的构造函数是私有的。
let v = Singleton.getInstance();
Abstract classes can declare abstract properties and,or accessors. A ll sub-classes will need to declare abstract properties or be marked as abstract. A bstract properties cannot be initialized. Abstract accessors cannot have concrete blocks of code.
abstract class Base {
abstract name: string;
abstract get value();
abstract set value(v: number);
}
class Derived extends Base {
name = "derived";
value = 1;
}
If all known properties in the object literal quantity are assigned to the index signature, the object literal type can now be assigned to the index signature type. This makes it possible to pass a variable that uses object literally initialization as an argument to a function that expects the argument to be map or dictionary:
function httpService(path: string, headers: { [x: string]: string }) { }
const headers = {
"Content-Type": "application/x-www-form-urlencoded"
};
httpService("", { "Content-Type": "application/x-www-form-urlencoded" }); // 可以
httpService("", headers); // 现在可以,以前不可以。
--lib
contains built-in type declarations
Getting the ES6/ES2015 built-in API declaration is
target: ES6
E
nter
--lib
you can use to
--lib
API required for a set of projects. F
or example, if you want your project to run
Map
Set
and
Promise
(for example, now silently update your
--lib es2015.collection,es2015.promise
Similarly, you can exclude claims that are not needed in a project, such as
--lib es5,es6
node project.
Here's a list of the available APIs:
tsc --target es5 --lib es5,es2015.promise
"compilerOptions": {
"lib": ["es5", "es2015.promise"]
}
--noUnusedParameters
--noUnusedLocals
claims
TypeScript 2.0 has two new compilation parameters to help you keep a clean code base.
-noUnusedParameters
to mark all unused function or method parameter errors.
--noUnusedLocals
like variables, functions, classes, imports, and so on, and unused private class members are also marked as errors under
--noUnusedLocals
import B, { readFile } from "./b";
// ^ 错误:`B`声明了,但是没有使用。
readFile();
export function write(message: string, args: string[]) {
// ^^^^ 错误:'arg'声明了,但是没有使用。
console.log(message);
}
Argument declarations
_
named after the beginning of the s are not checked by unused parameters.
For example:
function returnNull(_a) { // 正确
return null;
}
.js
to use the extension
Until TypeScript 2.0, the module name was always considered to have no extension. F
or example, importing a
import d from "./moduleA.js"
./moduleA.js.ts
or
./moduleA.js.d.ts
"moduleA.js"
This makes packaging
or loading tools like SystemJS
that expect module names to be URIs difficult to use.
Using TypeScript 2.0, the compiler will look for the definition of
./moduleA.ts
in
./moduleA.d.ts
"moduleA.js"
target : es5
module: es6
at the same time
Previously compiled
target : es5
module: es6
considered invalid at the same time, but are now valid.
This will help with ES2015-based tree-shaking (removing useless code) such as
rollup.
A comma is now allowed at the end of the function parameter and argument list. This is an implementation of the phase III ECMAScript proposal and compiles into available ES3/ES5/ES6.
function foo(
bar: Bar,
baz: Baz, // 形参列表末尾添加逗号是没有问题的。
) {
// 具体实现……
}
foo(
bar,
baz, // 实参列表末尾添加逗号同样没有问题
);
--skipLibCheck
TypeScript 2.0 adds a new compilation
--skipLibCheck
that skips the type check of the declaration
.d.ts
as its extension).
When a program contains a large number of claims files, the compiler spends a lot of time type checking claims that are known to contain errors, and by skipping the type check of the claims file, the compilation time can be greatly reduced.
Because declarations in one file can affect type checks in other files, some errors may not be detected when you specify
--skipLibCheck
F
or example, if a type in a non-declared file is used by a declared file, an error may only be found when the declaration file is checked.
However, this is not common in real-world use.
This is a common source of repeated definition errors. Multiple claims files define the same interface members.
TypeScript 2.0 relaxes this constraint and allows duplicate identifiers to appear in different blocks of code, as long as they have exactly the same type.
Repeated definitions in the same block of code are still not allowed.
interface Error {
stack?: string;
}
interface Error {
code?: string;
path?: string;
stack?: string; // OK
}
--declarationDir
--declarationDir
make the resulting claims file and JavaScript file out of the same place.