装饰器
装饰器 API 可以自定义 Fastify 核心对象,例如服务器实例以及在 HTTP 请求生命周期中使用的请求和响应对象。它可以将任何类型的属性附加到核心对象上,例如函数、普通对象或原生类型。
此 API 是 同步 的。如果异步定义装饰,则可能导致 Fastify 实例在装饰完成之前启动。要注册异步装饰,请使用 register
API 和 fastify-plugin
。有关更多详细信息,请参阅 插件 文档。
使用此 API 装饰核心对象,可以让底层 JavaScript 引擎优化服务器、请求和响应对象的处理。这是通过在实例化和使用这些对象之前定义它们的所有形状来实现的。例如,以下示例不推荐使用,因为它会在对象生命周期中更改其形状:
// 不好的示例!请继续阅读。
// 在调用请求处理器之前将用户属性附加到传入的请求上。
fastify.addHook('preHandler', function (req, reply, done) {
req.user = 'Bob Dylan'
done()
})
// 在请求处理程序中使用附加的用户属性。
fastify.get('/', function (req, reply) {
reply.send(`Hello, ${req.user}`)
})
``
上述示例在实例化后更改了请求对象,导致 JavaScript 引擎去优化访问。使用装饰 API 可以避免这种去优化:
```js
// 使用 'user' 属性装饰请求
fastify.decorateRequest('user', '')
// 更新我们的属性
fastify.addHook('preHandler', (req, reply, done) => {
req.user = 'Bob Dylan'
done()
})
// 最后访问它
fastify.get('/', (req, reply) => {
reply.send(`Hello, ${req.user}!`)
})
保持装饰字段的初始形状与其未来的动态值相近。
将字符串类型的装饰器初始化为 `''`,对象或函数类型的装饰器初始化为 `null`。
这仅适用于值类型;引用类型在 Fastify 启动时会抛出错误。有关更多信息,请参阅 [decorateRequest](#decorate-请求) 和
[JavaScript 引擎基础:Shapes 和 Inline Caches](https://mathiasbynens.be/notes/shapes-ics)
。
### 使用方法
<a id="使用方法"></a>
#### `decorate(name, value, [dependencies])`
<a id="decorate"></a>
此方法自定义 Fastify [服务器](./Server.md) 实例。
例如,要将新方法附加到服务器实例:
```js
fastify.decorate('utility', function () {
// 非常有用的东西
})
非函数值也可以附加到服务器实例:
fastify.decorate('conf', {
db: 'some.db',
port: 3000
})
要访问装饰的属性,请使用提供给装饰 API 的名称:
fastify.utility()
console.log(fastify.conf.db)
装饰的 Fastify 服务器 在路由处理程序中绑定到 this
:
fastify.decorate('db', new DbConnection())
fastify.get('/', async function (request, reply) {
// 使用 return
return { hello: await this.db.query('world') }
// 或者
// 使用 reply.send()
reply.send({ hello: await this.db.query('world') })
await reply
})
dependencies
参数是一个可选的装饰器列表,定义中的装饰器依赖于这些装饰器。此列表包含其他装饰器的名称。在以下示例中,“utility”装饰器依赖于“greet”和“hi”装饰器:
async function greetDecorator (fastify, opts) {
fastify.decorate('greet', () => {
return 'greet message'
})
}
async function hiDecorator (fastify, opts) {
fastify.decorate('hi', () => {
return 'hi message'
})
}
async function utilityDecorator (fastify, opts) {
fastify.decorate('utility', () => {
return `${fastify.greet()} | ${fastify.hi()}`
})
}
fastify.register(fastifyPlugin(greetDecorator, { name: 'greet' }))
fastify.register(fastifyPlugin(hiDecorator, { name: 'hi' }))
fastify.register(fastifyPlugin(utilityDecorator, { dependencies: ['greet', 'hi'] }))
fastify.get('/', function (req, reply) {
// 响应:{"hello":"greet message | hi message"}
reply.send({ hello: fastify.utility() })
})
fastify.listen({ port: 3000 }, (err, address) => {
if (err) throw err
})
使用箭头函数会破坏 `this` 绑定到 `FastifyInstance`。
如果依赖项未满足,`decorate` 方法将抛出一个异常。依赖项检查发生在服务器实例启动之前,而不是在运行时。
#### `decorateReply(name, value, [dependencies])`
<a id="decorate-reply"></a>
此 API 向核心 `Reply` 对象添加新的方法/属性:
```js
fastify.decorateReply('utility', function () {
// 非常有用的东西
})
使用箭头函数会破坏 this
绑定到 Fastify Reply
实例的关系。
如果在引用类型上使用 decorateReply
,将会抛出错误:
// 不要这样做
fastify.decorateReply('foo', { bar: 'fizz'})
在这个示例中,对象引用会被所有请求共享,并且 任何修改都会影响到所有请求,可能会导致安全漏洞或内存泄漏。Fastify 会阻止这种情况的发生。
为了在不同请求之间实现适当的封装,请为每个传入的请求配置一个新的值,在 'onRequest'
回调 中进行设置。
const fp = require('fastify-plugin')
async function myPlugin (app) {
app.decorateReply('foo')
app.addHook('onRequest', async (req, reply) => {
reply.foo = { bar: 42 }
})
}
module.exports = fp(myPlugin)
有关 dependencies
参数的信息,请参阅 decorate
。
decorateRequest(name, value, [dependencies])
与 decorateReply
类似,此 API 向核心 Request
对象添加新的方法/属性:
fastify.decorateRequest('utility', function () {
// 某些非常有用的东西
})
使用箭头函数会破坏 this
绑定到 Fastify Request
实例的绑定。
使用 decorateRequest
时,如果与引用类型一起使用,则会发出错误:
// 不要这样做
fastify.decorateRequest('foo', { bar: 'fizz'})
在此示例中,对象引用将被所有请求共享,并且 任何修改都会影响到所有请求,可能会导致安全漏洞或内存泄漏。Fastify 会阻止这种情况。
为了在不同请求之间实现适当的封装,请为每个传入的请求配置新的值:
const fp = require('fastify-plugin')
async function myPlugin (app) {
app.decorateRequest('foo')
app.addHook('onRequest', async (req, reply) => {
req.foo = { bar: 42 }
})
}
module.exports = fp(myPlugin)
钩子解决方案更灵活,允许进行更复杂的初始化,因为可以向 onRequest
钩子添加更多逻辑。
另一种方法是使用 getter/setter 模式,但需要两个装饰器:
fastify.decorateRequest('my_decorator_holder') // 定义持有者
fastify.decorateRequest('user', {
getter () {
this.my_decorator_holder ??= {} // 初始化持有者
return this.my_decorator_holder
}
})
fastify.get('/', async function (req, reply) {
req.user.access = 'granted'
// 其他代码
})
这确保了 user
属性对于每个请求都是唯一的。
有关 dependencies
参数的信息,请参阅 decorate
。
hasDecorator(name)
用于检查服务器实例装饰的存在:
fastify.hasDecorator('utility')
hasDecorator(name)
用于检查服务器实例是否存在装饰:
fastify.hasDecorator('utility')
hasRequestDecorator
用于检查请求对象是否存在装饰:
fastify.hasRequestDecorator('utility')
hasReplyDecorator
用于检查响应对象是否存在装饰:
fastify.hasReplyDecorator('utility')
装饰器和封装
在同一个封装上下文中多次定义具有相同名称的装饰器(使用 decorate
、decorateRequest
或 decorateReply
)将会抛出异常。例如,以下代码将抛出异常:
const server = require('fastify')()
server.decorateReply('view', function (template, args) {
// 强大的视图渲染引擎
})
server.get('/', (req, reply) => {
reply.view('/index.html', { hello: 'world' })
})
// 在代码库的其他地方,我们定义了另一个
// 视图装饰器。这将抛出异常。
server.decorateReply('view', function (template, args) {
// 另一个渲染引擎
})
server.listen({ port: 3000 })
但是以下代码不会抛出异常:
const server = require('fastify')()
server.decorateReply('view', function (template, args) {
// 强大的视图渲染引擎。
})
server.register(async function (server, opts) {
// 我们在当前封装的插件中添加了一个视图装饰器。这不会抛出异常,因为在该封装插件外部 `view` 是旧的一个,在内部则是新的一个。
server.decorateReply('view', function (template, args) {
// 另一个渲染引擎
})
server.get('/', (req, reply) => {
reply.view('/index.page', { hello: 'world' })
})
}, { prefix: '/bar' })
server.listen({ port: 3000 })
获取器和设置器
装饰器接受具有 getter
和可选的 setter
函数的特殊“获取器/设置器”对象。这允许通过装饰器定义属性,例如:
fastify.decorate('foo', {
getter () {
return 'a getter'
}
})
将在 Fastify 实例上定义 foo
属性:
console.log(fastify.foo) // 'a getter'
getDecorator<T>
API
Fastify 的 getDecorator<T>
API 从 Fastify 实例、Request
或 Reply
中检索现有的装饰器。如果装饰器未定义,则会抛出一个 FST_ERR_DEC_UNDECLARED
错误。
使用场景
早期插件依赖验证
getDecorator<T>
方法在 Fastify 实例上验证所需的装饰器是否在注册时可用。
例如:
fastify.register(async function (fastify) {
const usersRepository = fastify.getDecorator('usersRepository')
fastify.get('/users', async function (request, reply) {
// 我们可以确定 `usersRepository` 在运行时存在
return usersRepository.findAll()
})
})
处理缺失的装饰器
直接访问一个未声明的装饰器可能会导致意外行为:
const user = request.user;
if (user && user.isAdmin) {
// 执行管理员任务。
}
如果 request.user
不存在,那么 user
将被设置为 undefined
。这使得不清楚用户是否未认证或装饰器缺失。
使用 getDecorator
可以强制运行时安全:
// 如果装饰器缺失,则会立即抛出一个显式的 `FST_ERR_DEC_UNDECLARED` 错误。
const user = request.getDecorator('user');
if (user && user.isAdmin) {
// 执行管理员任务。
}
模块增强的替代方案
装饰器通常通过模块增强进行类型定义:
declare module 'fastify' {
interface FastifyInstance {
usersRepository: IUsersRepository
}
interface FastifyRequest {
session: ISession
}
interface FastifyReply {
sendSuccess: SendSuccessFn
}
}
这种方法会全局修改 Fastify 实例,这可能会导致多服务器设置或多插件封装中的冲突和不一致行为。
使用 getDecorator<T>
可以限制类型的作用域:
serverOne.register(async function (fastify) {
const usersRepository = fastify.getDecorator<PostgreUsersRepository>(
'usersRepository'
)
fastify.decorateRequest('session', null)
fastify.addHook('onRequest', async (req, reply) => {
// 是的,请求对象有一个 setDecorator 方法。更多信息将很快提供。
req.setDecorator('session', { user: 'Jean' })
})
fastify.get('/me', (request, reply) => {
const session = request.getDecorator<ISession>('session')
reply.send(session)
})
})
serverTwo.register(async function (fastify) {
const usersRepository = fastify.getDecorator<SqlLiteUsersRepository>(
'usersRepository'
)
fastify.decorateReply('sendSuccess', function (data) {
return this.send({ success: true })
})
fastify.get('/success', async (request, reply) => {
const sendSuccess = reply.getDecorator<SendSuccessFn>('sendSuccess')
await sendSuccess()
})
})
绑定函数推断
为了节省时间,通常会通过推断函数类型而不是手动编写它们:
function sendSuccess (this: FastifyReply) {
return this.send({ success: true })
}
export type SendSuccess = typeof sendSuccess
然而,getDecorator
返回的函数已经绑定了 this
上下文,这意味着 this
参数会从函数签名中消失。
为了正确地对其进行类型定义,你应该使用 OmitThisParameter
工具:
function sendSuccess (this: FastifyReply) {
return this.send({ success: true })
}
type BoundSendSuccess = OmitThisParameter<typeof sendSuccess>
fastify.decorateReply('sendSuccess', sendSuccess)
fastify.get('/success', async (request, reply) => {
const sendSuccess = reply.getDecorator<BoundSendSuccess>('sendSuccess')
await sendSuccess()
})
Request.setDecorator<T>
方法
setDecorator<T>
方法提供了一种安全且方便的方式来更新 Request
装饰器的值。如果装饰器不存在,则会抛出一个 FST_ERR_DEC_UNDECLARED
错误。
使用场景
运行时安全性
设置 Request
装饰器的一种典型方式如下所示:
fastify.decorateRequest('user', '')
fastify.addHook('preHandler', async (req, reply) => {
req.user = 'Bob Dylan'
})
然而,除非你手动检查装饰器是否存在,否则无法保证装饰器确实存在。此外,拼写错误也很常见,例如 account
、acount
或 accout
。
通过使用 setDecorator
,你可以确保装饰器始终存在:
fastify.decorateRequest('user', '')
fastify.addHook('preHandler', async (req, reply) => {
// 如果装饰器不存在,则抛出 FST_ERR_DEC_UNDECLARED 错误
req.setDecorator('user-with-typo', 'Bob Dylan')
})
类型安全性
如果 FastifyRequest
接口没有声明该装饰器,你通常需要使用类型断言:
fastify.addHook('preHandler', async (req, reply) => {
(req as typeof req & { user: string }).user = 'Bob Dylan'
})
setDecorator<T>
方法消除了显式类型断言的需要,并允许保持类型安全:
fastify.addHook('preHandler', async (req, reply) => {
req.setDecorator<string>('user', 'Bob Dylan')
})