QRL

QRL (Qwik URL) 是一种 Qwik 用于延迟加载内容的特定 URL 格式。

QRL

  • 是经过特殊格式化的 URL,它们作为 HTML 中的属性保留,以告知 Qwik 代码的处理程序应从何处加载。
  • 指向要延迟加载的 JavaScript 块。
  • 包含需要从块中检索的符号名称。
  • 可能包含词法作用域的对象引用。(从闭包捕获的变量。)
  • 如果为相对路径,请使用 q:base 进行解析。

QRL 编码

./path/to/chunk.js#SymbolName

在最简单的形式中,QRL 包含一个 URL(例如 ./path/to/chunk.js),浏览器可以使用它来延迟加载资源,以及一个 SymbolName,用于从延迟加载的块中检索。

如果 URL 为相对路径,Qwik 使用 q:base 将 QRL 解析为绝对 URL。(如果不存在 q:base 属性,则使用 document.baseURI 作为基准。)

编码词法作用域捕获的变量

QRL 还可以恢复词法作用域的变量。在这种情况下,变量以索引数组的形式编码在 QRL 的末尾,这些索引指向 q:obj 属性。

./path/to/chunk.js#SymbolName[0,1]

该数组由 useLexicalScope() 用于恢复变量。

示例

让我们看一个将 QRL 的所有部分结合在一起的示例。

开发人员为一个简单的组件编写代码。

export const Counter = component$((props: { step: number }) => {
  const count = useSignal(0);
 
  return <button onClick$={() => (count.value += props.step || 1)}>{count.value}</button>;
});

优化器将上面的代码分解成以下部分

const Counter = component(qrl('./chunk-a.js', 'Counter_onMount'));
chunk-a.js
export const Counter_onMount = (props) => {
  const count = useSignal(0);
  return qrl('./chunk-b.js', 'Counter_onRender', [count, props]);
};
chunk-b.js
const Counter_onRender = () => {
  const [count, props] = useLexicalScope();
  return (
    <button onClick$={qrl('./chunk-c.js', 'Counter_onClick', [count, props])}>{count.value}</button>
  );
};
chunk-c.js
const Counter_onClick = () => {
  const [count, props] = useLexicalScope();
  return (count.value += props.step || 1);
};

渲染后的 HTML

在执行完上面的代码后,它会生成以下 HTML。

假设:http://localhost/index.html

<html>
  <body q:base="/build/">
    <button q:obj="456, 123" on:click="./chunk-c.js#Counter_onClick[0,1]">0</button>
    <script>
      /*Qwikloader script*/
    </script>
    <script type="qwik/json">
      {...json...}
    </script>
  </body>
</html>

需要注意的主要事项是 on:click 属性。当用户点击按钮时,该属性会被 Qwikloader 读取。

  1. HTML 在浏览器中加载,Qwikloader 注册一个全局的 click 监听器。此时不会加载/执行其他 JavaScript 代码。
  2. 用户点击 <button>。这会触发一个 click 事件,该事件会冒泡并由 Qwikloader 处理。
  3. Qwikloader 追溯事件冒泡路径并查找 on:click 属性,它在 <button> 上找到该属性。
  4. Qwikloader 现在尝试加载相应的块。为此,Qwikloader 需要解析 ./chunk-c.js 的相对路径。它使用这些值从 <button> 开始,向文档方向构建一个绝对路径。
    • on:click="./chunk-c.js#Counter_onClick[0,1]"
    • <body q:base="/build/">
    • document.baseURI = "http://localhost/index.html"
    • 最终的绝对 URL 为 http://localhost/build/chunk-c.js,Qwikloader 会获取该 URL。
  5. Qwikloader 现在从 http://localhost/build/chunk-c.js 中检索 Counter_onClick 引用并调用它。
    const Counter_onClick = () => {
      const [count, props] = useLexicalScope();
      return (count.value += props.step || 1);
    };
  6. 此时,执行将从 Qwikloader 交给延迟加载的块。这样做是为了使 Qwikloader 的体积尽可能小,因为它被内联到 HTML 中。
  7. useLexicalScope@builder.io/qwik 导入,负责检索 countpropsconst [count, props] = useLexicalScope();
  8. 解析 <script type="qwik/json">{...json...}</script> JSON 并根据 q:obj 属性分配反序列化的对象。在我们的例子中
    • <div q:id="123" q:obj="456" q:host> 获取 ID 为 123 的对象。这将是 Counter_onMount 函数中创建的 count
    • <button q:obj="456, 123" 获取 count 以及对 <div q:id="457"> 的引用。
  9. 一旦 qwik/json 被反序列化,useLexicalScope 就可以使用 QRL 的 [0,1] 数组来查找 q:obj="456, 123",以检索 ID 为 456123 的对象,它们是 <div q:id="123" q:obj="456" q:host> 的 props,以及 Counter_onMount 函数中的 store

注意:出于性能原因,q:obj<script type="qwik/json"> 仅在应用程序被反序列化为 HTML 时更新。当应用程序运行时,这些属性可能具有过时值。

为什么不直接使用动态 import()

浏览器已经从 import() 提供了动态导入机制。为什么不使用它,而是发明新的 QRL 格式?

有几个原因

  1. 为了使 Qwik 正常工作,QRL 需要被序列化到 HTML 中。这对动态 import() 来说是个问题,因为没有简单的方法可以从 import('./some-path.js') 中检索相对 URL,以便将其放置在 HTML 中。
  2. 动态导入处理块,但它们没有机制来引用块中的特定符号。
  3. 动态导入具有指向导入它的文件的相对路径。这是一个问题,因为当相对路径被放置在 HTML 中时,它会丢失定义相对路径的上下文。当框架从 HTML 中读取路径并尝试将其导入为 import(element.getAttribute('on:click')) 时,框架将成为相对路径解析的上下文。这是错误的,因为在序列化到 HTML 之前的原始上下文是不同的。
  4. QRL 编码了关于在闭包中捕获并需要恢复的词法作用域变量的信息。
  5. 动态导入要求开发人员编写 import('./file-a.js'),这意味着开发人员负责决定延迟加载边界的位置。这限制了工具以自动化方式移动代码的能力。

由于上述差异,Qwik 引入了 QRL 作为一种机制,用于将闭包延迟加载到 Qwik 应用程序中。

另请参阅

贡献者

感谢所有帮助改进此文档的贡献者!

  • ahhshm
  • manucorporat
  • adamdbradley
  • literalpie
  • the-r3aper7
  • mhevery
  • wtlin1228
  • bebraw
  • mrhoodz