可变元组类型(Variadic Tuple Types)
旧版本Typescript
要实现一个concat
方法,一般实现方式是:
function concat<T, U>(arr1: T[], arr2: U[]) {
return [...arr1, ...arr2]
}
这种方式实现的concat
方法的返回值类型是一个联合类型的数组(T | U)[]
declare const arr1: number[]
declare const arr2: string[]
// type: (string | number)[]
const arr = concat(arr1, arr2)
如果要对元祖进行concat操作呢
declare const arr1: [number, string]
declare const arr2: [boolean]
// type: (string | number | boolean)[]
const arr = concat(arr1, arr2)
元祖具有特定长度和元素类型,上例的返回值类型显然是不精确的,期望的返回值类型是[number, string, boolean]
要实现期望的结果,在旧版本的TS中只能编写重载
function concat<T, U, V>(arr1: [T, U], arr2: [V]): [T, U, V]
function concat<T, U>(arr1: T[], arr2: U[]) {
return [...arr1, ...arr2]
}
但如果传入的元祖长度不能确定,我们只能不断的编写重载以尽可能覆盖所有的情况,这显然是不可接受的。
TypeScript 4.0 带来了两个基础更改,并在推断方面进行了改进。
其中一个更改是范型可用于扩展运算符。这意味着可以用范型声明一个可变的元祖。
由此就可以实现一个类型支持更好的concat
函数
function concat<T extends unknown[], U extends unknown[]>(t: [...T], u: [...U]): [...T, ...U] {
return [...t, ...u];
}
declare const arr1: [string, number]
declare const arr2: string[]
declare const arr3: ['hello']
concat(arr1, arr2) // [string, number, ...string[]]
concat(arr1, arr3) // [string, number, 'hello']
另一个更改是旧版本Typescript的rest
参数只支持数组类型,且必须放在元祖的最后;而现在可以放在元祖的任意位置。
// 旧版本ts
type t1 = [...string[]]
type t2 = [...[string, number]] // error: A rest element type must be an array type.
type t3 = [...string[], string] // A rest element must be last in a tuple type
不确定长度的数组类型使用扩展运算符,如果不放置于最后,那其后的所有元素都将被推断该数组元素的类型和其后元素类型的联合类型
type t1 = string[]
type t2 = [boolean, ...t1, number] // [boolean, ...(string | number)[]]
标记元祖元素(Labeled Tuple Elements)
如果我们要创建一个图标,可能有如下实现
function createIcon(url: string, size: [number, number]) {
const [width, height] = size
return new BMapGL.Icon(url, new BMapGL.Size(width, height));
}
其中的size参数是元祖类型,但我们在调用createIcon
方法的时候只清楚size
的类型,并不清楚每个元素的意义。在调用时还需要跳转到函数体去查看size每个元素的意义。
在Typescript 4.0中,元祖元素可以被标记。上例中的createIcon
方法可以这样实现:
function createIcon(url: string, size: [width: number, height: number]) {
const [width, height] = size
return new BMapGL.Icon(url, new BMapGL.Size(width, height));
}
这样在调用时就能查看size
参数每个元素的意义
一些使用规则
在标记一个元组元素时,还必须标记元组中的所有其他元素。
type Size = [width: number, number] // error: Tuple members must all have names or all not have names.
解构标记时无需使用不同名称命名变量。如下例,解构size
参数时变量命名无需使用width
和height
function createIcon(url: string, size: [width: number, height: number]) {
const [w, h] = size
return new BMapGL.Icon(url, new BMapGL.Size(w, h));
}
使用带标记的元祖可实现重载
从构造器函数中推断类属性(Class Property Inference from Constructors)
如下例,在旧版本的Typescript
中,当开启了noImplicitAny
选项,定义的实例属性area
和sideLength
会报错。因为其没有显式声明类型,从而被推断为any。
class Square {
area;
sideLength;
constructor(sideLength: number) {
this.sideLength = sideLength;
this.area = sideLength ** 2;
}
}
而在Typescript 4.0中该实例属性的类型会从 constructor
函数中推断,area
和sideLength
都被推断为number类型,不会报错。
如果对类实例属性的初始化没有写在constructor
函数中,Typescript
就无法推断该实例属性的类型。
class Square {
// error: Member 'sideLength' implicitly has an 'any' type.
sideLength;
constructor(sideLength: number) {
this.initialize(sideLength)
}
initialize(sideLength: number) {
this.sideLength = sideLength;
}
}
此时需要显示声明实例属性的类型,而如果开启strictPropertyInitialization
选项(检查已声明但未在构造函数中设置的类属性)还需要显示赋值断言来使类型系统识别类型
class Square {
sideLength!: number;
constructor(sideLength: number) {
this.initialize(sideLength)
}
initialize(sideLength: number) {
this.sideLength = sideLength;
}
}
短路赋值运算符(Short-Circuiting Assignment Operators)
ES2021新增的特性中包含了逻辑赋值运算符(Logical Assignment Operators)的提案。
当变量a为truthy时,将其值设置为b,即等价于a = a && b
a &&= b;
当变量a为falsy时,将其设置为b,即等价于a = a || b
a ||= b;
当变量a为nullish时,将其设置为b,即等价于a = a ?? b
。
??
操作符是ES2020的新增特性Nullish Coalescing
// set a to b only when a is nullish
a ??= b;
Typescript 4.0支持了上述特性
catch子句变量支持声明为unknown(unknown on catch Clause Bindings)
在旧版本的Typescript中,catch子句的变量拥有any
类型,且不可以被声明为其他类型
try {
} catch(err: unknown) { // error: Catch clause variable cannot have a type annotation.
}
在Typescript 4.0版本支持将该变量声明为unknown
,上例不会报错。
之所以这样做是因为any
类型可以兼容其他所有类型,如上例对err
变量进行任何操作都不会报类型错误。而unknown
比 any
更安全,因为它会在我们操作值之前提醒我们执行某种类型检查。
try {
} catch(err: unknown) {
if (err instanceof Error) {
console.error(err.message)
}
}
定制 JSX Fragment 工厂函数
旧版本的Typescript便已支持定制JSX
工厂函数,可通过jsxFactory
选项进行定制。
在Typescript 4.0中支持通过新的jsxFragmentFactory
选项来定制 Fragment
工厂函数。
如下tsconfig.json
配置告诉 TypeScript
以与 React
兼容的方式转换 JSX
,但将每个工厂函数切换为 h
而不是 React.createElement
,并使用 Fragment
而不是 React.Fragment
。
使用如下tsconfig.json
配置
{
"compilerOptions": {
"target": "esnext",
"module": "commonjs",
"jsx": "react",
"jsxFactory": "h",
"jsxFragmentFactory": "Fragment"
}
}
编译如下代码
import { h, Fragment } from "preact";
let stuff = <>
<div>Hello</div>
</>;
将输出
"use strict";
exports.__esModule = true;
/** @jsx h */
/** @jsxFrag Fragment */
var preact_1 = require("preact");
var stuff = preact_1.h(preact_1.Fragment, null,
preact_1.h("div", null, "Hello"));
JSX
工厂函数支持使用/** @jsx */
注释,去指定当前文件使用的JSX
工厂函数。同样,Fragment
工厂函数可通过新的/** @jsxFrag */
注释去指定。
如下,在文件头部指定当前文件使用的JSX
工厂函数和Fragment
工厂函数。
通过注释指定的方式比在tsconfig.json
文件中配置的优先级高
/** @jsx h */
/** @jsxFrag Fragment */
import { h, Fragment } from "preact";
let stuff = <>
<div>Hello</div>
</>;
重大更改
lib.d.ts
Typescript 4.0删除了 document.origin
,它仅在 IE 的旧版本中有效,而 Safari MDN 建议改用 self.origin。
如下,在Typescript 4.0版本访问document
的origin
属性将提示该属性不存在
document.origin // error: Property 'origin' does not exist on type 'Document'
如果要在旧版本的IE使用该属性,需要显式设置
interface Document {
origin: string
}
console.log(document.origin)
属性重写访问器(反之亦然)会报错(Properties Overriding Accessors (and vice versa) is an Error)
旧版本的Typescript中,子类的实例属性覆盖父类的访问器属性只有在使用useDefineForClassFields
选项时才会报错。
class Base {
get foo() {
return 100;
}
set foo(val) {
// ...
}
}
class Derived extends Base {
// 旧版本在使用useDefineForClassFields选项会报错 error: 'foo' is defined as an accessor in class 'Base', but is overridden here in 'Derived' as an instance property.
foo = 10;
}
而在Typescript 4.0版本中,无论是否使用useDefineForClassFields
选项,子类的实例属性覆盖父类的访问器属性(或子类的访问器属性覆盖父类的实例属性)总是报错。
delete 的操作对象必须是可选的
Typescript 4.0版本在启用 strictNullChecks
选项时,使用delete
运算符,操作对象现在必须为 any
、unknown
、never
或为可选(因为它在类型中包含 undefined
)。否则,使用 delete
运算符将会报错。
interface Thing {
prop: string;
a: unknown;
b: any;
c: never;
d: undefined;
}
function f(x: Thing) {
delete x.prop; // error: The operand of a 'delete' operator must be optional.
delete x.a
delete x.b
delete x.c
delete x.d
}
参考资料
- Announcing TypeScript 4.0
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!