渲染
渲染是指根據應用程式狀態和組件範本的變化來更新 DOM 的過程。
Qwik 的獨特之處在於它知道如何以非同步、細粒度的方式,按順序外渲染範本。
JSX
如同 React,Qwik 使用 JSX 來表示組件的模板。請注意,JSX 僅僅是一種語法,在底層,React 和 Qwik 完全不同。JSX != VDOM。
Qwik 與其他 JSX 框架有一些差異
- 組件始終使用
component$
函數聲明。 - 組件可以使用
useSignal
hook 來創建反應式狀態。 - 事件處理程序使用
$
後綴聲明。 - 對於
<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>;
});
在上面的例子中,父組件將一個不斷變化的
name
屬性傳遞給子組件。子組件只會在計數信號發生變化時才會重新渲染。
渲染項目列表
在許多情況下,您需要使用 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>
))}
</>
);
});
注意:除非您可以保證給定鍵的數據始終相同,否則不建議使用數組的索引作為鍵。始終建議使用數據中的一些唯一標識符作為鍵。
條件渲染
條件渲染可以使用 Javascipt 三元運算符 ?
、&&
運算符或僅使用 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
屬性是一個方便的 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>
);
})
該 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 的細粒度響應性,只有依賴於狀態的組件才會被更新。這在兩個方面都是一個很大的效能提升
- 執行程式碼更少意味著應用程式更新的渲染速度更快。
- 執行程式碼更少意味著通常不需要在應用程式啟動時(或永遠不需要)下載程式碼。