Typescript for React Native

前几天研究 settimeout 的问题的时候,发现 react-native-background-timer 自己没有 typescript 的 type 文件,但是有人给写了一个 @types/react-native-background-timer,这个包算偏门了,都有人写了 type 文件,我感觉是时候试试看 typescript 了。

搜了一下,发现没有多少在 rn 里面使用 ts 的,有一些关于 react 的,又很奇怪,大都基于 webpack 的。后来找到一篇官方的 blog 上面的,然后结合自己的研究,找到了思路。我是基于已有项目来做的,那个 blog 是基于新项目,大同小异。

首先装几个包,这几个包里面, =typescript 提供 typescript 的编译器, react-native-typescript-transformer 提供了从 ts 代码到 js 代码的转换支持, @types 的两个包提供了 react 和 react-native 的 type 文件。

$ yarn add -D typescript react-native-typescript-transformer @types/react @types/react-native

在项目的根目录还需要准几个文件。 tsconfig.json,你的目录里面可能已经有一个 jsconfig.json 了,那个是给 eslint 用的。tsconfig.json 同时给 typescript 和 tslint 使用。

{
  "compilerOptions": {
    "target": "es2015",
    "module": "es2015",
    "lib": [
      "es2015"
    ],
    "jsx": "react",
    "noEmit": true,
    "moduleResolution": "node",
    "strict": true,
    "esModuleInterop": true,
    "types": [
       "react",
       "react-native"
     ],
    "allowSyntheticDefaultImports": true
  },
  "include": [
     "./app/**/*"
  ],
  "exclude": [
    "node_modules"
  ]
}

这里面的 include/exclude 按照需要调整,注意里面没有 output,我们并不需要 typescript 输出 js 文件。(当然,也可以用输出 js 文件的方式来做这个事情,但是这样就不太好自动化了,细节不说了)

然后还需要一个 rn-cli.config.js,这个是给 react-native-typescript-transformer 用的。

module.exports = {
  getTransformModulePath() {
    return require.resolve('react-native-typescript-transformer');
  },
  getSourceExts() {
    return ['ts', 'tsx'];
  }
}

然后就可以写一些 .ts 文件了。 .ts 文件表示只有 js 代码, .tsx 文件表示里面有 react 代码。写完之后可以执行一下 yarn tsc 看看,是否有错误。没有错误的话,也可以在模拟器里面看看自己的 ts 代码是不是确实可以执行。

你的代码可以在模拟器里面执行,主要是下面这段代码的作用。 ts.transpileModule 会把 ts 代码转换成 js 代码,最终执行的是 js 代码。这里有一个需要注意的地方就是这里不管 ts 的语法错误,也就是你比如定义了一个 type 是 string 类型的变量,你给他做了 number 类型的赋值,这个在 js 里面是可以的,ts 是不允许的,但是这里并不会看到错误。执行 yarn tsc 可以看到错误提示。

module.exports.transform = function(src, filename, options) {
  if (typeof src === 'object') {
    // handle RN >= 0.46
    ;({ src, filename, options } = src)
  }

  if (filename.endsWith('.ts') || filename.endsWith('.tsx')) {
    const tsCompileResult = ts.transpileModule(src, {
      compilerOptions,
      fileName: filename,
      reportDiagnostics: true,
    })

    const errors = tsCompileResult.diagnostics.filter(
      ({ category }) => category === ts.DiagnosticCategory.Error
    )
.....

所以保证代码符合 typescript 有下面几个方法:

  • 使用支持 typescript 的编辑器,依靠编辑器的提示。vs code 配合 tslint 可以做到这个。
  • 提交代码之前执行 yarn tsc 验证代码没问题之后再提交。
  • 在 git 的 commit-hook 里面增加一个 hook 自动执行 yarn tsc 检查。git 也可以在 server 端做这个检查。

为了保证这个,我在 git 的 commit-hook 里面增加了一个 hook。放到 .git/hooks/pre-commit 就可以。

#!/bin/sh

has_ts_file=`git diff --cached --name-status | awk '$1 != "D" { print $2 }' | grep '.ts$' |wc -l`

exec 1>&2

if [ "$has_ts_file" -ge '1' ];then
    yarn tsc
fi

但是 git 的 hooks 文件并不是 repo 的一部份,如何保证大家都是一样的配置呢?有一个 npm 包可以做这个事情。。 yarn add -D pre-commit ,然后在 package.json 里面增加一些配置。

    "scripts": {
        "start": "node node_modules/react-native/local-cli/cli.js start",
        "test": "jest",
        "lint": "node_modules/.bin/eslint app",
        "version": "./version-ios.sh",
        "precommit": "./pre-commit"
    },
    "pre-commit": [
        "precommit"
    ],

scripts 里面的 precommit 和 pre-commit 是新加的。那个 pre-commit 就是上面的那个脚本,放到项目目录一起管理就可以。