❣️ Smart MiniApp view layer and logic layer data communication and use sjs to optimize performance.
❣️ How to modularize the Smart MiniApp.
❣️ Advanced usage guide for custom components.
What is an event
Event bindings are written similar to component properties, such as:
<view bind:tap="handleTap">
Click here!
</view>
If the user taps this view , the page's handleTap will be called.
The event binding function can be a data binding, such as:
<view bind:tap="{{ handlerName }}">
Click here!
</view>
Page({
data: {
handlerName: "handleTap",
},
handleTap() {
console.log("click handleTap");
},
});
The event will also trigger
<!-- Note: The bound SJS function must be enclosed in {{}} -->
<sjs module="sjs" src="./test.sjs"></sjs>
<view id="tapTest" data-hi="WeChat" bind:tap="{{sjs.tapName}}"> Click me! </view>
// The test.sjs file implements the tapName function
function tapName(event, ownerInstance) {
console.log("tap miniApp", JSON.stringify(event));
}
module.exports = {
tapName: tapName,
};
ownerInstance contains some methods to set the style and class of the component.
In addition to bind, you can also use catch to bind events. Unlike bind, catch prevents events from bubbling up.
inner view
to trigger handleTap3
, handleTap2
.middle view
to trigger handleTap2
.outer view
, trigger handleTap1
.<view id="outer" bind:tap="handleTap1">
outer view
<view id="middle" catch:tap="handleTap2">
middle view
<view id="inner" bind:tap="handleTap3">
inner view
</view>
</view>
</view>
The effect of frequent user interaction is relatively slow on the Miniapp. For example, there are two elements A and B on the page. The user makes a touchmove gesture on A and requires B to follow the movement. Movable-view is a typical example. The response process of a touchmove event is:
A touchmove response needs to go through two communications between the logic layer and the rendering layer and one rendering, and the communication takes a lot of time. In addition, setData rendering will block the execution of other scripts, resulting in a delay in the animation process of the entire user interaction.
The idea of SJS is to reduce the number of communications and let events respond at the view layer (Webview). The framework of the Miniapp is divided into the view layer (Webview) and the logic layer (App Service). The purpose of this layering is to control, and the developer's code can only run in the logic layer (App Service), and this idea must allow the development of The author's code runs in the view layer (Webview)
tyml defines events
<sjs module="test" src="./test.sjs"></sjs>
<view change:prop="{{test.propObserver}}" prop="{{propValue}}" bind:touchmove="{{test.touchmove}}" class="movable"></view>
The SJS file test.sjs defines and exports event handlers and functions triggered by property changes:
// event: event object
// ownerInstance: represents the ComponentDescriptor instance of the component where the component that triggered the event is located. If the component that triggered the event is in the page, ownerInstance represents the page instance
const touchmove = function (event, ownerInstance) {
console.log("log event", JSON.stringify(event));
};
// newValue: new value
// oldValue: old value
// ownerInstance: represents the ComponentDescriptor instance of the component where the component that triggered the event is located. If the component that triggered the event is in the page, ownerInstance represents the page instance
// instance: ComponentDescriptor instance representing the component that triggered the event
const propObserver = function (newValue, oldValue, ownerInstance, instance) {
console.log("prop observer", newValue, oldValue);
};
export default {
touchmove: touchmove,
propObserver: propObserver,
};
The above change:prop (property prefixed with change:) is to trigger the SJS function when the prop property is set, the value must be enclosed in {{}}. Similar to the observer property in the properties defined by Component, it will be triggered after setData({propValue: newValue}) is called.
Sometimes in the process of development, we need to query the current position, properties, style and other information of a node. We can use [ty.createSelectorQuery](https://developer.tuya.com/miniapp/api/tyml/ create-selector-query) It returns a SelectorQuery object instance.
Sample code
const query = ty.createSelectorQuery();
query.select("#the-id").boundingClientRect();
query.selectViewport().scrollOffset();
query.exec(function (res) {
res[0].top; // #the upper boundary coordinates of the-id node
res[1].scrollTop; // Vertical scroll position of the display area
});
ty.createIntersectionObserverAPI
can be used to monitor the intersection state of two or more component nodes at the layout position . This set of APIs
can often be used to infer whether certain nodes are visible to the user and to what percentage.
The main concepts involved in this set of APIs are as follows.
The following code indicates that the callback function is fired when .ball
and .scroll-view
intersect. See more
onLoad(){
this._observer = ty.createIntersectionObserver()
this._observer
.relativeTo('.scroll-view')
.observe('.ball', (res) => {
this.setData({
appear: res.intersectionRatio > 0
})
})
}
onUnload() {
if (this._observer) this._observer.disconnect()
}
In an Miniapp, you can often use CSS gradients and CSS animations to create simple interface animations. At the same time, you can also use ty.createAnimation interface to dynamically create simple animation effects.
Sample code
This sample code implements a simple rotation animation effect and achieves more effectsView
<view class="animation-element" animation="{{animation}}"></view>
<button bind:tap="rotate">rotate</button>
Page({
data: {
animation: [],
},
onReady: function () {
this.animation = ty.createAnimation();
},
rotate: function () {
this.animation.rotate(Math.random() * 720 - 360).step();
let anim = this.animation.export();
this.setData({ animation: anim });
},
});
The Smart Mini Program API mainly provides the following application-level events:
The lifecycle declaration object of the page where the custom component is located, which can be used to respond to event processing.
Sample code
Component({
pageLifetimes: {
show: function () {
// page is displayed
},
hide: function () {
// page is hidden
},
resize: function (size) {
// page size change
},
},
});
this.selectComponent
in the parent component to get the instance object of the child component. This allows direct access to arbitrary data and methods of the component.selector
, such as: this.selectComponent(".my-component")
.class
is my-component
, which is the this
of the child component.// parent component
Page({
data: {},
getChildComponent: function () {
const child = this.selectComponent(".my-component");
console.log(child);
},
});
behaviors
are features for code sharing 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 incorporated into the component, and the lifecycle functions will be called at the corresponding time. Each component can reference multiple behavior
, and behavior
can also reference other behavior
.
For detailed parameter meaning and usage, please refer to Behavior Reference Document.
When components are referenced, they can be listed one by one in the behaviors definition section.
Sample code
Sometimes it is necessary to implement a component 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 both custom components, they have a relationship with each other, and the communication between them is often complicated. At this point, adding the relations definition section when the component is defined can solve such a problem.
// path/to/custom-ul.js
Component({
relations: {
"./custom-li": {
type: "child", // The associated target node should be a child node, (relative relationship of target components, optional values are parent , child , ancestor , descendant)
linked: function (target) {
// Executed every time a custom-li is inserted, target is the node instance object, triggered after the attached life cycle of the node
},
linkChanged: function (target) {
// Every time a custom-li is moved, the target is the node instance object, which is triggered after the node's moved life cycle
},
unlinked: function (target) {
// Executed every time a custom-li is removed, target is the node instance object, triggered after the node's detached life cycle
},
},
},
});
// path/to/custom-li.js
Component({
relations: {
"./custom-ul": {
type: "parent", // The associated target node should be the parent node (relative relationship of target components, optional values are parent , child , ancestor , descendant)
linked: function (target) {
// Executed every time it is inserted into custom-ul, target is the custom-ul node instance object, triggered after the attached life cycle
},
linkChanged: function (target) {
// Executed after each move, target is the custom-ul node instance object, triggered after the moved life cycle
},
unlinked: function (target) {
// Executed every time it is removed, target is the custom-ul node instance object, triggered after the detached life cycle
},
},
},
});
**Note: relations definitions must be added to both component definitions, otherwise it will not take effect. **
Pure data fields are some data
fields that are not used for interface rendering and can be used to improve page update performance.
In some cases, some fields in data
(including fields set by setData
) are neither displayed on the interface nor passed to other components, but are only used inside the current component.
At this point, you can specify such data fields as "pure data fields", and they will only be recorded in this.data
without participating in any interface rendering process, which will help improve 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 field names match this regular expression will become pure data fields.
Component({
options: {
pureDataPattern: /^_/, // Specify all data fields starting with _ as pure data fields
},
data: {
a: true, // normal data field
_b: true, // pure data field
},
methods: {
myMethod() {
this.data._b; // pure data fields can be obtained in this.data
this.setData({
c: true, // normal data field
_d: true, // pure data field
});
},
},
});
Plain data fields in the above components will not be applied to TYML
:
<view ty:if="{{a}}"> this line will be displayed </view>
<view ty:if="{{_b}}"> this line will not be displayed </view>
To better customize the functionality of custom components, the custom component extension mechanism can be used.
Extended effect
// behavior.js
module.exports = Behavior({
definitionFilter(defFields) {
defFields.data.from = "behavior";
},
});
// component.js
const myBehavior = require("behavior.js");
Component({
data: {
from: "component",
},
behaviors: [myBehavior],
ready() {
console.log(this.data.from); // here you will find the output behavior instead of component
},
});
Through the example, we can find that the extension of the custom component actually provides the ability to modify the definition section of the custom component. The above example is to modify the content in the data definition section of the custom component.
For detailed usage, please check the document.
You can self-check the existing Mini Programs according to the Experience Optimization Guide to improve performance and user experience.