Refs
Refs have two stages, the definitions - which go inside your component definition, and the resolved items - which you receive in your setup function, and use for bindings.
Ref definition
The shape of the definition that you pass to the component is the following:
// refs definition
type ComponentRefItem =
// shortcut for element ref, will internally be turned into a `refElement()`
| string
| {
// Different refs have their own type, to execute slightly different logic on them.
type: 'element' | 'collection' | 'component' | 'componentCollection';
// The value of the `data-ref` attribute on the html element(s).
ref: string;
// Only used for element/component, and when true, it will log an error if the element
// doesn't exist in the DOM. Nothing will break, it's just that the bindings will not
// be executed.
isRequired?: boolean;
// A function that will find the right HTMLElement(s) that should be used for the refs.
// When `type` is element or component, it returns a single HTMLElement or null.
queryRef: (parent: HTMLElement) => HTMLElement | null | Array<HTMLElement>;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
However, you would never pass this by hand, but instead make use of the available helper functions described below.
ignoreGuard
By default, only refs directly within a component can be selected. Refs from child components are not accessible. All ref
functions have an ignoreGuard
option.
When setting ignoreGuard
to true, it disables the guarding behaviour, and allows you to query any ref.
refElement
refElement
selects a single DOM element that has the data-ref
attribute set.
declare function refElement(
refIdOrQuery: string | ((parent: HTMLElement) => HTMLElement | null),
options?: {
isRequired?: boolean;
ignoreGuard?: boolean;
},
): ComponentRefItemElement;
2
3
4
5
6
7
refIdOrQuery
refId: string
refQuery: (parent: HTMLElement) => HTMLElement | null)
When passing a string
, it's the value of the data-ref
attribute on one of the HTMLElements in the DOM.
When passing a function
, you can query your elements by using parent.querySelector(...)
to return a single element.
Any elements that are queried are filtered against the component guard unless options. ignoreGuard
is set to true
.
options.isRequired
isRequired?: boolean = true
By default, all refs are marked as required, and will log errors in the console when they cannot be found. If elements are optional, you can set isRequired
to false
, and by doing that, the type becomes optional as well.
options.ignoreGuard
ignoreGuard?: boolean = false
When set to true
, it disables the guarding behaviour, and allows you to query any ref of any child component.
Example
Shortcut
Passing 'string'
is a quick shortcut for refElement('string')
.
refCollection
refCollection
selects one or more DOM elements that have the data-ref
attribute set. If no elements are found, the collection will be empty.
declare function refCollection(
refIdOrQuery: string | ((parent: HTMLElement) => Array<HTMLElement>),
options?: {
minimumItemsRequired?: number;
ignoreGuard?: boolean;
},
): ComponentRefItemCollection;
2
3
4
5
6
7
In the setup
function, a collection can be used to apply the same binding to multiple items, or to loop over in a mapping function and specify a different binding based on each individual element or index in the collection.
refIdOrQuery
refId: string
refQuery: (parent: HTMLElement) => Array<HTMLElement>)
When passing a string
, it's the value of the data-ref
attribute on one or multiple HTMLElements in the DOM.
When passing a function
, you can query your elements by using parent.querySelectorAll(...)
to return one or multiple elements.
Any elements that are queried are filtered against the component guard unless options. ignoreGuard
is set to true
.
options.minimumItemsRequired
minimumItemsRequired?: number = 0
By default, the returned collection can be empty, and is thus optional by default. By setting minimumItemsRequired
to a specific value, an error is thrown when the collection contains fewer items.
options.ignoreGuard
ignoreGuard?: boolean = false
When set to true
, it disables the guarding behaviour, and allows you to query any ref of any child component.
Example
refComponent
refComponent
selects a single DOM element that either has the data-component
attribute match the one from the passed Component, or has the data-ref
attribute set when that is provided in the options.
After selecting the DOM element, it will create a new component instance for that element.
declare function refComponent(
component: ComponentFactory | Array<ComponentFactory>,
options?: {
ref: string | ((parent: HTMLElement) => HTMLElement | null);
isRequired?: boolean;
ignoreGuard?: boolean;
},
): ComponentRefItemComponent;
2
3
4
5
6
7
8
component
component: ComponentFactory | Array<ComponentFactory>
One or multiple components that will be created for this ref. In addition to almost all HTML bindings, a component ref also allows you to bind against child component props, updating their values when things change, or providing callback functions that can be called.
If options.refIdOrQuery
is not passed, it will search for the first occurrence of the data-component
matching the passed component(s).
If you pass an Array
of multiple components, it will use the first one it finds. The most common use case is by having one element with a specific data-ref
that should match one of the passed components.
When doing that, only the component props that exists on all the passed components can be used to bind against.
options.ref
ref: string
ref: (parent: HTMLElement) => HTMLElement | null)
If you have multiple component elements, and the ref should target a specific one, the ref
can be used. Or if you just want to be more strict about things, to make sure it doesn't break in the future. Otherwise, if you just have a single child component, you can omit the ref, and it still will be able to find the element based on the data-component
attribute.
When passing a string
, it's the value of the data-ref
attribute on one of the HTMLElements in the DOM.
When passing a function
, you can query your elements by using parent.querySelector(...)
to return a single element.
Any elements that are queried are filtered against the component guard unless options. ignoreGuard
is set to true
.
Any elements that are queried are also filtered with the data-component
attribute against the displayName
of the passed ComponentFactory
.
options.isRequired
isRequired?: boolean = true
By default, all refs are marked as required, and will log errors in the console when they cannot be found. If elements are optional, you can set isRequired
to false
, and by doing that, the type becomes optional as well.
options.ignoreGuard
ignoreGuard?: boolean = false
When set to true
, it disables the guarding behaviour, and allows you to query any ref of any child component.
Example
refComponents
refComponents
selects one or more DOM elements that either have the data-component
attribute match the one from the passed Component, or have the data-ref
attribute set when that is provided in the options. If no elements are found, the collection will be empty.
After selecting the DOM elements, it will create a new component instance for each of them.
declare function refComponents(
component: ComponentFactory,
options?: {
ref: string | ((parent: HTMLElement) => Array<TMLElement>);
minimumItemsRequired?: number;
ignoreGuard?: boolean;
},
): ComponentRefItemComponentCollection;
2
3
4
5
6
7
8
component
component: ComponentFactory
The component that will be created for this ref. In addition to almost all HTML bindings, a component ref also allows you to bind against child component props, updating their values when things change, or providing callback functions that can be called.
If options.ref
is not passed, it will search for the first occurrence of the data-component
matching the passed component.
options.ref
ref: string
ref: (parent: HTMLElement) => HTMLElement | null)
If you have multiple component elements, and the ref should target specific ones, the ref
can be used. Or if you just want to be more strict about things, to make sure it doesn't break in the future. Otherwise, if you just want to target all elements of this component, you can omit the ref, and it still will use all element based on the data-component
attribute.
When passing a string
, it's the value of the data-ref
attribute on the HTMLElements in the DOM.
When passing a function
, you can query your elements by using parent.querySelectorAll(...)
to return multiple elements.
Any elements that are queried are filtered against the component guard unless options. ignoreGuard
is set to true
.
Any elements that are queried are also filtered with the data-component
attribute against the displayName
of the passed ComponentFactory
.
options.minimumItemsRequired
minimumItemsRequired?: number = 0
By default, the returned collection can be empty, and is thus optional by default. By setting minimumItemsRequired
to a specific value, an error is thrown when the collection contains fewer items.
options.ignoreGuard
ignoreGuard?: boolean = false
When set to true
, it disables the guarding behaviour, and allows you to query any ref of any child component.
Example
Ref item
Ref items are "container objects" around the resolved ref definitions to be used in the setup
function of the component.
They are mainly used in the different binding helpers, but contains some additional information about the specific refs that could be useful in some situations.
The type
of each ref
is inferred from the input you pass, so the properties you can access is different between elements and components, or single items vs collections.
The public API for each type of ref can be seen below (note that some internal fields have been omitted here).
type ElementRef = {
element: HTMLElement | undefined;
};
type CollectionRef<> = {
// this is a function, and has refs inside, so causes `watch` or `watchEffect` to re-execute
// when the DOM updates and the items inside the collection change
getElements: () => Array<HTMLElement>;
// nested refs for each single individual element
getRefs: () => Array<ElementRef>;
};
type ComponentRef = {
component: ComponentApi | undefined;
};
type ComponentsRef = {
// this is a function, and has refs inside, so causes `watch` or `watchEffect` to re-execute
// when the DOM updates and the items inside the collection change
getComponents: () => Array<ComponentApi>;
// nested refs for each single individual component
getRefs: () => Array<ComponentRef>;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
As you can see, all 4 have a reference to the actual item(s) that they have selected as element
, getElements()
, component
or getComponents()
. The two collection refs have access to the "item" refs
, a list of "container refs" around each item in the collection, which could be useful when applying the individual bindings to each item.
Example
defineComponent({
refs: {
singleElement: refElement('single-element'),
elementCollection: refCollection('element-collection'),
singleComponent: refElement(Component, 'single-element'),
componentCollection: refElement(Component, 'element-collection'),
},
setup({ props, refs }) {
refs.singleElement.element; // HTMLElement
refs.elementCollection.getElements(); // Array<HTMLElement>
refs.singleComponent.component; // ComponentApi
refs.singleComponent.component.props; // Access the component props, useful for intial state
refs.componentCollection.getComponents(); // Array<ComponentApi>
return [
bind(refs.singleElement, { text: 'label' }), // bind to single element
bind(refs.elementCollection, { text: 'label' }), // bind to all elements in the collection
// bind to each ref individually
...refs.elementCollection.refs.map((ref, index) => bind(ref, { text: `item ${index}` })),
// but simpler
...bindMap(refs.elementCollection, (ref, index) => ({ text: `item ${index}` })),
// bind to single component, or to all components int the collection
bind(refs.component, { someProps: 'value', onSomethingHappens: () => console.log('Yo') }),
bind(refs.elementCollection, { someProps: 'value', onSomethingHappens: () => console.log('Yo') }),
// bind to each ref individually
...refs.singleComponent.refs.map((ref, index) => bind(ref, { itemIndex: index })),
// but simpler
...bindMap(refs.componentCollection, (ref, index) => ({ itemIndex: index })),
];
},
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36