← 返回目录

第 2 卷

第 2 / 4 卷日期:

// 来自官方 cordis-tutorial 第二章的 lifecycle.ts
import type { Context } from '@deepseek-ai/cordis'

export const name = 'lifecycle-demo'

// 一个子插件:启动心跳定时器,并返回关闭它的清理函数
function heartbeat(ctx: Context) {
  console.log('heartbeat plugin loading')
  ctx.effect(() => {
    const timer = setInterval(() => console.log('tick'), 200)
    return () => {
      clearInterval(timer)
      console.log('heartbeat cleaned up')
    }
  })
}

export function apply(ctx: Context) {
  // 挂载子插件,并保留它的 fiber(之后好单独卸载它)
  const fiber = ctx.plugin(heartbeat)
  // 0.7 秒后卸载 heartbeat,演示 effect 的清理
  ctx.effect(() => {
    const timer = setTimeout(async () => {
      await fiber.dispose()
      console.log('disposed')
      process.exit(0)
    }, 700)
    return () => clearTimeout(timer)
  })
}

它的输出是这样的:

heartbeat plugin loading
tick
tick
tick
heartbeat cleaned up
disposed

这仍然是一段很简单的代码,从 heartbeat plugin loading 到 heartbeat cleaned up,代码里没有任何手动调用 clearInterval 的地方,它只是作为 ctx.effect 的返回值被“提供”给了框架,框架就在 fiber.dispose() 那一刻替你执行了。你写“定时器”

(setInterval),同时写“清理定时器”(clearInterval),框架保证“清理定时器”一定会在卸载时被执行。

内置 Effect

看到这里,你已经会用 ctx.effect() 了。但其实,大多数时候,我们都不需要亲自编写 ctx.effect(),因为所有 Cordis 内置的注册 API 本身已经是 effect。只有那些 Cordis 不管理的资源,我们才应在 ctx.effect() 内获取它,并返回用来释放资源的 disposer。此后 Cordis 会确保在卸载期间调用该释放逻辑。

Registrations made through Cordis APIs are effects, and are undone when their owning plugin unloads.

(通过 Cordis API 做的注册都是副作用,会在所属插件卸载时被撤销)。

内置操作它背后的effect语义
ctx.on(event, listener)监听器会在插件卸载时自动移除
ctx.plugin(child)子插件会随父插件一同被卸载
服务注册(如ctx.provide(...))返回的disposer附着到调用插件上,自动撤销

撤销链路的完整路径

以下源码基于 @deepseek-ai/cordis 4.0.1 撰写,所有代码均针对该版本验证。该包是 dsh 从 Cordis fork 并 re-scope 的版本,版本号独立推进至 4.0.1,与上游 npm 的 cordis@4.0.0-rc.8 对应(核心 API 语义一致)。选择该版本是为了与后续 dsh 章节保持运行时一致。

藏在别处)。从这里,我们可以看到“撤销”这件事的起点。

第一环:启动撤销或卸载

// fiber.ts 里 Fiber 构造器中 dispose 的定义(节选)
this.dispose = parent.fiber.effect(() => {
  return async () => {        // ← 这个清理函数,就是 fiber.dispose
    this._setEpoch(INACTIVE)        // (这里先跳过)
    if (!this.inertia) {
      this._updateState(() => {
        this.inertia = this._unload()  // ① 触发真正的卸载
        return FiberState.UNLOADING    // ② 把 fiber 的状态切成
      })
    }
  }
}, 'ctx.plugin()')

真正的撤销逻辑在 _unload 方法中,这里我们不急着往下走,先介绍两个新概念:Fiber 和 FiberState。插件是我们写的代码,而 Fiber 是这段代码被 ctx.plugin(...) 挂载后、框架为它创建的那个运行时实例,你每挂载一次插件,框架就创建出一个 Fiber 来管它的运行。

FiberState 就是这个 Fiber 的状态机:从生到死是 PENDING(等待依赖)→ LOADING (加载中)→ ACTIVE(运行中)→ UNLOADING(卸载中)→ DISPOSED(已销毁)(中途可能 FAILED 失败),dispose 触发的就是“进入 UNLOADING(卸载中)”这一步。

第二环:逆序并发执行 Fiber 名下所有 disposer

走到 _unload() 里:

// fiber.ts 里 _unload() 的核心(节选)
private async _unload() {
全站分卷索引 · 4 卷
  1. 1第 1 卷
  2. 2第 2 卷
  3. 3第 3 卷
  4. 4第 4 卷