渲染
渲染是根据应用程序状态和组件模板的变化更新 DOM 的过程。
Qwik 的独特之处在于它知道如何以异步方式和细粒度的方式渲染模板,并且可以按顺序渲染。
JSX
与 React 一样,Qwik 使用 JSX 来表达组件的模板。请注意,JSX 只是语法,在幕后,React 和 Qwik 完全不同。JSX != VDOM。
Qwik 与其他 JSX 框架相比有一些区别
- 组件始终使用
component$
函数声明。 - 组件可以使用
useSignal
钩子来创建反应式状态。 - 事件处理程序使用
$
后缀声明。 - 对于
<input>
,onChange
事件在 Qwik 中称为onInput$
。 - 优先使用 HTML 属性。
class
而不是className
。for
而不是htmlFor
。
import { component$, useSignal } from '@builder.io/qwik';
export const MyComponent = component$((props) => {
const count = useSignal(0);
return (
<>
<button
onClick$={() => {
count.value = count.value + props.step;
}}
>
Increment by {props.step}
</button>
<main
class={{
even: count.value % 2 === 0, // conditional class
odd: count.value % 2 === 1,
}}
>
<h1>Count: {count.value}</h1>
</main>
</>
);
});
渲染子组件
Qwik 按需延迟加载组件。为了最大程度地减少要下载的组件数量,Qwik 仅在组件的 props 发生更改时才会进入子组件。
import { component$, useSignal } from '@builder.io/qwik';
export const Parent = component$(() => {
const count = useSignal(0);
return (
<>
<button onClick$={() => (count.value += 1)}>Increment</button>
<Child name={'World_' + count.value} />
</>
);
});
export const Child = component$((props: { name: string }) => {
return <p>Hello {props.name}</p>;
});
在上面的示例中,Parent 组件将一个不断变化的
name
属性传递给 Child 组件。Child 组件仅在 count 信号发生更改时才会重新渲染。
渲染项目列表
在许多情况下,您希望通过在渲染函数中使用 items.map()
来映射数组来渲染组件。对于列表中的每个项目,必须将其唯一的 key
属性传递给映射函数的第一个子元素。key
必须是字符串或数字,并且在列表中必须是唯一的。
import { component$ } from '@builder.io/qwik';
export const Parent = component$(() => {
return (
<>
{data.map(({ message, uniqueKey }) => (
<div key={uniqueKey}>
<p>{message}</p>
</div>
))}
</>
);
});
注意:除非您能保证给定键的数据始终相同,否则不建议使用数组的索引作为键。始终优先使用数据中的一些唯一标识符作为键。
条件渲染
条件渲染使用 Javascript 三元运算符 ?
、&&
运算符或仅使用 if
语句完成。
import { component$ } from '@builder.io/qwik';
export default component$(() => {
const show = useSignal(false);
return (
<>
<button onClick$={() => show.value = !show.value}>Toggle</button>
{show.value ? <h1>Visible</h1> : <h1>Hidden</h1>}
{show.value && <div>Only show when it's visible</div>}
</>
);
});
dangerouslySetInnerHTML
Qwik 在 HTML 元素上提供了一个名为 dangerouslySetInnerHTML
的属性,作为对 DOM 上调用 innerHTML
的替代。
由于在渲染不可信内容时存在跨站点脚本 (XSS) 的可能性,因此您必须使用 dangerouslySetInnerHTML
作为提醒,此操作可能很危险。
const htmlString = "<strong>hello</strong>";
<div dangerouslySetInnerHTML={htmlString}></div>
bind
属性
bind
属性是将 <input>
值双向绑定到 Signal
的便捷 API。
对于复选框输入,您可以使用 bind:checked
,它将 checked
布尔值绑定到指定的信号。
import { component$, useSignal } from '@builder.io/qwik';
export default component$(() => {
const firstName = useSignal('');
const acceptConditions = useSignal(false);
return (
<form>
<input type="text" bind:value={firstName} />
<input type="checkbox" bind:checked={acceptConditions} />
<div>First name: {firstName.value}</div>
</form>
);
})
该 API 不适用于
useStore
,因为它不返回信号。您仍然可以使用手动方法,将 value 和 onInput$ 组合起来,如下所示。
bind:
由 Qwik 优化器编译成属性设置和事件处理程序,即它只是语法糖。
import { component$, useSignal } from '@builder.io/qwik';
export default component$(() => {
const firstName = useSignal('');
const acceptConditions = useSignal(false);
return (
<form>
<input type="text"
value={firstName.value}
onInput$={(_, el) => firstName.value = el.value }
/>
<input type="checkbox"
checked={acceptConditions.value}
onChange$={(_, el) => acceptConditions.value = el.checked }
/>
<div>First name: {firstName.value}</div>
</form>
);
})
此 API 确保输入的
value
始终与信号的值同步,无论更改来自何处。
异步渲染
对于渲染管道来说,能够延迟加载子组件非常重要。由于延迟加载是异步操作,因此渲染也必须是异步的。在实践中,这意味着 render()
函数必须返回一个 promise。
大多数当前一代框架都有一个同步的 render()
过程。同步渲染无法轻松处理异步代码加载,因此同步渲染需要在渲染开始之前所有依赖组件都必须存在。
渲染批处理
每当应用程序的状态发生更改时,Qwik 都会安排一个渲染操作。渲染操作将在宏任务(例如 setTimeout(0)
)之后安排运行。这允许应用程序将多个状态更改批处理到单个渲染操作中。
此外,由于 Qwik 的异步性质,所有 DOM 写入都将被缓冲,并且仅在所有组件都已下载并执行其 JSX 函数后才刷新。结果是 UI 将作为原子操作更新,即使应用程序渲染速度很慢,用户也不会看到中间步骤。
最终目标是在快速变化的状态和缓慢的网络环境中实现性能和一致的渲染。
细粒度反应式
由于 Qwik 的细粒度反应式,只有依赖于状态的组件才会更新。这在两个方面带来了巨大的性能提升
- 要执行的代码更少意味着应用程序更新将更快地渲染。
- 要执行的代码更少意味着通常代码不必在应用程序启动时(或永远)下载。