TypeScript 进阶技巧

IES 前端架构 - 基础体验 - 龙逸楠

  • TypeScript 概览
  • 项目配置
  • 类型使用技巧
  • 旧项目如何渐进式迁移

TypeScript 概览

  • 典型特征
    • Superset of JavaScript
    • Optional types
    • No runtime overhead
  • Toolchains

Toolchains

IntelliSense

Toolchains

Hover information

Toolchains

Hover information

Toolchains

Rename

Toolchains

  • Code navigation

  • Refactoring

  • Auto imports

  • Code suggestions

  • ....

TypeScript 项目配置

  • 前端项目
  • NodeJS 项目

前端项目配置

{
  "compilerOptions": {
    "strict": true,
    "allowSyntheticDefaultImports": true,
    "skipLibCheck": true,
    "target": "ES5",
    "module": "ESNext",
    "importHelpers": true,
    "jsx": "react",
    "moduleResolution": "Node",
    "baseUrl": "./src",
    "paths": {
      "~/*": [
        "./*"
      ]
    }
  },
  "include": ["./src"],
  "exclude": ["node_modules"]
}

Webpack

  • Webpack alias
  • Webpack loader
  • Polyfill

Webpack alias

TypeScript 不处理 import 的路径,baseURL + paths 只影响 type resolution,不影响 emit 出来的代码。

  • https://github.com/dividab/tsconfig-paths-webpack-plugin
  • 手动 alias

Webpack loader

You may don't need babel/babel-loader

  • 没有使用稳定阶段小于 Stage3 的语法
  • 使用的第三方库里面没有任何依赖特定 babel plugin
  • Polyfill 很复杂,手动管理成本太高

TypeScript 也可以用来编译 JavaScript,只需要加上 allowJs 编译参数

只使用 TypeScript 编译的好处

  • 更快的编译速度
  • 更小的编译产物
  • 更好的运行时性能

https://github.com/ReactiveX/rxjs/pull/2093

Polyfill

需要手动配置

通过 core-js 可以配置出简单通用并且体积可控的 Polyfill

import 'core-js/features/promise'
import 'core-js/features/array'
import 'core-js/features/object'
import 'core-js/stage/3'

Async polyfill

const Polyfills: Promise<void>[] = []

export function asyncPolyfill() {
  if (typeof Promise === 'undefined') {
    // @ts-ignore
    Polyfills.push(import('core-js/features/promise'))
  }

  if (!Object.assign) {
    // @ts-ignore
    Polyfills.push(import('core-js/features/object/assign'))
  }

  return Promise.all(Polyfills)
}
import { render } from 'react-dom'

import { asyncPolyfill } from './async-polyfill'

asyncPolyfill().then(() => {
  render(...)
})

NodeJS 项目配置

{
  "compilerOptions": {
    "strict": true,
    "allowSyntheticDefaultImports": true,
    "skipLibCheck": true,
    "target": "ES2018",
    "module": "CommonJS",
    "importHelpers": true,
    "moduleResolution": "Node",
    "baseUrl": "./src",
    "paths": {
      "~/*": [
        "./*"
      ]
    }
  },
  "include": ["./src"],
  "exclude": ["node_modules"]
}

Development tools

  • ts-node
  • nodemon
  • module-alias

nodemon + ts-node + tsconfig-paths

Development toolchains

{
  "delay": 1000,
  "restartable": "rs",
  "env": {
    "NODE_ENV": "development"
  },
  "execMap": {
    "ts": "ts-node -r tsconfig-paths/register"
    // node --inspect-brk -r ts-node/register -r tsconfig-paths/register
  },
  "watch": [
    "src/**/*.ts"
  ]
}

nodemon ./src/index.ts

module-alias

Production setup

const { join } = require('path')
const moduleAlias = require('module-alias')

moduleAlias.addAlias('~', join(__dirname, 'lib'))

moduleAlias()

require('./lib')

TypeScript 类型使用技巧

Nullable type + Type Guards

import * as React from 'react'

export interface User {
  _id: string
  name: string
  avatarUrl?: string
}

const findUserById = (id: string): User | null => {
  // ...
  return null
}

const props = {
  router: {
    params: {
      id: ''
    }
  }
}

const user = findUserById(props.router.params.id)

const name = (<div>{user.name}</div>) // error

TypeScript 类型使用技巧

Union type + Type Guards

import * as React from 'react'

export interface User {
  _id: string
  name: string
  avatarUrl?: string
}

type UserInfo = User | string

const findUserById = (id: string): User | null => {
  // ...
  return null
}

declare const userInfo: UserInfo

let user: User | null = null
if (typeof userInfo === 'string') {
  user = findUserById('')
} else {
  user = userInfo
}

const name = (<div>{user?.name}</div>)

TypeScript 类型使用技巧

Literal type

interface User {}

type UserType = 'admin' | 'customer' | 'guest'

export function findUserByType(userType: UserType): User[] {
  return []
}

findUserByType('admin')

TypeScript 类型使用技巧

Literal type

interface User {}

type UserType = 'admin' | 'customer' | 'guest'

export function findUserByType(userType: UserType): User[] {
  return []
}

findUserByType('admin')

TypeScript 类型使用技巧

keyof 运算符

interface User {
  _id: string
  name: string
  avatarUrl: string
  age: number
  birthDay: Date
}

function checkPropertyValidate(user: User, key: keyof User) {
  
}

declare const user: User

checkPropertyValidate(user, '_id')

TypeScript 类型使用技巧

mapped type

interface User {
  _id: string
  name: string
  avatarUrl: string
  age: number
  birthDay: Date
}

type AsyncUser = {
  [K in keyof User]: Promise<User[K]>
}

function fillUserAsync(user: User): AsyncUser {
  return Object.create({})
}

declare const user: User

fillUserAsync(user).age

TypeScript 类型使用技巧

函数重载

interface UseUserIDConfig {
  useId: true
  // ....
}

interface UseUserEntityConfig {
  useId: false
  // ...
}

function getUser(config: UseUserIDConfig, id: string): void;
function getUser(config: UseUserEntityConfig): void;

function getUser(config: UseUserIDConfig | UseUserEntityConfig, id?: string) {

}

getUser({ useId: true })

https://www.typescriptlang.org/docs/handbook/advanced-types.html

旧项目如何渐进式迁移

https://github.com/Brooooooklyn/typescript-example-projects/tree/master/javascript-migrate-to-typescript

Q & A