# Create a Component
This section introduces how to create a custom component through the capabilities provided by UIExtension, and uses the implementation of a Counter functionality as an example to explain the usage of UIExtension components.
# Basic Structure of a Component
A basic component typically consists of the following parts:
- Layout template: This defines the structure and layout of the component's view. The template syntax can be referred to the layout template.
- Styles: The component's styles can be added using the
style
attribute or theclass
attribute, similar to HTML. Styles added using theclass
attribute require a separate css file. - Scripts: This contains the logic of the component. UIExtension components need to implement a new component class through integration to handle the behavior and interaction of the component.
- Module: If a component needs to be referenced by other components, it must be assigned a name and registered in a module. For more information about modularization, please refer to Modular.
Here is an example of a basic component structure:
/* my-component.css */
.my-counter {
display: flex;
}
// my-component.js
const { SeniorComponentFactory } = UIExtension;
class MyComponent extends SeniorComponentFactory.createSuperClass({
template: `
<div class="my-counter" @var.my_counter="$component">
<button class="my-btn" @on.click="my_counter.increment()">+</button>
<div class="my-viewer">@{my_counter.count}</div>
<button class="my-btn" @on.click="my_counter.decrement()">-</button>
</div>
`
}) {
static getName() {}
init() {
super.init();
this.count = 0;
}
increment() {
this.count ++;
this.digest(); // After data change, update must be triggered manually
}
decrement() {
this.count --;
this.digest(); // After data change, update must be triggered manually
}
}
modular.module('custom', []).registerComponent(MyComponent);
# Create a Simple Component
Based on the above description of the basic structure of a component, let's now create a component that displays a clock. Click the "run" button below to start the example:
Running the example above, you will see a clock component that updates in real-time. This is just a simple component, but it demonstrates how to create and use a component, initialize and run a timer in the init
and mounted
lifecycle of the component, and how to reference component object properties in the component template. You can further extend and customize this example based on your needs.
# Event Triggering and Binding in Components
Components can not only display data, but also interact with users. Parent components can also interact with child components by listening to their events. With UIExtension, we can implement event triggering and listening to achieve interactive functionality.
# Event Triggering
To trigger an event in a component, we can use the trigger
method. This method accepts an event name (required) and multiple data to be transmitted (optional). Here is an example:
class DidaComponent extends SeniorComponentFactory.createSuperClass({
template: `
<div></div>
`
}) {
static getName() {
return 'dida'
}
mounted() {
super.mounted();
const execute = () => {
if(this.isDestroyed) {
return;
}
this.trigger('dida', performance.now());
requestIdleCallback(execute);
};
requestIdleCallback(execute);
}
}
modular.module('custom', []).registerComponent(DidaComponent);
In this example, the <dida></dida>
component triggers a dida
event and passes a timestamp whenever it is idle.
# Event Listening
There are two ways to listen to the events in a component. One is to use the @on.event-name
directive, and the other is to use the Component#on
interface. The following example will continue to use the dida
component mentioned above to demonstrate these two usages:
class DidaBoxComponent extends SeniorComponentFactory.createSuperClass({
template: `<div @var.box="$component">
<custom:dida name="dida" @on.dida="box.onDidaDirectiveEvent($args[0])"></custom:dida>
</div>
`
}) {
static getName() {
return 'dida-box';
}
onDidaDirectiveEvent(time) {
console.log('Event listened through directive was triggered', time)
}
mounted() {
super.mounted();
this.getComponentByName('dida').on('dida', time => {
console.log('Event listened through "on" interface was triggered', time)
})
}
}
# Native DOM Event Listening
The @on
directive can not only listen to custom events triggered by components, but also listen to native DOM events. For specific usage, please refer to the @on section.
# Component Lifecycle
The lifecycle of a UIExtension component is relatively simple, with three commonly used methods: init
, mounted
, and destroy
. As shown in the examples above, init
and mounted
lifecycles can be implemented by overriding the methods of the parent class. The init
method is called when the component is constructed, and is usually used to initialize some properties. The mounted
method is called after the component is inserted into the DOM tree, and can be used to perform some DOM operations on itself or its child components. The destroy
method requires adding tasks to be destroyed using addDestroyHook
. These tasks usually involve removing side effects, such as unregistering events or clearing timers. You can refer to the ClockComponent
mentioned in the section Create a Simple Component.
# Component Communication
We know that child components can communicate with parent components by triggering events, while parent components can communicate with child components by calling their methods. But how can components communicate with each other if they are not parent-child? The UIExtension framework also supports simple injection functionality, which allows singleton objects to be injected and enables communication between any components. The implementation of injection is very simple. Here is an example of a counter functionality:
First, create a
CounterService
class. The role ofCounterService
is to keep track of acount
property that can be shared by any component:class CounterService { constructor() { this.count = 0; } }
Next, create two components: one for modifying the count and one for displaying the count. Both of these components inject the
CounterService
:class ModifyButtonComponent extends SeniorComponentFactory.createSuperClass({ template: `<button @on.click="$component.onClick()"></button>` }) { static getName() { return 'modify'; } static inject() { return { service: CounterService }; } createDOMElement() { return document.createElement('button'); } init() { this.step = 0; } onClick() { this.service.count += this.step; this.digest(); } setStep(step) { this.step = step; } } class ShowCountComponent extends SeniorComponentFactory.createSuperClass({ template: `<span style="border: 1px solid #ddd;padding: .5em 1em; display: inline-block;">@{$component.service.count}</span>` }) { static getName() { return 'show-count'; } static inject() { return { service: CounterService }; } }
Let's take a look at the final result:
In the above example, the CounterService
class is injected into both the ModifyButtonComponent
and ShowCountComponent
components. This allows the two components to access and modify the count
property of the CounterService
instance. The ModifyButtonComponent
component increments the count when clicked, while the ShowCountComponent
component displays the current count value.
By injecting the CounterService
into the two components, they share the same service instance, enabling communication between them. Any changes made to the count
property in one component will also be reflected in the other component.
Dependency injection is a powerful feature that enables components to communicate and share data without being tightly coupled. It promotes modularity and reusability in applications.