在React旧项目中安装并使用TypeScript的实践
前言
本篇文章默认您大概了解什么是typescript,主要讲解如何在react旧项目中安装并使用typescript。
写这个的目的主要是网上关于typescript这块的讲解虽然很多,但都是一些语法概念或者简单例子,真正改造一个react旧项目使用typescript的文章很少。
所以在这里记录下改造一个react项目的实践。
博客内容部分参照 typescript中文网,这个网站有官方文档的中文版。
安装typescript及相关库
对于集成了typescript的脚手架可以略过这一步,这里主要讲一下如何将typescript集成到一个react脚手架中。
首先执行
npm install --save @types/react @types/react-dom
这一步主要是为了获取react和react-dom的声明文件,因为并不是所有的库都有typescript的声明文件,所以通过运行
npm install --save @types/库名字
的方式来获取typescript的声明文件。
只有获取了声明文件,才能实现对这个库的类型检查。
如果你使用了一些其它的没有声明文件的库,那么可能也需要这么做。
然后运行命令:
npm install --save-dev typescript awesome-typescript-loader source-map-loader
这一步,我们安装了typescript、awesome-typescript-loader和source-map-loader。
awesome-typescript-loader可以让webpack使用typescript的标准配置文件tsconfig.json编译typescript代码。
source-map-loader使用typescript输出的sourcemap文件来告诉webpack何时生成自己的sourcemaps,源码映射,方便调试。
添加typescript配置文件
在项目根目录下创建一个tsconfig.json文件,以下为内容示例:
{ "compileroptions": { "allowsyntheticdefaultimports": true, // 允许从没有设置默认导出的模块中默认导入。这并不影响代码的输出,仅为了类型检查。 "outdir": "./dist/", // 重定向输出目录 "sourcemap": true, // 生成相应的 .map文件 "noimplicitany": true, // 在表达式和声明上有隐含的 any类型时报错。 "module": "esnext", // 模块引入方式 "target": "esnext", // 指定ecmascript目标版本 "moduleresolution": "node", // 决定如何处理模块 "lib": [ "esnext", "dom" ], // 编译过程中需要引入的库文件的列表。 "skiplibcheck": true, //忽略所有库中的声明文件( *.d.ts)的类型检查。 "jsx": "react" // 在 .tsx文件里支持jsx }, "include": [ "./src/**/*", // 这个表示处理根目录的src目录下所有的.ts和.tsx文件,并不是所有文件 ] }
skiplibcheck非常重要,并不是每个库都能通过typescript的检测。
moduleresolution设为node也很重要。如果不这么设置的话,找声明文件的时候typescript不会在node_modules这个文件夹中去找。
更多配置文件信息可以参考:。
配置webpack
这里列出一些typescript需要在webpack中使用的配置。
解析tsx文件的rule配置
示例如下:
module: { rules: [ { test: /\.jsx?$/, exclude: /(node_modules)/, use: { loader: 'babel-loader', options: { presets: ['react', 'env', 'stage-0', 'stage-3'], plugins: [ 'transform-decorators-legacy', ['import', { libraryname: 'antd', style: 'css' }], // `style: true` 会加载 less 文件 ], }, }, }, { test: /\.tsx?$/, loader: "awesome-typescript-loader" } //... ] //... }
其实就只是多加了一行:
{ test: /\.tsx?$/, loader: "awesome-typescript-loader" }
注意这一行需要加在解析jsx的rule下面,因为rule的执行顺序是从下往上的,先解析tsx和ts再解析js和jsx。
当然用
enforce: 'pre'
调整过rule顺序的可以不用在意这一点。
解决使用css-moudule的问题
如果代码中使用了以下这种代码:
import styles from './index.css'
那么很可能报下面的错:
cannot find module './index.css'
解决方法就是在根目录下新建文件一个叫declaration.d.ts的文件,内容为:
declare module '*.css' { const content: any; export default content; }
这行代码就是为所有的css文件进行声明。
同时需要更改一下我们之前的tsconfig.json文件,将这个文件路径放在include中:
"include": [ "./src/**/*", "./declaration.d.ts" ]
这个问题有通过安装一些库来解决的办法,但是会给每个css生成一个声明文件,感觉有点奇怪,我这里自己考虑了一下采用了上面这种方法。
用于省略后缀名的配置
如果你惯于在使用
import chart from './chart/index.jsx'
时省略后缀,即:
import chart from './chart/index'
那么在webpack的resolve中同样需要加入ts和tsx:
resolve: { extensions: [".ts", ".tsx", ".js", ".jsx"] },
引入ant design
实际上这个东西ant design的官网上就有怎么在typescript中使用:在 typescript 中使用。
那么为什么还是要列出来呢?
因为这里要指出,对于已经安装了ant design的旧项目而言(一般都是配了按需加载的吧),在安装配置typescript时上面这个文档基本没有任何用处。
在网上可以搜到的貌似都是文档中的方案,而实际上我们需要做的只是安装ts-import-plugin。
npm i ts-import-plugin --save-dev
然后结合之前的 awesome-typescript-loader ,在webpack中进行如下配置
const tsimportpluginfactory = require('ts-import-plugin') module.exports = { // ... module: { rules: [ { test: /\.tsx?$/, loader: "awesome-typescript-loader", options: { getcustomtransformers: () => ({ before: [tsimportpluginfactory([ { libraryname: 'antd', librarydirectory: 'lib', style: 'css' } ])] }), }, exclude: /node_modules/ } ] }, // ... }
配置完成,修改前的准备
注意,直到这一步,实际上您的项目在编译过程中仍然没有用到typescript。
因为我们这里只会用typescript处理.ts和.tsx后缀的文件,除非在配置中将allowjs设为true。
在使用之前,默认您已经对typescript语法有了了解,不了解可以参考:5分钟上手typescript。
也就是说,经过了上面的这些步骤,您的原有代码在不改动后缀的情况下应该是可以继续用的。
如果要使用typescript,那么新建tsx和ts文件,或者修改原有的文件后缀名即可。
接下来会列出一些典型的修改示例。
函数式组件的修改示例(含children)
import react from 'react' import styles from './index.css' interface computeitemprops { label: string; children: react.reactnode; } function computeitem({ label, children }: computeitemprops) { return <div classname={styles['item']}> <div classname={styles['label']}>{label}:</div> <div classname={styles['content']}>{children}</div> </div> } export default computeitem 这个例子中语法都可以在typescript的官网查到,唯一需要注意的是children的类型是react.reactnode。
class组件修改示例(含函数声明,事件参数的定义)
import react from 'react' import styles from './index.css' interface datasourceitem { dayofgrowth: string; netvaluedate: string; } interface computeprops { fundcode: string; datasource: datasourceitem[]; onchange(value: object): void; } export default class compute extends react.component<computeprops, object> { // 改变基金代码 handlechangefundcode = (e: react.changeevent<htmlinputelement>) => { const fundcode = e.target.value this.props.onchange({ fundcode }) } render() { //... ); } }
这个例子展示如何声明class组件:
react.component<computeprops, object>
语法虽然看起来很怪,但是这就是typescript中的泛型,以前有过c#或者java经验的应该很好理解。
其中,第一个参数定义props的类型,第二个参数定义state的类型。
而react的事件参数类型应该如下定义:
react.changeevent<htmlinputelement>
这里同样使用了泛型,上面表示input的change事件类型。
而组件的prop上有函数类型的定义,这里就不单独列出来了。
这几个例子算是比较典型的typescript与react结合的例子。
处理window上的变量
使用写在window上的全局变量会提示window上不存在这个属性。
为了处理这点,可以在declaration.d.ts这个文件中定义变量:
// 定义window变量 interface window{ r:string[] }
其中r是变量名。
总结
本来还想再多写几个示例的,但是dota2版本更新了,导致我不想继续写下去了,以后如果有时间再更新常用的示例吧。
本篇文章只专注于在react旧项目中安装并集成typescript,尽可能做到不涉及typescript的具体语法与介绍,因为介绍这些东西就不是一篇博客能搞定的了。
文章如有疏漏还请指正,希望能帮助到在typescript面前迟疑的你。
上一篇: 关于vue3编写挂载DOM的插件问题