Component anatomy
A component is a small, reusable piece of code that can be used to build a UI.
In Qwik, they are declared using the component$ method:
import { component$, useStore } from '@builder.io/qwik';
// Qwik components can be asynchronous
export const MyCmp = component$(async (props: MyCmpProps) => {
// Declare local state
const state = useStore({
count: 0,
});
// Returns JSX
return (
<>
<span>Hello, {props.name} {state.count}</span>
<div>Times: {state.count}</div>
<button onClick$={() => {
// This will update the local state and cause a re-render.
// Reactivity is at qwik's core!
state.count++;
}}
>
Increment
</button>
</>
);
});
Props
Props are used to pass data into the component. Props are declared as named arguments of the component.
In this example a component Item declares optional name, quantity, description, and price.
interface ItemProps {
name?: string,
quantity?: number,
description?: string,
price?: number
}
export const Item = component$((props: ItemProps) => {
return ...;
});
The resulting component can be used like so:
const MyApp = component$(() => {
return (
<>
- With no props: <Item />
- With some props: <Item description="Item description" />
- With all props: <Item name="Hammer" quantity={3} description="Best organic hammer" price={10.0} />
</>
);
});
Lazy Loading
The host component also serves an important role when breaking parent-child relationships for bundling purposes.
const Child = () => <span>child</span>;
const Parent = () => (
<section>
<Child />
</section>
);
In the above example, referring to the Parent component implies a transitive reference to the Child component. When the bundler is creating a chunk, a reference to Parent necessitates bundling Child as well. (Parent internally refers to Child.) These transitive dependencies are a problem because it means that having a reference to the root component will transitively refer to the remainder of the application—something which Qwik tries to avoid explicitly.
const Child = component$(() => {
return <span>child</span>;
});
const Parent = component$(() => {
return (
<section>
<Child />
</section>
);
});
In the above example the Optimizer transforms the above to:
const Child = componentQrl(qrl('./chunk-a', 'Child_onMount'));
const Parent = componentQrl(qrl('./chunk-b', 'Parent_onMount'));
const Parent_onMount = () => qrl('./chunk-c', 'Parent_onRender');
const Parent_onRender = () => (
<section>
<Child />
</section>
);
NOTE: for simplicity, not all of the transformations are shown; all resulting symbols are kept in the same file for succinctness.
Notice that after the optimizer transforms the code, the Parent no longer directly references Child. This is important because it allows the bundler (and tree shakers) to freely move the symbols into different chunks without pulling the rest of the application with it.
So what happens when the Parent component renders and Child component has not yet been downloaded? First, the Parent component renders its JSX like so.
<div>
<section>
<div></div>
</section>
</div>
As you can see in the above example, the <div/> acts as a marker where the Child component will be inserted once it is lazy-loaded.
Mental Model
The optimizer splits Qwik components into the host element and the behavior of the component. The host element gets bundled with the parent components OnRender function, whereas the component's behavior is something that gets lazy-loaded on an as-needed basis.