Typescript4.docx
《Typescript4.docx》由会员分享,可在线阅读,更多相关《Typescript4.docx(14页珍藏版)》请在冰豆网上搜索。
![Typescript4.docx](https://file1.bdocx.com/fileroot1/2023-8/13/3d25c3af-6773-40df-9c6b-a2ac3b522979/3d25c3af-6773-40df-9c6b-a2ac3b5229791.gif)
Typescript4
1引言
随着Typescript4Beta的发布,又带来了许多新功能,其中VariadicTupleTypes解决了大量重载模版代码的顽疾,使得这次更新非常有意义。
2简介
可变元组类型
考虑concat场景,接收两个数组或者元组类型,组成一个新数组:
functionconcat(arr1,arr2){
return[...arr1,...arr2];
}
如果要定义concat的类型,以往我们会通过枚举的方式,先枚举第一个参数数组中的每一项:
functionconcat<>(arr1:
[],arr2:
[]):
[A];
functionconcat(arr1:
[A],arr2:
[]):
[A];
functionconcat(arr1:
[A,B],arr2:
[]):
[A,B];
functionconcat(arr1:
[A,B,C],arr2:
[]):
[A,B,C];
functionconcat(arr1:
[A,B,C,D],arr2:
[]):
[A,B,C,D];
functionconcat(arr1:
[A,B,C,D,E],arr2:
[]):
[A,B,C,D,E];
functionconcat(arr1:
[A,B,C,D,E,F],arr2:
[]):
[A,B,C,D,E,F];)
再枚举第二个参数中每一项,如果要完成所有枚举,仅考虑数组长度为6的情况,就要定义36次重载,代码几乎不可维护:
functionconcat(arr1:
[],arr2:
[A2]):
[A2];
functionconcat(arr1:
[A1],arr2:
[A2]):
[A1,A2];
functionconcat(arr1:
[A1,B1],arr2:
[A2]):
[A1,B1,A2];
functionconcat(
arr1:
[A1,B1,C1],
arr2:
[A2]
):
[A1,B1,C1,A2];
functionconcat(
arr1:
[A1,B1,C1,D1],
arr2:
[A2]
):
[A1,B1,C1,D1,A2];
functionconcat(
arr1:
[A1,B1,C1,D1,E1],
arr2:
[A2]
):
[A1,B1,C1,D1,E1,A2];
functionconcat(
arr1:
[A1,B1,C1,D1,E1,F1],
arr2:
[A2]
):
[A1,B1,C1,D1,E1,F1,A2];
如果我们采用批量定义的方式,问题也不会得到解决,因为参数类型的顺序得不到保证:
functionconcat(arr1:
T[],arr2,U[]):
Array;
在Typescript4,可以在定义中对数组进行解构,通过几行代码优雅的解决可能要重载几百次的场景:
typeArr=readonlyany[];
functionconcat(arr1:
T,arr2:
U):
[...T,...U]{
return[...arr1,...arr2];
}
上面例子中,Arr类型告诉TST与U是数组类型,再通过[...T,...U]按照逻辑顺序依次拼接类型。
再比如tail,返回除第一项外剩下元素:
functiontail(arg){
const[_,...result]=arg;
returnresult;
}
同样告诉TST是数组类型,且arr:
readonly[any,...T]申明了T类型表示除第一项其余项的类型,TS可自动将T类型关联到对象rest:
functiontail(arr:
readonly[any,...T]){
const[_ignored,...rest]=arr;
returnrest;
}
constmyTuple=[1,2,3,4]asconst;
constmyArray=["hello","world"];
//type[2,3,4]
constr1=tail(myTuple);
//type[2,3,...string[]]
constr2=tail([...myTuple,...myArray]asconst);
另外之前版本的TS只能将类型解构放在最后一个位置:
typeStrings=[string,string];
typeNumbers=[number,number];
//[string,string,number,number]
typeStrStrNumNum=[...Strings,...Numbers];
如果你尝试将[...Strings,...Numbers]这种写法,将会得到一个错误提示:
Arestelementmustbelastinatupletype.
但在Typescript4版本支持了这种语法:
typeStrings=[string,string];
typeNumbers=number[];
//[string,string,...Array]
typeUnbounded=[...Strings,...Numbers,boolean];
对于再复杂一些的场景,例如高阶函数partialCall,支持一定程度的柯里化:
functionpartialCall(f,...headArgs){
return(...tailArgs)=>f(...headArgs,...tailArgs);
}
我们可以通过上面的特性对其进行类型定义,将函数f第一个参数类型定义为有顺序的[...T,...U]:
typeArr=readonlyunknown[];
functionpartialCall(
f:
(...args:
[...T,...U])=>R,
...headArgs:
T
){
return(...b:
U)=>f(...headArgs,...b);
}
测试效果如下:
constfoo=(x:
string,y:
number,z:
boolean)=>{};
//Thisdoesn'tworkbecausewe'refeedinginthewrongtypefor'x'.
constf1=partialCall(foo,100);
//~~~
//error!
Argumentoftype'number'isnotassignabletoparameteroftype'string'.
//Thisdoesn'tworkbecausewe'repassingintoomanyarguments.
constf2=partialCall(foo,"hello",100,true,"oops");
//~~~~~~
//error!
Expected4arguments,butgot5.
//Thisworks!
Ithasthetype'(y:
number,z:
boolean)=>void'
constf3=partialCall(foo,"hello");
//Whatcanwedowithf3now?
f3(123,true);//works!
f3();
//error!
Expected2arguments,butgot0.
f3(123,"hello");
//~~~~~~~
//error!
Argumentoftype'"hello"'isnotassignabletoparameteroftype'boolean'
值得注意的是,constf3=partialCall(foo,"hello");这段代码由于还没有执行到foo,因此只匹配了第一个x:
string类型,虽然后面y:
number,z:
boolean也是必选,但因为foo函数还未执行,此时只是参数收集阶段,因此不会报错,等到f3(123,true)执行时就会校验必选参数了,因此f3()时才会提示参数数量不正确。
元组标记
下面两个函数定义在功能上是一样的:
functionfoo(...args:
[string,number]):
void{
//...
}
functionfoo(arg0:
string,arg1:
number):
void{
//...
}
但还是有微妙的区别,下面的函数对每个参数都有名称标记,但上面通过解构定义的类型则没有,针对这种情况,Typescript4支持了元组标记:
typeRange=[start:
number,end:
number];
同时也支持与解构一起使用:
typeFoo=[first:
number,second?
:
string,...rest:
any[]];
Class从构造函数推断成员变量类型
构造函数在类实例化时负责一些初始化工作,比如为成员变量赋值,在Typescript4,在构造函数里对成员变量的赋值可以直接为成员变量推导类型:
classSquare{
//Previously:
implicitany!
//Now:
inferredto`number`!
area;
sideLength;
constructor(sideLength:
number){
this.sideLength=sideLength;
this.area=sideLength**2;
}
}
如果对成员变量赋值包含在条件语句中,还能识别出存在undefined的风险:
classSquare{
sideLength;
constructor(sideLength:
number){
if(Math.random()){
this.sideLength=sideLength;
}
}
getarea(){
returnthis.sideLength**2;
//~~~~~~~~~~~~~~~
//error!
Objectispossibly'undefined'.
}
}
如果在其他函数中初始化,则TS不能自动识别,需要用!
:
显式申明类型:
classSquare{
//definiteassignmentassertion
//v
sideLength!
:
number;
//^^^^^^^^
//typeannotation
constructor(sideLength:
number){
this.initialize(sideLength);
}
initialize(sideLength:
number){
this.sideLength=sideLength;
}
getarea(){
returnthis.sideLength**2;
}
}
短路赋值语法
针对以下三种短路语法提供了快捷赋值语法:
a&&=b;//a&&(a=b)
a||=b;//a||(a=b)
a?
?
=b;//a?
?
(a=b)
catcherrorunknown类型
Typescript4.0之后,我们可以将catcherror定义为unknown类型,以保证后面的代码以健壮的类型判断方式书写:
try{
//...
}catch(e){
//error!
//Property'toUpperCase'doesnotexistontype'unknown'.
console.log(e.toUpperCase());
if(typeofe==="string"){
//works!
//We'venarrowed'e'downtothetype'string'.
console.log(e.toUpperCase());
}
}
PS:
在之前的版本,catch(e:
unknown)会报错,提示无法为error定义unknown类型。
自定义JSX工厂
TS4支持了jsxFragmentFactory参数定义Fragment工厂函数:
{
"compilerOptions":
{
"target":
"esnext",
"module":
"commonjs",
"jsx":
"react",
"jsxFactory":
"h",
"jsxFragmentFactory":
"Fragment"
}
}
还可以通过注释方式覆盖单文件的配置:
//Note:
thesepragmacommentsneedtobewritten
//withaJSDoc-stylemultilinesyntaxtotakeeffect.
/**@jsxh*/
/**@jsxFragFragment*/
import{h,Fragment}from"preact";
letstuff=(
<>
Hello
>
);
以上代码编译后解析结果如下:
//Note:
thesepragmacommentsneedtobewritten
//withaJSDoc-stylemultilinesyntaxtotakeeffect.
/**@jsxh*/
/**@jsxFragFragment*/
import{h,Fragment}from"preact";
letstuff=h(Fragment,null,h("div",null,"Hello"));
其他升级
其他的升级快速介绍:
构建速度提升,提升了--incremental+--noEmitOnError场景的构建速度。
支持--incremental+--noEmit参数同时生效。
支持@deprecated注释,使用此注释时,代码中会使用删除线警告调用者。
局部TSServer快速启动功能,打开大型项目时,TSServer要准备很久,Typescript4在VSCode编译器下做了优化,可以提前对当前打开的单文件进行部分语法响应。
优化自动导入,现在package.jsondependencies字段定义的依赖将优先作为自动导入的依据,而不再是遍历node_modules导入一些非预期的包。
除此之外,还有几个BreakChange:
lib.d.ts类型升级,主要是移除了document.origin定义。
覆盖父Class属性的getter或setter现在都会提示错误。
通过delete删除的属性必须是可选的,如果试图用delete删除一个必选的key,则会提示错误。
3精读
Typescript4最大亮点就是可变元组类型了,但可变元组类型也不能解决所有问题。
拿笔者的场景来说,函数useDesigner作为自定义ReactHook与useSelector结合支持connectredux数据流的值,其调用方式是这样的:
constnameSelector=(state:
any)=>({
name:
state.nameasstring,
});
constageSelector=(state:
any)=>({
age:
state.ageasnumber,
});
constApp=()=>{
const{name,age}=useDesigner(nameSelector,ageSelector);
};
name与age是Selector注册的,内部实现方式必然是useSelector+reduce,但类型定义就麻烦了,通过重载可以这么做:
import*asReactfrom'react';
import{useSelector}from'react-redux';
typeFunction=(...args:
any)=>any;
exportfunctionuseDesigner();
exportfunctionuseDesigner(
t1:
T1
):
ReturnType;
exportfunctionuseDesigner(
t1:
T1,
t2:
T2
):
ReturnType&ReturnType;
exportfunctionuseDesigner<
T1extendsFunction,
T2extendsFunction,
T3extendsFunction
>(
t1:
T1,
t2:
T2,
t3:
T3,
t4:
T4,
):
ReturnType&
ReturnType&
ReturnType&
ReturnType&
;
exportfunctionuseDesigner<
T1extendsFunction,
T2extendsFunction,
T3extendsFunction,
T4extendsFunction
>(
t1:
T1,
t2:
T2,
t3:
T3,
t4:
T4
):
ReturnType&
ReturnType&
ReturnType&
ReturnType&
;
exportfunctionuseDesigner(...selectors:
any[]){
returnuseSelector((state)=>
selectors.reduce((selected,selector)=>{
return{
...selected,
...selector(state),
};
},{})
)asany;
}
可以看到,笔者需要将useDesigner传入的参数通过函数重载方式一一传入,上面的例子只支持到了三个参数,如果传入了第四个参数则函数定义会失效,因此业界做法一般是定义十几个重载,这样会导致函数定义非常冗长。
但参考TS4的例子,我们可以避免类型重载,而通过枚举的方式支持:
typeFunc=(state?
:
any)=>any;
typeArr=readonlyFunc[];
constuseDesigner=(
...selectors:
T
):
ReturnType&
ReturnType&
ReturnType&
ReturnType=>{
returnuseSelector((state)=>
selectors.reduce((selected,selector)=>{
return{
...selected,
...selector(state),
};
},{})
)asany;
};
可以看到,最大的变化是不需要写四遍重载了,但由于场景和concat不同,这个例子返回值不是简单的[...T,...U],而是reduce的结果,所以目前还只能通过枚举的方式支持。
当然可能存在不用枚举就可以支持无限长度的入参类型解析的方案,因笔者水平有限,暂未想到更好的解法,如果你有更好的解法,欢迎告知笔者。
4总结
Typescript4带来了更强类型语法,更智能的类型推导,更快的构建速度以及更合理的开发者工具优化,唯一的几个BreakChange不会对项目带来实质影响,期待正式版的发布。