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 会读取该属性。
- HTML 在浏览器中加载,并且 Qwikloader 注册一个全局的
click
监听器。此时不会加载/执行其他 JavaScript。 - 用户点击
<button>
。这会触发一个click
事件,冒泡并由 Qwikloader 处理。 - Qwikloader 追踪事件冒泡路径,并查找
<button>
上的on:click
属性。 - 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。
- Qwikloader 现在从
http://localhost/build/chunk-c.js
中检索Counter_onClick
引用,并调用它。const Counter_onClick = () => { const [count, props] = useLexicalScope(); return (count.value += props.step || 1); };
- 此时,执行从 Qwikloader 移交给了懒加载的块。这样做是为了使 Qwikloader 尽可能小,因为它被内联到 HTML 中。
useLexicalScope
从@builder.io/qwik
导入,并负责检索count
和props
。const [count, props] = useLexicalScope();
- 解析
<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">
的引用。
- 一旦反序列化了
qwik/json
,useLexicalScope
就可以使用 QRL 的[0,1]
数组查找q:obj="456, 123"
中的对象,以检索 id 为456
和123
的对象,它们是<div q:id="123" q:obj="456" q:host>
的 props,以及Counter_onMount
函数中的store
。
**注意:**出于性能原因,
q:obj
和<script type="qwik/json">
仅在将应用程序序列化为 HTML 时才会更新。当应用程序运行时,这些属性可能具有过时的值。
import()
?
为什么不使用动态 浏览器已经提供了从 import()
进行动态导入的机制。为什么不使用它来代替发明新的 QRL 格式?
有几个原因:
- 为了使 Qwik 正常工作,需要将 QRL 序列化为 HTML。对于动态
import()
来说,这是有问题的,因为没有简单的方法可以从import('./some-path.js')
中检索到相对 URL,以便将其放置在 HTML 中。 - 动态导入处理块,但它们没有机制来引用块中的特定符号。
- 动态导入具有相对于导入它的文件的相对路径。这是一个问题,因为当将相对路径放置在 HTML 中时,它失去了定义相对于什么的上下文。当框架从 HTML 中读取路径并尝试将其导入为
import(element.getAttribute('on:click'))
时,框架将成为相对路径解析的上下文。这是错误的,因为在将代码序列化为 HTML 之前的原始上下文是不同的。 - QRL 编码了在闭包中捕获的词法作用域变量的信息,需要恢复这些变量。
- 动态导入要求开发人员编写
import('./file-a.js')
,这意味着开发人员负责决定懒加载边界在哪里。这限制了工具在自动化方式下移动代码的能力。
由于上述差异,Qwik 引入了 QRL 作为将闭包懒加载到 Qwik 应用程序的机制。