0Pricing
TypeScript Academy · 课时

类型安全的 emit 与 on

在发送和监听事件时,确保负载类型正确。

类型安全的 emit 与 on 是 CoddyKit 上的免费 TypeScript Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 TypeScript Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 TypeScript Academy 课程共包含 4 节课。

类型安全的 emit 签名

让 emit 使用事件名称 K 作为泛型。这样,负载参数就是 Events[K],事件名称和负载必须匹配。

interface Events { login: { userId: string }; logout: void }
function emit<K extends keyof Events>(event: K, payload: Events[K]) {
  console.log("emit", event, JSON.stringify(payload));
}
emit("login", { userId: "u1" });

错误负载会导致编译错误

如果负载与事件名称不匹配,TypeScript 就会拒绝它。泛型将这两个参数关联起来。

interface Events { login: { userId: string } }
function emit<K extends keyof Events>(event: K, payload: Events[K]) {}
// emit("login", { id: "u1" }); // Error: id is not userId
emit("login", { userId: "u1" });
console.log("ok");

未知事件名称会被拒绝

由于 K extends keyof Events,您无法发出不在映射中的事件。

interface Events { login: { userId: string } }
function emit<K extends keyof Events>(event: K, payload: Events[K]) {}
// emit("signup", { userId: "x" }); // Error: "signup" not a key
emit("login", { userId: "x" });
console.log("only known events");

使用 on 的类型化处理函数

on 方法会注册一个处理函数,其参数正是 Events[K],因此处理函数接收到的负载具有正确的类型。

interface Events { login: { userId: string }; logout: void }
function on<K extends keyof Events>(event: K, handler: (payload: Events[K]) => void) {
  console.log("subscribed to", event);
}
on("login", (p) => console.log(p.userId));

处理函数参数会被推断

您无需为处理函数参数添加注解;TypeScript 会根据事件名称推断其类型,使处理函数体内立即拥有完整的自动补全。

interface Events { message: { text: string } }
function on<K extends keyof Events>(event: K, handler: (p: Events[K]) => void) {}
on("message", (p) => {
  // p is { text: string }
  console.log(p.text.toUpperCase());
});

处理没有负载的事件

对于 void 事件,处理函数只需不接受有用的参数。您可以在不提供数据的情况下调用对应的处理函数。

interface Events { logout: void }
function on<K extends keyof Events>(event: K, handler: (p: Events[K]) => void) {}
on("logout", () => console.log("logged out"));

组合使用 emit 和 on

emit 和 on 结合起来就构成了类型化的发布/订阅系统。发出事件会将类型正确的负载传递给其处理函数。

interface Events { ping: { at: number } }
type Handler<K extends keyof Events> = (p: Events[K]) => void;
const handlers: { ping: Handler<"ping">[] } = { ping: [] };
function on<K extends keyof Events>(e: K, h: Handler<K>) { handlers[e].push(h as Handler<"ping">); }
function emit<K extends keyof Events>(e: K, p: Events[K]) { handlers[e].forEach(h => h(p as Events["ping"])); }
on("ping", p => console.log("at", p.at));
emit("ping", { at: 5 });

约束负载参数位置

泛型确保负载参数会根据所选事件进行检查,从而在调用位置而不是处理函数内部深处捕获不匹配。

interface Events { add: { x: number; y: number } }
function emit<K extends keyof Events>(e: K, p: Events[K]) {
  return e;
}
emit("add", { x: 1, y: 2 });
// emit("add", { x: 1 }); // Error: y missing
console.log("payload checked");

自动补全事件名称

由于第一个参数是 keyof Events,编辑器会在您输入调用时自动补全有效的事件名称。

interface Events { open: void; close: void }
function emit<K extends keyof Events>(e: K, p: Events[K]) {}
// Typing emit(" suggests "open" and "close"
emit("open", undefined as void);
console.log("autocompleted names");

泛型为何不可或缺

没有泛型时,您需要为每个事件编写重载。一个泛型签名就能自动适应任意数量的事件。

interface Events { a: { x: number }; b: { y: string } }
function emit<K extends keyof Events>(e: K, p: Events[K]) { return p; }
console.log(emit("a", { x: 1 }), emit("b", { y: "hi" }));

可复用的模式

emit<K>(event, payload) 和 on<K>(event, handler) 这一模式是所有类型安全事件发射器的核心;下一步,我们会将其封装到一个类中。

interface Events { tick: { n: number } }
function emit<K extends keyof Events>(e: K, p: Events[K]) { return e; }
function on<K extends keyof Events>(e: K, h: (p: Events[K]) => void) { return e; }
console.log(emit("tick", { n: 1 }), on("tick", p => p.n));

快速检查:类型化的 emit 和 on

请测试您对类型安全的 emit 和 on 的理解。

回顾:类型安全的 emit 和 on

您构建了泛型 emit 和 on 签名,其中负载是 Events[K],因此事件名称可以自动补全,错误负载会成为编译错误,处理函数参数也会被推断。

interface Events { login: { userId: string } }
function emit<K extends keyof Events>(e: K, p: Events[K]) { return e; }
console.log(emit("login", { userId: "u1" }));

常见问题解答

「类型安全的 emit 与 on」课时是免费的吗?

是的 — 「类型安全的 emit 与 on」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 TypeScript Academy 课程的其余内容,请升级到 CoddyKit PRO。 TypeScript Academy 课程共包含 4 节课。

「类型安全的 emit 与 on」这节课中我会学到什么?

在发送和监听事件时,确保负载类型正确。 你通过在浏览器中直接运行的动手代码来练习 TypeScript Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 TypeScript Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 TypeScript Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。

「类型安全的 emit 与 on」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 TypeScript Academy 课中编写并运行代码吗?

能。每节 TypeScript Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 事件映射的类型标注
  2. 类型安全的 emit 与 on
  3. 泛型事件发射器类
  4. 推断监听器参数
← 返回 TypeScript Academy