Skip to content

Typescript 工程实践 ​

封装自己的 map 函数 ​

错误示例 ​

ts
const arrMap = (arr: any[], callback: (item: any, index?: number, arr: any[]) => any): any[] => {
  let i: number = 0
  const len = arr.length
  const arr2 = []
  while (i < len) {
    arr2.push(callback(arr[i], i, arr))
    i++
  }
  return arr2
}

arrMap([1, 2], (item) => item++).forEach((item) => {
  console.log(item.length) // any 类型不会报错,但运行有问题
})
const arrMap = (arr: any[], callback: (item: any, index?: number, arr: any[]) => any): any[] => {
  let i: number = 0
  const len = arr.length
  const arr2 = []
  while (i < len) {
    arr2.push(callback(arr[i], i, arr))
    i++
  }
  return arr2
}

arrMap([1, 2], (item) => item++).forEach((item) => {
  console.log(item.length) // any 类型不会报错,但运行有问题
})

正确示例 ​

使用泛型建立输入输出类型关联:

ts
const arrayMap = <T, U>(
  arr: T[],
  callback: (item: T, index?: number, array?: Readonly<T[]>) => U,
): U[] => {
  let i = 0
  const len = arr.length
  const arr2 = []
  while (i < len) {
    arr2.push(callback(arr[i], i, arr))
    i++
  }
  return arr2
}
const arrayMap = <T, U>(
  arr: T[],
  callback: (item: T, index?: number, array?: Readonly<T[]>) => U,
): U[] => {
  let i = 0
  const len = arr.length
  const arr2 = []
  while (i < len) {
    arr2.push(callback(arr[i], i, arr))
    i++
  }
  return arr2
}

为第三方库编写声明文件 ​

  1. 在 tsconfig.json 中配置 paths:
json
{
  "baseUrl": ".",
  "paths": {
    "*": ["./node_modules/@types", "./typings/*"]
  }
}
{
  "baseUrl": ".",
  "paths": {
    "*": ["./node_modules/@types", "./typings/*"]
  }
}
  1. 编写声明文件 typings/库名/index.d.ts:
ts
declare function indexof(arr: any[], obj: [any]): number
export = indexof
declare function indexof(arr: any[], obj: [any]): number
export = indexof

Node + TypeScript 开发 ​

bash
# 安装 express 项目模板
npm i express-generator -g
express --view=jade server-side

# 安装 typescript
npm i typescript
npm i @types/express @types/node -D
# 安装 express 项目模板
npm i express-generator -g
express --view=jade server-side

# 安装 typescript
npm i typescript
npm i @types/express @types/node -D

项目结构 ​

config/         # 全局配置
controller/     # 控制器
model/          # 数据库
utils/          # 工具函数
types/          # 声明文件
config/         # 全局配置
controller/     # 控制器
model/          # 数据库
utils/          # 工具函数
types/          # 声明文件

tsconfig 配置 ​

json
{
  "compilerOptions": {
    "outDir": "./dist",
    "moduleResolution": "node",
    "baseUrl": ".",
    "paths": { "*": ["node_modules/*", "./types/*"] },
    "esModuleInterop": true
  }
}
{
  "compilerOptions": {
    "outDir": "./dist",
    "moduleResolution": "node",
    "baseUrl": ".",
    "paths": { "*": ["node_modules/*", "./types/*"] },
    "esModuleInterop": true
  }
}

处理静态文件 ​

js
// handle-public.js
const shell = require('shelljs')
shell.cp('-R', './views/', './dist/')
shell.cp('-R', './public/', './dist/')
// handle-public.js
const shell = require('shelljs')
shell.cp('-R', './views/', './dist/')
shell.cp('-R', './public/', './dist/')

package.json ​

json
{
  "scripts": {
    "build": "tsc && node handle-public.js",
    "watch-dev": "cross-env NODE_ENV=development nodemon -e ts,tsx --exec 'ts-node' ./server.ts"
  }
}
{
  "scripts": {
    "build": "tsc && node handle-public.js",
    "watch-dev": "cross-env NODE_ENV=development nodemon -e ts,tsx --exec 'ts-node' ./server.ts"
  }
}

React + TypeScript ​

bash
yarn create react-app demo --template typescript
yarn create react-app demo --template typescript

组件传值 ​

tsx
// Parent.tsx
interface StateProps { count: number }
interface Parent { state: StateProps }

class Parent extends React.Component {
  state = { count: 0 }
  handleClick() {
    this.setState({ count: this.state.count + 1 })
  }
  render() {
    return (
      <div>
        <h1>{this.state.count}</h1>
        <Button onClick={this.handleClick.bind(this)}>+</Button>
      </div>
    )
  }
}

// Button.tsx
interface Props {
  onClick: (ev: React.MouseEvent<HTMLButtonElement>) => void
}
const Button: React.FC<Props> = (props) => {
  return <button onClick={props.onClick}>{props.children}</button>
}
// Parent.tsx
interface StateProps { count: number }
interface Parent { state: StateProps }

class Parent extends React.Component {
  state = { count: 0 }
  handleClick() {
    this.setState({ count: this.state.count + 1 })
  }
  render() {
    return (
      <div>
        <h1>{this.state.count}</h1>
        <Button onClick={this.handleClick.bind(this)}>+</Button>
      </div>
    )
  }
}

// Button.tsx
interface Props {
  onClick: (ev: React.MouseEvent<HTMLButtonElement>) => void
}
const Button: React.FC<Props> = (props) => {
  return <button onClick={props.onClick}>{props.children}</button>
}

常用类型声明 ​

tsx
// 组件声明
const App: React.FunctionComponent<AppProps>
const App: React.FC<AppProps>

// CSS 样式
interface Custom {
  style?: React.CSSProperties
  children: React.ReactNode
}

// 原始标签类型
type AppProps = AppProps & React.ButtonHTMLAttributes<HTMLElement>
// 组件声明
const App: React.FunctionComponent<AppProps>
const App: React.FC<AppProps>

// CSS 样式
interface Custom {
  style?: React.CSSProperties
  children: React.ReactNode
}

// 原始标签类型
type AppProps = AppProps & React.ButtonHTMLAttributes<HTMLElement>

Vue + TypeScript ​

使用 vue-property-decorator ​

bash
npm install vue-property-decorator vuex-class
npm install vue-property-decorator vuex-class

Vue 文件写法 ​

vue
<template>
  <div class="todo-page">
    <ul>
      <todo-item v-for="(item, index) in list" :key="index" :item="item" />
    </ul>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import TodoItem from '../components/todo-item'

@Component({ name: 'todo-page', components: { TodoItem } })
export default class Todo extends Vue {
  public isEdittingIndex = 0
  public list = [{ text: '学习 TS', complete: false }]
}
</script>
<template>
  <div class="todo-page">
    <ul>
      <todo-item v-for="(item, index) in list" :key="index" :item="item" />
    </ul>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import TodoItem from '../components/todo-item'

@Component({ name: 'todo-page', components: { TodoItem } })
export default class Todo extends Vue {
  public isEdittingIndex = 0
  public list = [{ text: '学习 TS', complete: false }]
}
</script>

vue-property-decorator 装饰器 ​

tsx
import { Vue, Component, Prop, Watch, Emit, Ref } from 'vue-property-decorator'

@Component
export default class YourComponent extends Vue {
  @Prop(Number) readonly propA!: number
  @Prop({ default: 'default' }) readonly propB!: string
  @Prop([String, Boolean]) readonly propC!: string | boolean

  @Watch('index', { immediate: true, deep: true })
  public indexChange() {}

  @Emit('on-save')
  public save() {}
}
import { Vue, Component, Prop, Watch, Emit, Ref } from 'vue-property-decorator'

@Component
export default class YourComponent extends Vue {
  @Prop(Number) readonly propA!: number
  @Prop({ default: 'default' }) readonly propB!: string
  @Prop([String, Boolean]) readonly propC!: string | boolean

  @Watch('index', { immediate: true, deep: true })
  public indexChange() {}

  @Emit('on-save')
  public save() {}
}

连接 Vuex ​

tsx
import { State, Mutation, Action, Getter } from 'vuex-class'

export default class Todo extends Vue {
  @State('list') public list
  @Mutation('setIncrement') public setIncrement

  get formatList() {}
  set formatList(val) {}
}
import { State, Mutation, Action, Getter } from 'vuex-class'

export default class Todo extends Vue {
  @State('list') public list
  @Mutation('setIncrement') public setIncrement

  get formatList() {}
  set formatList(val) {}
}

TSX 写法 ​

tsx
import { Component, Prop, Vue, Emit, Watch } from 'vue-property-decorator'

@Component({ name: 'todo-item' })
export default class TodoItems extends Vue {
  @Prop(Object) public item!: Item
  @Prop(Number) public index!: number

  protected render() {
    return <li>...</li>
  }
}
import { Component, Prop, Vue, Emit, Watch } from 'vue-property-decorator'

@Component({ name: 'todo-item' })
export default class TodoItems extends Vue {
  @Prop(Object) public item!: Item
  @Prop(Number) public index!: number

  protected render() {
    return <li>...</li>
  }
}

扩展 Vue 原型属性 ​

创建 src/types/vue.d.ts:

ts
import Vue from 'vue'
declare module 'vue/types/vue' {
  interface Vue {
    $message: Message
  }
}
import Vue from 'vue'
declare module 'vue/types/vue' {
  interface Vue {
    $message: Message
  }
}

值转类型技巧 ​

数组转联合类型 ​

ts
const arr = ['uat', 'pre', 'prod'] as const
type Keys = (typeof arr)[number]  // 'uat' | 'pre' | 'prod'
const arr = ['uat', 'pre', 'prod'] as const
type Keys = (typeof arr)[number]  // 'uat' | 'pre' | 'prod'

数组对象键转联合 ​

ts
const obj = [{ name: 'uat' }, { name: 'sit' }] as const
type Obj = (typeof obj)[number]['name']  // 'uat' | 'sit'
const obj = [{ name: 'uat' }, { name: 'sit' }] as const
type Obj = (typeof obj)[number]['name']  // 'uat' | 'sit'

对象键转联合 ​

ts
const obj = { env: 'uat', build: 'sit' }
type Obj = keyof typeof obj  // 'env' | 'build'
const obj = { env: 'uat', build: 'sit' }
type Obj = keyof typeof obj  // 'env' | 'build'

对象键值转联合 ​

ts
const obj = { env: 'uat', build: 'sit' } as const
type Obj = (typeof obj)[keyof typeof obj]  // 'uat' | 'sit'
const obj = { env: 'uat', build: 'sit' } as const
type Obj = (typeof obj)[keyof typeof obj]  // 'uat' | 'sit'