在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
TypeScript在node项目中的实践TypeScript可以理解为是JavaScript的一个超集,也就是说涵盖了所有JavaScript的功能,并在之上有着自己独特的语法。 为什么选择TS作为巨硬公司出品的一个静态强类型编译型语言,该语言已经出现了几年的时间了,相信在社区的维护下,已经是一门很稳定的语言。 而静态强类型编译能够带来很多的好处,其中最重要的一点就是可以帮助开发人员杜绝一些马虎大意的问题: 不难看出,因为类型不匹配、变量为空导致的异常比你敢承认的次数要多。 另外一个由静态编译类型带来的好处,就是函数签名。 而在TS中,对于一个函数,首先你需要定义所有参数的类型,以及返回值的类型。 这是最基础的、能够让程序更加稳定的两个特性,当然,还有更多的功能在TS中的:TypeScript | Handbook TypeScript在node中的应用在TS的官网中,有着大量的示例,其中就找到了Express版本的例子,针对这个稍作修饰,应用在了一个 koa 项目中。 环境依赖在使用TS之前,需要先准备这些东西:
以项目中使用的一些核心依赖:
项目结构首先,放出目前项目的结构: . ├── 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
controllers
鉴于公司绝大部分的Node项目版本都已经升级到了 使用 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 })
几乎每一个路由的头部都是在做着获取参数的工作,而参数很可能来自 所以,我们对原来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 } } }
为了使接口更易于检索、更清晰,所以我们抛弃了原有的 middleware如果是全局的中间件,则直接在class上添加 所有的中间件都需要继承对应的MiddlewareInterface接口,并需要实现 // 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.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}`)] // ... })
modelmodel的定位在于根据对应的实体创建抽象化的数据库对象,因为使用了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所有的公共函数,都放在这里。 // 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'
每添加一个新的 import {getUid, numberComma} from './utils'
configsconfigs下边存储的就是各种配置信息了,包括一些第三方接口URL、数据库配置、日志路径。 types这里存放的是所有的自定义的类型定义,一些开源社区没有提供的,但是我们用到的第三方插件,需要在这里进行定义,一般来说常用的都会有,但是一些小众的包可能确实没有TS的支持,例如我们有使用的一个 // 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 目前使用TS中的一些问题
目前遇到的唯一一个比较尴尬的问题就是: import module from '../../../../f**k-module'
小结初次尝试TypeScript,深深的喜欢上了这个语言,虽说也会有一些小小的问题,但还是能克服的:)。 基于上述描述的一个简单示例:代码仓库 希望大家玩得开心,如有任何TS相关的问题,欢迎来骚扰。 TypeScript在node项目中的实践
|
请发表评论