小程序中的WXS脚本
目录
[TOC]
WXS(WeiXin Script)是小程序的一套脚本语言,结合 WXML ,可以构建出页面的结构。
【注意】
- WXS 不依赖于运行时的基础库版本,可以在所有版本的小程序中运行。
- WXS 与 JavaScript 是不同的语言,有自己的语法,并不和 JavaScript 一致。
- WXS 的运行环境和其他 JavaScript 代码是隔离的,WXS 中不能调用其他 JavaScript 文件中定义的函数,也不能调用小程序提供的API。
- WXS 函数不能作为组件的事件回调。
<wxs>模块只能在定义模块的WXML文件中被访问到。使用<include>或<import>时,<wxs>模块不会被引入到对应的 WXML 文件中。
<template>标签中,只能使用定义该<template>的 WXML 文件中定义的<wxs>模块。
1.WXS 模块
WXS 代码可以编写在 wxml 文件中的 <wxs> 标签内,或以 .wxs 为后缀名的文件内。
模块
每一个 .wxs 文件和 <wxs> 标签都是一个单独的模块。
每个模块都有自己独立的作用域。即在一个模块里面定义的变量与函数,默认为私有的,对其他模块不可见。
一个模块要想对外暴露其内部的私有变量与函数,只能通过 module.exports 实现。
示例代码:
文件名:xxx.wxs
var foo = "'hello world' from tools.wxs";
var bar = function (d) {
return d;
}
module.exports = {
FOO: foo,
bar: bar,
};
module.exports.msg = "some msg";
使用处引用
<!-- page/index/index.wxml -->
<wxs src="./../xxx.wxs" module="tools" />
<view> {{tools.msg}} </view>
<view> {{tools.bar(tools.FOO)}} </view>
页面输出:
some msg
'hello world' from tools.wxs
require函数 && src 属性
在 .wxs 模块中引用其他 wxs 文件模块,可以使用 require 函数。
src 属性可以用来引用其他的 wxs 文件模块。
引用的时候,要注意如下几点:
- 只能引用
.wxs文件模块,且必须使用相对路径。 wxs模块均为单例,wxs模块在第一次被引用时,会自动初始化为单例对象。多个页面,多个地方,多次引用,使用的都是同一个wxs模块对象。- 如果一个
wxs模块在定义之后,一直没有被引用,则该模块不会被解析与运行。
示例代码:
// /pages/tools.wxs
var foo = "'hello world' from tools.wxs";
var bar = function (d) {
return d;
}
module.exports = {
FOO: foo,
bar: bar,
};
module.exports.msg = "some msg";
// /pages/logic.wxs
var tools = require("./tools.wxs");
console.log(tools.FOO);
console.log(tools.bar("logic.wxs"));
console.log(tools.msg);
<!-- /page/index/index.wxml -->
<wxs src="./../logic.wxs" module="logic" />
2.Wxs页面模块
<!--wxml-->
<wxs module="m1">
var msg = "hello world";
module.exports.message = msg;
</wxs>
<view> {{m1.message}} </view>
// 页面输出
hello world
标签
| 属性名 | 类型 | 说明 |
|---|---|---|
| module | String | 当前 <wxs> 标签的模块名。必填字段。 |
| src | String | 引用 .wxs 文件的相对路径。仅当本标签为 单闭合标签 或 标签的内容为空 时有效。 |
module 属性
module 属性是当前 <wxs> 标签的模块名。在单个 wxml 文件内,建议其值唯一。有重复模块名则按照先后顺序覆盖(后者覆盖前者)。不同文件之间的 wxs 模块名不会相互覆盖。
module 属性值的命名必须符合下面两个规则:
- 首字符必须是:字母(a-zA-Z),下划线(_)
- 剩余字符可以是:字母(a-zA-Z),下划线(_), 数字(0-9)
示例代码:
// page.js
Page({
data: {
array: [1, 2, 3, 4, 5, 1, 2, 3, 4]
}
})
<!--wxml-->
<!-- 下面的 getMax 函数,接受一个数组,且返回数组中最大的元素的值 -->
<wxs module="m1">
var getMax = function(array) {
var max = undefined;
for (var i = 0; i < array.length; ++i) {
max = max === undefined ?
array[i] :
(max >= array[i] ? max : array[i]);
}
return max;
}
module.exports.getMax = getMax;
</wxs>
<!-- 调用 wxs 里面的 getMax 函数,参数为 page.js 里面的 array -->
<view> {{m1.getMax(array)}} </view>
// 页面输出:
5
WXS响应事件
背景
有频繁用户交互的效果在小程序上表现是比较卡顿的,例如页面有 2 个元素 A 和 B,用户在 A 上做 touchmove 手势,要求 B 也跟随移动, movable-view 就是一个典型的例子。一次 touchmove 事件的响应过程为:
a、touchmove 事件从视图层(Webview)抛到逻辑层(App Service)
b、逻辑层(App Service)处理 touchmove 事件,再通过 setData 来改变 B 的位置
一次 touchmove 的响应需要经过 2 次的逻辑层和渲染层的通信以及一次渲染,通信的耗时比较大。此外 setData 渲染也会阻塞其它脚本执行,导致了整个用户交互的动画过程会有延迟。
实现方案
本方案基本的思路是减少通信的次数,让事件在视图层(Webview)响应。小程序的框架分为视图层(Webview)和逻辑层(App Service),这样分层的目的是管控,开发者的代码只能运行在逻辑层(App Service),而这个思路就必须要让开发者的代码运行在视图层(Webview),如下图所示的流程:
使用 WXS 函数用来响应小程序事件,目前只能响应内置组件的事件,不支持自定义组件事件。WXS 函数的除了纯逻辑的运算,还可以通过封装好的 ComponentDescriptor 实例来访问以及设置组件的 class 和样式,对于交互动画,设置 style 和 class 足够了。WXS 函数的例子如下:
var wxsFunction = function(event, ownerInstance) {
var instance = ownerInstance.selectComponent('.classSelector') // 返回组件的实例
instance.setStyle({
"font-size": "14px" // 支持rpx
})
instance.getDataset()
instance.setClass(className)
// ...
return false // 不往上冒泡,相当于调用了同时调用了stopPropagation和preventDefault
}
其中入参 event 是小程序 事件对象 基础上多了 event.instance 来表示触发事件的组件的 ComponentDescriptor 实例。 ownerInstance 表示的是触发事件的组件所在的组件的 ComponentDescriptor 实例,如果触发事件的组件是在页面内的, ownerInstance 表示的是页面实例。
ComponentDescriptor 的定义如下:
| 方法 | 参数 | 描述 | 最低版本 |
|---|---|---|---|
| selectComponent | selector对象 | 返回组件的 ComponentDescriptor 实例。 | |
| selectAllComponents | selector对象数组 | 返回组件的 ComponentDescriptor 实例数组。 | |
| setStyle | Object/string | 设置组件样式,支持 rpx 。设置的样式优先级比组件 wxml 里面定义的样式高。不能设置最顶层页面的样式。 | |
| addClass/removeClass/hasClass | string | 设置组件的 class。设置的 class 优先级比组件 wxml 里面定义的 class 高。不能设置最顶层页面的 class。 | |
| getDataset | 无 | 返回当前组件/页面的 dataset 对象 | |
| callMethod | (funcName:string, args:object) | 调用当前组件/页面在逻辑层(App Service)定义的函数。funcName表示函数名称,args表示函数的参数。 | |
| requestAnimationFrame | Function | 和原生 requestAnimationFrame 一样。用于设置动画。 | |
| getState | 无 | 返回一个object对象,当有局部变量需要存储起来后续使用的时候用这个方法。 | |
| triggerEvent | (eventName, detail) | 和组件的 triggerEvent 一致。 | |
| getComputedStyle | Array. | 参数与 SelectorQuery 的 computedStyle 一致。 | 2.11.2 |
| setTimeout | (Function, Number) | 与原生 setTimeout 一致。用于创建定时器。 | 2.14.2 |
| clearTimeout | Number | 与原生 clearTimeout 一致。用于清除定时器。 | 2.14.2 |
| getBoundingClientRect | 无 | 返回值与 SelectorQuery 的 boundingClientRect 一致。 | 2.14.2 |
WXS 运行在视图层(Webview),里面的逻辑毕竟能做的事件比较少,需要有一个机制和逻辑层(App Service)开发者的代码通信,上面的 callMethod 是 WXS 里面调用逻辑层(App Service)开发者的代码的方法,而 WxsPropObserver 是逻辑层(App Service)开发者的代码调用 WXS 逻辑的机制。
使用方法
- WXML定义事件:
<wxs module="test" src="./test.wxs"></wxs>
<view change:prop="{{test.propObserver}}" prop="{{propValue}}" bindtouchmove="{{test.touchmove}}" class="movable"></view>
上面的 change:prop (属性前面带change:前缀)是在 prop 属性被设置的时候触发 WXS 函数,值必须用 {{}} 括起来。类似 Component 定义的 properties 里面的 observer 属性,在 setData({propValue: newValue}) 调用之后会触发。
注意 :WXS函数必须用 {{}} 括起来。当 prop 的值被设置 WXS 函数就会触发,而不只是值发生改变,所以在页面初始化的时候会调用一次 WxsPropObserver 的函数。
-
WXS文件
test.wxs里面定义并导出事件处理函数和属性改变触发的函数: -
WXS文件
test.wxs里面定义并导出事件处理函数和属性改变触发的函数:
module.exports = {
touchmove: function(event, instance) {
console.log('log event', JSON.stringify(event))
},
propObserver: function(newValue, oldValue, ownerInstance, instance) {
console.log('prop observer', newValue, oldValue)
}
}
更多示例请查看 在开发者工具中预览效果
Tips
- 目前还不支持 原生组件 的事件、 input 和 textarea 组件的 bindinput 事件
- 1.02.1901170及以后版本的开发者工具上支持交互动画,最低版本基础库是2.4.4
- 目前在WXS函数里面仅支持console.log方式打日志定位问题,注意连续的重复日志会被过滤掉。
wxml 文件中使用 Array.includes
由于wxml里的模板支持的语法有限,在 wxml 文件中不能使用 Array.includes 方法,详情可以参考 微信官方文档
解决方法:
- 在wxs 中写一个方法
<wxs module="tools">
var includes = function (array, searchElement) {
return array.indexOf(searchElement) !== -1
}
module.exports = {
includes:includes
}
</wxs>
wxs 的编写,可以点击查看 文档
- 在wxml 中使用
tools.includes(resubmitFieldList,'name')