渲染
渲染是根据应用程序和组件模板的状态变化来更新 DOM 的过程。
Qwik 是独特的,因为它知道如何异步地以细粒度的方式无序地渲染模板。
JSX
与 React 一样,Qwik 使用 JSX 来表达组件的模板。请注意,JSX 只是语法,在底层,React 和 Qwik 是完全不同的。JSX != VDOM。
Qwik 与其他 JSX 框架有一些不同之处:
- 组件总是使用
component$
函数声明。 - 组件可以使用
useSignal
钩子创建响应式状态。 - 事件处理程序使用
$
后缀声明。 - 对于
<input>
,在 Qwik 中,onChange
事件被称为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 在需要时按需加载组件。为了减少要下载的组件数量,只有当组件的 props 发生变化时,Qwik 才会进入子组件。
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 组件。只有当 count 信号发生变化时,Child 组件才会重新渲染。
渲染列表项
在许多情况下,您可能希望通过在渲染函数中使用 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)的可能性,您必须使用此属性来提醒自己此操作可能是危险的。
const htmlString = "<strong>hello</strong>";
<div dangerouslySetInnerHTML={htmlString}></div>
bind
属性
这是一个方便的 API,用于将 <input>
的值与 Signal
进行双向数据绑定。
对于复选框输入,可以使用 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>
);
})
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 具有细粒度的响应性,只有依赖于状态的组件才会被更新。这对性能有很大的提升,原因如下:
- 执行的代码越少,应用程序更新的渲染速度就越快。
- 执行的代码越少,往往意味着代码不必在应用程序启动时(或永远)下载。 å