旧项目TypeScript革新题目与解决方案记

概述

由于本次革新的项目为一个经由过程NPM举行宣布的基本效劳包,因而本次采纳TypeScript举行革新的目的是移除Babel百口桶,减小包体积,同时增增强范例束缚从而防止今后开辟时能够的题目。

本次革新运用的是TypeScript v2.9.2,采纳Webpack v4.16.0举行打包编译。开辟东西运用的是VSCode,运用中文语言包。预期目的是直接将TypeScript代码经由过程loader直接编译为ES5的代码。

本文中触及的题目有部份是TypeScript设置和运用的题目,也有部份是VSCode自身设置相干题目。

革新题目纪录与剖析

VSCode相干

“没法找到相干模块”报错

在项目中,假如我们运用了webpack.alias,能够会提醒找不到模块。

详细毛病以下:

终端编译报错:TS2307: Cannot find module '_utils/index'.
编辑器报错:[ts]找不到模块“_utils/index”。

这是由于编辑器没法读取对应的别号信息致使的。

此时我们须要搜检对应的模块是不是存在。假如确认模块存在,且终端编译编译时不报错,而只是编辑器报错,则是由于编辑器没法读取webpack设置,我们须要增添别的的设置。

处理要领:除了设置webpack.alias,还须要设置相对应的tsconfig.json,详细设置以下所示:

"compilerOptions": {
    "baseUrl": ".",
    "paths": {
        "_util/*": [
            "src/core/utils/*"
        ]
    }
}

注:假如设置了tsconfig.json今后照样报错的话,须要重启下VSCode,猜想是由于VSCode只在项目加载时读取相干设置信息。在JavaScript项目中的jsconfig.json同理。

TypeScript相干

对象属性赋值报错

在JavaScript中,我们常常会声明一个空对象,然后再给这个属性举行赋值。然则这个操纵放在TypeScript中是会发作报错的:

let a = {};

a.b = 1;
// 终端编译报错:TS2339: Property 'b' does not exist on type '{}'.
// 编辑器报错:[ts] 范例“{}”上不存在属性“b”。

这是由于TypeScript不允许增添没有声明的属性。

因而,我们有两个要领来处理这个报错:

  1. 在对象中增添属性定义(引荐)。详细体式格局为:let a = {b: void 0};。这个要领能够从根本上处理当前题目,也能够防止对象被随便赋值的题目。
  2. 在对象中增加范例定义(引荐)。详细体式格局为以下:

   [propName: string]: any

};
let a: obj = {};

a.a = 1;


如许也能够防止报错题目,而且不引入全对象any状况。

3. 给`a`对象增添any属性(应急)。详细体式格局为:`let a: any = {};`。这个要领能够让TypeScript范例搜检时疏忽这个对象,从而编译经由过程不报错。这个要领适用于大批旧代码革新的状况。

### Window对象属性赋值报错

与上一个状况相似,我们给一个对象中赋值一个不存在的属性,会涌现编辑器和编译报错:

window.a = 1;
// 终端编译报错:TS2339: Property ‘a’ does not exist on type ‘Window’.
// 编辑器报错:[ts] 范例“Window”上不存在属性“a”。


这也是由于TypeScript不允许增添没有声明的属性致使的。

由于我们没有要领声明windows属性的值(或许说很难题),因而我们须要经由过程下面这一种体式格局来处理:

1. 我们在windows运用时增添一个范例转换,即`(window as any).a = 1;`。如许就能够保证编辑器和编译时不会失足。不过该要领只发起用于旧项目革新,我们照样要只管防止在window对象上面增添属性,应当经由过程一个全局的数据管理器来举行数据存取。

### ES2015 Object新增的原型链上的要领报错

在项目中,运用到了一些Object原型链上面的一些ES2015新增的要领,如`Object.assign`和`Object.values`等,此时编译会失利,同时VSCode会提醒报错:

终端编译报错:TS2339: Property ‘assign’ does not exist on type ‘ObjectConstructor’.
编辑器报错:[ts] 范例“ObjectConstructor”上不存在属性“assign”。


这是由于我们在`tsconfig.json`中指定的`target`是ES5,而TypeScript并没有相干的polyfill,因而我们没法运用ES2015中新增的要领。

经由过程以上剖析,我们能够运用以下要领处理:

1. 能够运用lodash东西集合的相干要领,装置时须要装置`lodash.assign`和`@types/lodash.assign`。而且`lodash.assign`是一个CMD范例的包,须要经由过程`import _assign = require('lodash.assing');`体式格局引入。

2. 我们能够运用rest写法,比方`let a = {...b};`,也能够到达一级浅拷贝的结果,详细结果以下:

   ![image.png](https://user-gold-cdn.xitu.io/2018/7/24/164cb3c419a9f512?w=245&h=152&f=png&s=11291)

### ES2015新增的数据结构Map初始化报错

将ES2015的代码革新成为TypeScript代码时,假如你运用了ES2015新增的Map范例,那在编辑器照样终端编译中编译时都邑报错:

终端编译报错:TS2693: ‘Map’ only refers to a type, but is being used as a value here.
编辑器报错报错:[ts] “Map”仅示意范例,但在此处却作为值运用。


这是由于TypeScript并没有供应相干的数据范例,也没有对应的polyfill。

因而,我们处理这个题目的思绪有三种:

1. 将`tsconfig.json`设置中的`target`属性改成`es6`,即输出相符ES2015范例的代码。由于ES2015存在全局的Promise对象,因而编译和编辑器都不会报错。该要领长处为设置简朴,无需修改代码,瑕玷为须要高等浏览器的支撑或许Babel百口桶的支撑。
2. 舍弃Map范例,改用Object举行替代。这类革新比较费时辛苦,适用于工作量较小和不愿意引入其他文件的场景。
3. 自行完成或许装置一个Map包。这类要领革新本钱较小,瑕玷就是会引入分外的代码或许包,而且代码效力没法保证。比方`ts-map`和`typescript-map`,这两个包的查找效力都是o(n),低于原生范例的Map。因而引荐本身运用Object完成一个简朴的Map,详细完成体式格局能够去网上找相干的Map道理剖析与实践(大抵道理为运用多个Object,存储差别范例元素时运用差别容器,防止范例转换题目)。

### ES2015新增的Promise运用报错

将ES2015的代码革新成为TypeScript代码时,假如你运用了ES2015的新增的Promise范例,那在编辑器照样终端编译编译时都邑报错:

终端编译报错: TS2693: ‘Promise’ only refers to a type, but is being used as a value here.
编辑器报错:[ts] “Promise”仅示意范例,但在此处却作为值运用。


这是由于TypeScript并没有供应Promise数据范例,也没有对应的polyfill。

因而,我们处理这个题目的思绪依然有三种:

1. 将`tsconfig.json`设置文件设置中的`target`属性改成`es6`,即输出相符ES2015范例的代码。由于ES2015存在全局的Promise对象,因而编译和编辑器都不会报错。该要领长处为设置简朴,无需修改代码,瑕玷为须要高等浏览器的支撑或许Babel百口桶的支撑。

2. 引入一个Promise库,如bluebird等比较着名的Promise库。在装置bluebird时须要同时装置@types/bluebird声明文件。瑕玷就是引入的Promise库较大,而且假如你的库作为一个基本库时,能够会与其他的挪用方的Promise库发生争执。

3. 在`tsconfig.json`设置文件中增添lib。此要领的道理是让TypeScript编译时援用外部的Promise对象,因而在编译时不会报错。此体式格局长处是不会引入任何其他代码,然则瑕玷是肯定要保证在援用此库的前提下,肯定存在Promise对象。详细设置以下:

“compilerOptions”: {

   "lib": ["es2015.promise"]

}


### SetTimeout运用报错

将ES2015代码革新成TypeScript代码时,假如运用了setTimeout和setInterval函数时,能够会涌现没法找到该函数的报错:

终端编译报错:TS2304: Cannot find name ‘setTimeout’.
编辑器报错:[ts] 找不到称号“setTimeout”。


这是由于编辑器和编译时不晓得当前代码运转环境致使的。

因而,我们处理这个题目的思绪有两种:

1. 在`tsconfig.json`设置文件中增添lib。让TypeScript能够晓得当前的代码容器。详细示例以下:

“compilerOptions”: {

   "lib": ["dom"]

}


2. 装置`@types/node`。该要领适用于node环境下或许采纳webpack打包时能够引入node代码。该要领直接经由过程`npm install @types/node`即可装置完成,处理报错题目。

### 模块援用和导出报错

在ES2015的代码中,我们能够经由过程`@babel/plugin-proposal-export-default-from`插件来直接导出引入的文件,详细示例以下:

export Session from ‘./session’; // 报错
export * from ‘_models/read-item’; // 不报错


而在TypeScript中,这类写法是会报错的:

终端编译报错:TS1128: Declaration or statement expected.
编辑器报错:[ts] 应为声明或语句。


这是由于二者的模块语法不一样致使的。

因而,我们处理这个题目只须要用下面这一种要领:

1. 将上面的`export from`的语法略加调解来适配TypeScript语法。详细革新以下:

export {default as Session} from ‘_models/session’; //调解后不报错
export * from ‘_models/read-item’;// 之前不报错不须要调解


### 泛型定义

我们在项目中常常会碰到这类状况,我们须要保证传入的属性范例的同时,还须要保证其与某个函数的参数一致,如:

interface props {

value: number | string, 
onChange: (v: string | number) => void // 参数范例值须要与value一致

}


为了处理这个题目,我们须要用到泛型定义:

interface Props<T extends string | number> {

value: T,
onChange: (v: T) => void

}


此时,当value的范例肯定时,参数的范例也就变得和value一样肯定了。

## 模块援用

当我们运用TypeScript时,常常会涌现援用其他模块以至是JavaScript其他包的状况。在TypeScript中,有多重差别的导出体式格局,差别的导出体式格局也对应着差别的援用体式格局。

现在我在项目革新中,碰到的模块有这么几种体式格局:

1. CMD范例。
2. ES2015 Module范例。

而关于这几种模块,我们也有差别的导入体式格局:

import _assign = require(‘lodash.assign’); //CMD范例
import constant from ‘./constant’; // ES2015 Module范例


假如你引入的文件是一个非TypeScript而是JavaScript文件时,你能够还须要增添声明文件。我们能够经由过程以下要领来增加声明文件:

1. 增添@types文件。这个体式格局针关于一些比较着名的类库能够运用此要领。

2. 在.d.ts文件中增添声明,这个声明全局有用。详细体式格局以下:

declare module ‘promiz’;


关于JSON文件,你也须要采纳这类声明体式格局,详细体式格局以下:

declare module “*.json” {

   const value: any;
   export const version: string;
   export default value;

}


经由过程以上要领,我们就能够应对差别模块的范例和差别范例的文件。

## TypeScript部分替代

在举行重构革新的时刻,我们在最最先能够只能逐一模块举行替代。我们须要新的TypeScript文件和旧的JavaScript文件能够和平共存举行编译运转。

针对这类需求,我们只须要在webpack编译的loader中增添相干ts文件的设置,而且在extension中增添`.ts`后缀的支撑。相干设置以下:

{

module: {
    rules: [
        {
            test: /ts$/,
            use: [{
                loader: 'ts-loader',
                options: {
                    silent: process.env.env === 'production' ? true : false
                }
            }]
        }
    ]
},
extensions: ['.ts', '.js']

}


然后,我们只须要在JavaScript中文件引入时,带上`.ts`后缀即可,以下例所示:

// 本人之前运用的是CMD范例,因而引入ES2015模块须要接见default
var EventEmitter = require(‘eventemitter3’);
var Session = require(‘./session.ts’).default;


如许,我们就能够逐渐的举行模块替代和革新,而不须要举行大规模的文件替代和更名。

# 总结
    原文作者:hjava
    原文地址: https://segmentfault.com/a/1190000015747038
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞