TypeScript在node项目中的实践

TypeScript可以理解为是JavaScript的一个超集,也就是说涵盖了一切JavaScript的功用,并在之上有着自身奇特的语法。
近来的一个新项目最先了TS的踩坑之旅,现分享一些可以自创的套路给人人。

为何挑选TS

作为巨硬公司出品的一个静态强范例编译型言语,该言语已涌现了几年的时候了,置信在社区的保护下,已是一门很稳固的言语。
我们晓得,JavaScript是一门动态弱范例诠释型脚本言语,动态带来了许多的轻易,我们可以在代码运转中随便的修正变量范例以到达预期目标。
但同时,这是一把双刃剑,当一个巨大的项目涌现在你的眼前,面临异常庞杂的逻辑,你很难经由过程代码看出某个变量是什么范例,这个变量要做什么,极能够一不小心就会踩到坑。

而静态强范例编译可以带来许多的优点,个中最重要的一点就是可以协助开辟人员根绝一些纰漏粗心的题目:
《TypeScript在node项目中的实践》
图为rollbar统计的数千个项目中数目最多的前十个异常

不难看出,由于范例不婚配、变量为空致使的异常比你敢认可的次数要多。
比如
《TypeScript在node项目中的实践》
而这一点在TS中得到了很好的改良,任何一个变量的援用,都须要指定自身的范例,而你下边在代码中可以用什么,支撑什么要领,都须要在上边举行定义:
《TypeScript在node项目中的实践》
这个提醒会在开辟、编译期来提醒给开辟者,避免了上线今后发现有题目,再去修正。

别的一个由静态编译范例带来的优点,就是函数署名。
照样就像上边所说的,由于是一个动态的脚本言语,所以很难有编辑器可以在开辟时期正确地通知你所要挪用的一个函数须要通报什么参数,函数会返回什么范例的返回值。

《TypeScript在node项目中的实践》

而在TS中,关于一个函数,起首你须要定义一切参数的范例,以及返回值的范例。
如许在函数被挪用时,我们就可以很清楚的看到这个函数的结果:
《TypeScript在node项目中的实践》

这是最基础的、可以让顺序越发稳固的两个特征,固然,另有更多的功用在TS中的:TypeScript | Handbook

TypeScript在node中的运用

在TS的官网中,有着大批的示例,个中就找到了Express版本的例子,针对这个稍作润饰,运用在了一个 koa 项目中。

环境依靠

在运用TS之前,须要先预备这些东西:

  1. VS code,同为巨硬公司出品,自身就是TS开辟的,遂该编辑器是现在对TS支撑度最高的一个
  2. Node.js 引荐8.11版本以上
  3. npm i -g typescript,全局装置TS,编译所运用的tsc敕令在这里
  4. npm i -g nodemon,全局装置nodemon,在tsc编译后自动革新服务器顺序

以及项目中运用的一些中心依靠:

  1. reflect-metadata: 大批装潢器的包都邑依靠的一个基础包,用于注入数据
  2. routing-controllers: 运用装潢器的体式格局来举行koa-router的开辟
  3. sequelize: 抽象化的数据库操纵
  4. sequelize-typescript: 上述插件的装潢器版本,定义实体时运用

项目构造

起首,放出现在项目标构造:

.
├── README.md
├── copy-static-assets.ts
├── nodemon.json
├── package-lock.json
├── package.json
├── dist
├── src
│   ├── config
│   ├── controllers
│   ├── entity
│   ├── models
│   ├── middleware
│   ├── public
│   ├── app.ts
│   ├── server.ts
│   ├── types
│   └── utils
├── tsconfig.json
└── tslint.json

src为重要开辟目次,一切的TS代码都在这里边,在经由编译事后,会天生一个与src同级的dist文件夹,这个文件夹是node引擎现实运转的代码。
src下,重要代码分为了以下构造(依据自身项目标现实情况举行增删):

|folder|desc

1controllers用于处置惩罚接口要求,原appsroutes文件夹。
2middleware寄存了种种中间件、全局 or 自定义的中间件
3config种种设置项的位置,包含端口、log途径、种种巴拉巴拉的常量定义。
4entity这里寄存的是一切的实体定义(运用了sequelize举行数据库操纵)。
5models运用来自entity中的实体举行sequelize来完成初始化的操纵,并将sequelize对象抛出。
6utils寄存的种种一样平常开辟中提炼出来的大众函数
7types寄存了种种客制化的复合范例的定义,种种构造、属性、要领返回值的定义(现在包含经常使用的Promise版redis与qconf)

controllers

controllers只担任处置惩罚逻辑,经由过程操纵model对象,而不是数据库来举行数据的增编削查

鉴于公司绝大部分的Node项目版本都已晋级到了Node 8.11,理所应当的,我们会尝试新的语法。
也就是说我们会扬弃Generator,拥抱async/await

运用KoaExpress写过接口的童鞋应当都晓得,当一个项目变得巨大,现实上会发生许多反复的非逻辑代码:

router.get('/', ctx => {})
router.get('/page1', ctx => {})
router.get('/page2', ctx => {})
router.get('/page3', ctx => {})
router.get('/pageN', ctx => {})

而在每一个路由监听中,又做着大批反复的事情:

router.get('/', ctx => {
  let uid = Number(ctx.cookies.get('uid'))
  let device = ctx.headers['device'] || 'ios'
  let { tel, name } = ctx.query
})

险些每一个路由的头部都是在做着猎取参数的事情,而参数极能够来自headerbody以至是cookiequery

所以,我们对本来koa的运用要领举行了一个较大的修改,并运用routing-controllers大批的运用装潢器来协助我们处置惩罚大部分的非逻辑代码。

原有router的定义:

module.exports = function (router) {
  router.get('/', function* (next) {
    let uid = Number(this.cookies.get('uid'))
    let device = this.headers['device']
    
    this.body = {
      code: 200
    }
  })
}

运用了TypeScript与装潢器的定义:

@Controller
export default class {
  @Get('/')
  async index (
    @CookieParam('uid') uid: number,
    @HeaderParam('device') device: string
  ) {
    return {
      code: 200
    }
  }
}

为了使接口更易于检索、更清楚,所以我们扬弃了原有的bd-router的功用(依据文件途径作为接口途径、TS中的文件途径仅用于文件分层)。
直接在controllers下的文件中声明对应的接口举行监听。

middleware

假如是全局的中间件,则直接在class上增加@Middleware装潢器,并设置type: 'after|before'即可。
假如是特定的一些中间件,则竖立一个一般的class即可,然后在须要运用的controller对象上指定@UseBefore/@UseAfter(可以写在class上,也可以写在method上)。

一切的中间件都须要继续对应的MiddlewareInterface接口,并须要完成use要领

// middleware/xxx.ts
import {ExpressMiddlewareInterface} from "../../src/driver/express/ExpressMiddlewareInterface"

export class CompressionMiddleware implements KoaMiddlewareInterface {
  use(request: any, response: any, next?: Function): any {
    console.log("hello compression ...")
    next()
  }
}

// controllers/xxx.ts
@UseBefore(CompressionMiddleware)
export default class { }

entity

文件只担任定义数据模子,不做任何逻辑操纵

一样的运用了sequelize+装潢器的体式格局,entity只是用来竖立与数据库之间通信的数据模子。

import { Model, Table, Column } from 'sequelize-typescript'

@Table({
  tableName: 'user_info_test'
})
export default class UserInfo extends Model<UserInfo> {
  @Column({
    comment: '自增ID',
    autoIncrement: true,
    primaryKey: true
  })
  uid: number

  @Column({
    comment: '姓名'
  })
  name: string

  @Column({
    comment: '岁数',
    defaultValue: 0
  })
  age: number

  @Column({
    comment: '性别'
  })
  gender: number
}

由于sequelize竖立衔接也是须要对应的数据库地点、账户、暗码、database等信息、所以引荐将同一个数据库的一切实体放在一个目次下,轻易sequelize加载对应的模子
同步的引荐在config下竖立对应的设置信息,并增加一列用于寄存实体的key。
如许在竖立数据库链接,加载数据模子时就可以动态的导入该途径下的一切实体:

// config.ts
export const config = {
  // ...
  mysql1: {
    // ... config
+   entity: 'entity1' // 增加一列用来标识是什么实体的key
  },
  mysql2: {
    // ... config
+   entity: 'entity2' // 增加一列用来标识是什么实体的key
  }
  // ...
}

// utils/mysql.ts
new Sequelize({
  // ...
  modelPath: [path.reolve(__dirname, `../entity/${config.mysql1.entity}`)]
  // ...
})

model

model的定位在于依据对应的实体竖立抽象化的数据库对象,由于运用了sequelize,所以该目次下的文件会变得异常简约。
基础就是初始化sequelize对象,并在加载模子后将其抛出。

export default new Sequelize({
  host: '127.0.0.1',
  database: 'database',
  username: 'user',
  password: 'password',
  dialect: 'mysql', // 或许一些其他的数据库
  modelPaths: [path.resolve(__dirname, `../entity/${configs.mysql1.entity}`)], // 加载我们的实体
  pool: { // 衔接池的一些相干设置
    max: 5,
    min: 0,
    acquire: 30000,
    idle: 10000
  },
  operatorsAliases: false,
  logging: true // true会在控制台打印每次sequelize操纵时对应的SQL敕令
})

utils

一切的大众函数,都放在这里。
同时引荐编写对应的索引文件(index.ts),大抵的花样以下:

// utils/get-uid.ts
export default function (): number {
  return 123
}

// utils/number-comma.ts
export default function(): string {
  return '1,234'
}

// utils/index.ts
export {default as getUid} from './get-uid'
export {default as numberComma} from './number-comma'

每增加一个新的util,就去index中增加对应的索引,如许带来的优点就是可以经由过程一行来引入一切想引入的utils

import {getUid, numberComma} from './utils'

configs

configs下边存储的就是种种设置信息了,包含一些第三方接口URL、数据库设置、日记途径。
种种balabala的静态数据。
假如设置文件多的话,发起拆分为多个文件,然后根据utils的体式格局编写索引文件。

types

这里寄存的是一切的自定义的范例定义,一些开源社区没有供应的,然则我们用到的第三方插件,须要在这里举行定义,一般来说经常使用的都邑有,然则一些小众的包能够确切没有TS的支撑,比方我们有运用的一个node-qconf

// types/node-qconf.d.ts
export function getConf(path: string): string | null
export function getBatchKeys(path: string): string[] | null
export function getBatchConf(path: string): string | null
export function getAllHost(path: string): string[] | null
export function getHost(path: string): string | null

范例定义的文件划定后缀为 .d.ts
types下边的一切文件可以直接援用,而不必体贴相对途径的题目(其他一般的model则须要写相对途径,这是一个很为难的题目)。

现在运用TS中的一些题目

《TypeScript在node项目中的实践》
当前GitHub堆栈中,有2600+的开启状况的issues,挑选bug标签后,依旧有900+的存在。
所以很难保证在运用的过程当中不会踩坑,然则一个项目具有这么多活泼的issues,也能从正面申明这个项目标受欢迎水平。

现在碰到的唯一一个比较为难的题目就是:
援用文件途径一定要写全。。

import module from '../../../../f**k-module'

小结

首次尝试TypeScript,深深的喜好上了这个言语,虽然说也会有一些小小的题目,但照样能克服的:)。
运用一门静态强范例编译言语,可以将许多bug都祛除在开辟时期。

基于上述形貌的一个简朴示例:代码堆栈

愿望人人玩得高兴,若有任何TS相干的题目,欢迎来骚扰。NPM loves U.

    原文作者:贾顺名
    原文地址: https://segmentfault.com/a/1190000015719697
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞