什么是 Hooks?
在 Sequelize 中,Hooks 是一个非常重要的概念。Hooks 可以让你在执行数据库操作之前或之后执行一些自定义的逻辑。可以用 Hooks 来实现一些非常强大的功能,例如:
- 在创建一个新纪录之前生成一个唯一的 ID。
- 在更新一条记录之前进行数据校验。
- 在删除一条记录之前进行权限验证。
如何使用 Model 实例的 Hooks?
Sequelize 中的 Hooks 可以被定义在 Model 层或实例层。在 Model 层定义的 Hook 将会应用于该 Model 的所有实例,而在实例层定义的 Hook 则仅仅应用于该实例。
在 Model 层定义 Hooks
在 Model 层定义 Hooks 非常简单,只需要在 Model 的定义中添加相应的钩子函数即可。下面的例子将展示如何在 Model 中定义 beforeCreate
和 beforeUpdate
钩子。
// javascriptcn.com 代码示例 const { Sequelize, DataTypes } = require('sequelize'); const sequelize = new Sequelize('sqlite::memory:'); const User = sequelize.define('User', { username: { type: DataTypes.STRING, allowNull: false }, password: { type: DataTypes.STRING, allowNull: false } }, { hooks: { beforeCreate: (user, options) => { user.password = encryptPassword(user.password); }, beforeUpdate: (user, options) => { if (user.changed('password')) { user.password = encryptPassword(user.password); } } } });
上面的例子将用户的密码加密操作放在了 beforeCreate
和 beforeUpdate
钩子中。注意,钩子函数的参数是一个 Model 实例,可以通过这个实例进行数据的修改和校验。
在实例层定义 Hooks
在实例层定义 Hook 就需要实例化一个 Model 后,再调用实例对象的 addHook()
方法。下面的例子将展示如何在实例层定义 beforeDestroy
钩子。
// javascriptcn.com 代码示例 const user = await User.findByPk(1); user.addHook('beforeDestroy', async () => { // 在删除用户之前,判断是否有未过期的订单 const orders = await user.getOrders({ where: { expiredAt: { [Op.gte]: new Date() } } }); if (orders.length > 0) { throw new Error('该用户存在未过期订单,不能删除!'); } });
上面的例子在用户删除之前,判断该用户是否存在未过期订单。如果存在,则抛出错误,禁止删除该用户。
Hooks 的一些注意事项
错误处理与异步钩子
Sequelize 中的 Hooks 函数默认是同步调用,那么如果需要在 Hooks 函数中进行异步操作(如数据库查询、网络请求等),则需要将 Hooks 函数改为异步函数。并且需要注意,Hooks 函数中如果抛出错误,Sequelize 会自动将错误包装成一个 SequelizeHookError
对象抛出。
// javascriptcn.com 代码示例 const User = sequelize.define('User', { username: { type: DataTypes.STRING, allowNull: false }, password: { type: DataTypes.STRING, allowNull: false } }, { hooks: { beforeCreate: async (user, options) => { const existingUser = await User.findOne({ where: { username: user.username } }); if (existingUser) { throw new Error('该用户名已被占用!'); } user.password = await encryptPassword(user.password); } } });
上面的例子中,如果新建用户时,用户名已经存在,则抛出错误,否则执行密码加密操作。注意,这里的 beforeCreate
钩子是一个异步函数。
Hook 的执行顺序
Sequelize 中的 Hook 可以定义多个,但是执行顺序并不是固定的。Hook 的执行顺序有多种,具体执行顺序取决于每个 Hook 的优先级和定义顺序。默认情况下,Sequelize 中 Hook 的执行顺序如下:
- beforeBulkCreate
- beforeBulkUpdate
- beforeValidate
- afterValidate
- beforeCreate
- beforeUpdate
- beforeDestroy
- afterCreate
- afterUpdate
- afterDestroy
- afterBulkCreate
- afterBulkUpdate
即先执行 before
钩子,然后执行 after
钩子。在同一类钩子之间,执行顺序取决于定义的顺序。
取消 Hook 的执行
有些情况下,我们需要取消某个 Hook 的执行,例如用户不存在时,不需要执行 beforeUpdate
钩子。Sequelize 提供了 removeHook()
方法和 options.hooks
来取消 Hook 的执行。使用 removeHook()
方法可以对特定的 Model 实例取消某个 Hook 的执行,而 options.hooks
可以在 Model 的定义中取消特定的 Hook 执行。下面的例子演示如何取消 beforeUpdate
Hook 的执行。
// javascriptcn.com 代码示例 const User = sequelize.define('User', { username: { type: DataTypes.STRING, allowNull: false }, password: { type: DataTypes.STRING, allowNull: false } }, { hooks: { beforeCreate: (user, options) => { user.password = encryptPassword(user.password); }, beforeUpdate: (user, options) => { if (options.ignoreHook) { return; } user.password = encryptPassword(user.password); } } }); const user = await User.findByPk(1); user.update({ password: 'newpassword' }, { ignoreHook: true });
上面的例子中,当传入 options.ignoreHook
为 true
时,就不会执行 beforeUpdate
钩子。这样就可以在某些情况下取消 Hook 的执行,实现更加复杂的业务逻辑。
总结
Hooks 在 Sequelize 中是非常重要的概念,它可以让你在对数据库进行操作之前或之后执行自定义的逻辑。使用 Hooks 可以实现一些非常强大的功能,例如数据校验、权限控制等。在使用 Hooks 时,需要注意 Hooks 的执行顺序、异步操作以及如何取消 Hooks 的执行。掌握 Hooks 的使用技巧,可以让开发者更加高效、灵活的操作数据库。
来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/652b976f7d4982a6ebd64a63