上期我們說到了TypeScript裝飾器(decorators)和JavaScript裝飾器編譯出的代碼不同,我們的組件庫已經(jīng)改成了TypeScript,但很多項目還在使用JavaScript,所以這里來說說怎么在我們JavaScript版的React項目中直接使用TypeScript的包,并用babel編譯。
安裝TypeScript
npm install typescript
書寫配置文件
TypeScript使用tsconfig.json文件管理工程配置,例如你想包含哪些文件和進行哪些檢查。 讓我們先創(chuàng)建一個簡單的工程配置文件:
{
"compilerOptions": {
"outDir": "./dist/",
"sourceMap": true,
"noImplicitAny": true,
"strictNullChecks": false,
"module": "commonjs",
"target": "ESNext",
"jsx": "react",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"moduleResolution": "node",
"allowJs": true
}
}
這里我們?yōu)門ypeScript設(shè)置了一些東西:
讀取所有可識別的src目錄下的文件(通過include)。
接受JavaScript做為輸入(通過allowJs)。
生成的所有文件放在dist目錄下(通過outDir)。
...
你可以在這里了解更多關(guān)于tsconfig.json文件的說明。
修改webpack配置文件
修改工程根目錄下的webpack.config.js文件。
module.exports = {
// ...
resolve: {
// Add '.ts' and '.tsx' as resolvable extensions.
extensions: ['.js', '.ts', '.tsx']
},
module: {
rules: [
// All files with a '.ts' or '.tsx' extension will be handled by 'babel-loader'.
{
test: /\.tsx?$/,
loader: 'babel-loader',
options: {
presets: ['@babel/preset-typescript'],
plugins: [
['@babel/plugin-transform-typescript', { allowNamespaces: true }],
]
},
},
{
test: /\.jsx?$/,
loader: 'babel-loader',
},
]
}
};
這里我們使用@babel/plugin-transform-typescript插件來處理TypeScript。
那么,TypeScript的類型檢測怎么辦呢?不是相當于廢了嗎?這里我們使用 fork-ts-checker-webpack-plugin來啟用TypeScript類型檢測。
配置TypeScript類型檢查器
Install
npm install fork-ts-checker-webpack-plugin fork-ts-checker-notifier-webpack-plugin
webpack.config.js
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const ForkTsCheckerNotifierWebpackPlugin = require('fork-ts-checker-notifier-webpack-plugin');
module.exports = {
// ...
plugins: [
new ForkTsCheckerWebpackPlugin({
// 將async設(shè)為false,可以阻止Webpack的emit以等待類型檢查器/linter,并向Webpack的編譯添加錯誤。
async: false
}),
// 將TypeScript類型檢查錯誤以彈框提示
// 如果fork-ts-checker-webpack-plugin的async為false時可以不用
// 否則建議使用,以方便發(fā)現(xiàn)錯誤
new ForkTsCheckerNotifierWebpackPlugin({
title: 'TypeScript',
excludeWarnings: true,
skipSuccessful: true,
}),
]
};
準備工作完成。
終于能試試期待已久的TypeScript了,心情好happy ??
但是,等等,What?為什么報錯了?
TS2304: Cannot find name 'If'.
TS2304: Cannot find name 'Choose'.
TS2304: Cannot find name 'When'.
原來是我們在React項目中使用了jsx-control-statements導(dǎo)致的。
怎么辦?在線等,挺急的... ??
我們發(fā)現(xiàn),這里我們可以用tsx-control-statements來代替。
配置 tsx-control-statements
安裝
npm install tsx-control-statements
在tsconfig.json文件的files選項中添加
{
"compilerOptions": {
"outDir": "./dist/",
"sourceMap": true,
"noImplicitAny": true,
"strictNullChecks": false,
"module": "commonjs",
"target": "ESNext",
"jsx": "react",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"moduleResolution": "node",
"allowJs": true
},
"files": [
"./node_modules/tsx-control-statements/index.d.tsx"
]
}
接下來我們按照TypeScript官網(wǎng)指南來把我們的代碼改成TypeScript就可以了,這里就不作詳細介紹了。
更便利的與ECMAScript模塊的互通性
但是這就結(jié)束了么,no no no...
在編譯過程中,我們發(fā)現(xiàn)有些包的導(dǎo)入有問題
比如,將i18next作為外部資源引用時(webpack的externals可以幫助我們實現(xiàn)該方式),我們發(fā)現(xiàn)代碼被編譯成
i18next_1['default'].t
但是i18next_1['default']的值是undefined,執(zhí)行出錯
為什么?哪里又雙叒叕...有問題了???
ECMAScript模塊在ES2015里才被標準化,在這之前,JavaScript生態(tài)系統(tǒng)里存在幾種不同的模塊格式,它們工作方式各有不同。 當新的標準通過后,社區(qū)遇到了一個難題,就是如何在已有的“老式”模塊模式之間保證最佳的互通性。
TypeScript與Babel采取了不同的方案,并且直到現(xiàn)在,還沒出現(xiàn)真正地固定標準。
在之前的版本,TypeScript 對 CommonJs/AMD/UMD 模塊的處理方式與 ES6 模塊不同,這會導(dǎo)致一些問題:
- 當導(dǎo)入一個 CommonJs/AMD/UMD 模塊時,TypeScript 視
import * as koa from 'koa'與const koa = require('koa')等價,但使用import * as創(chuàng)建的模塊對象實際上不可被調(diào)用以及被實例化。 - 類似的,當導(dǎo)入一個 CommonJs/AMD/UMD 模塊時,TypeScript 視
import koa from 'koa'與const koa = require('koa').default等價,但在大部分 CommonJs/AMD/UMD 模塊里,它們并沒有默認導(dǎo)出。
在 2.7 后的版本里,TypeScript提供了一個新的 esModuleInterop標記,旨在解決上述問題。
當使用這個新的esModuleInterop標記時,可調(diào)用的CommonJS模塊必須被做為默認導(dǎo)入:
import express from "express";
let app = express();
我們將其加入tsconfig.json文件中
{
"compilerOptions": {
"outDir": "./dist/",
"sourceMap": true,
"noImplicitAny": true,
"strictNullChecks": false,
"module": "commonjs",
"target": "ESNext",
"jsx": "react",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"allowSyntheticDefaultImports": true, // 允許使用 ES2015 默認的 import 風格
"esModuleInterop": true, // 可調(diào)用的CommonJS模塊必須被做為默認導(dǎo)入,在已有的“老式”模塊模式之間保證最佳的互通性
"moduleResolution": "node",
"allowJs": true
},
"files": [
"./node_modules/tsx-control-statements/index.d.tsx"
]
}
到了這里,我們的程序終于能完美的運行起來了。
我們不想再區(qū)分哪些需要使用import * as,哪些使用import,因此我們將格式統(tǒng)一為
import XX from 'XX'