yuheng/doc/fastify-docs/docs/Decorators.md
2025-03-19 15:54:28 +08:00

7.8 KiB
Raw Blame History

Fastify

装饰器

装饰器 API 允许你自定义服务器实例或请求周期中的请求/回复等对象。任意类型的属性都能通过装饰器添加到这些对象上,包括函数、普通对象 (plain object) 以及原生类型。

装饰器 API 是 同步 的。如果异步地添加装饰器,可能会导致在装饰器完成初始化之前, Fastify 实例就已经引导完毕了。因此,必须将 register 方法与 fastify-plugin 结合使用。详见 Plugins

通过装饰器 API 来自定义对象,底层的 Javascript 引擎便能对其进行优化。这是因为引擎能在所有的对象实例被初始化与使用前,定义好它们的形状 (shape)。下文的例子则是不推荐的做法,因为它在对象的生命周期中修改了它们的形状:

// 不推荐的写法!请继续阅读。

// 在调用请求处理函数之前
// 将 user 属性添加到请求上。
fastify.addHook("preHandler", function (req, reply, done) {
  req.user = "Bob Dylan";
  done();
});

// 在处理函数中使用 user 属性。
fastify.get("/", function (req, reply) {
  reply.send(`Hello, ${req.user}`);
});

由于上述例子在请求对象初始化完成后,还改动了它的形状,因此 JavaScript 引擎必须对该对象去优化。使用装饰器 API 能避开去优化问题:

// 使用装饰器为请求对象添加 '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

请注意,上述例子仅可用于基本类型的值,因为引用类型会在所有请求中共享。详见 decorateRequest

更多此话题的内容,请见 JavaScript engine fundamentals: Shapes and Inline Caches

使用方法

decorate(name, value, [dependencies])

该方法用于自定义 Fastify server 实例。

例如,为其添加一个新方法:

fastify.decorate("utility", function () {
  // 新功能的代码
});

正如上文所述,还可以传递非函数的值:

fastify.decorate("conf", {
  db: "some.db",
  port: 3000,
});

通过装饰属性的名称便可访问值:

fastify.utility();

console.log(fastify.conf.db);

路由函数的 this 指向 Fastify server

fastify.decorate("db", new DbConnection());

fastify.get("/", async function (request, reply) {
  reply({ hello: await this.db.query("world") });
});

可选的 dependencies 参数用于指定当前装饰器所依赖的其他装饰器列表。这个列表包含了其他装饰器的名称字符串。在下面的例子里,装饰器 "utility" 依赖于 "greet" 和 "log"

fastify.decorate("utility", fn, ["greet", "log"]);

注:使用箭头函数会破坏 FastifyInstancethis 绑定。

一旦有依赖项不满足,decorate 方法便会抛出异常。依赖项检查是在服务器实例启动前进行的,因此,在运行时不会发生异常。

decorateReply(name, value, [dependencies])

顾名思义,decorateReplyReply 核心对象添加新的方法或属性:

fastify.decorateReply("utility", function () {
  // 新功能的代码
});

注:使用箭头函数会破坏 this 和 Fastify Reply 实例的绑定。

注:使用 decorateReply 装饰引用类型,会触发警示:

// 反面教材
fastify.decorateReply("foo", { bar: "fizz" });

在这个例子里,该对象的引用存在于所有请求中,导致任何更改都会影响到所有请求,并可能触发安全漏洞或内存泄露。要合适地封装请求对象,请在 'onRequest' 钩子里设置新的值。示例如下:

const fp = require("fastify-plugin");

async function myPlugin(app) {
  app.decorateRequest("foo", null);
  app.addHook("onRequest", async (req, reply) => {
    req.foo = { bar: 42 };
  });
}

module.exports = fp(myPlugin);

关于 dependencies 参数,请见 decorate

decorateRequest(name, value, [dependencies])

同理,decorateRequestRequest 核心对象添加新的方法或属性:

fastify.decorateRequest("utility", function () {
  // 新功能的代码
});

注:使用箭头函数会破坏 this 和 Fastify Request 实例的绑定。

注:使用 decorateRequest 装饰引用类型,会触发警示:

// 反面教材
fastify.decorateRequest("foo", { bar: "fizz" });

在这个例子里,该对象的引用存在于所有请求中,导致任何更改都会影响到所有请求,并可能触发安全漏洞或内存泄露。要合适地封装请求对象,请在 'onRequest' 钩子里设置新的值。示例如下:

const fp = require("fastify-plugin");

async function myPlugin(app) {
  app.decorateRequest("foo", null);
  app.addHook("onRequest", async (req, reply) => {
    req.foo = { bar: 42 };
  });
}

module.exports = fp(myPlugin);

关于 dependencies 参数,请见 decorate

hasDecorator(name)

用于检查服务器实例上是否存在某个装饰器:

fastify.hasDecorator("utility");

hasRequestDecorator

用于检查 Request 实例上是否存在某个装饰器:

fastify.hasRequestDecorator("utility");

hasReplyDecorator

用于检查 Reply 实例上是否存在某个装饰器:

fastify.hasReplyDecorator("utility");

装饰器与封装

封装 的同一个上下文中,如果通过 decoratedecorateRequest 以及 decorateReply 多次定义了一个同名的的装饰器,将会抛出一个异常。

下面的示例会抛出异常:

const server = require("fastify")();
server.decorateReply("view", function (template, args) {
  // 页面渲染引擎的代码。
});
server.get("/", (req, reply) => {
  reply.view("/index.html", { hello: "world" });
});
// 当在其他地方定义
// view 装饰器时,抛出异常。
server.decorateReply("view", function (template, args) {
  // 另一个渲染引擎。
});
server.listen(3000);

但下面这个例子不会抛异常:

const server = require("fastify")();
server.decorateReply("view", function (template, args) {
  // 页面渲染引擎的代码。
});
server.register(
  async function (server, opts) {
    // 我们在当前封装的插件内添加了一个 view 装饰器。
    // 这么做不会抛出异常。
    // 因为插件外部和内部的 view 装饰器是不一样的。
    server.decorateReply("view", function (template, args) {
      // another rendering engine
    });
    server.get("/", (req, reply) => {
      reply.view("/index.page", { hello: "world" });
    });
  },
  { prefix: "/bar" },
);
server.listen(3000);

Getter 和 Setter

装饰器接受特别的 "getter/setter" 对象。这些对象拥有着名为 gettersetter 的函数 (尽管 setter 是可选的)。这么做便可以通过装饰器来定义属性。例如:

fastify.decorate("foo", {
  getter() {
    return "a getter";
  },
});

上例会在 Fastify 实例中定义一个 foo 属性:

console.log(fastify.foo); // 'a getter'