Skip to content

Latest commit

 

History

History
123 lines (89 loc) · 9.58 KB

File metadata and controls

123 lines (89 loc) · 9.58 KB

生命周期与就绪时序

文档中心 · 架构图 · 实现细节

Dimina 的生命周期跨越 service、render 和 container 三个执行环境。本文记录当前实现中对业务最重要的顺序与就绪边界;父子组件或同级组件之间没有在本文声明的顺序,不应作为业务依赖。

1. App 生命周期

场景 调用
冷启动并创建 App 实例 App.onLaunch(options) → App.onShow(options)
宿主切到前台 App.onShow(options)
宿主切到后台 App.onHide()

App 实例在同一小程序逻辑运行时中只创建一次。页面切换不会重新触发 onLaunch;只有逻辑运行时被销毁并重建后才会再次创建 App。

2. 首次打开页面

sequenceDiagram
    participant C as Container
    participant S as Service
    participant R as Render

    C->>S: resourceLoaded(pagePath, query)
    S->>S: 创建 Page 实例
    S->>R: firstRender
    S->>R: 页面初始数据
    C->>S: pageShow(紧随 resourceLoaded 的同通道消息)
    S->>S: 暂存首次显示

    R->>R: 创建 Vue 页面与自定义组件
    R->>S: mC:创建组件实例
    S->>S: component created
    R->>S: mA:组件节点已 mounted
    S->>S: component attached(父组件优先)
    R->>S: mR:组件首轮视图更新完成
    S->>S: 暂存 component ready
    R->>S: pageAttached:初始组件已挂载
    S->>S: Page.onLoad
    S->>S: component pageLifetimes.show
    S->>S: Page.onShow
    S->>S: component ready(子组件优先)
    R->>S: pageReady:页面根视图已挂载
    S->>S: 等待已初始化组件全部 ready
    S->>S: Page.onReady
Loading

pageShow 由容器的真实页面可见状态驱动。Web 与 Android 容器会在双线程资源未就绪时缓存最新可见状态,先发送 resourceLoaded,再通过同一 service 通道发送 pageShow 或 pageHide;iOS 与 Harmony 的早到事件由 service 暂存。

每次 Web / Android start 都会生成独立的 resourceLoadId。service 和 render 回传同一标识,容器会丢弃已销毁实例或前一次加载的延迟确认。service 不根据“页面已创建且未隐藏”推测首次显示,只消费容器信号并对重复事件去重。

首次 pageShow 还要等待 render 的 pageAttached 握手。即使 mR 或 pageReady 先到,service 也会先完成 onLoad 和 onShow,再释放组件 ready 与页面 onReady。可依赖的顺序是 onLoad → onShow → 组件 ready → onReady,各阶段边界如下:

  • onLoad:页面实例已创建,可以读取路由参数、初始化状态并调用 setData();此时不能假设 DOM 已存在。
  • onShow:页面已经进入前台,但不保证第一次渲染完成。
  • 组件 ready:该组件的 render 实例已挂载。
  • onReady:页面根视图已挂载,并且当前已初始化的自定义组件都已执行 ready。

3. 页面与组件顺序

当前页面初始化分为三个阶段:

  1. service 先建立 Page 实例并发送基础初始数据,但暂不执行 onLoad;render 因而可以先创建真实的页面组件树。
  2. render 根据页面模板创建自定义组件;先注册该组件的初始数据监听,再通过 mC 请求 service 建立实例并执行 created。render 节点 mounted 后通过 mA 触发 attached,首轮视图更新完成后通过 mR 报告 ready 条件。“先监听、后请求”保证 service 同步返回时不会丢失初始数据。
  3. 页面根 mounted 的 nextTick 发送 pageAttached。同一 render 消息队列中,所有初始组件的 mA / mR 已先发出;service 此时执行 onLoad、消费暂存的真实 pageShow,再按子到父释放组件 ready,最终由 pageReady 触发页面 onReady。

页面 onReady 使用就绪屏障:render 报告页面根节点挂载完成后,service 仍会等待已初始化组件全部 ready,再调用页面 onReady。这样可以避免页面测量早于组件真实挂载。

父子组件同时进入节点树时,attached 按父组件到子组件的顺序执行;尚未 attached 的子组件会等待父组件。组件的 pageLifetimes 按真实组件树深度优先、父先于子的顺序传播,不使用仅按层级深度的排序,并且先于页面自身的 onShow、onHide 和 onResize。销毁页面时先调用页面 onUnload,再按子到父的顺序执行自定义组件 detached,最后执行页面根组件的 detached;每个组件的 detached 回调结束后再触发 relation unlinked。ready 由各自的视图完成信号驱动,页面 onReady 则统一等待当前已初始化组件。

首次启动时,container 的真实 pageShow 可能先于 render 的初始组件 attached 消息到达,service 会暂存该信号并等到 pageAttached 后统一遍历初始组件树。若 onLoad 等首次初始化逻辑又插入了组件,使其 attachment 跨过首次 onShow,service 会在第一次 onReady 前补齐这次初始 pageLifetimes.show。页面已经 onReady 后通过 wx:if 等方式插入的组件只加入当前可见状态,不补发 show,但会收到下一次 hide,并在后续重新显示页面时正常收到 show。

使用 Component({...}) 构造页面时,根实例同时具有组件生命周期、pageLifetimes 和 methods 中的页面回调。behavior 回调先于根实例自身回调;显示阶段依次执行 behavior pageLifetimes.show、自身 pageLifetimes.show、methods.onShow,ready 阶段依次执行 behavior ready、自身 ready、methods.onReady。页面方法和 pageLifetimes 独立保存,不会因为都使用 onShow / onHide 语义而互相覆盖。

组件声明同时包含 lifetimes.created 与旧式顶层 created 等同名字段时,只要 lifetimes 中的字段已定义,就以该字段为准;不会因为其值为 null 等非函数值而回退执行旧式字段。behavior 使用相同规则。

service 内的 created、初始 property observer、attached 和 onLoad 分别在对应生命周期消息的同一调用栈内执行。生命周期函数返回 Promise 不会延迟后续生命周期或实例初始化;同一阶段的每个生命周期、observer、relation 和 setData 回调独立隔离异常,某个回调抛错不会截断剩余回调或组件树遍历,组件错误会传给 error 生命周期。需要异步更新数据时,应在异步任务完成后显式调用 setData(),不能依赖 async onLoad() 或 async attached() 的返回值控制框架顺序。页面基础初始数据由 service 明确安排在 firstRender 消息之后发送;onLoad 中的 setData() 则更新已经挂载的 render 树。这些都是显式消息协议,不依赖固定延时或微任务猜测。

4. setData() 的时机

调用位置 行为
onLoad / 初始化生命周期 数据先写入 service 状态;视图模块就绪后再同步到 render
页面已就绪 更新进入队列,经 container 转发到 render
带回调的初始化更新 回调会暂存,等对应模块就绪并完成更新后执行

setData() 表示发起一次跨线程状态更新,不代表下一行代码执行时 DOM 已更新。需要读取布局时,应使用 setData(data, callback)、onReady,或选择器 API 的回调结果,而不是固定延时。

路径解析与数据传递采用小程序语义:方括号只接受数字下标,点号和方括号可通过反斜杠转义为字段名;逻辑层在调用时深拷贝引用值,传给 render 的数据则在入队时生成 JSON 快照。调用后继续修改原对象不会污染逻辑数据或待发送的视图更新。

5. 显示、隐藏与销毁

stateDiagram-v2
    [*] --> Loading: 创建页面
    Loading --> Visible: pageShow
    Visible --> Hidden: pageHide
    Hidden --> Visible: pageShow
    Visible --> Destroyed: pageUnload
    Hidden --> Destroyed: pageUnload
    Destroyed --> [*]
Loading
容器事件 页面回调 组件回调
pageShow 组件 pageLifetimes.show 完成后执行 Page.onShow 按组件树父到子执行 pageLifetimes.show
pageHide 组件 pageLifetimes.hide 完成后执行 Page.onHide 按组件树父到子执行 pageLifetimes.hide
pageUnload Page.onUnload detached,并清理实例记录

页面从后台返回前台时会再次触发 onShow,但不会再次触发 onLoad 或 onReady。重新创建页面实例后,才会重新经历完整初始化流程。

pageUnload 的完整顺序是:页面 onUnload → 后代组件 detached(子先于父)→ 页面根组件 detached。退出过程中会先标记卸载与 detached 状态,再调用业务回调;即使回调重入触发销毁消息,也不会重复执行生命周期。

6. 编写与排查建议

  • 在 onLoad 中处理路由参数、请求和初始状态;在 onReady 中处理首次 DOM 测量或依赖组件布局的逻辑。
  • 不要用 setTimeout、连续 nextTick 或固定 animation frame 模拟跨 service/render 的就绪信号。
  • 排查时分别记录 container、service 和 render 的事件,并携带 bridgeId、moduleId、页面路径和消息类型。
  • 至少验证首次进入、返回后再次显示、销毁后重进、快速切换以及条件组件出现/消失等路径。
  • 生命周期兼容目标以微信小程序语义为基准,但具体支持范围仍应结合当前实现和测试确认。

有关消息通道与页面容器的说明,继续阅读实现细节。