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}
<!-- 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;}
Component()
and provide the component's property definitions, internal data, and custom methods.wxml
. The property values are passed in from outside the component. For details, see Component Constructor.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 datasomeData: {}},methods: {// Here is a custom methodcustomMethod: function(){}}})
{"usingComponents": {"component-tag-name": "path/to/the/custom/component"}}
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.<view><!-- The following is a reference to a custom component --><component-tag-name inner-text="Some text"></component-tag-name></view>
wxml
node structure, after being combined with the data, will be inserted into the reference location.usingComponents
in the page file will make the prototype of the page's this
object slightly different, including:usingComponents
page differs from the prototype that does not use usingComponents
, i.e. the result of Object.getPrototypeOf(this)
is different.usingComponents
is used.wxml
template and wxss
style.<slot>
node can be provided in the component template to host the child nodes provided via component reference.<!-- 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>
<!--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>
propA
and propB
receive data passed from the page, and the page can change linked data field via setData
.slot
node can be included in the component's wxml to host the wxml structure provided by the component user.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: { /* ... */ }})
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>
<!--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>
#a
), property selector ([a]
) and tag name selector, should be used;.a .b
) used in the components and the pages that reference the components in extreme cases. If so, avoid using it;.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;font
and color
, are inherited into the components from outside the components;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 */
:host
selector /* Component custom-component.wxss */:host {color: yellow;}
<!-- page's WXML --><custom-component>The text here is highlighted in yellow</custom-component>
/* 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>
<!-- 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;}
addGlobalClass
option is activated, there is a risk of external styles contaminating the component style, so please choose carefully./* 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;}
Component({behaviors: [],properties: {myProperty: { // Property nametype: String,value: ''},myProperty2: String // Simplified definition},data: {}, // Private data, used for template renderinglifetimes: {// Lifecycle function, which can be a function or a method name defined in the methods fieldattached: function () { },moved: function () { },detached: function () { },},// Lifecycle function, which can be a function or a method name defined in the methods fieldattached: function () { }, // The declaration in the attached field is overwritten by that in the lifetimes fieldready: function() { },pageLifetimes: {// Component page lifecycle functionshow: 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) {}}})
{"usingComponents": {}}
Component({properties: {paramA: Number,paramB: String,},methods: {onLoad: function() {this.data.paramA // Value of page parameter paramAthis.data.paramB // Value of page parameter paramB}}})
// page-common-behavior.jsmodule.exports = Behavior({attached: function() {// On page creationconsole.info('Page loaded!')},detached: function() {// When the page is destroyedconsole.info('Page unloaded!')}})
// page Avar pageCommonBehavior = require('./page-common-behavior')Component({behaviors: [pageCommonBehavior],data: { /* ... */ },methods: { /* ... */ },})
// page Bvar pageCommonBehavior = require('./page-common-behavior')Component({behaviors: [pageCommonBehavior],data: { /* ... */ },methods: { /* ... */ },})
<!-- 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}})
triggerEvent
method should be used to specify the event name, detail object, and event options:<!-- 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 functionvar myEventOption = {} // Event triggering optionsthis.triggerEvent('myevent', myEventDetail, myEventOption)}}})
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 |
// 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.jsComponent({methods: {onTap: function(){this.triggerEvent('customevent', {}) // Only pageEventListener2 is triggeredthis.triggerEvent('customevent', {}, { bubbles: true }) // pageEventListener2 and pageEventListener1 are triggered in sequencethis.triggerEvent('customevent', {}, { bubbles: true, composed: true }) // pageEventListener2, anotherEventListener, and pageEventListener1 are triggered in sequence}}})
this.selectComponent(".my-component").
// parent componentPage({data: {},getChildComponent: function () {const child = this.selectComponent('.my-component');console.log(child)}})
// Custom Components my-component InternalComponent({behaviors: ['wx://component-export'],export() {return { myField: 'myValue' }}})
<! -- When using custom components --><my-component id="the-id" />
// Called by the parent componentconst child = this.selectComponent('#the-id') // be tantamount to { myField: 'myValue' }
{ myField: 'myValue' }
when it gets the child component instance with id
the-id
.created
, attached
, and detached
, which include the main time points in the entire lifecycle process of a component instance.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.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.detached
lifecycle is triggered. When you exit a page, if the component is still in the page node tree, detached is triggered.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.3attached: 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 | 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 |
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 |
Component({pageLifetimes: {show: function() {// Page displayed},hide: function() {// Page hidden},resize: function(size) {// Page resized}}})
behaviors
feature is used to share code between components, similar to "mixins" or "traits" in certain programing languages.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.// my-behavior.jsmodule.exports = Behavior({behaviors: [],properties: {myBehaviorProperty: {type: String}},data: {myBehaviorData: {}},attached() {},methods: {myBehaviorMethod() {}}})
// my-component.jsvar 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')},}})
Component({behaviors: ['wx://form-field']})
wx://form-field
represents a built-in behavior. It makes the custom component behave like a form control.Property | Type | Description |
name | String | Field name in the form |
value | Any | Field value in the form |
// custom-form-field.jsComponent({behaviors: ['wx://form-field'],data: {value: ''},methods: {onChange: function (e) {this.setData({value: e.detail.value,})}}})
<custom-ul><custom-li> item 1 </custom-li><custom-li> item 2 </custom-li></custom-ul>
// path/to/custom-ul.jsComponent({relations: {'./custom-li': {type: 'child', // The linked target node should be a child nodelinked: 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 ordervar nodes = this.getRelationNodes('path/to/custom-li')}},ready: function(){this._getAllLi()}})
// path/to/custom-li.jsComponent({relations: {'./custom-ul': {type: 'parent', // The linked target node should be a parent nodelinked: 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.}}}})
<custom-form><view>input<custom-input></custom-input></view><custom-submit> submit </custom-submit></custom-form>
// path/to/custom-form-controls.jsmodule.exports = Behavior({// ...})
// path/to/custom-input.jsvar 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.jsvar customFormControls = require('./custom-form-controls')Component({behaviors: [customFormControls],relations: {'./custom-form': {type: 'ancestor', // The linked target node should be an ancestor node}}})
// path/to/custom-form.jsvar customFormControls = require('./custom-form-controls')Component({relations: {'customFormControls': {type: 'descendant', // The linked target node should be a descendant nodetarget: customFormControls}}})
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. |
Component({attached: function() {this.setData({numberA: 1,numberB: 2,})},observers: {'numberA, numberB': function(numberA, numberB) {// Execute this function when numberA or numberB is setthis.setData({sum: numberA + numberB})}}})
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]},}})
**
.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 observerthis.setData({'some.field': { /* ... */ }})// This will also trigger the above observerthis.setData({'some.field.xxx': { /* ... */ }})// This will still trigger the above observerthis.setData({'some': { /* ... */ }})}})
**
to monitor all setData.Component({observers: {'**': function() {// Triggered upon each setData operation},},})
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 fieldmethods: {myMethod() {this.data._b // Plain data fields can be retrieved in this.datathis.setData({c: true, // Plain data field_d: true, // Plain data field})}}})
<view wx:if="{{a}}"> This line will be displayed </view><view wx:if="{{_b}}"> This line will not be shown </view>
Component({options: {pureDataPattern: /^_/},properties: {a: Boolean,_b: {type: Boolean,observer() {// Don't do this! This observer will never be triggered}},}})
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>
<!-- selectable-group.wxml --><view wx:for="{{labels}}"><label><selectable disabled="{{false}}"></selectable>{{item}}</label></view>
{"componentGenerics": {"selectable": true}}
<selectable-group generic:selectable="custom-radio" />
<selectable-group generic:selectable="custom-checkbox" />
{"usingComponents": {"custom-radio": "path/to/custom/radio","custom-checkbox": "path/to/custom/checkbox"}}
{"componentGenerics": {"selectable": {"default": "path/to/default/component"}}}
// behavior.jsmodule.exports = Behavior({definitionFilter(defFields) {defFields.data.from = 'behavior'},})// component.jsComponent({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}})
// behavior3.jsmodule.exports = Behavior({definitionFilter(defFields, definitionFilterArr) {},})// behavior2.jsmodule.exports = Behavior({behaviors: [require('behavior3.js')],definitionFilter(defFields, definitionFilterArr) {// definitionFilterArr[0](defFields)},})// behavior1.jsmodule.exports = Behavior({behaviors: [require('behavior2.js')],definitionFilter(defFields, definitionFilterArr) {},})// component.jsComponent({behaviors: [require('behavior1.js')],})
// behavior.jsmodule.exports = Behavior({lifetimes: {created() {this._originalSetData = this.setData // Original setDatathis.setData = this._setData // Packaged setData}},definitionFilter(defFields) {const computed = defFields.computed || {}const computedKeys = Object.keys(computed)const computedCache = {}// Calculate computedconst calcComputed = (scope, insertToData) => {const needUpdate = {}const data = defFields.data = defFields.data || {}for (let key of computedKeys) {const value = computed[key].call(scope) // Calculate new valueif (computedCache[key] !== value) needUpdate[key] = computedCache[key] = valueif (insertToData) data[key] = needUpdate[key] // Insert the value directly into the data, which is only needed during initialization}return needUpdate}// Override the setData methoddefFields.methods = defFields.methods || {}defFields.methods._setData = function (data, callback) {const originalSetData = this._originalSetData // Original setDataoriginalSetData.call(this, data, callback) // Perform setData for dataconst needUpdate = calcComputed(this) // Calculate computedoriginalSetData.call(this, needUpdate) // Perform setData for computed}// Initialize computedcalcComputed(defFields, true) // Calculate computed}})
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>
{"usingComponents": {"comp-a": "../comp/compA","comp-b": "../comp/compB","comp-c": "../comp/compC"},"componentPlaceholder": {"comp-a": "view","comp-b": "comp-c"}}
<button ontap="onTap">display component</button><comp-a wx-if="{{ visible }}"><comp-b prop="{{ p }}">text in slot</comp-b></comp-a>
<button>display component</button><view><comp-c prop="{{ p }}">text in slot</comp-c></view>
<button>display component</button><comp-a><comp-b prop="{{ p }}">text in slot</comp-b></comp-a>
Was this page helpful?