使用TS+Sequelize实现更简洁的CRUD (2)

Sequelize-typescript是基于Sequelize针对TypeScript所实现的一个增强版本,抛弃了之前繁琐的模型定义,使用装饰器直接达到我们想到的目的。

Sequelize-typescript的使用方式

首先因为是用到了TS,所以环境依赖上要安装的东西会多一些:

# 这里采用ts-node来完成举例 npm i ts-node typescript npm i sequelize reflect-metadata sequelize-typescript

其次,还需要修改TS项目对应的tsconfig.json文件,用来让TS支持装饰器的使用:

{ "compilerOptions": { + "experimentalDecorators": true, + "emitDecoratorMetadata": true } }

然后就可以开始编写脚本来进行开发了,与Sequelize不同之处基本在于模型定义的地方:

// /modles/animal.ts import { Table, Column, Model } from 'sequelize-typescript' @Table({ tableName: 'animal' }) export class Animal extends Model<Animal> { @Column({ primaryKey: true, autoIncrement: true, }) id: number @Column name: string @Column weight: number } // 创建与数据库的链接、初始化模型 // app.ts import path from 'path' import { Sequelize } from 'sequelize-typescript' import Animal from './models/animal' const sequelize = new Sequelize('mysql://root:jarvis@127.0.0.1:3306/ts_test') sequelize.addModels([path.resolve(__dirname, `./models/`)]) // 查询 const results = await Animal.findAll({ raw: true, }) // 新增 const name = 'Niko' const weight = 70 await Animal.create({ name, weight, })

与普通的Sequelize不同的有这么几点:

模型的定义采用装饰器的方式来定义

实例化Sequelize对象时需要指定对应的model路径

模型相关的一系列方法都是支持Promise的

如果在使用过程中遇到提示XXX used before model init,可以尝试在实例化前边添加一个await操作符,等到与数据库的连接建立完成以后再进行操作

但是好像看起来这样写的代码相较于Sequelize多了不少呢,而且至少需要两个文件来配合,那么这么做的意义是什么的?
答案就是OOP中一个重要的理念:继承

使用Sequelize-typescript实现模型的继承

因为TypeScript的核心开发人员中包括C#的架构师,所以TypeScript中可以看到很多类似C#的痕迹,在模型的这方面,我们可以尝试利用继承减少一些冗余的代码。

比如说我们基于animal表又有了两张新表,dog和bird,这两者之间肯定是有区别的,所以就有了这样的定义:

CREATE TABLE dog ( id INT AUTO_INCREMENT, name VARCHAR(14) NOT NULL, weight INT NOT NULL, leg INT NOT NULL, PRIMARY KEY (`id`) ); CREATE TABLE bird ( id INT AUTO_INCREMENT, name VARCHAR(14) NOT NULL, weight INT NOT NULL, wing INT NOT NULL, claw INT NOT NULL, PRIMARY KEY (`id`) );

关于dog我们有一个腿leg数量的描述,关于bird我们有了翅膀wing和爪子claw数量的描述。
特意让两者的特殊字段数量不同,省的有杠精说可以通过添加type字段区分两种不同的动物 :p

如果要用Sequelize的方式,我们就要将一些相同的字段定义define三遍才能实现,或者说写得灵活一些,将define时使用的Object抽出来使用Object.assign的方式来实现类似继承的效果。

但是在Sequelize-typescript就可以直接使用继承来实现我们想要的效果:

// 首先还是我们的Animal模型定义 // /models/animal.ts import { Table, Column, Model } from 'sequelize-typescript' @Table({ tableName: 'animal' }) export default class Animal extends Model<Animal> { @Column({ primaryKey: true, autoIncrement: true, }) id: number @Column name: string @Column weight: number } // 接下来就是继承的使用了 // /models/dog.ts import { Table, Column, Model } from 'sequelize-typescript' import Animal from './animal' @Table({ tableName: 'dog' }) export default class Dog extends Animal { @Column leg: number } // /models/bird.ts import { Table, Column, Model } from 'sequelize-typescript' import Animal from './animal' @Table({ tableName: 'bird' }) export default class Bird extends Animal { @Column wing: number @Column claw: number }

有一点需要注意的:每一个模型需要单独占用一个文件,并且采用export default的方式来导出
也就是说目前我们的文件结构是这样的:

├── models │   ├── animal.ts │   ├── bird.ts │   └── dog.ts └── app.ts

得益于TypeScript的静态类型,我们能够很方便地得知这些模型之间的关系,以及都存在哪些字段。
在结合着VS Code开发时可以得到很多动态提示,类似findAll,create之类的操作都会有提示:

Animal.create<Animal>({ abc: 1, // ^ abc不是Animal已知的属性 }) 通过继承来复用一些行为

上述的例子也只是说明了如何复用模型,但是如果是一些封装好的方法呢?
类似的获取表中所有的数据,可能一般情况下获取JSON数据就够了,也就是findAll({raw: true})
所以我们可以针对类似这样的操作进行一次简单的封装,不需要开发者手动去调用findAll:

// /models/animal.ts import { Table, Column, Model } from 'sequelize-typescript' @Table({ tableName: 'animal' }) export default class Animal extends Model<Animal> { @Column({ primaryKey: true, autoIncrement: true, }) id: number @Column name: string @Column weight: number static async getList () { return this.findAll({raw: true}) } } // /app.ts // 这样就可以直接调用`getList`来实现类似的效果了 await Animal.getList() // 返回一个JSON数组

同理,因为上边我们的两个Dog和Bird继承自Animal,所以代码不用改动就可以直接使用getList了。

const results = await Dog.getList() results[0].leg // TS提示错误

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsxddd.html