Open yaoningvital opened 4 years ago
示例:https://github.com/Microsoft/TypeScript-React-Conversion-Guide#typescript-react-conversion-guide
示例中是一个九宫格的小游戏,完全由JS编写,按照下面的步骤操作完成后,将变成一个完全由TypeScript 编写的小游戏。
在一个项目中添加TypeScript 可以分解为以下两个步骤: 1、在构建流程中添加 TypeScript 编译器(TypeScript Compiler)。 2、将 JS 文件转为 TS 文件。
在动手之前,首先要充分地理解当前这个项目,了解它的项目结构、代码逻辑、用的什么JS编译器、什么打包工具等等。
命令行切换到TicTacToe_JS 目录,先安装 package.json 中定义的依赖:
npm install
npm install --save-dev typescript ts-loader source-map-loader
1、ts-loader 是一个webpack插件,作用是将 TypeScript 文件编译为 JavaScript 文件。就像Babel的 babel-loader 一样。还有其他可选的 loader 也可以完成这个工作(比如 awesome-typescript-loader)。 2、source-map-loader 为调试时增加 source-map 支持。
这个项目用到了 react 、react-dom。
npm install --save-dev @types/react @types/react-dom
在项目根目录下添加 tsconfig.json 文件,来配置TypeScript。添加以下内容:
// tsconfig.json { "compilerOptions": { "outDir": "./dist/", // path to output directory "sourceMap": true, // allow sourcemap support "strictNullChecks": true, // enable strict null checks as a best practice "module": "es6", // specify module code generation "jsx": "react", // use typescript to transpile jsx to js "target": "es5", // specify ECMAScript target version "allowJs": true // allow a partial TypeScript and JavaScript codebase }, "include": [ "./src/" ] }
要在构建流程中添加对TypeScript的编译,需要修改webpack配置文件 webpack.config.js 。
这里假设用的构建工具是 webpack ,只说明怎么修改 webpack.config.js 。如果你用的是其他的构建工具,比如 Gulp,方法是一致的,就是将构建流程中的 Babel构建步骤 用 TypeScript 取代。因为TypeScript也提供了 将JS编译为低版本JS 和 编译JSX 的功能,而且在大多数情况下,编译时间更短。当然也可以保留Babel,可以在构建流程中添加 TypeScript 的编译,然后将输出传入 Babel编译器。
总的来说,我们需要对 webpack.config.js 文件进行如下方面的修改: 1、对文件扩展名的识别要添加对 .ts、.tsx的识别。 2、将 babel-loader 取代为 ts-loader。 3、增加 source-map 支持。
.ts
.tsx
修改后的 webpack.config.js 文件内容如下:
module.exports = { // change to .tsx if necessary entry: './src/app.jsx', output: { filename: './dist/bundle.js' }, resolve: { // changed from extensions: [".js", ".jsx"] extensions: [".ts", ".tsx", ".js", ".jsx"] }, module: { rules: [ // changed from { test: /\.jsx?$/, use: { loader: 'babel-loader' }, exclude: /node_modules/ }, { test: /\.(t|j)sx?$/, use: { loader: 'ts-loader' }, exclude: /node_modules/ }, // addition - add source-map support { enforce: "pre", test: /\.js$/, exclude: /node_modules/, loader: "source-map-loader" } ] }, externals: { "react": "React", "react-dom": "ReactDOM", }, // addition - add source-map support devtool: "source-map" }
如果不再需要 babel 了,可以删除 .babelrc 文件 和 package.json 中所有对 babel 的依赖。
现在,你已经完成了在构建流程中添加TypeScript编译器的操作。可以用下面的命令打包,并且在浏览器中打开 index.html 文件查看页面。
npx webpack
在这个部分,我们将按照下面的步骤逐步地完成 :
以 gameStateBar.jsx 文件为例。
gameStateBar.jsx
第一步是将 gameStateBar.jsx 重命名为 gameStateBar.tsx。改变名字之后,在编辑器中能看到类型报错。
gameStateBar.tsx
需要将第一行的:
import React from 'react'
改为:
`import * as React from 'react'
这是因为原来用Babel编译时,Babel将 CommonJS 模块中的 module.exports当做默认导出的对象,但是 TypeScript不是这样处理的。
module.exports
将第三行的 :
export class GameStateBar extends React.Component {
export class GameStateBar extends React.Component<any, any> {
这是因为,React.Component 的类型定义使用了 泛型,需要提供这个组件实例的 props 属性 和 state 属性对象的类型。 我们在这里将 这两个对象(props和state)的类型设置为 any,表示可以传递任何类型的值。 any 对真正的类型检查 并不会起到什么作用,但是满足了我们第一步的目标:使用最少的步骤来通过TypeScript编译器的类型检查。
到这里,这个文件已经能通过TypeScript编译器的类型检查了。使用npx webpack命令打包,然后用浏览器打开 index.html 文件查看。
提供越多的类型给TypeScript,就能得到越强大的类型检查的功能。
作为一个最佳实践,我们推荐对所有的声明提供类型。
接下来,还是以 gameStateBar 组件举例。
gameStateBar
对于任何的 React.Component ,应当给出 props 和 state 对象的 合适的类型定义。 gameStateBar 组件没有properties,所以我们可以使用 {}当类型。
{}
组件的 state 对象 只包含一个属性:gameState,它表示的是游戏的状态,可能的值有:
"" | "X Wins!" | "O Wins!" | "Draw"
因为 gameState 只有确定的已知的字符串字面量值,所以可以使用“字符串字面量类型”来定义这个接口,如下:
interface GameStateBarState { gameState: "" | "X Wins!" | "O Wins!" | "Draw"; }
这个接口定义需要写在类声明的前面。
使用这个定义的接口, GameStateBar 的声明修改为:
GameStateBar
export class GameStateBar extends React.Component<{}, GameStateBarState> {...}
接下来还可以做这些事情:
// 给 函数的参数 添加类型声明 // add types for params constructor(props: {}) {...} handleGameStateChange(e: CustomEvent) {...} handleRestart(e: Event) {...}
// 给 箭头函数的参数 添加类型声明 // add types in arrow functions componentDidMount() { window.addEventListener("gameStateChange", (e: CustomEvent) => this.handleGameStateChange(e)); window.addEventListener("restart", (e: CustomEvent) => this.handleRestart(e)); } componentWillUnmount() { window.removeEventListener("gameStateChange", (e: CustomEvent) => this.handleGameStateChange(e)); window.removeEventListener("restart", (e: CustomEvent) => this.handleRestart(e)); }
为了使用更加严格的类型声明,可以在 tsconfig.json 中配置一些有用的编译选项。
比如使用 noImplicitAny 选项,如果将它设置为true,那么如果代码中有隐式指明类型为 any 的地方,TypeScript 编译器就会报错。比如:
noImplicitAny
function logMe(x) { console.log(x); } // error TS7006: Parameter 'x' implicitly has an 'any' type.
这里的x的类型可以是任何值,但是没有用 any 做出明确的类型声明,所以就会报错。 必须明确声明 x 的类型为 any ,修改为:
function logMe(x: any) { console.log(x); } // OK
你还可以为类的成员添加private/protected 修饰器,来实现类成员的访问控制。可以给 handleGameStateChange 和 handleRestart 两个方法添加 private 修饰器,因为它们是组件的内部方法:
private/protected
private handleGameStateChange(e: CustomEvent) {...} private handleRestart(e: Event) {...}
现在,可以使用npx webpack打包,用浏览器打开 index.html 文件查看。
在全部代码中采用TypeScript,就是或多或少地在所有的 .js .jsx 文件中 重复前面的两个步骤。 全部转换完之后,重新打包,用浏览器打开 index.html 文件查看。
.js
.jsx
示例:https://github.com/Microsoft/TypeScript-React-Conversion-Guide#typescript-react-conversion-guide
示例中是一个九宫格的小游戏,完全由JS编写,按照下面的步骤操作完成后,将变成一个完全由TypeScript 编写的小游戏。
在动手之前,首先要充分地理解当前这个项目,了解它的项目结构、代码逻辑、用的什么JS编译器、什么打包工具等等。
1、在构建流程中添加 TypeScript 编译器
step1: 安装依赖
先安装 package.json 中定义的依赖
命令行切换到TicTacToe_JS 目录,先安装 package.json 中定义的依赖:
安装 typescript、ts-loader、source-map-loader
1、ts-loader 是一个webpack插件,作用是将 TypeScript 文件编译为 JavaScript 文件。就像Babel的 babel-loader 一样。还有其他可选的 loader 也可以完成这个工作(比如 awesome-typescript-loader)。 2、source-map-loader 为调试时增加 source-map 支持。
为项目中用到的库,从@types中获取类型声明文件(.d.ts文件)
这个项目用到了 react 、react-dom。
step2:配置TypeScript
在项目根目录下添加 tsconfig.json 文件,来配置TypeScript。添加以下内容:
step3:设置构建流程
要在构建流程中添加对TypeScript的编译,需要修改webpack配置文件 webpack.config.js 。
这里假设用的构建工具是 webpack ,只说明怎么修改 webpack.config.js 。如果你用的是其他的构建工具,比如 Gulp,方法是一致的,就是将构建流程中的 Babel构建步骤 用 TypeScript 取代。因为TypeScript也提供了 将JS编译为低版本JS 和 编译JSX 的功能,而且在大多数情况下,编译时间更短。当然也可以保留Babel,可以在构建流程中添加 TypeScript 的编译,然后将输出传入 Babel编译器。
修改后的 webpack.config.js 文件内容如下:
如果不再需要 babel 了,可以删除 .babelrc 文件 和 package.json 中所有对 babel 的依赖。
现在,你已经完成了在构建流程中添加TypeScript编译器的操作。可以用下面的命令打包,并且在浏览器中打开 index.html 文件查看页面。
2、将 JS 文件转为 TS 文件
在这个部分,我们将按照下面的步骤逐步地完成 :
step1:最少的转换步骤
以
gameStateBar.jsx
文件为例。第一步是将
gameStateBar.jsx
重命名为gameStateBar.tsx
。改变名字之后,在编辑器中能看到类型报错。需要将第一行的:
改为:
这是因为原来用Babel编译时,Babel将 CommonJS 模块中的
module.exports
当做默认导出的对象,但是 TypeScript不是这样处理的。将第三行的 :
改为:
这是因为,React.Component 的类型定义使用了 泛型,需要提供这个组件实例的 props 属性 和 state 属性对象的类型。 我们在这里将 这两个对象(props和state)的类型设置为 any,表示可以传递任何类型的值。 any 对真正的类型检查 并不会起到什么作用,但是满足了我们第一步的目标:使用最少的步骤来通过TypeScript编译器的类型检查。
到这里,这个文件已经能通过TypeScript编译器的类型检查了。使用
npx webpack
命令打包,然后用浏览器打开 index.html 文件查看。step2:添加类型
提供越多的类型给TypeScript,就能得到越强大的类型检查的功能。
作为一个最佳实践,我们推荐对所有的声明提供类型。
接下来,还是以
gameStateBar
组件举例。对于任何的 React.Component ,应当给出 props 和 state 对象的 合适的类型定义。
gameStateBar
组件没有properties,所以我们可以使用{}
当类型。组件的 state 对象 只包含一个属性:gameState,它表示的是游戏的状态,可能的值有:
因为 gameState 只有确定的已知的字符串字面量值,所以可以使用“字符串字面量类型”来定义这个接口,如下:
这个接口定义需要写在类声明的前面。
使用这个定义的接口,
GameStateBar
的声明修改为:接下来还可以做这些事情:
为了使用更加严格的类型声明,可以在 tsconfig.json 中配置一些有用的编译选项。
比如使用
noImplicitAny
选项,如果将它设置为true,那么如果代码中有隐式指明类型为 any 的地方,TypeScript 编译器就会报错。比如:这里的x的类型可以是任何值,但是没有用 any 做出明确的类型声明,所以就会报错。 必须明确声明 x 的类型为 any ,修改为:
你还可以为类的成员添加
private/protected
修饰器,来实现类成员的访问控制。可以给 handleGameStateChange 和 handleRestart 两个方法添加 private 修饰器,因为它们是组件的内部方法:现在,可以使用
npx webpack
打包,用浏览器打开 index.html 文件查看。step3:在整个代码中采用TypeScript
在全部代码中采用TypeScript,就是或多或少地在所有的
.js
.jsx
文件中 重复前面的两个步骤。 全部转换完之后,重新打包,用浏览器打开 index.html 文件查看。