内容简介:现在越来越多的项目放弃了javascript,而选择拥抱了typescript,就比如我们熟知的ant-design就是其中之一。面对越来越火的typescript,我们公司今年也逐渐开始拥抱typescript。至于为什么要使用typescript?本文不做深入探讨,对这方面有兴趣的小伙伴们可以去看一下这篇文章:这篇文章比较全面地介绍了TypeScript,并且和Javascript做了一个对比。看完上面这篇文章,你会对TypeScript有一个比较深入的认识,另外在TypeScript和Javascri
抛转引用
现在越来越多的项目放弃了javascript,而选择拥抱了typescript,就比如我们熟知的ant-design就是其中之一。面对越来越火的typescript,我们公司今年也逐渐开始拥抱typescript。至于为什么要使用typescript?本文不做深入探讨,对这方面有兴趣的小伙伴们可以去看一下这篇文章:
这篇文章比较全面地介绍了TypeScript,并且和Javascript做了一个对比。看完上面这篇文章,你会对TypeScript有一个比较深入的认识,另外在TypeScript和Javascript的取舍上,可以拿捏得更好。
开始迁移
在开始迁移之前,我要说点题外话,本篇文章仅是记录我在迁移过程中遇到的问题以及我是如何解决的,并不会涉及typescript的教学。所以大家在阅读本篇文章之前,一定要对typescript有一个基础的认识,不然你读起来会非常费力。
环境调整
由于Typescript是Javascript的超集,它的很多语法浏览器是不能识别的,因此它不能直接运行在浏览器上,需要将其编译成JavaScript才能运行在浏览器上,这点跟ES6需要经过babel编译才能支持更多低版本的浏览器是一个道理。
tsconfig.json
首先我们得装一个typescript,这就跟我们在用babel前需要先装一个babel-core是一个道理。
yarn global add typescript
这条命令是将typescript安装在全局,其实我个人建议是装在项目目录下的,因为每个项目的typescript版本是不完全一样的,装在全局容易因为版本不同而出现问题。但是后面我要执行tsc命令,所以我装在了全局。最好的情况就是全局和项目都装一个,但是如果你把tsc命令放在package.json中的script中去用的话,那么在项目里装就够了。接下来我们执行如下命令生成tsconfig.json,这玩意就跟.babelrc是一个性质的。
tsc --init
执行完之后,你的项目根目录下便会有一个tsconfig.json这么一个东西,但是里面会有很多注释,我们先不用管他的。
webpack
安装ts-loader用于处理ts和tsx文件,类似于babel-loader。
yarn add ts-loader -D
相应的webpack需要加上ts的loader规则:
module.exports = { //省略部分代码... module: { rules: [ { test:/\.tsx?$/, loader:'ts-loader' } //省略部分代码... ] } //...省略部分代码 }
之前用javascript的时候,可能有人不使用.jsx文件,整个项目都是用的.js文件,webapck里面甚至都不配.jsx的规则。但是在typescript项目中想要全部使用.ts文件这就行不通了,会报错,所以当用到了jsx的用法的时候,还是得乖乖用.tsx文件,因此这里我加入了.tsx的规则。
删除babel
关于babel这块,网上有不少人是选择留着的,理由很简单,说是为了防止以后会使用到JavaScript,但是我个人觉得是没有必要留着babel。因为我们整个项目里面基本上只有使用第三方包的时候才会用到javascript,而这些第三方包基本上都是已经编译成了es5的代码了,不需要babel再去处理一下。而业务逻辑里面用javascript更是不太可能了,因为这便失去了使用typescript的意义。综上所述,我个人觉得是要删除babel相关的东西,降低项目复杂度。但是有一个例外情况:。
那就是你用了某些babel插件,而这些插件的功能typescript无法提供,那你可以保留babel,并且与typescript结合。
文件名调整
整个src目下所有的.js结尾的文件都要修改文件名,使用到tsx语法的就改成.tsx文件,未使用的就改成.ts文件,这块工作量比较大,会比较头疼。另外改完之后文件肯定会有很多标红的地方,不要急着去改它,后面我们分类统一去改。
解决报错
webpack入口文件找不到
由于我们在做文件名调整的时候,把main.js改成main.tsx,因此webpack的入口文件要改成main.tsx。
module.exports = { //省略部分代码... entry: { app: './src/main.tsx' }, //省略部分代码... }
提示不能使用jsx的语法
这个解决很简单,去tsconfig配置一下即可。
{ "compilerOptions":{ "jsx": "react" } }
jsx这个配置项有三个值可选择,分别是" preserve "," react-native "和" react "。在 preserve 和 react-native 模式下生成代码中会保留JSX以供后续的转换操作使用(比如:Babel)。另外, preserve 输出文件会带有.jsx扩展名,而 react-native 是.js拓展名。 react 模式会生成React.createElement,在使用前不需要再进行转换操作了,输出文件的扩展名为.js。
模式 | 输入 | 输出 | 输出文件扩展名 |
---|---|---|---|
preserve | <div /> | <div /> | .jsx |
react | <div /> | React.createElement("div") | .js |
react-native | <div /> | <div /> | .js |
webpack里面配置的alias无法解析
module.exports = { //省略部分代码... resolve: { alias:{ '@':path.join(__dirname,'../src') } //省略部分代码... }, //省略部分代码... }
这里需要我们额外在tsconfig.json配置一下。
{ "compilerOptions":{ "baseUrl": ".", "paths": { "@/*":["./src/*"] } } }
具体如何配置,请看typescript的文档,我就不展开介绍了,但是要注意的是baseUrl和paths一定要配合使用。
https://www.tslang.cn/docs/ha...
无法自动添加拓展名而导致找不到对应的模块
原先我们在webpack里是这么配置的:
module.exports = { //省略部分代码... resolve: { //省略部分代码... extensions: ['.js', '.jsx', '.json'] }, //省略部分代码... }
但是我们项目里所有.js和.jsx的文件都改成了.ts和.tsx文件,因此配置需要调整。
{ //省略部分代码... resolve: { //省略部分代码... extensions: ['.ts','.tsx','.js', '.jsx', '.json'] }, //省略部分代码... }
Could not find a declaration file for module '**'
这个比较简单,它提示找不到哪个模块的声明文件,你就装个哪个模块的就好了,安装格式如下:
yarn add @types/**
举个:chestnut:,如果提示Could not find a declaration file for module 'react',那你应该执行如下命令:
yarn add @types/react
这个仅限于第三方包,如果是项目自己的模块提示缺少声明文件,那就需要你自己写对应的声明文件了,比如你在window这个全局对象挂载了一个对象,就需要做一下声明,否则就会报错。至于具体怎么写,这得看typescript的文档,这里就不展开说明了。
https://www.tslang.cn/docs/ha...
Cannot find type definition file for '**'
这些并没有在我们的业务代码里直接用到,而是第三方包用到的,遇到这种情况,需要检查一下tsconfig.json中的typeRoots这个配置项有没有配置错误。一般来说是不用配置typeRoots,但是如果需要加入额外的声明文件路径,就需要对其进行修改。typeRoots是有一个默认值,有人会误以为这个默认值是“["node_modules"]”,因此会有人这样配置:
{ "compilerOptions":{ "typeRoots":["node_modules",...,"./src/types"] } }
实际上typeRoots的默认值“["@types"]”,所有可见的"@types"包都会在编辑过程中被加载进来,比如“./node_modules/@types/”,“../node_modules/@types/”和“../../node_modules/@types/”等等都会被加载进来。所以遇到这种问题,你的配置应该改成:
{ "compilerOptions":{ "typeRoots":["@types",...,"./src/types"] } }
在实际项目中,@types基本上存在于根目录下的node_modules下,因此这里你可以改成这样:
{ "compilerOptions":{ "typeRoots":["node_modules/@types",...,"./src/types"] } }
不支持decorators(装饰器)
typescript默认是关闭实验性的ES装饰器,所以需要在tsconfig.json中开启。
{ "compilerOptions":{ "experimentalDecorators":true } }
Module '**' has no default export
提示模块代码里没有“export
default”,而你却用“import from ”这种默认导入的形式。对于这个问题,我们需要把tsconfig.json配置项“allowSyntheticDefaultImports”设置为true。允许从没有设置默认导出的模块中默认导入。不过不必担心会对代码产生什么影响,这个仅仅为了类型检查。
{ "compilerOptions":{ "allowSyntheticDefaultImports":true } }
当然你也可以使用“esModuleInterop”这个配置项,将其设置为true,根据“allowSyntheticDefaultImports”的默认值,如下:
module === "system" or --esModuleInterop
对于“esModuleInterop”这个配置项的作用主要有两点:
- 提供__importStar和__importDefault两个helper来兼容babel生态
- 开启allowSyntheticDefaultImports
对于“esModuleInterop”和“allowSyntheticDefaultImports”选用上,如果需要typescript结合babel,毫无疑问选“esModuleInterop”,否则的话,个人习惯选用“allowSyntheticDefaultImports”,比较喜欢需要啥用啥。当然“esModuleInterop”是最保险的选项,如果对此拿捏不准的话,那就乖乖地用“esModuleInterop”。
无法识别document和window这种全局对象
遇到这种情况,需要我们在tsconfig.json中lib这个配置项加入一个dom库,如下:
{ "compilerOptions":{ "lib":[ "DOM", ..., "ESNext" ] } }
文件中的标红问题
关于这个问题,我们需要分两种情况来考虑,第一种是.ts的文件,第二种是.tsx文件。下面来看一下具体是哪些注意的点(Ps:以下提到的注意的点并不能完全解决文件中标红的问题,但是可以解决大部分标红的问题):
第一种:.ts文件
这种文件在你的项目比较少,比较容易处理,根据实际情况去加一下类型限制,没有特别需要讲的。
第二种:.tsx文件
这种情况都是react组件了,而react组件又分为无状态组件和有状态组件组件,所以我们分开来看。
无状态组件
对于无状态组件,首先得限制他是一个FunctionComponent(函数组件),其次限制其props类型。举个:chestnut::
import React, { FunctionComponent, ReactElement } from 'react'; import {LoadingComponentProps} from 'react-loadable'; import './style.scss'; interface LoadingProps extends LoadingComponentProps{ loading:boolean, children?:ReactElement } const Loading:FunctionComponent<LoadingProps> = ({loading=true,children})=>{ return ( loading?<div className="comp-loading"> <div className="item-1"></div> <div className="item-2"></div> <div className="item-3"></div> <div className="item-4"></div> <div className="item-5"></div> </div>:children ) } export default Loading;
其中你要是觉得FunctionComponent这个名字比较长,你可以选择用类型别名“SFC”或者“FC”。
有状态组件
对于有状态组件,主要注意三点:
- props和state都要做类型限制
- state用readonly限制“this.state=**”的操作
- 对event对象做类型限制
import React,{MouseEvent} from "react"; interface TeachersProps{ user:User } interface TeachersState{ pageNo:number, pageSize:number, total:number, teacherList:{ id: number, name: string, age: number, sex: number, tel: string, email: string }[] } export default class Teachers extends React.PureComponent<TeachersProps,TeachersState> { readonly state = { pageNo:1, pageSize:20, total:0, userList:[] } handleClick=(e:MouseEvent<HTMLDivElement>)=>{ console.log(e.target); } //...省略部分代码 render(){ return <div onClick={this.handleClick}>点击我</div> } }
实际项目里,组件的state可能会有很多值,如果按照我们上面这种方式去写会比较麻烦,所以可以考虑一下下面这个简便写法:
import React,{MouseEvent} from "react"; interface TeachersProps{ user:User } const initialState = { pageNo:1, pageSize:20, total:0, teacherList:[] } type TeachersState = Readonly<typeof initialState> export default class Teachers extends React.PureComponent<TeachersProps,TeachersState> { readonly state = initialState handleClick=(e:MouseEvent<HTMLDivElement>)=>{ console.log(e.target); } //...省略部分代码 render(){ return <div onClick={this.handleClick}>点击我</div> } }
这种写法会简便很多代码,但是类型限制效果上明显不如第一种,所以这种方法仅仅作为参考,可根据实际情况去选择。
Ant Design丢失样式文件
当我们把项目启动起来之后,某些同学的页面可能会出现样式丢失的情况,如下:
打开控制台,我们发现Ant Design的类名都找不到对应的样式:
出现这种情况是因为我们把babel删除之后,用来按需记载组建样式文件的babel插件 babel-plugin-import 也随着丢失了。不过typescript社区有一个 babel-plugin-import 的Typescript版本,叫做“ ts-import-plugin ”,我们先来安装一下:
yarn add ts-import-plugin -D
这个插件需要结合ts-loader使用,所以webpack配置中需要做如下调整:
const tsImportPluginFactory = require('ts-import-plugin') module.exports = { //省略部分代码... module:{ rules:[{ test: /\.tsx?$/, loader: "ts-loader", options: { transpileOnly: true,//(可选) getCustomTransformers: () => ({ before: [ tsImportPluginFactory({ libraryDirectory: 'es', libraryName: 'antd', style: true }) ] }) } }] } //省略部分代码... }
这里要注意一下 transpileOnly: true 这个配置,这是个可选配置,我建议是只有大项目中才加这个配置,小项目就没有必要了。由于typescript的语义检查器会在每次编译的时候检查所有文件,因此当项目很大的时候,编译时间会很长。解决这个问题的最简单的方法就是用 transpileOnly: true 这个配置去关闭typescript的语义检查,但是这样做的代价就是失去了类型检查以及声明文件的导出,所以除非在大项目中为了提升编译效率,否则不建议加这个配置。
配置完成之后,你的浏览器控制台可能会报出类似下面这个错误:
出现这个原因是因为你的typescript配置文件tsconfig.json中的module参数设置不对,两种情况会导致这个问题:
- module设置成了“commonjs”
- target设置"ES5"但是并未设置module(当target不为“ES6”时,module默认为“commonjs”)
解决这个办法就是把module设置为“ESNEXT”便可解决这个问题。
{ "compilerOptions":{ "module":"ESNext" } }
可能会有小伙们说设置成“ES6”或者“ES2015”也是可以的,至于我为什么选择“ESNEXT”而不是“ES6”或者“ES2015”,主要原因是设置成“ES6”或者“ES2015”之后,就不能动态导入了,因为项目使用了react-loadable这个包,要是设置成“ES6”或者“ES2015”的话,会报如下这个错误:
typescript提示我们需要设置成“commonjs”或者“ESNext”才可动态导入,所以保险起见,我是建议大家设置成ESNext。完成之后我们的页面就可以正常显示了。
说到module参数,这里要再多提一嘴说一下moduleResolution这个参数,它决定着typescript如何处理模块。当我们把module设置成“ESNext”时,是可以不用管moduleResolution这个参数,但是大家项目里要是设置成“ES6”的话,那就要设置一下了。先看一下moduleResolution默认规则:
module === "AMD" or "System" or "ES6" ? "Classic" : "Node"
当我们module设置为“ES6”时,此时moduleResolution默认是“Classic”,而我们需要的是“Node”。为什么要选择“node”,主要是因为node的模块解析规则更符合我们要求,解析速度会更快,至于详情的介绍,可以参考Typescript的文档。
https://www.tslang.cn/docs/ha...
同样为了保险起见,我是建议大家强行将moduleResolution设置为“node”。
总结
以上就是我自己在迁移过程中遇到的问题,可能无法覆盖大家在迁移过程中所遇到的问题,如果出现我上面没有涉及的报错,欢迎大家在评论区告诉我,我会尽可能地完善这篇文章。最后再强调一下,本篇文章仅仅只是介绍了我个人在迁移至typescript的经验总结,并未完全覆盖tsconfig.json的所有配置项,文章未涉及到的配置项,还需大家多花点时间看看typescript的文档。最后附上我已迁移到typescript的项目的地址:
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- [译] 波兰在线购物网站 Allegro.pl 迁移到服务网格经验分享
- 1年将30PB数据迁移到Spark,eBay的经验有何可借鉴之处?
- SaaS管理系统开发经验------Dva(Redux)实战经验分享
- 20年程序员分享经验:20条编程经验,一定要看完
- 银行核心海量数据无损迁移:TDSQL数据库多源异构迁移方案
- 再无需从头训练迁移学习模型!亚马逊开源迁移学习数据库 Xfer
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
创业时, 我们在知乎聊什么?
知乎 / 中信出版社 / 2014-1 / 42.00元
★前所未有的互联网出版实验,500万知友亲手甄选内容,知乎三年创业问答精华大集结 ★史上最真诚创业书,用互联网思维讲透创业的逻辑 ★在知乎,最强大互联网创业群体真实分享创业路上的荣耀与隐忧 ★从Idea到步入正轨,创业公司如何招人、做技术、卖产品、找融资、建团队、处理法务? 他们在知乎聊创业: 创新工场创始人李开复 天使投资人 徐小平 小米科技创始人 雷军......一起来看看 《创业时, 我们在知乎聊什么?》 这本书的介绍吧!