什么是 Vue Lynx?

Vue Lynx 是一个用于 Lynx 的 Vue 3 自定义渲染器,让你能够使用 Vue 熟悉的组合式 API、单文件组件和响应式数据模型来构建原生 Lynx 应用。

如果你了解 Vue 3 并且熟悉 Lynx,那你已经知道如何使用 Vue Lynx 了。

主要特性

  • 熟悉的 Vue 3 API — 使用 ref()computed()v-forv-if、SFC <script setup> 以及 Vue 中你喜爱的一切功能。
  • 跨平台原生渲染 — Lynx 在移动端和桌面端提供高性能原生渲染引擎,同时具备一流的 Web 渲染支持。你的 UI 绝非 WebView。
  • 双线程架构 — Vue 运行在后台线程;主线程负责原生渲染。这种分离使你的应用始终保持流畅响应。对于延迟敏感的交互(拖拽、滚动),主线程脚本允许你使用 'main thread' 标记函数,使其直接在主线程上运行,消除跨线程延迟。

面向 Vue Web 开发者

如果你从 Web 端的 Vue 转过来,你所知道的大部分内容都可以沿用。以下是一些关键差异。

更改你的 import

Vue Lynx 重新导出了 Vue API,因此你可以将 vue 替换为 vue-lynx

- import { ref, computed, onMounted } from 'vue';
+ import { ref, computed, onMounted } from 'vue-lynx';

不同的元素

Lynx 提供原生元素而非 HTML。使用 <view><text><image> 等来替代 <div><span><img>

- <div class="card">
-   <span>{{ message }}</span>
- </div>
+ <view class="card">
+   <text>{{ message }}</text>
+ </view>
Info

文本内容必须包裹在 <text> 中 — Lynx 不支持 <view> 内的裸文本节点。

不同的事件

Lynx 使用自己的事件系统。使用 :bindeventname 处理会冒泡的事件,使用 :catcheventname 处理不会冒泡的事件:

- <div @click="handleClick" @touchstart="onTouch">
+ <view :bindtap="handleClick" :bindtouchstart="onTouch">

更多详情请参阅 Lynx 事件处理指南

没有 documentwindow

Lynx 不提供浏览器全局对象。依赖 documentwindow 的库需要适配才能使用。Lynx 通过 lynx 全局对象提供自己的 API

理解双线程模型

如果你使用过 Web Workers,Lynx 的架构会让你感到熟悉。Vue 运行在后台线程上,而原生渲染发生在主线程上 — 就像将工作交给 Web Worker 以保持 UI 线程的流畅响应。不同之处在于,在 Lynx 中,这是_默认_行为 — Vue 的协调过程永远不会阻塞主线程。(设计原理请参阅负责任地使用主线程实现交互性。)

在底层,Vue Lynx 使用自定义渲染器在后台线程上构建 ShadowElement 树。变更被批量收集为 ops 并刷新到主线程。用户交互以事件的形式回传。这形成了一个类似于浏览器事件循环的周期:

        ┌──────────────────────────────────────────────────────┐
        │                  Background Thread                   │
        │  Vue 3 runtime · reactivity · lifecycle · your code  │
        └──────────────┬──────────────────────▲────────────────┘
                  ops  │                      │  events
                       ▼                      │
        ┌──────────────────────────────────────┴───────────────┐
        │                    Main Thread                       │
        │  Native elements · layout · rendering · MTS handlers │
        └──────────────────────────────────────────────────────┘

一个完整的周期 — ops 向下、事件向上 — 就是一个 tick。Vue Lynx 的 nextTick 在一个完整周期后 resolve,因此当你的回调触发时,原生元素已经完全创建完毕。这与 Vue 在 Web 上的 nextTick(等待 DOM 更新)是相同的心智模型,只是扩展到了跨线程场景。

Vue API:完全兼容

Vue Lynx 直接复用 @vue/runtime-core。响应式系统、组件模型、生命周期钩子、模板指令 — 一切都按照 Vue 3 文档中的描述工作。详细的功能对照请参阅 Vue 兼容性

双线程环境的注意事项

由于你的代码和原生元素分别运行在不同的线程上,有一些行为需要注意。

生命周期运行在后台线程 — 使用 nextTick 等待主线程

就像在 Web 上一样,onMounted 表示"组件树已构建完成"。不同之处在于原生元素是在主线程上异步创建的。如果你需要在挂载后与原生元素交互,请等待一个 tick:

<script setup lang="ts">
import { onMounted, nextTick } from 'vue-lynx'

onMounted(() => {
  nextTick(() => {
    // Native elements are now ready
    lynx.createSelectorQuery().select('.my-list').invoke(...)
  })
})
</script>

如果你的 onMounted 只是设置响应式状态、定时器或数据请求,则不需要 nextTick — 这些操作不依赖原生元素。

后台线程只有异步访问 — 对于同步需求使用主线程脚本

从后台线程访问原生元素始终是异步的。模板 ref 返回一个 ShadowElement(原生元素在后台线程上的引用),而 getBoundingClientRect() 等布局查询需要使用异步 API

<script setup lang="ts">
import { onMounted, nextTick } from 'vue-lynx'

onMounted(() => {
  nextTick(() => {
    lynx.createSelectorQuery()
      .select('.target')
      .invoke({
        method: 'boundingClientRect',
        success: (res) => console.log(res),
      })
      .exec()
  })
})
</script>

当你需要同步访问 — 流畅的动画、手势处理、布局测量 — 请使用主线程脚本。使用 'main thread' 标记的函数直接在主线程上运行,可以调用主线程 API(如 getComputedStyleProperty())、监听 main-thread-bindlayoutchange,以及通过 useMainThreadRef() 访问原生元素:

<script setup lang="ts">
import { ref, useMainThreadRef } from 'vue-lynx'

const divRef = ref(null)               // ShadowElement (background thread)
const mtRef = useMainThreadRef(null)    // Native element (main thread, MTS only)
</script>

<template>
  <view ref="divRef" :main-thread-ref="mtRef" />
</template>

详细的功能特性说明和不支持的功能,请参阅 Vue 功能兼容性

下一步