Rendering 渲染过程
Rendering指的是(基于以下两点)更新DOM的过程:
- 应用状态的变化
- 组件模板
QWIK是独特的因为它知道怎么无序和异步渲染模板。
- 无序: 意味着Qwik在渲染一个组件时,不需要强制要求父组件或子组件也要渲染好。
- 异步: 意味着Qwik组件的渲染函数理解它可能需要下载子组件,因此渲染操作是异步的。
简单的计数器例子:
export const Counter = component$(() => {
const store = useStore({ count: 0 });
return <button onClick$={() => store.count++}>{store.count}</button>;
});
一旦渲染完,HTML片段大概长下面这样:
<div>
<button q:obj="123" on:click="./chunk-a.js#Counter_button_click[0]">0</button>
</div>
- 关于
$
符号的解释, 参考$
and optimizer rules. - 关于
q:obj
的解释, 参考 serialization. - 关于
on:click
的解释, 参考 qwikloader.
JSX
Qwik 使用 JSX 编写组件模板。 JSX 的讨论不在本文范畴。 如果在其他框架里使用过JSX语法,那就会对QWIK的JSX很熟悉,不过QWIK里JSX的渲染过程是不同的,让我们来看看。
渲染子组件
Qwik 按需懒加载组件。 为了最小化要下载的组件数量,QWIK只会当属性改变时才下载子组件。
export const Parent = component$(() => {
const store = useStore({ count: 0, step: 1 });
return (
<>
<button onClick$={(store.step *= -1)}>direction</button>
<button onClick$={() => (store.count += store.step)}>{store.step}</button>
<Greeter name={'World_' + store.count} />
</>
);
});
export const Greeter = component$((props: { name: string }) => {
return <span>Hello {props.name}</span>;
});
上面例子,有两个按钮:
- 单击第一个按钮改变计数器的方向 (
store.step
在+1
和-1
直接切换)。 改变store
会引发组件的OnRender
函数执行,JSX将会更新DOM以展示+1
和-1
(如果模板有引用store.step的话)。 但是改变计数器方向不会改变<Greeter name={'World_' + store.count}/>
的属性值。 正是这个原因,组件Greeter
的模板不需要下载或执行。 这样渐进式的修剪让QWIK可以最小化 当下要渲染的组件 的代码量。 - 单击第二个按钮增加 (或减小)
store.count
. 这会引起<Greeter name={'World_' + store.count}/>
属性值的改变。 属性的改变意味着QWIK也要渲染<Greeter>
了。 但是这时候可能子组件还没有下载,此时,QWIK会懒在家这个组件,当它的渲染函数已经可用时(子组件代码下载完了),就继续渲染。
render()
是异步的
上面例子展示了为什么渲染过程必须是异步的。
渲染流水线可用懒加载子组件是重要的。
懒加载是异步操作;因此渲染过程就需要是异步的。
在实践上,这意味着render()
函数是返回promise的。
大多数现在的框架都是同步render()
过程。
同步渲染不能很容易地处理异步代码加载,所以同步渲染需要所有依赖的组件 在渲染前 就得早早地下载到浏览器。
DOM 更新缓冲
render()
的异步特性意味着在组件下载时 用户可能看到UI的中间渲染过程。
看到中间状态是不好的;因此,QWIK会缓冲所有DOM的更新,一旦所有组件都下载好了,组件的JSX执行完了,再一次性地做DOM更新。
因此UI更新是原子操作,用户不会看到中间步骤。