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 (
<Host>
<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>
</Host>
);
});
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} />
</>
);
});
Host element
The host element is an element in the DOM that represents component boundaries.
Qwik-components rely on host elements because it must be possible by looking on the HTML to determine where one component starts and another ends. Without knowing the boundaries, it would not be possible to render components independently without forcing parent/child components to render as well. This is a crucial feature of Qwik.
const Child = component$(() => <span>child</span>);
const Parent = component$(() => (
<section>
<Child />
</section>
));
Will result in:
<div>
<section>
<div>
<span>child</span>
</div>
</section>
</div>
As you can see, component$
will always create an extra element, by default is div
, but that can be changed with component$
options argument or the host:tagName
attribute:
const MyArticle = component$(() => (
<span>My article</span>
), {
tagName: 'article'
});
and:
const MyArticle = component$(() => (
<span>My article</span>
));
<MyArticle host:tagName="article"/>
Will both result in:
<article>
<span>
My article
</span>
</article>
useHostElement()
Since the host element is implicitly created by component$
, it is not possible to access it directly. Instead, you can use useHostElement()
to get the host element.
Qwik uses host elements in various ways. For example when using useHostElement()
function which retrieves it. It is also used to attach props to the components for serialization.
Host Element Attributes & Styling
Assume you have a component defined as so:
const Greeter = component$((props: {salutation?: string, name: string, }) => {
...
});
The component can be used like so:
<Greeter name="World" />
or
<Greeter salutation="Hello" name="World" />
In both above cases the rendered HTML would be:
<div></div>
Because the host element is an actual element, there may be a desire to place HTML classes, styles, or attributes on the host element.
What if you wanted to add a name
attribute to the host element? The issue is that the name
is already used by the component props. For this reason, we use host:
prefix to refer to the host element's attributes.
<Greeter
host:name="abc"
host:id="greeter"
host:onClick$={() => {}}
name="world"
/>
would render as:
<div name="abc" id="greeter" onClick$="..."></div>
Using a host:
prefix allows the developer to control the component's host element attributes independently from the component's props.
One can use the same approach for class
and styles
.
<Greeter host:class="greeter" host:style={{ backgroundColor: 'red' }} name="world" />
would render:
<div class="greeter" style="background-color: red;"></div>
However, for convenience class
and styles
are special, and they will automatically map to host:class
and host:styles
when placed on the host element.
<Greeter
class="greeter"
style={{ backgroundColor: 'red' }}
name="world"
/>
would also render to the same output.
<div class="greeter" style="background-color: red;"></div>
<Host/>
It may also be desirable for a component to set class
, style
and attributes
on the host element associated with the component itself. To do that, one can use <Host>
tag.
const Greeter = component$(() => {
return (
<Host class="padded" id="greeter">
<span>Hello World</span>
</Host>
);
});
will result in:
<div class="padded" id="greeter">
<span>Hello World</span>
</div>
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.