Everything you need to know about WeChat Mini-Program component
And how it differs from the template
WeChat Mini-Program has evolved in the past couple of years. Every time I dig into their official documents, I can still learn something new.
In this article, we will dive deep into everything that WeChat Mini-Program’s component can do.
Prerequisites
- Basic understanding of WeChat Mini-Program
- Basic JavaScript knowledge
When to use a component?
- When you find yourself copying and pasting the same code
- When you want something to be reusable
- When you want a more maintainable codebase
How to use a component?
Usually, I create a folder named, “components”
And inside of the “components” folder, I will have the individual component names as the folder name.
- By right-clicking on the empty folder in DevTool, you can choose “New Component”
2. By typing in the component will generate the four files necessary for a component. Personally, I prefer Pascal Casing for component just because it looks nicer, but you can use whatever naming pattern.
3. And let’s say you want to use it in the “index” page. In index.json
include
{ "usingComponents": { "NewComponent": "/components/NewComponent/NewComponent" }}
4. Within the index.wxml
include the component as a tag
<view> <NewComponent/></view>
Lifecycle of Component
Similar to the pages of Mini-Program, component has its own lifecycles.
Component({
lifetimes: {
// When component is initialized
created() {}, // When component is attached to parent and this.setData is available
attached() {}, // When component is rendered
ready() {}, // When component is moved to other node, never used this lifecycle before
moved() {}, // When page is removed on page stack
detached() {}, error(e) {} },
})
Note: You might have seen these methods written outside of the lifetimes
object, on the official doc, they mention that it’s the old way. Not sure when they will deprecate it.
How to pass data to a component?
- Define the properties in the JS of the component
properties: { dataFromParent: { type: String, value: "" // Default value }}
You can also use the shorthand if you don’t need a default value
properties: { dataFromParent: String}
2. In the parent, you can pass in data like so
<NewComponent dataFromParent="test"/>
Or dynamic data, like so
<NewComponent dataFromParent="{{dynamicData}}"/>
You can access the property in this.data
once the component is attached.
How to pass data back to the parent component?
In most cases, data should only flow in one direction.
But if you must, there’s a way to trigger a method of a parent from the child.
- Bind the parent method to the component
<NewComponent bindparentmethod="parentMethod" />
2. Use this.triggerEvent
in the component to trigger the parent method
methods: { triggerParent() { this.triggerEvent("parentmethod", {dataForParent: 'hello'}); }}
Notice that whatever is after bind
becomes the event name for the first param of this.triggerEvent
3. Pass data in the second param of this.triggerEvent
The second param of this.triggerEvent
takes an object.
4. Get data from detail
of the event object
In the parent’s method, it will return an event object
parentMethod({detail}) { const { dataForParent } = detail; this.setData({ dynamicData: dataForParent })}
Note: The third param of this.triggerEvent
takes an option object
The options object takes bubbles
and composed
this.triggerEvent('parentmethod', {}, { bubbles: true })
If bubbles
is set to true, this triggers the parent component’s parentmethod
this.triggerEvent('parentmethod', {}, { bubbles: true, composed: true })
If composed
is set to true, it will trigger the method within the component with the same event name, parentmethod
Opinion: I suggest to stay away from bubbling events because it leads to unexpected behavior.
How to use a component as a wrapper?
As mentioned above, a component can wrap another component.
But what if you want the content of the component to be dynamic?
That’s where <slot/>
can be useful.
For example, if you have a component called NewComponent
<!-- components/NewComponent/NewComponent.wxml --><text>{{dataFromParent}}</text><button bindtap="triggerParent">Trigger Parent Method</button><slot/>
In the parent where NewComponent
is used, whatever you insert inside of NewComponent
will show up in the component as <slot/>
<view>
<NewComponent dataFromParent="{{dynamicData}}" bindparentmethod="parentMethod">
<text>Inserted content</text>
</NewComponent>
<navigator url="../other/other">Go</navigator>
</view>
So what if you have different sets of elements that you want to insert in different places of the component?
In the component’s options, add multipleSlots: true
Component({
options: {
multipleSlots: true
},
properties: { /* ... */ },
methods: { /* ... */ }
})
And inside the component, you can define <slot/>
with name
<text>{{dataFromParent}}</text><slot name="first"/><button bindtap="triggerParent">Trigger Parent Method</button><slot name="second"/>
When using the component, add slot="slotName"
on the element
<view>
<NewComponent dataFromParent="{{dynamicData}}" bindparentmethod="parentMethod">
<text slot="first">Inserted content</text>
<text slot="second">Inserted content 2</text>
</NewComponent>
<navigator url="../other/other">Go</navigator>
</view>
What’s a generic selectable component?
Usually, if you want to include component within a component, using usingComponents
in the .json
file and use wx:if
to determine if the component should show is good enough.
But if you want to add more abstraction to your code, you can use <selectable/>
<selectable/>
let you inject component to another component when you declare it
In the .json
file of the component where you want to inject the component, write
"componentGenerics": { "selectable": true}
And in the same component’s .wxml
file, you can add <selectable/>
<!-- components/NewComponent/NewComponent.wxml --><selectable />
In the page where you plan to use the component, add BOTH components to .json
file
{ "usingComponents": { "NewComponent": "../components/NewComponent/NewComponent", "ChildComponent": "../../components/ChildComponent/ChildComponent" }}
Then you can use ChildComponent
to take place of <selectable/>
<NewComponent dataFromParent="{{dynamicData}}" generic:selectable="ChildComponent"/>
Note: If you want a default component to take place of <selectable/>
, you can define a default in the component’s .json
file
{
"componentGenerics": {
"selectable": {
"default": "path/to/default/component"
}
}
}
Special Note: generic:xxx="yyy"
, the name of the component can only be a static value.
How to use behavior?
A behavior in Mini-Program is a set of logic that can be shared across components. It has its own set of data
, properties
, methods
and life cycles.
In other frameworks, it is similar to mixins.
You can define a behavior like so
// my-behavior.js
export default Behavior({
behaviors: [],
properties: {
myBehaviorProperty: {
type: String
}
},
data: {
myBehaviorData: {}
},
attached: function(){},
methods: {
myBehaviorMethod: function(){}
}
})
You can use a behavior like so
import MyBehavior from "../../behaviors/MyBehavior";Component({
behaviors: [MyBehavior]
})
Currently, there are two built-in behaviors
wx://form-field
This is used to manipulate the <form/>
tag.
You could use it to validate your form, sanitize your data, etc.
2. wx://component-export
This allows you to use a component as a function
In the component’s .js
file, you can write
// MyComponent
Component({behaviors: ['wx://component-export'], export: function() { return { myField: 'myValue' } }})
In the parent’s .wxml
file, add in the component with id
<MyComponent id="the-id" />
In the parent’s .js
file, you can access the export function
Page({
onLoad: function () {
console.log(this.selectComponent('#the-id').myField)
// should output "myValue"
},
})
As you can see, this export behavior allows you to access the values and methods of the child if you expose them.
Note: You can use this.selectComponent
to access the element.
How to use relations?
Relations define the relationship between components and its nested components.
You might never need to use them. Unless you plan to publish a package that everyone can use.
Say you have a list component that nests other components
<custom-ul>
<custom-li> item 1 </custom-li>
<custom-li> item 2 </custom-li>
</custom-ul>
Then for <custom-ul/>
, you can add some logic in linked
, linkChanged
, and unlinked
// path/to/custom-ul.js
Component({
relations: {
'./custom-li': {
type: 'child', // can be parent, child, ancestor, descendant
linked: function(target) {
// Execute whnenever custom-li is inserted, target is the element being included,triggered after the attached life cycle
},
linkChanged: function(target) {
// Every time custom-li is moved, triggered after the moved life cycle
},
unlinked: function(target) {
// When custom-li is remove, triggered after the detached life cycle
}
}
},
methods: {
_getAllLi: function(){
// Get all the nodes ofcustom-li if you need
var nodes = this.getRelationNodes('path/to/custom-li')
}
},
ready: function(){
this._getAllLi()
}
})
You can also do the same thing for the child component, where you swap the type to parent
How to use observers?
Observer listens to the changes of this.data
And since a component’s properties are bound to this.data
of the component, you can listen to the value changes from the parent too.
For example,
Component({
observers: {
'dataFromParent': function (field) {
// field is the new value of dataFromParent
if(field !== this.data.localData) {
console.log('new data', field)
// Data changed, do something here
}
this.setData({
localData: field
})
}},
})
Note: Do not setData
for the value that you are listening to, this will create an infinite loop.
Some other syntax for observers
Component({
observers: {
'some.subfield': function(subfield) {
// Any time some or some.subfield is changed
},
'arr[12]': function(arr12) {
// Any time arr[12] or arr is changed
},
}
})
To listen to all this.setData
Component({
observers: {
'**': function() {
// Trigger every setData
},
},
})
Pure data = private data
Pure data is data that will not be evaluated in .wxml
which also means it can only be used within the component
Component({
options: {
pureDataPattern: /^_/ // All keys that start with _ are pure data
},
data: {
a: true, // normal data
_b: true, // pure data, but still accessible in this.data
},
methods: {
myMethod() { this.setData({
c: true, // normal data
_d: true, // pure data
})
}
}
})
In .wxml
<view wx:if="{{a}}"> This will display </view>
<view wx:if="{{_b}}"> This will not display </view>
Sharing Styles
The Mini-Program component is defaulted to isolating its own styles.
Component({
options: {
styleIsolation: 'isolated'
}
})
You can also set it to apply-shared
which the page’s styles will apply to the component, but the component’s style doesn’t apply to the page.
Or set it to shared
if you want all the styles to share both ways.
If you want app.wxss
styles to apply to your component use
Component({
options: {
addGlobalClass: true,
}
})
Conversely, if you don’t want app.wxss
to apply, use page-isolate
, page-apply-shared
, or page-shared
If you want to pass styles from parent to component, you can do it with externalClasses
/* custom-component.js */
Component({
externalClasses: ['my-class']
})
In component’s .wxml
<!-- custom-component.wxml -->
<custom-component class="my-class">Text here is styled by external class</custom-component>
In the page where the component is used
<custom-component my-class="red-text" />
<custom-component my-class="large-text" />
Links
Repo for component example:
https://github.com/davidyu37/WeChat-Mini-Program-Component-Example
If you already have WeChat DevTool opened, click here:
https://developers.weixin.qq.com/s/KR93v9mN7seH
Official Doc:
https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/
Call to Action
If you want to level up your WeChat Mini-Program skills, my friend George and I have a program called Advance Mini-Program which we update every month on all the Mini-Program tips and tricks.