Skip to content

Custom Components

1. Introduction

Developers can abstract the functional modules in the page into custom components for reuse in different pages; they can also split complex pages into multiple low-coupling modules to help maintain code. Custom components are very similar to basic components when used.

1.1 Create custom components

Similar to pages, a custom component consists of json, wxml, wxss, and js4 files. To write a custom component, you first need to declare the custom component in the json file (set the component field to true to set this group of files as a custom component):

js
{
  "component": true
}

At the same time, you also need to write the component template in the wxml file and add the component style in the wxss file. Their writing methods are similar to those of pages. For specific details and precautions, see Component template and style

Code example:

js
<!-- This is the internal WXML structure of the custom component -->
<view class="inner">
  {{innerText}}
</view>
<slot></slot>
js
/*  This style only applies to this custom component  */
.inner {
  color: red;
}

TIP

ID selectors, attribute selectors, and tag name selectors should not be used in component wxss.

In the js file of the custom component, you need to use Component() to register the component and provide the component's attribute definition, internal data and custom methods. The component's attribute values

The component's attribute values ​​and internal data will be used for rendering the component's WXML. Attribute values ​​can be passed in from outside the component; see more details. Component Constructor

Code example:

js
Component({
  properties: {
    // The innerText property is defined here, and the property value can be specified when the component is used.
    innerText: {
      type: String,
      value: "default value",
    },
  },
  data: {
    // Here are some component internal data
    someData: {},
  },
  methods: {
    // Here is a custom method
    customMethod: function () {},
  },
});

1.2 Using custom components

Before using registered custom components, you must first make a reference declaration in the json file of the page. At this time, you need to provide the tag name of each custom component and the corresponding custom component file path:

js
{
  "usingComponents": {
    "component-tag-name": "path/to/the/custom/component"
  }
}

In this way, you can use the custom component node name, that is, the tag name of the custom component, in the wxml of the page just like using the basic component, and the node attribute is the attribute value passed to the component.

TIP

The usingComponents field declared in app.json is considered a global custom component and can be used directly in the page or custom component in the mini program without further declaration.

Code example:

js
<view>
  <!-- The following is a reference to a custom component -->
  <component-tag-name inner-text="Some text"></component-tag-name>
</view>

The wxml node structure of the custom component will be inserted into the reference location after being combined with the data.

1.3 Notes

Some details to note:

  • Because the WXML node tag name can only be a combination of lowercase letters, hyphens, and underscores, the tag name of the custom component can only contain these characters.
  • Custom components can also reference custom components, and the reference method is similar to the way the page references custom components (using the usingComponents field).
  • The project root directory name where the custom component and page are located cannot be prefixed with 'wx-', otherwise an error will be reported.
Note that whether usingComponents is used in the page file will make the prototype of the page's this object slightly different, including:
  • The prototype of the page using usingComponents is inconsistent with that when not using it, that is, the result of Object.getPrototypeOf(this) is different.
  • When using usingComponents, there are more methods, such as selectComponent.
  • For performance reasons, when usingComponents is used, the setData content will not be directly deep copied, that is, after this.setData({ field: obj }) this.data.field === obj. (Deep copying occurs when this value is passed between components.)
If the page is complex, it is recommended to retest when adding or deleting the usingComponents definition segment.

2. Component templates and styles

Similar to pages, custom components have their own wxml templates and wxss styles.

2.1 Component templates

The writing method of component templates is the same as that of page templates. The node tree generated by combining the component template with the component data will be inserted into the reference position of the component.

A <slot> node can be provided in the component template to carry the child nodes provided when the component is referenced.

Code example:

js
<!-- Component template -->
<view class="wrapper">
  <view>This indicates the internal node of the component</view>
  <slot></slot>
</view>
js
<!--Page template where the component is referenced-->
<view>
  <component-tag-name>
    <!-- This part is added to the location of the  <slot>  component -->
    <view>This is inserted in the slot component</view>
  </component-tag-name>
</view>

TIP

The custom component referenced in the template and its corresponding node name need to be explicitly defined in the json file, otherwise it will be treated as a meaningless node. In addition, the node name can also be declared as Abstract node

2.2 Template data binding

Similar to ordinary WXML templates, data binding can be used, so that dynamic data can be passed to the properties of child components.

Code example:

js
<!--Page template where the component is referenced-->
<view>
  <component-tag-name prop-a="{{dataFieldA}}" prop-b="{{dataFieldB}}">
    <!-- This part is added to the location of the  <slot>  component -->
    <view>This is inserted in the slot component</view>
  </component-tag-name>
</view>

In the above example, the component's properties propA and propB will receive the data passed by the page. The page can change the bound data field through setData.

TIP

Such data binding can only pass JSON compatible data

2.3 Component WXML slot

The component's wxml can contain slot nodes to carry the wxml structure provided by the component user.

By default, there can only be one slot in a component's wxml. When multiple slots are needed, you can declare it in the component js.

js
Component({
  options: {
    multipleSlots: true, // Enable multiple slots in the options of component definition.
  },
  properties: {
    /* ... */
  },
  methods: {
    /* ... */
  },
});

At this point, you can use multiple slots in the wxml of this component, distinguished by different names.

js
<!--Page template where the component is referenced-->
<view>
  <component-tag-name>
    <!-- This part is added to the location of the  <slot name="before">  component -->
    <view slot="before">This is inserted in the slot name="before" component</view>
    <!-- This part is added to the location of the  <slot name="after">  component -->
    <view slot="after">This is inserted in the slot name="after" component</view>
  </component-tag-name>
</view>

When using it, use the slot attribute to insert nodes into different slots.

js
<!--Page template where the component is referenced-->
<view>
  <component-tag-name>
    <!-- This part is added to the location of the  <slot name="before">  component -->
    <view slot="before">This is inserted in the slot name="before" component</view>
    <!-- This part is added to the location of the  <slot name="after">  component -->
    <view slot="after">This is inserted in the slot name="after" component</view>
  </component-tag-name>
</view>

2.4 Component style

The style of the component corresponds to the wxss file, which is only effective for the nodes in the component wxml. When writing component styles, you need to pay attention to the following points:

  • Components and pages that reference components cannot use id selectors (#a), attribute selectors ([a]) and tag name selectors. Please use class selectors instead.
  • Using descendant selectors (.a .b) in components and pages that reference components may have unexpected performance in some extreme cases. If you encounter such situations, please avoid using them.
  • Child element selectors (.a>.b) can only be used between view components and their child nodes. Using them in other components may cause unexpected situations.
  • Inherited styles, such as font and color, will be inherited from outside the component to the component.
  • Except for inherited styles, styles in app.wxss and styles of the page where the component is located are invalid for custom components (unless the component style isolation option is changed).
js
#a { } /* Cannot be used in components */
[a] { } /* Cannot be used in components */
button { } /* Cannot be used in components */
.a > .b { } /* Invalid unless  .a  is  view  component node */

In addition, a component can specify the default style of the node it is in, using the :host selector.

Code example:

js
/* Component custom-component.wxss */
:host {
  color: yellow;
}
js
<!-- page's WXML -->
<custom-component>The text here is highlighted in yellow</custom-component>

2.5 External style

Sometimes, a component wants to accept style classes passed in from outside. In this case, you can define several external style classes in the Component using the externalClasses definition section.

This feature can be used to implement a hover-class attribute similar to the view component: the page can provide a style class, which is assigned to the view's hover-class. This style class itself is written in the page rather than in the view component's implementation.

TIP

When using a normal style class and an external style class on the same node, the priority of the two classes is undefined, so it is best to avoid this situation.

Code example:

js
/* Component custom-component.js */
Component({
  externalClasses: ["my-class"],
});
js
<!-- Component  custom-component.wxml -->
<custom-component class="my-class">The colour of this text is determined by the class outside the component.</custom-component>

In this way, the user of the component can specify the class corresponding to this style class, just like using a normal attribute.

Code example:

js
<!-- page's WXML -->
<custom-component my-class="red-text" />
<custom-component my-class="large-text" />
js
.red-text {
  color: red;
}
.large-text {
  font-size: 1.5em;
}

2.6 Make components accept global styles

By default, the style of custom components is only affected by the custom component wxss unless in the following two cases:

  • The tag name selector (or some other special selectors) is used in app.wxss or the page's wxss to directly specify the style, which will affect the page and all components. This is usually not recommended.
  • The addGlobalClass option is activated in a specific custom component, which makes this custom component affected by all style definitions in app.wxss or the page's wxss.
To activate the addGlobalClass option, just set the options.addGlobalClass field to true in the Component constructor.

TIP

When the addGlobalClass option is activated, there is a risk of external styles polluting the component style, please choose carefully.

Code example:

js
/* Component  custom-component.js */
Component({
  options: {
    addGlobalClass: true,
  }
}
js
<!-- Component  custom-component.wxml -->
<text class="red-text">
  The colour of this text is determined by the style definitions in `app.wxss` and the page `wxss
</text>
js
/* app.wxss */
.red-text {
  color: red;
}

3.Component constructor

The Component constructor can be used to define components. When calling the Component constructor, you can specify the component's properties, data, methods, etc.

For detailed parameter meanings and usage, please refer to Component component

js
Component({
  behaviors: [],

  properties: {
    myProperty: {
      // Property name
      type: String, // Type (required), currently accepted types include: String, Number, Boolean, Object, Array, null (indicating any type)
      value: "", //Property initial value (optional), if not specified, one will be selected based on the type
      observer(newVal, oldVal, changedPath) {
        // The function to be executed when the property is changed (optional) can also be written as a method name string defined in the methods section, such as: '_propertyChange'
        // Usually newVal is the newly set data, oldVal is the old data
      }

    },
    myProperty2: String, // Simplified definition
  },

  data: {}, // Private data, used for template rendering

  lifetimes: {
    // Lifecycle function, which can be a function or a method name defined in the methods field
    attached: function () {},
    moved: function () {},
    detached: function () {},
  },

  // Lifecycle function, which can be a function or a method name defined in the methods field
  attached: function () {}, // The declaration in the attached field is overwritten by that in the lifetimes field
  ready: function () {},

  pageLifetimes: {
    // Component page lifecycle function
    show: function () {},
    hide: function () {},
    resize: function () {},
  },

  methods: {
    onMyButtonTap: function () {
      this.setData({
        // The properties and data are updated in the same way as with the page data
      });
    },
    // The internal method starts with an underscore
    _myPrivateMethod: function () {
      // Here, data.A[0].B is set to 'myPrivateData'
      this.setData({
        "A[0].B": "myPrivateData",
      });
    },
    _propertyChange: function (newVal, oldVal) {},
  },
});

In the properties definition section, the property name is written in camelCase (propertyName); in wxml, hyphens are used when specifying property values ​​(component-tag-name property-name="attr value"), and camelCase is used when applied to data binding (attr="{{propertyName}}").

3.1 Use Component constructor to construct page

In fact, the page of the mini program can also be regarded as a custom component. Therefore, the page can also be constructed using the Component constructor, with the same definition section and instance method as ordinary components, but the corresponding json file is required to contain the usingComponents definition section.

At this time, the properties of the component can be used to receive the parameters of the page, such as visiting the page /pages/index/index?paramA=123&paramB=xyz. If the properties paramA or paramB are declared, they will be assigned 123 or xyz.

The life cycle methods of the page (that is, the methods starting with on) should be written in the methods definition section.

Code example:

js
{
  "usingComponents": {}
}
js
Component({
  properties: {
    paramA: Number,
    paramB: String,
  },

  methods: {
    onLoad: function () {
      this.data.paramA; // Value of page parameter paramA
      this.data.paramB; // Value of page parameter paramB
    },
  },
});

One advantage of using the Component constructor to construct the page is that behaviors can be used to extract the common code segments in all pages.

For example, if you want to execute the same code when all pages are created and destroyed, you can extract this code into behaviors.

js
// page-common-behavior.js
module.exports = Behavior({
  attached: function () {
    // On page creation
    console.info("Page loaded!");
  },
  detached: function () {
    // When the page is destroyed
    console.info("Page unloaded!");
  },
});
js
// page A
var pageCommonBehavior = require("./page-common-behavior");
Component({
  behaviors: [pageCommonBehavior],
  data: {
    /* ... */
  },
  methods: {
    /* ... */
  },
});
js
// page B
var pageCommonBehavior = require("./page-common-behavior");
Component({
  behaviors: [pageCommonBehavior],
  data: {
    /* ... */
  },
  methods: {
    /* ... */
  },
});

4. Communication and events between components

4.1 Communication between components

There are several basic ways of communication between components.

  • WXML data binding: used by parent components to set data to specified properties of child components. Only JSON compatible data can be set. The details are introduced in the Component Templates and Styles
  • Events: used by child components to pass data to parent components. Any data can be passed.
  • If the above two methods are not enough to meet the needs, the parent component can also obtain the child component instance object through the this.selectComponent method, so that any data and methods of the component can be directly accessed.

4.2 Listening to events

The event system is one of the main ways of communication between components. Custom components can trigger any events, and pages that reference components can listen to these events. For the basic concepts and usage of events, see Events

The method of listening to custom component events is exactly the same as the method of listening to basic component events:

Code example:

js
<!-- The "onMyEvent" method is called when the custom component triggers "myevent" -->
<component-tag-name bindmyevent="onMyEvent" />
<!-- Or write it like this -->
<component-tag-name bind:myevent="onMyEvent" />
js
Page({
  onMyEvent: function (e) {
    e.detail; // The detail object provided when the custom component triggers an event
  },
});

4.3 Triggering events

When a custom component triggers an event, you need to use the triggerEvent method to specify the event name, detail object, and event options:

Code example:

js
<!-- In the custom component -->
<button bindtap="onTap">Click this button to trigger the "myevent" event</button>
js
Component({
  properties: {},
  methods: {
    onTap: function () {
      var myEventDetail = {}; // detail object, provided to the event monitoring function
      var myEventOption = {}; // Event triggering options
      this.triggerEvent("myevent", myEventDetail, myEventOption);
    },
  },
});

Options for triggering events include:

Option name
Type
Is it required?
Default value
Description
bubbles
Boolean
No
false
Whether the event bubbles
composed
Boolean
No
false
Whether the event can cross component boundaries. When false, the event can only be triggered on the node tree of the referenced component and will not enter any other components
capturePhase
Boolean
No
false
Whether the event has a capture phase

For the concepts of bubbling and capturing phases, please read Detailed explanation of events Related instructions in the chapter.

Code example:

js
// page page.wxml
<another-component bindcustomevent="pageEventListener1">
  <my-component bindcustomevent="pageEventListener2"></my-component>
</another-component>
js
// Component  another-component.wxml
<view bindcustomevent="anotherEventListener">
  <slot />
</view>
js
// Component  my-component.wxml
<view bindcustomevent="myEventListener">
  <slot />
</view>
js
// Component  my-component.js
Component({
  methods: {
    onTap: function () {
      this.triggerEvent("customevent", {}); // Only pageEventListener2 is triggered
      this.triggerEvent("customevent", {}, { bubbles: true }); // pageEventListener2 and pageEventListener1 are triggered in sequence
      this.triggerEvent("customevent", {}, { bubbles: true, composed: true }); // pageEventListener2, anotherEventListener, and pageEventListener1 are triggered in sequence
    },
  },
});

4.4 Get component instance

You can call this.selectComponent in the parent component to get the instance object of the child component.

When calling, you need to pass in a matching selector selector, such as: this.selectComponent('.my-component').

For detailed selector syntax, see selector syntax reference document

Code example:

js
// parent component
Page({
  data: {},
  getChildComponent: function () {
    const child = this.selectComponent(".my-component");
    console.log(child);
  },
});

In the above example, the parent component will get the child component instance object with class my-component, that is, this of the child component.

If you need to customize the data returned by selectComponent, you can use the built-in behavior: wx://component-export.

When using this behavior, the export definition section in the custom component will be used to specify the return value when the component is called by selectComponent.

Code example:

js
// Custom Components my-component Internal
Component({
  behaviors: ["wx://component-export"],
  export() {
    return { myField: "myValue" };
  },
});
js
<! -- When using custom components -->
<my-component id="the-id" />
js
// Called by the parent component
const child = this.selectComponent("#the-id"); // be tantamount to { myField: 'myValue' }

In the above example, when the parent component obtains the child component instance with id the-id, it gets the object { myField: 'myValue' }.

5. Component Lifecycle

The component lifecycle refers to some functions of the component itself, which are automatically triggered at special time points or when encountering some special framework events.

Among them, the most important lifecycles are created attached detached, which contain the most important time points in the life cycle of a component instance.

  • When the component instance is just created, the created lifecycle is triggered. At this time, the component data this.data is the data data defined in the Component constructor. setData cannot be called at this time. Usually, this lifecycle should only be used to add some custom attribute fields to the component this.
  • After the component is fully initialized and enters the page node tree, the attached lifecycle is triggered. At this time, this.data has been initialized to the current value of the component. This lifecycle is very useful, and most initialization work can be done at this time.
  • After the component leaves the page node tree, the detached lifecycle is triggered. When exiting a page, if the component is still in the page node tree, detached will be triggered.

5.1 Defining lifecycle methods

Lifecycle methods can be defined directly in the first-level parameters of the Component constructor.

Component lifecycles can also be declared in the lifetimes field (this is the recommended way and has the highest priority).

Code example:

js
Component({
  lifetimes: {
    attached: function () {
      // Executed when the component instance enters the page node tree
    },
    detached: function () {
      // Executed when the component instance is removed from the page node tree
    },
  },
  // The older definition methods below can be used for compatibility with base library versions under 2.2.3
  attached: function () {
    // Executed when the component instance enters the page node tree
  },
  detached: function () {
    // Executed when the component instance is removed from the page node tree
  },
  // ...
});

Lifecycle methods can also be written in behaviors, and they will not overlap with the same-name lifecycles in other behaviors. But please note that if a component directly or indirectly references the same behavior multiple times, the lifecycle functions in this behavior will only be executed once in one execution opportunity.

All available lifecycles are shown in the following table

Lifecycle
Parameter
Description
created
None
Component life cycle function-executed when the component instance is just created. Note that setData cannot be called at this time
attached
None
Component life cycle function-executed when the component instance enters the page node tree
ready
None
Component life cycle function-executed after component layout is completed
moved
None
Component life cycle function-executed when the component instance is removed from the page node tree
detached
None
Component life cycle function-executed when the component instance is removed from the page node tree
error
Object Error
Definition of inter-component relationships, see

5.2 Life cycle function of the page where the component is located

Lifecycle
Parameter
Description
show
None
Executed when the page where the component is located is displayed
hide
None
Executed when the page where the component is located is hidden
resize
Object Size
Executed when the size of the page where the component is located changes

Description

The pageLifetime of the custom tabBar will not be triggered.

Code example:

js
Component({
  pageLifetimes: {
    show: function () {
      // Page displayed
    },
    hide: function () {
      // Page hidden
    },
    resize: function (size) {
      // Page resized
    },
  },
});

6.behaviors

Behaviors are features used to share code between components, similar to 'mixins' or 'traits' in some programming languages.

Each behavior can contain a set of properties, data, lifecycle functions, and methods. When a component references it, its properties, data, and methods will be merged into the component, and the lifecycle function will be called at the corresponding time. Each component can reference multiple behaviors, and behaviors can also reference other behaviors.

For detailed parameter meanings and usage, please refer to Behavior Reference documentation.

6.1 Used in components

Behavior needs to be defined using the Behavior() constructor.

Code example:

js
// my-behavior.js
module.exports = Behavior({
  behaviors: [],
  properties: {
    myBehaviorProperty: {
      type: String,
    },
  },
  data: {
    myBehaviorData: {},
  },
  attached() {},
  methods: {
    myBehaviorMethod() {},
  },
});

When referencing a component, just list them one by one in the behaviors definition section.

js
// my-component.js
var myBehavior = require("my-behavior");
Component({
  behaviors: [myBehavior],
  properties: {
    myProperty: {
      type: String,
    },
  },
  data: {
    myData: "my-component-data",
  },
  created: function () {
    console.log("[my-component] created");
  },
  attached: function () {
    console.log("[my-component] attached");
  },
  ready: function () {
    console.log("[my-component] ready");
  },
  methods: {
    myMethod: function () {
      console.log("[my-component] log by myMethod");
    },
  },
});

In the above example, my-behavior is added to the my-component component definition,

and the structure of my-behavior is:

  • Property: myBehaviorProperty
  • Data field: myBehaviorData
  • Method: myBehaviorMethod
  • Lifecycle functions: attached, created, ready
This will make the final structure of my-component:
  • Properties: myBehaviorProperty, myProperty
  • Data fields: myBehaviorData, myData
  • Methods: myBehaviorMethod, myMethod
  • Lifecycle functions: attached, created, ready
When a component triggers the lifecycle, the execution order of the lifecycle functions in the above example is:
  1. [my-behavior] created
  2. [my-component] created
  3. [my-behavior] attached
  4. [my-component] attached
  5. [my-behavior] ready
  6. [my-component] ready

Detailed rule reference Override and combination rules for fields with the same name

6.2 Override and combination rules for fields with the same name

A component and the behavior it references can contain fields with the same name. These fields are handled as follows:

  • If there are properties or methods with the same name:
    • If the component itself has this property or method, the component's property or method will override the property or method with the same name in the behavior;
    • If the component itself does not have this property or method, the property or method of the behavior defined later in the component's behaviors field will override the property or method with the same name earlier;
    • Based on 2, if there is a nested reference to behavior, the rule is: the referencer behavior overrides the property or method with the same name in the referenced behavior.
  • If there are data fields with the same name (data):
    • If the data fields with the same name are all object types, objects will be merged;
    • In other cases, data will be overwritten, and the overwriting rules are: referencer behavior > referenced behavior, later behavior > earlier behavior (higher priority overrides lower priority, and the largest one has the highest priority).
  • Lifecycle functions and observers will not overwrite each other, but will be called one by one at the corresponding triggering time:
    • For different lifecycle functions, follow the execution order of component lifecycle functions;
    • For the same lifecycle function and the same field observers, follow the following rules:
      • Behavior takes precedence over component execution;
      • The referenced behavior takes precedence over the referenced behavior;
      • The earlier behavior takes precedence over the later behavior;
    • If the same behavior is referenced multiple times by a component, the lifecycle functions and observers it defines will not be executed repeatedly.

6.3 Built-in behaviors

Custom components can obtain some behaviors of built-in components by referencing built-in behaviors.

js
Component({
  behaviors: ["wx://form-field"],
});

In the above example, wx://form-field represents a built-in behavior, which makes this custom component behave like a form control.

Built-in behaviors often add some properties to components. If there is no special description, the component can override these properties to change its type or add an observer.

6.3.1 wx://form-field

Make custom components behave like form controls The form component can recognize these custom components and return the component's field name and its corresponding field value in the submit event.

Property nameTypeDescription
nameStringField name in the form
valueAnyField value in the form

Code example:

js
// custom-form-field.js
Component({
  behaviors: ["wx://form-field"],
  data: {
    value: "",
  },
  methods: {
    onChange: function (e) {
      this.setData({
        value: e.detail.value,
      });
    },
  },
});

6.3.2 wx://form-field

Make custom components support export definition section This definition section can be used to specify the return value when the component is called by selectComponent.

Detailed usage and code examples can be found at: selectComponent reference document

7.Relationships between components

7.1Define and use relationships between components

Sometimes you need to implement such components:

js
<custom-ul>
  <custom-li> item 1 </custom-li>
  <custom-li> item 2 </custom-li>
</custom-ul>

In this example, custom-ul and custom-li are both custom components. They have relationships with each other, and the communication between them is often complicated. In this case, adding relations definition section in component definition can solve such problems. Example:

js
// path/to/custom-ul.js
Component({
  relations: {
    "./custom-li": {
      type: "child", // The linked target node should be a child node
      linked: function (target) {
        // Executed each time custom-li is inserted. "target" is the instance object of this node, which is triggered after the attached lifecycle of this node.
      },
      linkChanged: function (target) {
        // Executed each time custom-li is moved. "target" is the instance object of this node, which is triggered after the moved lifecycle of this node.
      },
      unlinked: function (target) {
        // Executed each time custom-li is removed. "target" is the instance object of this node, which is triggered after the detached lifecycle of this node.
      },
    },
  },
  methods: {
    _getAllLi: function () {
      // You can get the nodes array via getRelationNodes, including all linked custom-li nodes listed in order
      var nodes = this.getRelationNodes("path/to/custom-li");
    },
  },
  ready: function () {
    this._getAllLi();
  },
});
js
// path/to/custom-li.js
Component({
  relations: {
    "./custom-ul": {
      type: "parent", // The linked target node should be a parent node
      linked: function (target) {
        // Executed after each insertion to custom-ul. "target" is the instance object of the custom-ul node, which is triggered after the attached lifecycle.
      },
      linkChanged: function (target) {
        // Executed after each move. "target" is the instance object of the custom-ul node, which is triggered after the moved lifecycle.
      },
      unlinked: function (target) {
        // Executed after each removal. "target" is the instance object of the custom-ul node, which is triggered after the detached lifecycle.
      },
    },
  },
});

TIP

Relations definitions must be added to both component definitions, otherwise they will not take effect.

7.2 Associating a class of components

Sometimes, a class of components needs to be associated, such as:

js
<custom-form>
  <view>
    input
    <custom-input></custom-input>
  </view>
  <custom-submit> submit </custom-submit>
</custom-form>

The custom-form component wants to associate custom-input and custom-submit components. At this time, if these two components have the same behavior:

js
// path/to/custom-form-controls.js
module.exports = Behavior({
  // ...
});
js
// path/to/custom-input.js
var customFormControls = require("./custom-form-controls");
Component({
  behaviors: [customFormControls],
  relations: {
    "./custom-form": {
      type: "ancestor", // he linked target node should be an ancestor node
    },
  },
});
js
// path/to/custom-submit.js
var customFormControls = require("./custom-form-controls");
Component({
  behaviors: [customFormControls],
  relations: {
    "./custom-form": {
      type: "ancestor", // The linked target node should be an ancestor node
    },
  },
});

Then in the relations definition, this behavior can be used instead of the component path as the target node of the association:

js
// path/to/custom-form.js
var customFormControls = require("./custom-form-controls");
Component({
  relations: {
    customFormControls: {
      type: "descendant", // The linked target node should be a descendant node
      target: customFormControls,
    },
  },
});

7.3relations definition section

The relations definition section contains the target component path and its corresponding options. The options that can be included are shown in the table below.

Option nameTypeIs it required?Description
typeStringYesThe relative relationship of the target component. The optional values
linkedFunctionNoRelationship lifecycle function, triggered when the relationship is established in the page node tree, and the triggering time is after the component attached lifecycle
linkChangedFunctionNoRelationship lifecycle function, triggered when the relationship changes in the page node tree, and the triggering time is after the component moved lifecycle
unlinkedFunctionNoRelationship lifecycle function, triggered when the relationship is detached from the page node tree, and the triggering time is after the component detached lifecycle
targetFunctionNoIf this item is set, it indicates the behavior that the associated target node should have, and all component nodes with this behavior will be associated

8.Data Listener

Data listeners can be used to listen to and respond to changes in any attributes and data fields.

8.1Using data listeners

Sometimes, when some data fields are set by setData, some operations need to be performed.

For example, this.data.sum is always the sum of this.data.numberA and this.data.numberB. At this time, data listeners can be used to implement it as follows

js
Component({
  attached: function() {
    this.setData({
      numberA: 1,
      numberB: 2,
    })
  },
  observers: {
    'numberA, numberB': function(numberA, numberB) {
      // Execute this function when numberA or numberB is set
      this.setData({
        sum: numberA + numberB
      })
    }
  }
})

8.2Listening field syntax

Data listeners support listening to changes in attributes or internal data. You can listen to multiple listeners at the same time. Each setData triggers each listener at most once.

At the same time, listeners can listen to sub-data fields, as shown in the following example.

js
Component({
  observers: {
    'some.subfield': function(subfield) {
      // Triggered when this.data.some.subfield is set via setData
      // (Also triggered when this.data.some is set via setData)
      subfield === this.data.some.subfield
    },
    'arr[12]': function(arr12) {
      // Triggered when this.data.arr[12] is set via setData
      // (Also triggered when this.data.arr is set via setData)
      arr12 === this.data.arr[12]
    },
  }
})

If you need to listen to changes in all sub-data fields, you can use the wildcard **.

js
Component({
  observers: {
    'some.field.**': function(field) {
      // Triggered when setData is used to set this.data.some.field itself or any of its sub-data fields
      // (Also triggered when this.data.some is set via setData)
      field === this.data.some.field
    },
  },
  attached: function() {
    // This will trigger the above observer
    this.setData({
      'some.field': { /* ... */ }
    })
    // This will also trigger the above observer
    this.setData({
      'some.field.xxx': { /* ... */ }
    })
    // This will still trigger the above observer
    this.setData({
      'some': { /* ... */ }
    })
  }
})

In particular, using only the wildcard ** can listen to all setData.

js
Component({
  observers: {
    '**': function() {
      // Triggered upon each setData operation
    },
  },
})

8.3 Notes

  • The data listener listens to the data fields involved in setData. Even if the values
  • If you use setData to set the data fields that it listens to in the data listener function, it may cause an infinite loop, so you need to pay special attention;
  • Compared with the attribute observer, the data listener is more powerful and usually has better performance.

9.Pure data field

Pure data fields are some data fields that are not used for interface rendering and can be used to improve page update performance.

9.1Pure data fields in component data

In some cases, some fields in data (including fields set by setData) will neither be displayed on the interface nor passed to other components, but only used within the current component.

At this time, you can specify such data fields as 'pure data fields', which will only be recorded in this.data and will not participate in any interface rendering process, which will help improve page update performance.

The method to specify 'pure data fields' is to specify pureDataPattern as a regular expression in the options definition section of the Component constructor. Fields whose field names match this regular expression will become pure data fields.

Code example:

js
Component({
  options: {
    pureDataPattern: /^_/ // Specify all data fields starting with _ as pure data fields
  },
  data: {
    a: true, // Plain data field
    _b: true, // Plain data field
  }, methods: { a: true, // Normal data field _b: true, // Plain data field
  methods: {
    myMethod() {
      this.data._b // Plain data fields can be retrieved in this.data
      this.setData({
        c: true, // Plain data field
        _d: true, // Plain data field
      })
    }
  }
})

Pure data fields in the above components will not be applied to WXML:

js
<view wx:if="{{a}}"> This line will be displayed </view>
<view wx:if="{{_b}}"> This line will not be shown </view>

9.2Pure data fields in component properties

Properties can also be specified as pure data fields (following the regular expression of pureDataPattern).

The pure data field in the property can receive the property value passed in from the outside like a normal property, but it cannot be used directly in the component's own WXML.

Code example:

js
Component({
  options: {
    pureDataPattern: /^_/
  },
  properties: {
    a: Boolean,
    _b: {
      type: Boolean,
      observer() {
        // Don't do this! This observer will never be triggered
      }
    },
  }
})

TIP

The property observer of the pure data field in the property will never be triggered! If you want to listen to the property value changes, use data listeners data listeners

9.3 Use data listeners to listen to pure data fields

data listeners It can be used to listen to pure data fields (just like normal data fields). In this way, you can change the interface by listening and responding to changes in pure data fields.

The following example is a custom component that converts JavaScript timestamps to readable times.

Code example:

js
Component({
  options: {
    pureDataPattern: /^timestamp$/ // Specifying the timestamp attribute as a data-only field
  },
  properties: {
    timestamp: Number,
  },
  observers: {
    timestamp: function () {
      // When timestamp is set, display it as a readable time string.
      var timeString = new Date(this.data.timestamp).toLocaleString()
      this.setData({
        timeString: timeString
      })
    }
  }
})
js
<view>{{timeString}}</view>

10.Abstract node

10.1Using abstract nodes in components

Sometimes, the corresponding custom components of some nodes in the custom component template are not determined by the custom component itself, but by the caller of the custom component. In this case, this node can be declared as an 'abstract node'.

For example, let's now implement a 'selectable-group' component, in which radio buttons (custom-radio) or check boxes (custom-checkbox) can be placed. The wxml of this component can be written as follows:

Code example:

js
<!-- selectable-group.wxml -->
<view wx:for="{{labels}}">
  <label>
    <selectable disabled="{{false}}"></selectable>
    {{item}}
  </label>
</view>

Wherein, 'selectable' is not any component declared in the usingComponents field of the json file, but an abstract node. It needs to be declared in the componentGenerics field:

js
{
  "componentGenerics": {
    "selectable": true
  }
}

10.2Using components containing abstract nodes

When using the selectable-group component, you must specify which component 'selectable' is:

js
<selectable-group generic:selectable="custom-radio" />

In this way, when generating an instance of this selectable-group component, the 'selectable' node will generate a 'custom-radio' component instance. Similarly, if used like this:

js
<selectable-group generic:selectable="custom-checkbox" />

the 'selectable' node will generate a 'custom-checkbox' component instance.

TIP

The custom-radio and custom-checkbox mentioned above need to be included in the usingComponents definition section of the corresponding json file of this wxml.

js
{
  "usingComponents": {
    "custom-radio": "path/to/custom/radio",
    "custom-checkbox": "path/to/custom/checkbox"
  }
}

10.3Default component of abstract node

Abstract node can specify a default component. When the specific component is not specified, an instance of the default component will be created. The default component can be specified in the componentGenerics field:

js
{
  "componentGenerics": {
    "selectable": {
      "default": "path/to/default/component"
    }
  }
}

10.4Notes

In the generic reference generic:xxx='yyy' of the node, the value yyy can only be a static value and cannot contain data binding. Therefore, the abstract node feature is not suitable for scenarios where the node name is dynamically determined.

11.Custom component extension

In order to better customize the functions of custom components, you can use the custom component extension mechanism.

11.1Custom component extension

In order to better understand the effect after extension, let's take an example:

js
// behavior.js
module.exports = Behavior({
  definitionFilter(defFields) {
    defFields.data.from = 'behavior'
  },
})

// component.js
Component({
  data: {
    from: 'component'
  },
  behaviors: [require('behavior.js')],
  ready() {
    console.log(this.data.from) // You can see that the output here is behavior instead of component
  }
})

From the example, you can find that the extension of custom components actually provides the ability to modify the definition section of custom components. The above example modifies the content in the data definition section of the custom component.

11.2Using extensions

The Behavior() constructor provides a new definition section definitionFilter to support custom component extensions. definitionFilter is a function that injects two parameters when called. The first parameter is the definition object of the component/behavior that uses the behavior, and the second parameter is the definitionFilter function list of the behavior used by the behavior.

Here is an example to illustrate:

js
// behavior3.js
module.exports = Behavior({
  definitionFilter(defFields, definitionFilterArr) {},
})

// behavior2.js
module.exports = Behavior({
behaviors: [require('behavior3.js')],
definitionFilter(defFields, definitionFilterArr) {
  // definitionFilterArr[0](defFields)
},
})

// behavior1.js
module.exports = Behavior({
behaviors: [require('behavior2.js')],
definitionFilter(defFields, definitionFilterArr) {},
})

// component.js
Component({
behaviors: [require('behavior1.js')],
})

The above code declares 1 custom component and 3 behaviors. Each behavior uses the definitionFilter definition segment. Then the following will happen in the order of declaration:

  1. When behavior2 is declared, the definitionFilter function of behavior3 will be called, where the defFields parameter is the definition segment of behavior2, and the definitionFilterArr parameter is an empty array, because behavior3 does not use other behaviors;
  2. When behavior1 is declared, the definitionFilter function of behavior2 will be called, where the defFields parameter is the definition segment of behavior1, and the definitionFilterArr parameter is an array of length 1. definitionFilterArr[0] is the definitionFilter function of behavior3, because behavior2 uses behavior3. Users can decide whether to call the definitionFilter function of behavior3 when declaring behavior1. If you need to call it, add the code definitionFilterArr[0](defFields) here. The definitionFilterArr parameter will be supplemented and passed in by the basic library;
  3. Similarly, when declaring a component, it will be called behavior1's definitionFilter function.
In simple terms, the definitionFilter function can be understood as when A uses B, A declares that it will call B's definitionFilter function and pass in A's definition object for B to filter. At this time, if B also uses C and D, then B can decide whether to call C and D's definitionFilter function to filter A's definition object.

Code example:

The following uses extensions to simply implement the calculation property function of custom components:

js
// behavior3.js
module.exports = Behavior({
  definitionFilter(defFields, definitionFilterArr) {},
})

// behavior2.js
module.exports = Behavior({
behaviors: [require('behavior3.js')],
definitionFilter(defFields, definitionFilterArr) {
  // definitionFilterArr[0](defFields)
},
})

// behavior1.js
module.exports = Behavior({
behaviors: [require('behavior2.js')],
definitionFilter(defFields, definitionFilterArr) {},
})

// component.js
Component({
behaviors: [require('behavior1.js')],
})

Use in component:

js
const beh = require('./behavior.js')
Component({
  behaviors: [beh],
  data: {
    a: 0,
  },
  computed: {
    b() {
      return this.data.a + 100
    },
  },
  methods: {
    onTap() {
      this.setData({
        a: ++this.data.a,
      })
    }
  }
})
js
<view>data: {{a}}</view>
<view>computed: {{b}}</view>
<button bindtap="onTap">click</button>

The implementation principle is very simple. Encapsulate the existing setData twice, calculate the values

12.Placeholder component

When using features such as Subpackage asynchronization , other custom components referenced by custom components may be unavailable at the beginning of rendering. At this time, in order to prevent the rendering process from being blocked, the unavailable custom component needs a "placeholder component" (Component placeholder). The basic library will use the placeholder component to replace the unavailable component for rendering, and then replace the placeholder component back to the component after the component is available.

The placeholder component of a custom component can be another custom component or a built-in component.

12.1Configuration

The componentPlaceholder field in the JSON configuration corresponding to the page or custom component is used to specify the placeholder component, such as:

js
{
  "usingComponents": {
    "comp-a": "../comp/compA",
    "comp-b": "../comp/compB",
    "comp-c": "../comp/compC"
  },
  "componentPlaceholder": {
    "comp-a": "view",
    "comp-b": "comp-c"
  }
}

This configuration means:

  • The placeholder component of component comp-a is the built-in component view
  • The placeholder component of component comp-b is the custom component comp-c (its path is configured in usingComponents)
Assume that the template corresponding to this configuration is as follows:
js
<button ontap="onTap">display component</button>
<comp-a wx-if="{{ visible }}">
 <comp-b prop="{{ p }}">text in slot</comp-b>
</comp-a>

When the mini program starts, visible is false, then only button will be rendered; after clicking the button, this.setData( visible: true ) is executed. At this time, if comp-a and comp-b are both unavailable, the page will be rendered as:

js
<button>display component</button>
<view>
  <comp-c prop="{{ p }}">text in slot</comp-c>
</view>

After comp-a and comp-b are prepared, the page is replaced with:

js
<button>display component</button>
<comp-a>
  <comp-b prop="{{ p }}">text in slot</comp-b>
</comp-a>

12.2Notes

  1. When a component is designated as a placeholder component (such as comp-c in the above example), it is invalid to specify a placeholder component for it. It can be understood that if a component needs to be a placeholder component for other components, it must be available at the beginning;
  2. Currently, the situations where custom components are unavailable include:
    2.1. When using the asynchronous subpackage feature, components of other subpackages are referenced, but the corresponding subpackages have not been downloaded;
    2.2. When using the injection feature, the component has not been injected;
  3. If a component is unavailable and its placeholder component does not exist, an error will be reported and thrown when rendering;
  4. If a component does not exist, but an available placeholder component is specified for it, the placeholder component can be rendered normally, but an error will be reported and thrown when trying to replace it later.

Appendix: Rendering process with placeholder components

When the basic library tries to render a component, it will first recursively check usingComponents to collect information about all components it will use; in this process, if a used component is unavailable, the basic library will first check whether it has a corresponding placeholder component. If not, the basic library will interrupt the rendering and throw an error; if so, it will be marked and used in the subsequent rendering process to replace the unavailable component for rendering. The unavailable component will try to prepare (download subpackages or inject code, etc.) after the current rendering process ends; after the preparation process is completed, try to render the component (actually executing this process) and replace the previously rendered placeholder component.