翻譯 | 開始使用 TypeScript 和 React

iKcamp發表於2017-07-05

Tom Dale 和其他人有一些關於 TypeScript 比較好的博文,跟隨這些博文,我最近開始使用 TypeScript。今天,我將展示如何從零開始建立一個 TypeScript 工程,以及如何使用 Webpack 管理構建過程。我也將陳述關於 TypeScript 的第一印象,尤其是使用 TypeScript 和 ReactJS。

我不會深入到 TypeScript 語法的具體細節,你可以閱讀 TypeScript handbook 或者免費書籍 TypeScript Deep Dive,它們是關於 TypeScript 比較好的入門材料。

更新:如果你想用德語閱讀這篇文章,你可以 thanks to the awesome folks at Reactx.de

安裝配置 TypeScript

第一步要做的事情是使用 Yarn 將 TypeScript 安裝到本地的 node_modules 目錄,首先,使用 yarn init 建立一個工程:

yarn init
yarn add typescript複製程式碼

當你安裝了 TypeScript,你就可以使用 tsc 命令列工具,這個工具可以編譯 TypeScript,編譯時會建立一個開始檔案 tsconfig.json,你可以編輯這個檔案。你可以執行 tsc --init 獲得這個檔案 — 如果你已經在本地安裝了 TypeScript,你需要執行 ./node_modules/.bin/tsc --init

注意:你可以在我的點開頭的配置檔案中看到,我將 $PATH 定義為 ./node_modules/.bin 這個目錄。這有點危險,因為我可能不經意地執行這個目錄下的任何可執行的檔案,但是我願意承擔這個風險,因為我知道這個目錄下安裝了什麼,而且這能節省很多打字時間!

tsc --init 這個命令會生成一個 tsconfig.json 檔案,所有 TypeScript 編譯器的配置都存在於這個檔案中。在預設配置的基礎上,我做了一些修改,下面是我正在用的一個配置:

{
  "compilerOptions": {
    "module": "es6", // 使用 ES2015 模組
    "target": "es6", // 編譯成 ES2015 (Babel 將做剩下的事情)
    "allowSyntheticDefaultImports": true, // 看下面
    "baseUrl": "src", // 可以相對這個目錄 import 檔案
    "sourceMap": true, // 使 TypeScript 生成 sourcemaps
    "outDir": "ts-build", // 構建輸出目錄 (因為我們大部分時間都在使用 Webpack,所以不太相關)
    "jsx": "preserve", // 開啟 JSX 模式, 但是 "preserve" 告訴 TypeScript 不要轉換它(我們將使用 Babel)
    "strict": true,
  },
  "exclude": [
    "node_modules" // 這個目錄下的程式碼不會被 typescript 處理
  ]
}複製程式碼

allowSyntheticDefaultImports

將這個屬性的值設定為 true,它允許你使用 ES2015 預設的 imports 風格, 即使你匯入的程式碼沒有使用 ES2015 預設的 export。

舉個例子,當你 import 不是用 ES2015 編寫的 React 時(雖然原始碼是,但是 React 使用一個構建好的版本),就可以利用上面的屬性設定。這意味著,嚴格意義上來講,它沒有使用 ES2015 預設的 export,所以當你使用 import 的時候, TypeScript 會警告你。儘管如此,像 Webpack 這樣的構建工具能夠匯入正確的程式碼,所以我將這個屬性設定為 true,相比使用 import * as React from 'react',我更喜歡 import React from 'react' 這種方式。

strict:true

TypeScript 2.3 版本引入了一種新的配置選項,strict。當將這個值設定為 true 時,TypeScript 編譯器會盡可能的嚴格 - 如果你將一些 JS 轉為 TS,這可能不是你想要的,但是對於一些新的專案,使其儘可能的嚴格是有意義的。它還引入了一些不同的配置,其中幾個比較重要的的有 noImplicitAnystrictNullChecks:

noImplicitAny

將 TypeScript 引入一個現有的專案,當你不宣告變數的型別時,TypeScript 不會丟擲錯誤。但是,當我從零開始新建一個 TypeScript 專案,我希望編譯器儘可能地嚴格。
TypeScript 預設做的一件事是將變數設定為 any 型別。any 是 TypeScript 中避免型別檢查的有效手段,它可以是任何值。當你轉換 JavaScript 時,使用 any 是很有用的,但是最好還是儘可能地嚴格。當將 noImplicitAny 設定為 true,你必須為變數設定型別。舉個例子,當將 noImplicitAny 設定為 true 時,下面的程式碼會報錯:

function log(thing) {
  console.log('thing', thing)
}複製程式碼

如果你想了解更多關於 noImplicitAny 的資訊,可以閱讀 TypeScript Deep Dive

strictNullChecks

這是另一個使 TypeScript 編譯器更嚴格的選項。TypeScript Deep Dive 這本書有一個很好的章節介紹這個選項。如果將這個選項設定為true,TypeScript 會更容易識別出你引用的一個可能是 undefined 值的地方,並將展示這個錯誤。例如:

person.age.increment()複製程式碼

當將 strictNullChecks 設定為 true,TypeScript 會認為 person 或者 person.age 可能是 undefined,它會報個錯以確保你處理它。這會防止出現執行時錯誤,所以這看起來是一個從一開始就要開啟的很棒的選項。

配置 Webpack, Babel and TypeScript

我是 Webpack 的腦殘粉;我喜歡它的外掛生態系統、開發者工作流,喜歡它擅長管理複雜的應用和構建流程。所以,即使我們可能僅僅使用 TypeScript 編譯器,我仍然喜歡引入 Webpack。因為 TypeScript 輸出 React 和 es6(也就是 es2015,babel 把 es6 轉成 es5,所以我們還需要 babel。讓我們安裝 Webpack,Babel 和相關的 presets 及 ts-loader,ts-loader 是 TypeScript 在 Webpack 中的外掛。

還有 awesome-typescript-loader ,也是 TypeScript 在 Webpack 中的外掛,但是我首先找到的是 ts-loader 而且到目前為止它非常不錯。如果誰使用了 awesome-typescript-loader,我很樂意看到關於它們兩者的對比。

yarn add webpack babel-core babel-loader babel-preset-es2015 babel-preset-react ts-loader webpack-dev-server複製程式碼

此時此刻,我必須感謝 Tom Duncalf,他在部落格中發表的 TypeScript 1.9 + React,對我來說,是一個特別好的開始,我極力推薦它。

在 Webpack 中沒有特別的配置,但是我還是在程式碼中列出一些註釋來解釋它:

const webpack = require('webpack')
const path = require('path')

module.exports = {
  // 設定 sourcemaps 為 eval 模式,將模組封裝到 eval 包裹起來
  devtool: 'eval',

  // 我們應用的入口, 在 `src` 目錄 (我們新增到下面的 resolve.modules):
  entry: [
    'index.tsx'
  ],

  // 配置 devServer 的輸出目錄和 publicPath
  output: {
    filename: 'app.js',
    publicPath: 'dist',
    path: path.resolve('dist')
  },

  // 配置 devServer 
  devServer: {
    port: 3000,
    historyApiFallback: true,
    inline: true,
  },

  // 告訴 Webpack 載入 TypeScript 檔案
  resolve: {
    // 首先尋找模組中的 .ts(x) 檔案, 然後是 .js 檔案
    extensions: ['.ts', '.tsx', '.js'],

    // 在模組中新增 src, 當你匯入檔案時,可以將 src 作為相關路徑
    modules: ['src', 'node_modules'],
  },

  module: {
    loaders: [
      // .ts(x) 檔案應該首先經過 Typescript loader 的處理, 然後是 babel 的處理
      { test: /\.tsx?$/, loaders: ['babel-loader', 'ts-loader'], include: path.resolve('src') }
    ]
  },
}複製程式碼

我們按照上面的方式配置 loaders ,從而使 .ts(x) 檔案首先經過 ts-loader 的處理。按照 tsconfig.json 中的配置,使用 TypeScript 編譯 .ts(x) 檔案 - 輸出 ES2015。然後,我們使用 Babel 將它降級到 ES5。為了實現這些,我建立了一個包含需要的 presets 的 .babelrc 檔案:

{
  "presets": ["es2015", "react"]
}複製程式碼

現在我們已經做好了寫 TypeScript 應用的準備。

寫一個 TypeScript React 元件

現在,我們準備好建立 src/index.tsx,這是我們這個應用的入口。我們可以建立一個虛擬的元件,渲染它,檢視它是否正常執行。

import React from 'react'
import ReactDOM from 'react-dom'

const App = () => {
  return (
    <div>
      <p>Hello world!</p>
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('app'))複製程式碼

如果你執行 webpack,會看到下面的錯誤:

ERROR in ./src/index.tsx
(1,19): error TS2307: Cannot find module 'react'.

ERROR in ./src/index.tsx
(2,22): error TS2307: Cannot find module 'react-dom'.複製程式碼

發生上面的錯誤是因為 TypeScript 試圖確認 React 的型別、React 匯出了什麼。對於 React DOM,TypeScript 會做同樣的事情。React 並不是使用 TypeScript 編寫的,所以它並沒有包含那些資訊。幸運地是,為了應對這種情況,社群已經建立了 DefinitelyTyped,這是一個大型的元件型別庫。

最近,安裝機制改變了;所有的型別被髮布到 npm @types scope 下。為了獲得 React 和 ReactDOM 的型別,我們執行下面的命令:

yarn add @types/react
yarn add @types/react-dom複製程式碼

通過上面的處理,錯誤不見了。無論何時,你安裝一個依賴時,都應該試著安裝 @types 包,或者你想檢視是否有被支援的型別,你可以在 TypeSearch 網站上檢視。

本地執行 app

為了在本地執行 app,我們只需要執行 webpack-dev-server 命令。我配置了一個指令碼 start, 它能做上面的事情:

"scripts": {
  "start": "webpack-dev-server"
}複製程式碼

服務會找到 webpack.config.json 這個檔案,使用它建立我們的應用。

如果你執行 yarn start ,你會看到來自於 webpack-dev-server 的輸出,包含 ts-loader 的輸出,這些能夠確認應用是否正常執行。

$ webpack-dev-server
Project is running at http://localhost:3000/
webpack output is served from /dist
404s will fallback to /index.html
ts-loader: Using typescript@2.3.0 and /Users/jackfranklin/git/interactive-react-introduction/tsconfig.json
Version: webpack 2.4.1
Time: 6077ms
 Asset     Size  Chunks                    Chunk Names
app.js  1.14 MB       0  [emitted]  [big]  main
webpack: Compiled successfully.複製程式碼

為了能夠在本地看到效果,我建立了一個 index.html 檔案,讓它載入編譯後的程式碼:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>My Typescript App</title>
  </head>
  <body>
    <div id="app"></div>
    <script src="dist/app.js"></script>
  </body>複製程式碼

在埠 3000,我們將會看到 Hello world!,我們讓 TypeScript 執行了!

定義一個模組型別

在現在的工程中,我想使用 React Ace module 包含一個程式碼編輯器。但是這個模組並不提供 types,並且也沒有 @types/react-ace。在這種情況下,我們必須在應用中增加型別,這樣可以使 TypeScript 知道如何去檢查它的型別。這看起來非常煩人,讓 TypeScript 至少知道所有第三方依賴關係的好處是,可以節省除錯時間。

定義一個只包含型別的檔案,字尾是 .d.ts( ‘d‘ 代表 ‘declaration‘ ),你可以從 TypeScript docs 瞭解更多。在你的工程中,TypeScript 將會自動地找到這些檔案,你不需要顯式地匯入它們。

我建立了 react-ace.d.ts 檔案,新增下面的程式碼,建立模組,定義它的預設 export 為一個 React 元件。

declare module 'react-ace' {
    interface ReactAceProps {
      mode: string
      theme: string
      name: string
      editorProps?: {}
      showPrintMargin?: boolean
      minLines?: number
      maxLines?: number
      wrapEnabled?: boolean
      value: string
      highlightActiveLine?: boolean
      width?: string
      fontSize?: number
    }

    const ReactAce: React.ComponentClass<ReactAceProps>
    export = ReactAce
}複製程式碼

我首先建立了一個 TypeScript 介面,這個介面包含元件的屬性,export = ReactAce 標明元件通過模組被匯出。通過定義屬性的型別,TypeScript 會告訴我是否弄錯了屬性的型別或者忘記設定一個型別,這是非常有價值的。

測試

最後,使用 TypeScript,我也想有一個很好的測試方案。我是 Facebook 的 Jest 的超級粉絲,我在 google 上做了一些搜尋,確認它是否能用 TypeScript 執行。結果發現,這是可行的,ts-jest 包可以做一些很重的轉換。除此之外,還有一個做型別檢查的 @types/jest 包,可以讓你所有的測試都是型別確認過的。

非常感謝 RJ Zaworski,他在部落格上發表了 TypeScript and Jest ,這使我開始瞭解這個主題。如果你安裝了 ts-jest,你只需要在 package.json 中配置 Jest,下面是配置:

"jest": {
  "moduleFileExtensions": [
    "ts",
    "tsx",
    "js"
  ],
  "transform": {
    "\\.(ts|tsx)$": "<rootDir>/node_modules/ts-jest/preprocessor.js"
  },
  "testRegex": "/*.spec.(ts|tsx|js)$"
},複製程式碼

第一個配置告訴 Jest 尋找 .ts.tsx 檔案。 transform 物件告訴 Jest 通過 ts-jest 前處理器執行任何 TypeScript 檔案,ts-jest 前處理器通過 TypeScript 編譯器執行 TypeScript 檔案,產出能讓 Jest 識別的 JavaScript。最後,我更新了 testRegex 設定,目的是尋找任何 *.spec.ts(x) 檔案,我更喜歡用這種方式命名轉換。

通過上面這些配置,我可以執行 jest 並且讓每一件事情都如預期一樣執行。

使用 TSLint 規範程式碼

儘管 TypeScript 在程式碼中會給出很多檢查提示,我仍然想要一個規範器,做些程式碼風格和質量檢查。就像 JavaScript 的 ESLint,TSLint 是檢查 TypeScript 的最好選擇。它和 ESlint 的工作方式相同 - 用一系列生效或不生效的規則,還有一個 TSLint-React 包增加 React 的具體規則。

你可以通過 tslint.json 檔案配置 TSLint,我的配置檔案如下。我用 tslint:latesttslint-react presets,它們可以使用很多規則。我不贊成一些預設設定,所以我重寫了它們 - 你可以和我的配置不同 - 這完全取決於你!

{
    "defaultSeverity": "error",
    "extends": ["tslint:latest", "tslint-react"],
    "jsRules": {},
    "rules": {
      // 用單引號, 但是在 JSX 中,強制使用雙引號
      "quotemark": [true, "single", "jsx-double"],
      // 我更喜歡沒有分號 :)
      "semicolon": [true, "never"],
      // 這個規則使每個介面以 I 開頭,這點我不喜歡
      "interface-name": [true, "never-prefix"],
      // 這個規則強制物件中的 key 按照字母順序排列 
      "object-literal-sort-keys": false
    },
    "rulesDirectory": []
}複製程式碼

我可以執行 tslint --project tsconfig.json 規範我的專案

結論

總之,到目前為止,用 TypeScript 開發我很高興。我肯定會發表更多博文來描述這門語言的細節和我是如何使用 TypeScript 的。但僅就如下操作而言,構建過程、配置所有的工具、開始使用型別,這真是一種享受。如果你正在將你的 JS 應用結構化,想要一個更強大的編譯器避免錯誤並減少除錯時間,我極力推薦你嘗試 TypeScript。

如果你想看原始碼或者以本文中的例子作為開始,我在 GitHub 上放了一個例子,如果你有任何問題,可以提 issue。

iKcamp原創新書《移動Web前端高效開發實戰》已在亞馬遜、京東、噹噹開售。

相關文章