Rendering
Rendering is the process of updating the DOM based on
- Changes in the state of the application
- Component templates
Qwik is unique because it knows how to render templates out-of-order and asynchronously.
- Out-of-order: this means that Qwik does not require that the parent component or child component must also be rendered when rendering a component.
- Asynchronously: this means that Qwik's
render
function understands that it may need to download child components, and therefore the rendering operation is asynchronous.
Simple counter-example:
export const Counter = component$(() => {
const count = useSignal(0);
return <button onClick$={() => count.value++}>{count.value}</button>;
});
Once rendered, the HTML fragment may look something like this:
<div>
<button q:obj="123" on:click="./chunk-a.js#Counter_button_click[0]">0</button>
</div>
- For an explanation of
$
, see$
and optimizer rules. - For an explanation of
q:obj
, see serialization. - For an explanation of
on:click
, see qwikloader.
JSX
Qwik uses JSX to express the component's template. JSX discussion is outside of the scope of this document, but Qwik JSX should feel very familiar if you have used JSX with other frameworks. For this reason, let's focus on how Qwik JSX is different.
Rendering child components
Qwik lazy loads components on an as-needed basis. To minimize the number of components to download, Qwik only descends into child components if the component's props have changed.
export const Parent = component$(() => {
const count = useSignal(0);
const step = useSignal(1);
return (
<>
<button onClick$={() => (step.value *= -1)}>direction</button>
<button onClick$={() => (count.value += step.value)}>{step.value}</button>
<Greeter name={'World_' + count.value} />
</>
);
});
export const Greeter = component$((props: { name: string }) => {
return <span>Hello {props.name}</span>;
});
In the above example, there are two buttons:
- Clicking the first button changes the direction of the counter (
store.step
flips between+1
and-1
). Changing thestore
requires that the component'sOnRender
function executes. The resulting JSX updates the DOM to show+1
and-1
. However, changing the direction will not change the props on<Greeter name={'World_' + store.count}/>
. For this reason, Qwik will not descend into the<Greeter>
component, and therefore theGreeter
's template does not need to be downloaded or executed. Such aggressive pruning allows Qwik to minimize the amount of code that needs to be present to render a component. - Clicking the second button increments (or decrements)
store.count
. This in turn causes the props on<Greeter name={'World_' + store.count}/>
to change. A change in props means that Qwik will also descend and render<Greeter>
. However, it is possible that Qwik does not have a reference to the child component. In that case, Qwik will lazy-load the component and continue the rendering once the component's render function is available.
.map()
Rendering with In many cases you'll want to render components by mapping an array in the render function with data.map()
. In these cases, it is required to provide a key to the first child of the mapping function.
export const Parent = component$(() => {
return (
<>
{data.map(({ content, uniqueKey }) => (
<div key={uniqueKey}>
<p>{content}</p>
</div>
))}
</>
);
});
note: it is not recommended to use the array's index as the key unless you can guarantee that the data for a given key will always be the same. It is always preferred to use some unique identifier from the data as the key.
dangerouslySetInnerHTML
Qwik offers an attribute on HTML Elements called dangerouslySetInnerHTML
as a replacement for calling innerHTML
on the DOM. Due to cross-site-scripting (XSS) possibilities when rendering not trustworthy content, you must use this attribute to remind yourself that this operation might be dangerous.
<div dangerouslySetInnerHTML={content}></div>
render()
is async
The above examples demonstrate why the rendering process must be asynchronous. It is important that the rendering pipeline can lazy load child components. Lazy loading is an asynchronous operation; therefore, rendering needs to be asynchronous. In practice, this means that the render()
function returns a promise.
Most current-generation frameworks have a synchronous render()
process. Synchronous rendering can't easily deal with asynchronous code loading, so synchronous rendering necessitates that all dependant components are eagerly present before rendering can commence.
DOM update buffering
The asynchronous nature of render()
means that users may see an intermediate rendering of the UI as components download. Seeing an intermediate state is undesirable; therefore, Qwik will buffer all DOM updates and only flush the DOM operations once all of the components have been downloaded and their JSX functions executed. The result is that the UI will update as an atomic operation, and the user will not see the intermediate steps.