将手上的angular项目升级最新版本,并将typescript也升级到最新版本,结果vs code中打开tsconfig.json有个红色的警告,说是添加strict选项,以减少类型错误。作为一个有强迫症的程序员,决定一试,用这个选项来提高代码的优雅度。
加入strict选项后,运行ng build之后,原本运行正常的程序,突然之间出现了一堆错误。
后边对修正的内容进行一一记录。
用了strict参数后,所有参数变量都要指定类型。否则报这样的错误:
error TS7006: Parameter 'name' implicitly has an 'any' type.
本来any是没问题的,但现在必须显示指定。比如一个类方法是这样的:
getDataStore(name, keyExpr, needpage?, aditional?)
现在就变成,最后一个参数可以直接指定any类型:
getDataStore(name:string, keyExpr:string, needpage?:boolean, aditional?:any)
再如下边这个错误:
error TS7005: Variable 'items' implicitly has an 'any[]' type.
const items=[];
......
return items;
编译器认为这样隐匿any[]类型不行,要显式指出,改成这样就过了:
const items:any[] = [];
……
return items;
当然有条件的话,能定义相应的接口类型,这样可以更加有利于定位错误。
用strict编译选项后,如果上下文不能保证变量不为null,则会报错,比如:
error TS2531: Object is possibly 'null'.
24 const modes = localStorage.getItem('modes').split(',');
这里从本地存储中获取一个值。虽然业务上保证这地方不可能为null,但从编译器的角度来讲,只要不是直接定义赋值的地方都有可能为null,所以报了这个错。实际上就是要求split()方法前的对象必须有效。
那这里修改思路有两个,一是给定默认值,另一个如果为null则不调用split(),这里采用后者,代码自改成如下:
- const str = localStorage.getItem('modes');
- if (str) {
- const modes = str.split(',');
- }
编译通过。
再比如以下错误:
error TS2322: Type 'null' is not assignable to type '{ email: string; userName: string; }'.
394 this._user = null;
原因是_user在定义的时候,显式给字类型,而null并不是从给定类型继承的,所以不能赋值,这跟java,c#之类的编程语言不同,不能直接给null,此时还是用any大法,在定义的时候指定any类型:
private _user: any = { email: '', userName: '' };
还有属性为空的情况:
error TS2531: Object is possibly 'null'.
16 let p=route.routeConfig.path??'';
改成这样:
let p=route.routeConfig?.path??'';
再来一个null问题:
Error: src/app/shared/components/side-navigation-menu/side-navigation-menu.component.ts:85:7 - error TS2531: Object is possibly 'null'.
85 this.menu.instance.collapseAll();
代码修改为:
this.menu?.instance?.collapseAll();
编译通过。
当一个值可能为null的时候,会引起类型匹配的异常。比如下边这个错误,它是将null当成一种类型,因为userId有可能为null,所以出现了因类型不匹配而不能赋值的错误。考虑到此处null没有什么实际意义,可以强制为string类型。
Argument of type 'string | null' is not assignable to parameter of type 'string | Blob'.
Type 'null' is not assignable to type 'string | Blob'.353 formData.append('userId', this.userId);
代码修改如下:
formData.append('userId', this.userId + '');
编译通过。
看一个参数类型不一致的情况:
error TS2345: Argument of type 'number' is not assignable to parameter of type 'never'.
43 this.viewBox.push(width);
这里把width改any类型即可:
let width:any=....;
类属性如果没做初始化,也会编译报错:
error TS2564: Property 'user' has no initializer and is not definitely assigned in the constructor.
21 user: { email: string, userName: string };
这里给个初始值:
- @Input()
- user: { email: string; userName: string } = { email: '', userName: '' };
感觉这地方有点多余,本来可以进行空值判断的,这么一改变成默认值判断了。
不能初始化null的错误:
Error: src/app/shared/components/side-navigation-menu/side-navigation-menu.component.ts:39:3 - error TS2322: Type 'null' is not assignable to type 'DxTreeViewComponent'.
39 menu: DxTreeViewComponent = null;
因为null是个独立的类型,如果初始化需要事先设为null,那么要给变量指定null类型:
@ViewChild('mapdiv', { static: true }) mapdiv: ElementRef | any= {};
再看一个 never类型的错误:
error TS2339: Property 'name' does not exist on type 'never'.
72 return t?.name;
原来代码是这样的:
- const t = this.usingVehicleTypes.find((el) => el.id === id);
- return t?.name;
大概是find()返回值不符合Array所定义的返回类型,因此造成类型不匹配的错误,修改如下:
- const t:any = this.usingVehicleTypes.find((el:any) => el.id === id);
- return t?.name;
暂时先去除类型检查,编译通过。
这个项目开始时,是当成js来写了,充分使用了js的动态特性,所以大部分地方都没标注变量类型,在strict选项下,给变量加类型标注成了最主要的任务。由于json对象,基本从服务器端获得的,客户端并未定义相应的数据类型,有时加类型标注成了困难,好在有个any对象,暂时全部标注成any。总之,强类型的编程语言有利于代码的维护,但ts建立js之上的,有时不知不觉会去使用js的特性,而ts的强类型特性就会带来困扰,所以在用ts的时候,我们要注意这当中的变化。