相對于 JavaScript,TypeScript 增加了類型系統(tǒng)。通過靜態(tài)檢查,開發(fā)者可以更早地發(fā)現(xiàn)語法錯誤,同時類型標(biāo)注也帶來了清晰的文檔。這篇文章記錄了幾個關(guān)于 React 項(xiàng)目從 JavaScript 遷移到 TypeScript 筆者處理過的一些問題,這些問題雖然并不復(fù)雜,但是如果在遷移的過程中遇到了,也需要花上不少的時間去調(diào)查和尋找解決方案。
首先推薦閱讀微軟的一篇 React 項(xiàng)目遷移文檔。
簡單總結(jié)一下,整個遷移的過程可以分為下面兩類操作:
- 配置 TypeScript 編譯器。
- 將 JavaScript 文件轉(zhuǎn)化為 TypeScript 文件。
配置編譯器
1. 安裝依賴
依賴可以分為兩類,一類是 TypeScript 編譯工具,另一類是第三方依賴的類型聲明文件(declarations)。
安裝編譯工具,TypeScript 的主流 loader 有 ts-loader 和 awesome-typescript-loader.
npm install --save-dev typescript ts-loader source-map-loader
安裝第三方依賴類型文件,下面的例子只包括了 react 和 react-dom,其他依賴聲明可以根據(jù)自己項(xiàng)目進(jìn)行安裝。
npm install --save -D @types/react @types/react-dom
如果最終發(fā)布的內(nèi)容是會被其他項(xiàng)目引用的工具,應(yīng)該把 @types 包括在發(fā)布的文件當(dāng)中,即 npm i @types/xxx,如果發(fā)布的內(nèi)容不會被其他項(xiàng)目引用,那么安裝為 devDependencies 即可,可以參考 stackoverflow 上的這篇討論。
如果項(xiàng)目開發(fā)時使用了熱更新(HMR),類型檢查會報(bào)錯 module.hot 類型問題,需要安裝 @types/webpack-env 來解決這個類型問題。
2. webpack 配置更新
- 添加 resolve 的文件后綴:
resolve: {
// changed from extensions: [".js", ".jsx"]
extensions: [".ts", ".tsx", ".js", ".jsx"]
},
- 使用 ts-loader 處理 js, jsx, ts, tsx 文件:
module: {
rules: [
// changed from { test: /\.jsx?$/, use: { loader: 'babel-loader' } },
{
test: /\.(t|j)sx?$/,
use: {
loader: ts-loader',
options: { // options for spped up compilation
transpileOnly: true
}
}
},
// addition - add source-map support
{ enforce: "pre", test: /\.js$/, loader: "source-map-loader" }
]
},
項(xiàng)目冷啟動開發(fā)時(npm start),如果順序進(jìn)行類型檢查和編譯,會花費(fèi)不少的時間。我們可以另開一個線程來進(jìn)行類型檢查,從而節(jié)約時間。fork-ts-checker-webpack-plugin 就是這樣一個 webpack plugin,非常推薦使用。
3. 添加 ts 編譯器配置
// tsconfig.js
{
"compilerOptions": {
"outDir": "./dist/",
"sourceMap": true,
"noImplicitAny": true,
"strictNullChecks": true,
"module": "es6",
"target": "es5",
"jsx": "react",
"allowJs": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true
},
"includes": [
"./src/",
]
}
在之前的打包配置里面,babel 實(shí)現(xiàn)的功能主要有兩項(xiàng),包括:編譯 jsx 語法(preset-react)和 將晚進(jìn)的語法編譯為 es5 語法(preset-env),從而實(shí)現(xiàn)更好的兼容性。而 TypeScript 編譯器同樣可以實(shí)現(xiàn)這些操作,那么如果 babel 配置沒有其他特殊操作(比如編譯其他特殊語法),那么完全可以使用 TypeScript loader 替代 babel。
tsconfig 文件里面可以配置編譯器的各種選項(xiàng)。如果類型檢查的時候,因?yàn)橐氲慕M件沒有 default export (通過 module.exports 輸出),可能會報(bào)錯,那么配置allowSyntheticDefaultImport 可以讓 ts 忽略引用報(bào)錯。
文件轉(zhuǎn)換
源代碼文件
從 .js 遷移到 .ts,主要是添加各種類型聲明。
一些小 tips:
- 事件類型(event):可以使用 React 事件注釋:
export type InputEvent = React.ChangeEvent<HTMLInputElement>;
export type ButtonEvent = React.MouseEvent<HTMLButtonElement>;
- 組件類型(element):組件可以用
JSX.Element標(biāo)注。 - 組件方法聲明問題,在 React 組件里面,有的時候會直接在 constructor 里面對創(chuàng)建的對象綁定方法:
constructor(props) {
...
this.foo = () => {
...
}
}
而下面這種方式是將方法聲明給了該組件 class 的 prototype,然后在 constructor 里面綁定新建對象。
constructor(props) {
...
this.foo = this.foo.bind(this);
}
foo() {
...
}
這兩種寫法對于程序運(yùn)行沒有實(shí)際的區(qū)別,但是第一種方式聲明的方法并不在 prototype 上,所以類型檢查無法將生成對象的方法和 class 方法對應(yīng)起來,會報(bào)錯。解決方法是按照第二種寫法,將方法綁定到 class prototype 上即可。
樣式文件
對于預(yù)處理樣式(比如 stylus,sass,less等),需要相應(yīng)的 declarations 文件,通過配置 css-modules-typescript-loader 可以幫助我們自動生成 d.ts 文件。
- 安裝 css-modules-typescript-loader,在 webpack 配置其在 css-loader 之后處理樣式文件。
- 創(chuàng)建一個 declaration 文件,聲明預(yù)處理樣式文件命名格式,因?yàn)槲覀兊捻?xiàng)目文件名字采用 .cm.styl 后綴表示,所以聲明如下:
// declarations.d.ts
declare module '*.cm.styl';