Typescript助力项目开发:JS切换TS、TS类型定制与思考
TS已经成为可以帮助项目顺利开发的存在了。在上半年笔者就被要求采用TS开发新的项目,并在一些老项目中用TS去改造(因为沟通原因我以为某个远程组件只有TS版本)。在其中也有了一些思考。
首先是目录结构。毫无疑问一个拥有TS的项目必须有像tsconfig.json
、types/typings
这样的说明文件,根据项目需要可能还有tslint.json
这样的配置文件 —— 它们都和 src
目录同级。我们的目录结构大致如下:
如图,有 src 目录组织开发代码、types 目录组织ts类型声明定义(重要‼️)、build 目录作为Web项目的构建产物、package和package-lock两个json文件限制三方包版本、tsconfig ts配置文件、还有 lint 规范文件和我司定制的脚手架配置文件。(如果项目中有node,还会有lib目录作为 Node.js 模块的构建产物)
图中蓝线圈住的就是我们需要注意的(项目改造时需要手动添加的)了。
配置tsconfig
我们的目的在于尽可能少地改动源码、让项目正常运行。所以我们应该尽量宽松地配置 tsconfig。如下所示:
{"compilerOptions": {"module": "es2015","target": "ES5","strict": true,"jsx": "preserve","importHelpers": true,"moduleResolution": "node","experimentalDecorators": true,"esModuleInterop": true,"strictPropertyInitialization": false,"allowSyntheticDefaultImports": true,"forceConsistentCasingInFileNames": false,"emitDecoratorMetadata": true,"isolatedModules": true,"sourceMap": true,"baseUrl": ".","typeRoots": ["node_modules/@types", "./types"],"paths": {"@/*": ["src/*"]},"lib": ["esnext","dom","dom.iterable","scripthost"]},"exclude": ["node_modules","config","build","test"],
}
其中比较重要的是:
- 第4行
target
为ES5
,用来将TS转译为低版本、兼容性好的ES5代码; - 第18行我们把 types 目录添加到类型查找路径,让Typescript可以查找到自定义类型声明,比如为缺少类型声明的第三方模块补齐类型声明;
- 最后的“exclude”是设置Typescript不需要识别的文件(这里你也可以直接用 include);
- 第6行我们把将 jsx 选项设置为 “preserve” 意味着 TypeScript 不应处理JSX;
你甚至可以启用 allowJS: true
,让 js 和 ts 能够混用。
因为Web项目中不会直接使用tsc转译Typescript,所以我们不需要配置 rootDir、outDir,甚至我们可以直接开启 noEmit 配置:
noEmit: true
构建工具集成Typescript
下面以我司的脚手架为例,它和webpack书写结构差不多一致。
首先需要安装typescript依赖:
npm install -D typescript
然后笔者所在组是选择了 webpack loader来加载并转译typescript代码:
npm install -D ts-loader
并在config.js中添加 resolve 和module 规则:
module.exports = ({ userFolder, srcFolder, buildFolder, currentEnv, webpack, webpackDevServer }) => {// 其他配置 ...,webpackConfig: { //webpack中怎么配置,这里面就怎么写resolve: {extensions: [".ts", ".tsx", ".js", ".jsx", ".json"],},module: {rules: [// 其他配置 loader 规则...,{test: /\.tsx?$/,use: [{loader: "ts-loader",options: { transpileOnly: true }}]}],},plugins: [// ...其他配置new require('fork-ts-checker-webpack-plugin')({async: false,tsconfig: '...' // tsconfig.json 文件地址});]// 其他配置...}
};
一个比较好的实践是,我们可以开启 ts-loader 的 transpileOnly 配置,让 ts-loader 在处理 TypeScript 文件时,只转译而不进行静态类型检测,这样就可以提升构建速度了。
不过,这并不意味着构建时静态检测不重要,相反这是保证类型安全的最后一道防线。此时,我们可以通过其他性能更优的插件做静态类型检测。
在最后我们引入了 fork-ts-checker-webpack-plugin
专门对 TypeScript 文件进行构建时静态类型检测。这样,只要出现任何 TypeScript 类型错误,构建就会失败并提示错误信息。
npm install -D fork-ts-checker-webpack-plugin;
实际上,静态类型检测确实会耗费性能和时间,尤其是项目特别庞大的时候,这个损耗会极大地降低开发体验。此时,我们可以根据实际情况优化 Webpack 配置,比如仅在生产构建时开启静态类型检测、开发构建时关闭静态类型检测,这样既可以保证开发体验,也能保证生产构建的安全性。
除此之外,我们还可以用 babel-loader
作为typescript的加载器(注意:版本号必须大于7!)
npm i -D babel-loader; // 确保安装版本 > 7
npm i -D @babel/preset-typescript;
然后,我们在config.js中添加支持 Typescript 的配置:
resolve: {extensions: [".ts", ".tsx", ".js", ".jsx", ".json"]
},
module: {rules: [{test: /\.(js|jsx|ts|tsx)$/,use: ['babel-loader']},// ...其他配置]
},
最后,我们在 babel 配置文件中添加了如下所示的 typescript presets。
{"presets": [//...['@babel/preset-typescript', { allowNamespaces: true }]],// ...其他配置
}
注意:因为每个项目中使用的模板不同,所以 babel 配置项可能在 .babelrc
、babel.config.js
单独的配置文件中或者内置在 package.json
中。
这样,babel-loader 就可以加载并转换 TypeScript 代码了。
需要注意:因为 babel-loader 也是只对 TypeScript 代码做转换,而不进行静态类型检测,所以我们同样需要引入
fork-ts-checker-webpack-plugin 插件做静态类型检测。
解决问题
缺少类型声明
整体的结构引入以后,就需要解决ts文件中的类型错误了。
其中,“某个模块的类型声明文件缺失”可能是遇到概率最大的错误了。比如说我司的 sku 组件。
此时,我们有两种方案:可以直接命令行安装可能存在的类型声明依赖:
npm install -D @types/@vdian/vue-sku;
如果命令执行成功,则说明类型声明存在,并且安装成功,这也意味着我们快速且低成本地解决了一个错误。如果 DefinitelyTyped 上恰好没有定义好的依赖类型声明,那么我们就需要自己解决这个问题了。
首先我们需要频繁使用 declare module 补齐类型声明。然后,我们将各种补齐类型声明的文件统一放在 types 目录中:
declare module "@vdian/vue-sku";
对于全局变量、属性等缺少类型定义的问题,我们也可以使用 declare 或者补充相应的接口类型进行解决:
// 身份全局接口
interface StoreIdentityData {baseVersionStore: boolean, // 判断是否是基础版paidStore: boolean, // 付费商家 - 连锁店 或 商城版mallVersionExpired: boolean, // 付费商家 过期//...createToolEnable: boolean, //是否该店铺能够创建营销工具 如果不指定toolCode,返回truedisableCreateToolReason: string, // 不能创建的原因
}
动态类型
另一类极有可能出现的错误是 JavaScript 动态类型特性造成的。
如下示例第 1~3 行所示,我们习惯先定义一个空对象,再动态添加属性,迁移到 TypeScript 后就会提示一个对象上属性不存在的 ts(2339) 错误 。
const obj = {};
obj.id = 1; // ts(2339)
obj.obj = 22; // ts(2339)
此时,我们需要通过重构代码解决这个问题,具体操作是预先定义完整的对象结构或类型断言。
代码重构后的示例如下:
interface IUserInfo {id: number;name: number;
}
const obj = {} as IUserInfo;
obj.id = 1; // ok
obj.obj = 23; // ok
在第 5 行中,我们使用了类型断言解决了 ts(2339) 错误。
定制工具类型
工具类型的本质就是构造复杂类型的泛型。它接受类型入参,并返回我们需要的东西。
Equal
我们实现一个自定义工具类型 Equal<S, T>
,它可以用来判断入参 S 和 T 是否是相同的类型。如果相同,则返回布尔字面量类型 true,否则返回 false。
首先我们很容易想到,如果 S 是 T 的子类型且 T 也是 S 的子类型,则说明 S 和 T 是相同的类型。
这里,我们需要注意!never 和 any 类型!
type IsAny<T> = 0 extends (1 & T) ? true : false;type Equal<S, T> = IsAny<S> extends true? IsAny<T> extends true? true: false: IsAny<T> extends true? false: [S] extends [T]? [T] extends [S]? true: false: false;type Example1 = Equal<1 | number & {}, number>; // true but false gottype Example2 = Equal<never, never>; // truetype Example4 = Equal<any, any>; // truetype Example3 = Equal<any, number>; // falsetype Example5 = Equal<never, any>; // false
在第 1 行,我们定义了可以区分 any 和其他类型的泛型 IsAny,因为只有 any 和 1 交叉得到的类型(any)是 0 的父类型,所以如果入参是 any 则会返回 true,否则返回 false。
在第 2 ~ 7 行,我们定义了 Equal(首先特殊处理了类型入参 S 和 T 至少有一个是 any 的情况),当 S 和 T 都是 any 才返回 true,否则返回 false。因此,在第 15~17 行,Equal 是可以区分 any 和其他类型的。
在第 8 ~ 12 行,我们通过 []
解除了条件分配类型,所以第 13 ~ 14 行 Equal 可以判断出联合类型 1 | number & {}
和 number、never 和 never 是相同的类型。
在条件判断类型的定义中,将泛型参数使用
[]
括起来,即可阻断条件判断类型的分配,此时,传入参数 T 的类型将被当做一个整体,不再分配。
Merge
接下来我们再基于映射类型将类型入参 A 和 B 合并为一个类型的泛型 Merge<A, B>
,如下示例:
type Merge<A, B> = {[key in keyof A | keyof B]: key extends keyof A? key extends keyof B? A[key] | B[key]: A[key]: key extends keyof B? B[key]: never;};type Merged = Merge<{ id: number; name: string }, { id: string; age: number }>;
在第 2 行,我们限定了返回类型属性 key 为入参 A、B 属性的联合类型。当 key 为 A、B 的同名属性,合并后的属性类型为联合类型 A[key] | B[key]
(第 2 ~ 4 行);当 key 为 A 或者 B 的属性,合并后的属性类型为 A[key]
或者 B[key]
(第 5 ~ 7 行)。
最后,我们在第 10 行使用了 Merge 合并两个接口类型,从而得到了 { id: number | string; name: string; age: number }
。
思考
我仍然认为,ts 强校验失去了 js 弱检查的一些特性。这在开发中可能或多或少的会带来一些困扰。所以笔者比较认同“js 和 ts 混用”的开发模式。目前我认为,在非前后端交互和非业务方间数据传递(微前端)的业务逻辑中,都可以不用 TS 去强校验数据模型。后续看进一步的使用吧~
Typescript助力项目开发:JS切换TS、TS类型定制与思考相关推荐
- Vue.js + Vuex + TypeScript 实战项目开发与项目优化
技术栈: Vue.js + Vuex + TypeScript Todo List: 使用Vue CLI初始化项目 调整初始目录结构 删除初始化的默认文件(对于我们项目是多余的组件components ...
- typescript + react 项目开发体验之起手式
目录 起手式 typescript react一些小姿势 react的状态管理 前言 typescript(以下简称ts) 官推是脚手架 create-react-app 的ts版本,可自行查阅.但是 ...
- (005)RN开发 js jsx ts tsx的区别
背景:在入门RN开发的过程中,发现公司项目代码中,有的项目是全是.js的后缀文件.有的项目全是ts.tsx.js 文件.心中很是疑惑,所以就写个文章记录下自己的解惑的过程. js:是弱类型语言,容易出 ...
- Vue老项目由js转换ts指南
Vue(2.x)老项目由js转换ts指南 其实在网上有不少关于,vue迁入ts的教程,但是很多并不完善,故做此篇 本项目基于 vue-admin-template进行改造 ,它是有ts版本的,因此本文 ...
- 【新项目开发】vue3+ts+elementPlus+ffmpegjs开发纯web端的视频编辑器
新项目开发的流程 当在项目中使用新技术时,我们应该首先进行调研,了解其特点和使用方法.在实现功能时,我们可以采用最简单的方式,而不必过于关注项目的设计和结构.一旦掌握了新技术,我们可以根据其API属性 ...
- 项目总结 - ts中的declare let type: any和对.d.ts文件类型的认识
项目中用的是angular5.0+ionic3.0,由于现在angular现在都是用typescript写的了,虽然说typescript是Javascript的超集,但是在项目中还是会有一些问题,那 ...
- 第三方模块——nodemon是一个命令行工具,用以辅助项目开发、nrm ( npm registry manager ):npm下载地址切换工具
什么是第三方模块 别人写好的.具有特定功能的.我们能直接使用的模块即第三方模块,由于第三方模块通常都是由多个文件组成并且被放置在一个文件夹中,所以又名包. 第三方模块 nodemon nodemon是 ...
- Typescript.中文.接口声明.lib.es5.d.ts
Typescript.中文.接口声明.lib.es5.d.ts jcLee95 的个人博客 邮箱 :291148484@163.com CSDN 主页:https://blog.csdn.net/qq ...
- Vue项目开发中优雅的切换服务端ip
Vue项目开发中优雅的切换服务端ip 在进行Vue开发的时候,需要配置项目对应服务端的ip地址,但如果需要在多个服务端间进行切换,通常的做法是:手动修改vue.config.js配置文件中的服务端ip ...
最新文章
- SQL 查询语句总是先执行 SELECT?你们都错了
- C语言编译过程总结详解
- 什么是C语言中的条件编译?
- 解决报错:java.lang.NoSuchMethodException: com.tangyuan.entity.RicherProduct.<init>()
- 菜鸟关于mvc导出Excel的想法
- Java集合ArrayList的应用
- LeetCode-C#实现-二叉树/二叉搜索树(#98/104/111/230)
- vue学习笔记-3-双向数据绑定
- 【Shawn-Git】gitlub的使用指导(针对六届软件杯)
- 拆解PowerApps - 请假申请 - 4
- 微信公众平台完整开发教程
- 最新宝塔自助建站系统8.0源码
- 视频教程-JavaScript全套课程-JavaScript
- 数学建模中四大模型总结
- (附源码)Springboot网上购物系统 毕业设计 311236
- 服务器可视化显示空间,云服务器可视化面板
- Druid连接池参考配置和说明
- h5应用 vue 钉钉_钉钉企业内部H5微应用开发
- 魔兽假设把mysql卸了_【原创】假设在有最后一次全库备份之后,你误删除了一张表,请使用备份+归档来将数据库...
- c语言基础编程题文库,C语言基础编程题资料.doc