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
}为第三方库编写声明文件
- 在
tsconfig.json中配置 paths:
json
{
"baseUrl": ".",
"paths": {
"*": ["./node_modules/@types", "./typings/*"]
}
}{
"baseUrl": ".",
"paths": {
"*": ["./node_modules/@types", "./typings/*"]
}
}- 编写声明文件
typings/库名/index.d.ts:
ts
declare function indexof(arr: any[], obj: [any]): number
export = indexofdeclare function indexof(arr: any[], obj: [any]): number
export = indexofNode + 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 typescriptyarn 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-classnpm install vue-property-decorator vuex-classVue 文件写法
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'
Cao Note