tencent cloud

Feedback

Custom Components

Last updated: 2024-11-21 18:34:19

    1. Introduce

    Developers can abstract feature modules in a page into custom components to reuse them in different pages, or split a page into multiple low-coupling modules for easy code maintenance. Custom components are used in almost the same way as base components.

    1.1 Creating Custom Component

    Similar to a page, a custom component consists of json, wxml, wxss, and js files. To write a custom component, you first need to make a custom component declaration in the json file (set the files as custom components by setting the component field to true):
    {
    "component": true
    }
    In the meanwhile, write a component template in the wxml file and a component style in the wxss file in a same way that you write a page. For details and considerations, see Component Template and Style.
    Code example:
    <!-- This is the internal WXML structure of the custom component -->
    <view class="inner">
    {{innerText}}
    </view>
    <slot></slot>
    /* This style only applies to this custom component */
    .inner {
    color: red;
    }
    Note:
    ID selectors, attribute selectors, and tag name selectors should not be used in the component's wxss.
    In the custom component's js file, you need to register the component using Component() and provide the component's property definitions, internal data, and custom methods.
    The property values and internal data of the component are used to render the component's wxml. The property values are passed in from outside the component. For details, see Component Constructor.
    Code example:
    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 Component

    Before using a registered custom component, make a reference declaration in the json file of the page. You need to provide the tag name of each custom component and the file path of the corresponding custom component:
    {
    "usingComponents": {
    "component-tag-name": "path/to/the/custom/component"
    }
    }
    As such, you can use custom components like you would with base components in the wxml of the page. The node name is the tag name of the custom component, and the node property is the property value passed to the component.
    Note:
    The usingComponents field declared in app.json is considered to be a global custom component and can be used directly in pages or custom components within the mini program without having to declare it again.
    Code example:
    <view>
    <!-- The following is a reference to a custom component -->
    <component-tag-name inner-text="Some text"></component-tag-name>
    </view>
    The custom component's wxml node structure, after being combined with the data, will be inserted into the reference location.

    1.3 Notes

    Please note the following:
    Since the tag name of a WXML node can only be a combination of lowercase letters, hyphens (-), and underscores (_), the tag name of a custom component can contain these characters only.
    Custom components can also reference other custom components in a similar way to referencing custom components by pages (the usingComponents field is used).
    The name of the project's root directory where the custom components and pages are located cannot be prefixed with "wx-". Otherwise, an error will occur.
    Note that using usingComponents in the page file will make the prototype of the page's this object slightly different, including:
    The prototype that uses the usingComponents page differs from the prototype that does not use usingComponents, i.e. the result of Object.getPrototypeOf(this) is different.
    More methods, such selectComponent, are available when usingComponents is used.
    For performance reasons, when usingComponents is used, the content of setData is not deep copied directly, that is, this.data.field === obj after this.setData({ field: obj }) is called. (Deep copy occurs when this value is passed between components.)
    For complicated pages, retest is required if the usingComponents definition field is added or removed.

    2. Component Template and Style

    Similar to a page, each custom component has its own wxml template and wxss style.

    2.1 Component Template

    Component templates are written in the same way as page templates. The node tree generated by binding the component template with the component data will be inserted into the location where the component is referenced.
    A <slot> node can be provided in the component template to host the child nodes provided via component reference.
    Code example:
    <!-- Component template -->
    <view class="wrapper">
    <view>This indicates the internal node of the component</view>
    <slot></slot>
    </view>
    <!--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>
    Note:
    the custom component referenced in the template and the name of its corresponding node should be explicitly defined in the json file, otherwise the node will be deemed as meaningless. Besides, the node name can also be declared as an Abstract Node.

    2.2 Template Data Binding

    Like an ordinary WXML template, data binding can also be employed to pass dynamic data to child component properties.
    Code example:
    <!--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 properties propA and propB receive data passed from the page, and the page can change linked data field via setData.
    Note:
    Only JSON compatible data can be passed via the above data binding.

    2.3 slot of Component's WXML

    The slot node can be included in the component's wxml to host the wxml structure provided by the component user.
    By default, a component's wxml can contain only one slot. Multiple slots can be enabled by declaring in the component js if needed.
    Component({
    options: {
    multipleSlots: true // Enable multiple slots in the options of component definition.
    },
    properties: { /* ... */ },
    methods: { /* ... */ }
    })
    In this case, multiple slots can be used in the wxml of this component, each with a unique name.
    <!-- Component template -->
    <view class="wrapper">
    <slot name="before"></slot>
    <view>This indicates the internal details of the component</view>
    <slot name="after"></slot>
    </view>
    You can use the slot property to insert nodes into different slots.
    <!--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 a component for the wxss file is only valid for the nodes in the component's wxml. Note the following when writing a component style:
    For the components and the pages that reference the components, the class selector, instead of the id selector (#a), property selector ([a]) and tag name selector, should be used;
    Unexpected behaviors may occur with the descendant selector (.a .b) used in the components and the pages that reference the components in extreme cases. If so, avoid using it;
    The child selector (.a>.b) can be only used for the view component and its child nodes; unexpected behaviors may occur if it is used in other components;
    Inheritance styles, such as font and color, are inherited into the components from outside the components;
    In addition to inheritance styles, the styles in app.wxss and the styles of the pages that reference the components are invalid for custom components (unless the component style isolation option is changed).
    #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 default style can be specified for the node where the component resides by using the :host selector
    Code example:
    /* Component custom-component.wxss */
    :host {
    color: yellow;
    }
    <!-- page's WXML -->
    <custom-component>The text here is highlighted in yellow</custom-component>

    2.5 Component Style Isolation

    Sometimes a component wants to accept incoming style classes. In this case, several external style classes can be defined in the Component using the externalClasses definition segment.
    This feature can be used to implement a hover-class property similar to that of the view component: the page can provide a style class that gives the view its hover-class, which is itself written in the page rather than in the view component's implementation.
    Note:
    When using a normal style class and an external style class on the same node, the priority of both classes is undefined, so it is best to avoid this situation.
    Code example:
    /* Component custom-component.js */
    Component({
    externalClasses: ['my-class']
    })
    <!-- 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 with normal properties.
    Code example:
    <!-- page's WXML -->
    <custom-component my-class="red-text" />
    <custom-component my-class="large-text" />
    .red-text {
    color: red;
    }
    .large-text {
    font-size: 1.5em;
    }

    2.6 Making components accept global styles

    By default, the styling of a custom component is only affected by the custom component wxss. Except in the following two cases:
    The app.wxss or the page's wxss uses tag name selectors (or some other special selectors) to directly specify styles that affect the page and the component in its entirety. This is generally not a recommended practice.
    Activating the addGlobalClass option on a specific custom component makes it possible for that custom component to be affected by all style definitions in app.wxss or the page's wxss.
    To activate the addGlobalClass option, simply set the options.addGlobalClass field to true in the Component constructor.
    Note:
    When the addGlobalClass option is activated, there is a risk of external styles contaminating the component style, so please choose carefully.
    Code example:
    /* Component custom-component.js */
    Component({
    options: {
    addGlobalClass: true,
    }
    }
    <!-- 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>
    /* app.wxss */
    .red-text {
    color: red;
    }

    3. Component Constructor

    The Component constructor is used to define components. You can specify component properties, data, and methods when calling the Custom Components.
    Component({
    
    behaviors: [],
    
    properties: {
    myProperty: { // Property name
    type: String,
    value: ''
    },
    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 segment, the property name is written in camel case (propertyName); in wxml, the corresponding property value is written in hyphenated case (component-tag-name property-name="attr value") when specifying the property value, and in camel case when applying it to data binding (attr=" {{propertyName}}").

    3.1 Construct a Page via Component Constructor

    In fact, the Mini Program's page can also be considered as a custom component. Thus, its page can also be constructed using the Component constructor, as it has the same definition field and instance method as a general component. However, you must include the usingComponents definition field in the corresponding json file.
    In this case, the properties of the component can be used to receive page parameters, such as visited pages /pages/index/index?paramA=123&paramB=xyz. If the property paramA or paramB is declared, they will be assigned 123 or xyz.
    The page's lifecycle method (i.e., the method starting with on) must be written in the methods definition field.
    Code example:
    {
    "usingComponents": {}
    }
    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 of the benefits of using the Component constructor to construct pages is that you can use behaviors to extract code segments that are common to all pages.
    For example, if you want to execute the same piece of code when all pages are created and destroyed, you can extract that code into behaviors.
    Code example:
    // 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!')
    }
    })
    // page A
    var pageCommonBehavior = require('./page-common-behavior')
    Component({
    behaviors: [pageCommonBehavior],
    data: { /* ... */ },
    methods: { /* ... */ },
    })
    // page B
    var pageCommonBehavior = require('./page-common-behavior')
    Component({
    behaviors: [pageCommonBehavior],
    data: { /* ... */ },
    methods: { /* ... */ },
    })

    4. Inter-component Communication and Events

    4.1 Inter-component Communication

    The following methods are used for communication between components.
    WXML data binding: This is used by a parent component to set data for the specified property of a child component. Only JSON-compatible data is supported. For more information, see Component Template and Style.
    Event: This is used by a child component to send any type of data to a parent component.
    If the two methods above cannot meet your needs, the parent component can also get the instance objects of the child component via the this.selectComponent method. This allows you to directly access any component data or method.

    4.2 Monitoring Events

    The event system is one of the methods of communication between components. Custom components can trigger any events, and pages that references the components can monitor these events. For the basic concept and usage of events, see Events.
    Customer component events are monitored in the same way as with basic component events:
    Code example:
    <!-- 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" />
    Page({
    onMyEvent: function(e){
    e.detail // The detail object provided when the custom component triggers an event
    }
    })

    4.3 Triggering Event

    When a custom component triggers an event, the triggerEvent method should be used to specify the event name, detail object, and event options:
    Code example:
    <!-- In the custom component -->
    <button bindtap="onTap">Click this button to trigger the "myevent" event</button>
    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)
    }
    }
    })
    Event triggering options include:
    Option
    Type
    Required
    Default
    Description
    bubbles
    Boolean
    No
    false
    Indicates whether the event is a bubbling event
    composed
    Boolean
    No
    false
    Indicates whether the event can cross component boundaries. If it is false, the event is only triggered in the node tree of the referenced component and will not enter other components.
    capturePhase
    Boolean
    No
    false
    Indicates whether the event has a capture phase
    For more information on bubbling and capture phase, see Events.
    Code example:
    // page page.wxml
    <another-component bindcustomevent="pageEventListener1">
    <my-component bindcustomevent="pageEventListener2"></my-component>
    </another-component>
    // Component another-component.wxml
    <view bindcustomevent="anotherEventListener">
    <slot />
    </view>
    // Component my-component.wxml
    <view bindcustomevent="myEventListener">
    <slot />
    </view>
    // 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 Getting component instances

    This.selectComponent can be called from the parent component to get the instance of the child component.
    You need to pass a match selector, e.g. this.selectComponent(".my-component").
    The detailed syntax of selector can be found in the selector.
    Code example:
    // 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 instance object of the child component with class my-component, i.e. this of the child component.
    If you want to customise the data returned by selectComponent, you can use the built-in behavior: wx://component-export
    When this behaviour is used, the export definition field in the custom component will be used to specify the return value of the component when it is called by selectComponent.
    Code example:
    // Custom Components my-component Internal
    Component({
    behaviors: ['wx://component-export'],
    export() {
    return { myField: 'myValue' }
    }
    })
    <! -- When using custom components -->
    <my-component id="the-id" />
    // Called by the parent component
    const child = this.selectComponent('#the-id') // be tantamount to { myField: 'myValue' }
    In the above example, the parent component gets the object { myField: 'myValue' } when it gets the child component instance with id the-id.

    5. Component Lifecycle

    Component lifecycles refer to the functions in the component itself. These functions are automatically triggered at certain timepoints or when specific framework events occur.
    The most important lifecycles are created, attached, and detached, which include the main time points in the entire lifecycle process of a component instance.
    When a component instance is created, the created lifecycle is triggered. In this case, the component data, this.data is defined as data in the Component constructor. You cannot call setData yet. Generally, this lifecycle should only be used to add custom property fields to the component this.
    After the component has been completely initialized and entered the page node tree, the attached lifecycle is triggered. In this case, this.data has been initialized to the current value of the component. This lifecycle is very useful, where most initialization work can be completed.
    After the component leaves the page node tree, the detached lifecycle is triggered. When you exit a page, if the component is still in the page node tree, detached is triggered.

    5.1 Defining Lifecycle Methods

    Lifecycle methods can be directly defined in level-one parameters of the Component constructor.
    component lifecycles can also be declared in the lifetimes field. We recommend this method as it has the highest priority.
    Code example:
    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
    },
    // ...
    })
    You can also write lifecycle methods in behaviors, which will not be overwritten by lifecycles of the same name in other behaviors. Note that, if a component directly or indirectly references the same behavior multiple times, the lifecycle function in this behavior is only executed once during a single execution time.
    All available lifecycles are listed below:
    Lifecycle
    Parameter
    Description
    created
    None
    Executed when the component instance is created
    attached
    None
    Executed when the component instance enters the page node tree
    ready
    None
    Executed when the component layout is completed in the view layer
    moved
    None
    Executed when the component instance is moved to another node tree
    detached
    None
    Executed when the component instance is removed from the page node tree
    error
    Object Error
    Executed each time the component method throws an exception

    5.2 Component Page Lifecycles

    There are also some special lifecycles that are not strongly associated with components but must sometimes be known by components for internal processing. These lifecycles are called "component page lifecycles" and defined in the pageLifetimes definition field. The available component page lifecycles include:
    Lifecycle
    Parameter
    Description
    show
    None
    Executed when the component's page is displayed
    hide
    None
    Executed when the component's page is hidden
    resize
    Object Size
    Executed when the component's page is resized
    Note:
    The pageLifetime of the custom tabBar will not be triggered.
    Code example:
    Component({
    pageLifetimes: {
    show: function() {
    // Page displayed
    },
    hide: function() {
    // Page hidden
    },
    resize: function(size) {
    // Page resized
    }
    }
    })

    6. behaviors

    The behaviors feature is used to share code between components, similar to "mixins" or "traits" in certain programing languages.
    A behavior can include a set of properties, data, lifecycle functions, and methods. When referenced by a component, the behavior's properties, data, and methods are merged into the component. Each lifecycle function is also called at the appropriate time. A component can reference more than one behavior and a behavior can reference another behavior.
    For specific definitions and usage of the parameters, see the Behavior.

    6.1 Use in Components

    When referenced by a component, behaviors are listed in the behaviors definition field.
    Code example:
    // my-behavior.js
    module.exports = Behavior({
    behaviors: [],
    properties: {
    myBehaviorProperty: {
    type: String
    }
    },
    data: {
    myBehaviorData: {}
    },
    attached() {},
    methods: {
    myBehaviorMethod() {}
    }
    })
    When referencing components, just list them one by one in the behaviors definition section.
    // 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 definition of the my-component component.
    The structure of my-behavior is:
    Property: myBehaviorProperty;
    Data field: myBehaviorData;
    method: myBehaviorMethod;
    Lifecycle functions: attached, created, ready;
    This will give my-component a final structure of:
    Properties: myBehaviorProperty, myProperty;
    Data fields: myBehaviorData, myData;
    Methods: myBehaviorMethod, myMethod;
    Lifecycle functions: attached, created, ready;
    When the component triggers the lifecycle, the above example lifecycle functions are executed in the order:
    1. [my-behavior] created;
    2. [my-component] created;
    3. [my-behavior] attached;
    4. [my-component] attached;
    5. [my-behavior] ready;
    6. [my-component] ready;
    For detailed rules, see Field Coverage and Combination Rules.

    6.2 Field Coverage and Combination Rules

    The component and the behaviour it references can contain fields with the same name, which are handled as follows:
    If there are properties or methods with the same name:
    If the component itself has the property or method, the property or method of the component overrides 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 behaviour defined next in the behaviors field of the component overrides the property or method of the same name defined before it;
    If there are nested references to behaviours in addition to 2, the rule is that the referencing behaviour overrides the property or method of the same name in the referenced behaviour.
    If there are data fields with the same name:
    If the data fields with the same name are both object types, object merging is performed;
    In the rest of the cases, the data will be overwritten, and the overwriting rules are: referrer behaviour > referenced behaviour, backward behaviour > forward behaviour (higher priority overwrites lower priority, and the largest is the highest priority).
    Lifecycle functions and observers do not override each other, but are called one by one at the corresponding trigger time:
    For different lifecycle functions, the execution order of the component lifecycle functions is followed;
    For the same kind of lifecycle function and the same field observers, the following rules are followed:
    behaviour takes precedence over component execution;
    The referenced behaviour takes precedence over the referrer behaviour;
    The preceding behaviour takes precedence over the following behaviour;
    If the same behaviour is referenced multiple times by a component, the lifecycle functions and observers defined by it will not be executed repeatedly.

    6.3 Built-in Behaviors

    Custom components can reference built-in behaviors to get some behaviors of built-in components.
    Code example:
    Component({
    behaviors: ['wx://form-field']
    })
    In the above example, wx://form-field represents a built-in behavior. It makes the custom component behave like a form control.
    A built-in behavior generally adds properties to a component. Unless otherwise specified, components can overwrite these properties to change their type or add an observer.

    6.3.1 wx://form-field

    Make custom components behave like form controls. The form component recognises these custom components and returns the component's field name and its corresponding field value in the submit event.
    Property
    Type
    Description
    name
    String
    Field name in the form
    value
    Any
    Field value in the form
    Code sample:
    // 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://component-export

    Enables custom components to support the export definition field. This field can be used to specify the return value of the component when it is called by selectComponent.
    Detailed usage and code examples can be found in the selectComponent reference documentation.

    7. Inter-component Relationship

    7.1 Defining and Using Inter-component Relationship

    Sometimes, you may need to implement components like this:
    <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 custom components that are mutually related and communicate with each other in a complex manner. In this case, we can add the relations definition field when defining components. Example:
    // 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()
    }
    })
    // 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.
    }
    }
    }
    })
    Note:
    The relations definition must be added in the definitions of both components. Otherwise, it will be invalid.

    7.2 Linking Components of the Same Class

    Sometimes, you need to link components of the same class. For example:
    <custom-form>
    <view>
    input
    <custom-input></custom-input>
    </view>
    <custom-submit> submit </custom-submit>
    </custom-form>
    The custom-form component wants to link the custom-input and custom-submit components. In this case, if the two components have the same behavior:
    // path/to/custom-form-controls.js
    module.exports = Behavior({
    // ...
    })
    // 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
    }
    }
    })
    // 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 to replace the component path as the linked target node:
    // 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.2 relations Definition Field

    The relations definition field includes the target component path and its corresponding options. Available options are listed below.
    Option
    Type
    Required
    Description
    type
    String
    Yes
    The relative relationship of the target component, including parent, child, ancestor, and descendant.
    linked
    Function
    No
    A relationship lifecycle function, triggered when the relationship is created in the page node tree after the component's attached lifecycle.
    linkChanged
    Function
    No
    A relationship lifecycle function, triggered when the relationship is changed in the page node tree after the component's moved lifecycle.
    unlinked
    Function
    No
    A relationship lifecycle function, triggered when the relationship is removed from the page node tree after the component's detached lifecycle.
    target
    String
    No
    If this option is set, it indicates a behavior that should be possessed by the linked target node. All component nodes that have this behavior will be linked.

    8. Data Listener

    Data listener is used to monitor and respond to changes in any properties or data fields.

    8.1 Using Data Listener

    When certain data fields are set via setData, some operations must be executed.
    For example, this.data.sum is always the sum of this.data.numberA and this.data.numberB. In this case, you can use a data listener to implement the following operation.
    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.2 Field Listening Syntax

    Data listeners can monitor multiple changes to properties or internal data simultaneously. A listener can only be triggered one time by a setData operation.
    Listeners can also monitor sub-data fields, as shown below.
    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]
    },
    }
    })
    To monitor changes to all sub-data fields, you can use a wildcard**.
    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': { /* ... */ }
    })
    }
    })
    You can simply use the wildcard ** to monitor all setData.
    Component({
    observers: {
    '**': function() {
    // Triggered upon each setData operation
    },
    },
    })

    8.3 Bugs & Tips:

    Data listeners monitor the data fields set via setData. Even if the values of these data fields do not change, the data listeners will still be triggered;
    Be aware that, if you use setData to set the monitored data fields in the data listener function, this can create an infinite loop;
    Compared to a property observer, a data listener is more powerful with better performance.

    9. Data-only field

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

    9.1 Plain data fields in component data

    In some cases, some fields in data (including those set by setData) are neither displayed on the interface nor passed to other components, but are only used internally by the current component.
    In this case, you can specify such data fields as "pure data fields", which will only be recorded in this.data and will not be involved in any interface rendering process, which can help improve the page update performance.
    The way to specify "pure data fields" is to specify pureDataPattern as a regular expression in the options definition section of the Component constructor, and fields whose names match this regular expression will become pure data fields.
    Code example:
    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
    })
    }
    }
    })
    Plain data fields in the above components will not be applied to WXML:
    <view wx:if="{{a}}"> This line will be displayed </view>
    <view wx:if="{{_b}}"> This line will not be shown </view>

    9.2 Pure Data Fields in Component Properties

    Properties can also be specified as pure data fields (regular expressions that follow the pureDataPattern).
    A pure data field in a property can receive external incoming property values just like a normal property, but it cannot be used directly in the component's own WXML.
    Code Example:
    Component({
    options: {
    pureDataPattern: /^_/
    },
    properties: {
    a: Boolean,
    _b: {
    type: Boolean,
    observer() {
    // Don't do this! This observer will never be triggered
    }
    },
    }
    })
    Note:
    The property observer is never triggered for data-only fields in properties! If you want to listen for property value changes, use a data listener instead.

    9.3 Listening to Pure Data Fields with Data Listeners

    data listener can be used to listen to plain data fields (as with normal data fields). In this way, the interface can be changed by listening and responding to changes in plain data fields.
    The following example is a custom component that converts a JavaScript timestamp to a readable time.
    Code example:
    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
    })
    }
    }
    })
    <view>{{timeString}}</view>

    10. Abstract Node

    10.1 Using Abstract Nodes in Components

    For a node in a custom component template, its corresponding custom component is not determined by the component itself, but the custom component caller. In this case, you can declare that this node is an "abstract node".
    For example, we can implement a selectable-group component by adding custom-radio or custom-checkbox to it. The wxml of this component is written as follows:
    Code example:
    <!-- selectable-group.wxml -->
    <view wx:for="{{labels}}">
    <label>
    <selectable disabled="{{false}}"></selectable>
    {{item}}
    </label>
    </view>
    Where, "selectable" is not any component declared in the usingComponents field of the json file, but an abstract node. It must be declared in the componentGenerics field:
    {
    "componentGenerics": {
    "selectable": true
    }
    }

    10.2 Using Components with Abstract Nodes

    When using the selectable-group component, you must specify the specific component as "selectable":
    <selectable-group generic:selectable="custom-radio" />
    As such, when a selectable-group component instance is generated, a "custom-radio" component instance is generated for the "selectable" node. Likewise, if you do the following:
    <selectable-group generic:selectable="custom-checkbox" />
    A "custom-checkbox" component instance is generated for the "selectable" node.
    Note:
    The above-mentioned custom-radio and custom-checkbox must be included in the usingComponents definition field of the json file corresponding to this wxml.
    {
    "usingComponents": {
    "custom-radio": "path/to/custom/radio",
    "custom-checkbox": "path/to/custom/checkbox"
    }
    }

    10.3 Default Components of Abstract Nodes

    You can specify a default component for an abstract node. When no specific component is specified, an instance of the default component is created. The default component can be specified in the componentGenerics field:
    {
    "componentGenerics": {
    "selectable": {
    "default": "path/to/default/component"
    }
    }
    }

    10.4 Tips

    yyy in generic:xxx="yyy" referenced by the node's generic field must be a static value. Therefore, abstract nodes are not suitable for scenarios where the node name is determined dynamically.

    11. Custom Component Extension

    To better customize the functionality of custom components, you can use the custom component extension mechanism.

    11.1 Effect of Extension

    In order to better understand the effect of the extension, look at the example below:
    // 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
    }
    })
    As you can see from the example, extensions to custom components provide the ability to modify the data definition field in custom component.

    11.2 Using Extensions

    The Behavior() constructor provides a new definition field, definitionFilter to support the custom component extension. The definitionFilter function is injected with the following two parameters when it is called: the component/behavior definition object that uses this behavior, and the definitionFilter function list of the behavior used by this behavior.
    Here is an example:
    // 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 code above declares 1 custom component and 3 behaviors. Each behavior uses a definitionFilter definition field. Then, the following events occur in the order of the declaration:
    1. When the behavior2 declaration is made, the definitionFilter function of behavior3 is called. The defFields parameter is the definition field of behavior2, and the definitionFilterArr parameter is an empty array because behavior3 does not use any other behavior.
    2. When the behavior1 declaration is made, the definitionFilter function of behavior2 is called. The defFields parameter is the definition field of behavior1, the definitionFilterArr parameter is an array with a length of 1, and definitionFilterArr[0] is the definitionFilter function of behavior3 because behavior3 is used by behavior2. You can decide whether to call the definitionFilter function of behavior3 when the behavior1 declaration is made. If the call is needed, add the code definitionFilterArr[0](defFields) here and the definitionFilterArr parameter will be passed by the base library.
    3. Similarly, when component is declared, the definitionFilter function of behavior1 is called.
    To put it simply, the definitionFilter function can be interpreted as the fact that when A uses B, the A declaration calls the definitionFilter function of B and passes the definition object of A for B to filter. If B also uses C and D, it can decide whether to call the definitionFilter function of C and D to filter the defined object of A.
    Code example:
    The following is a simple implementation of the Calculated Properties feature of the custom component using extensions:
    // behavior.js
    module.exports = Behavior({
    lifetimes: {
    created() {
    this._originalSetData = this.setData // Original setData
    this.setData = this._setData // Packaged setData
    }
    },
    definitionFilter(defFields) {
    const computed = defFields.computed || {}
    const computedKeys = Object.keys(computed)
    const computedCache = {}
    
    // Calculate computed
    const calcComputed = (scope, insertToData) => {
    const needUpdate = {}
    const data = defFields.data = defFields.data || {}
    
    for (let key of computedKeys) {
    const value = computed[key].call(scope) // Calculate new value
    if (computedCache[key] !== value) needUpdate[key] = computedCache[key] = value
    if (insertToData) data[key] = needUpdate[key] // Insert the value directly into the data, which is only needed during initialization
    }
    
    return needUpdate
    }
    
    // Override the setData method
    defFields.methods = defFields.methods || {}
    defFields.methods._setData = function (data, callback) {
    const originalSetData = this._originalSetData // Original setData
    originalSetData.call(this, data, callback) // Perform setData for data
    const needUpdate = calcComputed(this) // Calculate computed
    originalSetData.call(this, needUpdate) // Perform setData for computed
    }
    
    // Initialize computed
    calcComputed(defFields, true) // Calculate computed
    }
    })
    Used in components:
    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,
    })
    }
    }
    })
    <view>data: {{a}}</view>
    <view>computed: {{b}}</view>
    <button bindtap="onTap">click</button>
    The implementation principle is simple: Perform a secondary encapsulation for an existing setData, calculate the value of each field in computed each time you perform setData, and then set it into the data to implement computed property.

    12. Placeholder

    When using features such as the Packet Asynchronisation feature, other custom components referenced by the custom component may be unavailable at the start of the rendering process. In this case, in order for the rendering process not to be blocked, the unavailable custom component needs a Component placeholder. The base library renders the unavailable component with the placeholder, and then replaces the placeholder with the component when it becomes available.
    The placeholder for a custom component can be another custom component, or a built-in component.

    12.1 configure

    The componentPlaceholder field in the JSON configuration corresponding to the page or custom component is used to specify the placeholder component, for example:
    {
    "usingComponents": {
    "comp-a": "../comp/compA",
    "comp-b": "../comp/compB",
    "comp-c": "../comp/compC"
    },
    "componentPlaceholder": {
    "comp-a": "view",
    "comp-b": "comp-c"
    }
    }
    This configuration indicates:
    the placeholder component for component comp-a is the built-in component view;
    The placeholder component for component comp-b is the custom component comp-c (the path to which is configured in usingComponents).
    Assume that the template corresponding to this configuration is as follows:
    <button ontap="onTap">display component</button>
    <comp-a wx-if="{{ visible }}">
    <comp-b prop="{{ p }}">text in slot</comp-b>
    </comp-a>
    If visible is false when the mini program starts, only the button will be rendered; when the button is clicked, this.setData({ visible: true }) is executed, and if comp-a, comp-b are not available, the page will be rendered as:
    <button>display component</button>
    <view>
    <comp-c prop="{{ p }}">text in slot</comp-c>
    </view>
    When comp-a and comp-b are ready, the page is replaced:
    <button>display component</button>
    <comp-a>
    <comp-b prop="{{ p }}">text in slot</comp-b>
    </comp-a>

    12.2 Cautions

    1. When a component is specified as a placeholder component (such as comp-c in the above example), it is not valid to specify a placeholder component for it. It is understood that if a component needs to be used as a placeholder component for other components, it must be available in the first place;
    2. Currently custom components unavailable include:
    2.1 In the case of using the subpackage asynchronisation feature, a component from another subpackage is referenced and the corresponding subpackage has not yet been downloaded;
    2.2 the component has not yet been injected in the case of the use of the inject-when-injected feature;
    3. If a component is not available 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 subsequent attempts to prepare a replacement will report an error and throw.
    Attachment: Rendering flow with placeholder components involved
    When the base library tries to render a component, it first recursively checks usingComponents to collect information about all the components it will use; in this process, if a component being used is not available, the base library will first check if it has a corresponding placeholder component. If not, the library interrupts the rendering and throws an error; if it does, it flags the unavailable component and replaces it with a placeholder component in the subsequent rendering process. The unavailable component will try to prepare (download a subpackage or inject code, etc.) at the end of the current rendering process; when the preparation process is complete, it will try to render the component again (in effect, performing the same process) and replace the previously rendered placeholder component.
    
    Contact Us

    Contact our sales team or business advisors to help your business.

    Technical Support

    Open a ticket if you're looking for further assistance. Our Ticket is 7x24 avaliable.

    7x24 Phone Support