裝飾器
裝飾器
裝飾器 API 允許自訂 Fastify 核心物件,例如伺服器實例本身,以及在 HTTP 請求生命週期中使用的任何請求和回覆物件。裝飾器 API 可用於將任何類型的屬性附加到核心物件,例如函數、純物件或原生類型。
此 API 是同步的。嘗試以非同步方式定義裝飾可能會導致 Fastify 實例在裝飾完成初始化之前啟動。為了避免這個問題,並註冊一個非同步裝飾,必須改用 register
API,並結合 fastify-plugin
。要了解更多資訊,請參閱外掛程式 文件。
使用此 API 裝飾核心物件,可以讓底層 JavaScript 引擎最佳化伺服器、請求和回覆物件的處理。這是透過在實例化和使用之前,定義所有此類物件實例的形狀來完成的。例如,不建議使用以下方法,因為它會在物件的生命週期中改變物件的形狀
// Bad example! Continue reading.
// Attach a user property to the incoming request before the request
// handler is invoked.
fastify.addHook('preHandler', function (req, reply, done) {
req.user = 'Bob Dylan'
done()
})
// Use the attached user property in the request handler.
fastify.get('/', function (req, reply) {
reply.send(`Hello, ${req.user}`)
})
由於上面的範例在請求物件已經被實例化之後才修改它,因此 JavaScript 引擎必須取消最佳化對請求物件的存取。透過使用裝飾 API,可以避免這種取消最佳化
// Decorate request with a 'user' property
fastify.decorateRequest('user', '')
// Update our property
fastify.addHook('preHandler', (req, reply, done) => {
req.user = 'Bob Dylan'
done()
})
// And finally access it
fastify.get('/', (req, reply) => {
reply.send(`Hello, ${req.user}!`)
})
請注意,盡可能將裝飾欄位的初始形狀保持與未來打算動態設定的值盡可能接近是很重要的。如果預期的值是字串,則將裝飾器初始化為 ''
,如果它是物件或函數,則初始化為 null
。
請記住,此範例僅適用於值類型,因為參考類型會在 fastify 啟動期間拋出錯誤。請參閱 decorateRequest。
請參閱 JavaScript 引擎基礎知識:形狀和內嵌快取,以了解有關此主題的更多資訊。
用法
decorate(name, value, [dependencies])
此方法用於自訂 Fastify 伺服器實例。
例如,要將新方法附加到伺服器實例
fastify.decorate('utility', function () {
// Something very useful
})
如上所述,非函數值也可以附加到伺服器實例,如下所示
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) {
// using return
return { hello: await this.db.query('world') }
// or
// using 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) {
// Response: {"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])
顧名思義,此 API 用於將新的方法/屬性新增至核心 Reply
物件
fastify.decorateReply('utility', function () {
// Something very useful
})
注意:使用箭頭函數會中斷將 this
綁定到 Fastify Reply
實例。
注意:如果將 decorateReply
與參考類型一起使用,則會拋出錯誤
// Don't do this
fastify.decorateReply('foo', { bar: 'fizz'})
在此範例中,物件的參考將會與所有請求共享,任何變異都會影響所有請求,可能會產生安全性漏洞或記憶體洩漏,因此 Fastify 會阻止它。
為了在請求之間實現適當的封裝,請在 'onRequest'
hook 中為每個傳入的請求配置一個新值。範例
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)
請參閱 decorate
以了解有關 dependencies
參數的資訊。
decorateRequest(name, value, [dependencies])
如同上面的 decorateReply
,此 API 用於將新的方法/屬性新增至核心 Request
物件
fastify.decorateRequest('utility', function () {
// something very useful
})
注意:使用箭頭函數會中斷將 this
綁定到 Fastify Request
實例。
注意:如果將 decorateRequest
與參考類型一起使用,則會發出錯誤
// Don't do this
fastify.decorateRequest('foo', { bar: 'fizz'})
在此範例中,物件的參考將會與所有請求共享,任何變異都會影響所有請求,可能會產生安全性漏洞或記憶體洩漏,因此 Fastify 會阻止它。
為了在請求之間實現適當的封裝,請在 'onRequest'
hook 中為每個傳入的請求配置一個新值。
範例
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)
hook 解決方案更靈活,並允許更複雜的初始化,因為您可以在 onRequest
hook 中新增更多邏輯。
另一種方法是使用 getter/setter 模式,但它需要 2 個裝飾器
fastify.decorateRequest('my_decorator_holder') // define the holder
fastify.decorateRequest('user', {
getter () {
this.my_decorator_holder ??= {} // initialize the holder
return this.my_decorator_holder
}
})
fastify.get('/', async function (req, reply) {
req.user.access = 'granted'
// other code
})
這確保了 user
屬性對於每個請求都是唯一的。
請參閱 decorate
以了解有關 dependencies
參數的資訊。
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) {
// Amazing view rendering engine
})
server.get('/', (req, reply) => {
reply.view('/index.html', { hello: 'world' })
})
// Somewhere else in our codebase, we define another
// view decorator. This throws.
server.decorateReply('view', function (template, args) {
// Another rendering engine
})
server.listen({ port: 3000 })
但這不會
const server = require('fastify')()
server.decorateReply('view', function (template, args) {
// Amazing view rendering engine.
})
server.register(async function (server, opts) {
// We add a view decorator to the current encapsulated
// plugin. This will not throw as outside of this encapsulated
// plugin view is the old one, while inside it is the new one.
server.decorateReply('view', function (template, args) {
// Another rendering engine
})
server.get('/', (req, reply) => {
reply.view('/index.page', { hello: 'world' })
})
}, { prefix: '/bar' })
server.listen({ port: 3000 })
Getter 和 Setter
裝飾器接受特殊的「getter/setter」物件。這些物件具有名為 getter
和 setter
的函數(儘管 setter
函數是可選的)。這允許透過裝飾器定義屬性,例如
fastify.decorate('foo', {
getter () {
return 'a getter'
}
})
將會在 Fastify 實例上定義 foo
屬性
console.log(fastify.foo) // 'a getter'