微信小程序之自定义组件
小程序1.6.3之后已经支持自定义组件,本文已经过时,不建议继续阅读
微信小程序出了挺长一段时间了,但我对这个一直被人吹捧的框架没有半点感冒。出于工作需要写了一个商城demo,由于小程序暂时不支持自定义组件,相关的插件也很少,因此代码写的很烂(这完全是由于我的水平不够导致的)。
最近回过头整理了一下相关的开发文档,决定尝试实现小程序的自定义组件,也算是对最近学习Vue
的一点扩展(脑残粉/斜眼)。
我目前暂时还没有去了解Vue
组件的实现原理,但是组件用起来很爽啊!可惜微信小程序处理本身提供的一些组件,并没有提供自定义组件的实现。
下面让我们尝试着实现微信小程序的自定义组件。
原理
小程序使用Page()
函数用来注册一个页面,页面的初始数据、生命周期函数、事件处理函数等都挂在载Page()
函数的参数上。因此为了实现组件,我们也必须将组件的属性和方法挂载到当前page
对象上。
wxml
使用template
标签声明模板,然后传入data
,渲染出页面结构。这里的一个问题是某些组件可能需要动态插入内容,比如tab
选项卡面板
wxss
样式表的管理就比较方便了,可以直接使用@import
引入样式表。遇见的问题
- 字体图标。由于不能直接使用字体资源,可以将字体转为
base64
格式然后使用,为了方便我直接使用了font-awsome
。 wxss
不支持scss语法,这个配置一下gulp
就好了,将scss
文件编译输出wxss
文件。- 样式命名依旧参照
BEM
进行管理
javascript
组件上需要封装一些数据和方法,由于小程序的数据和方法都是挂载到Page
对象上的,可以使用因此需要合并传入的参数,并改变组件方法的this指向。
此外不同的组件需要考虑命名冲突:
- 为每个组件定义一个特定的
name
属性,作为整个组件的命名空间 - 变量的作用域可以使用对象进行封装,如
$tab.active
,$tab.index
等 - 事件处理函数只能注册在
Page
上,因此使用"$tab.click":()=>{}
的方式处理,由于bindtap
等事件绑定方法支持{}
变量解析,因此可以通过为模板传入变量名来调用组件中的方法,这意味着需要将方法名也保存在变量的作用域中
挂载属性和方法
这里实现了一个注册组件到page
对象上的类,将数据和方法合并到page
对象上的工作都是在Component
内部实现的:
上面描述了基本原理,大致思路是通过循环和遍历组件的data
和methods
属性,将组件的方法挂载到page.data.$test
下,将组件的方法使用page["$test.click"]
的形式注册,并将方法名同时挂在到page.data.$test
下。
class Component {
constructor(options = {}){
this.options = options;
this.page = null;
this.init();
}
init(){
this.setPage();
this.mergeData();
this.mergeMethods();
}
setPage(){
let pages = getCurrentPages(),
len = pages.length;
this.page = pages[len - 1];
}
setData(data){
this.page.setData(data);
}
getData(name){
return this.page.data[name];
}
mergeData(){
let { name, data } = this.options;
// 在page.data定义的[name]值会覆盖组件的默认值
let pageData = this.getData(name);
this.setData({
[name]: Object.assign(data, pageData)
})
}
mergeMethods(){
let { name, methods } = this.options;
for (let key in methods){
let method = methods[key];
if (methods.hasOwnProperty(key) && typeof method === "function") {
// 在页面上注册方法
this.page[`${name}.${key}`] = ()=>{
let data = this.getData(name);
method.call(data);
// 同步方法,更新page数据,这里可能有BUG
this.setData({
[name]: data
});
};
// 将方法名同步至 page.data上面,实现模板上注册事件
this.setData({
[`${name}.${key}`]: `${name}.${key}`,
})
}
}
}
}
使用方式
完成了Component
的实现,但我们应该如何实现一个组件呢?所有组件都由Component
类加工并挂在到page
对象上,定义工作分为两步
注册
组件的具体属性包括:
- 命名空间
- 数据
- 方法
- 外部接口
下面是实现一个简单的计数器,是不是看见了Vue
的影子呢。
import Component from "../component"
export default {
name: '$counter',
data: {
count: 1,
},
methods:{
click(page){
this.count++;
}
},
init(name){
this.name = name;
new Component(this);
}
}
模板
<template name="counter">
<view>{{count}}</view>
<view>{{msg}}</view>
<button bindtap="{{click}}">click</button>
</template>
导出引用
在对应的页面引入对应组件,并对组件进行初始化
- 在
pageName.js
引入组件数据 - 在
pageName.wxml
引入组件模板 - 在
app.wxss
引入全局组件样式表
引入数据
可以在page
对象上为组件添加新的属性,同上各组件使用各自的命名空间进行区分
import { $counter }from "../../components/index"
Page({
data:{
$counter1: {
msg: "hello world"
},
},
onLoad(){
// 多个组件根据init参数名作为命名空间
let $counter1 = Object.create($counter);
$counter1.init("$counter1");
},
});
引入模板
模板的数据只能通过data
传入,正在尝试为模板添加slot
的功能,具体怎么实现还没有思路,限制太多了的感觉。
<import src="../../components/counter/counter.wxml"/>
<template is="counter" data="{{...$counter1}}"/>
目录管理
微信小程序除了最基本的配置之外,对目录并没有什么要求。下面是我 在开发中总结的目录管理。
在/components/componentsName
下定义组件,组件由下面三部分组成
*.js
对应组件的数据和方法,建议放在componentsName
目录下*.wxml
对应组件的模板,建议放在componentsName
目录下*.wxss
对应组件的样式,由于使用scss
进行样式编写,因此组件样式建议放在/styles/components/
下,使用componentsName
进行区分
组件建立好之后,统一由/components/index.js
导出,方便管理。
import $counter from "counter/counter"
export {
$counter,
}
实际上,在一番折腾之后,我在开发小程序时采用下面的目录层次进行文件管理
wxdemo/
|-assets/
|- img/
|- fonts/
|- ...
|-api/
|- _config.js
|- goods.js
|- ...
|-components/
|- component.js
|- index.js
|- components01/
|- components01.js
|- components01.wxml
|- components02/
|- ...
|-styles/
|- main.scss
|- main.wxss
|- base
|- _reset.scss
|- _color.scss
|-utils/
|- wxPromise.js
|- ...
|-pages/
|- start
|- index.js
|- index.json
|- index.wxml
|- index.wxss
|- ...
|-app.js
|-app.json
|-app.wxss
|-...
小结
就这样,一个基本的自定义组件就实现了。
实现自定义组件的目的主要是为了减少页面上的重复代码,但是官方并没有提供对应的接口,这是一个十分蛋疼的事,但是我们实现的这个自定义路由还有很多问题,暂时我是不太敢用在实际项目中(我甚至不太喜欢去开发微信小程序~),主要还是用来学习吧。
你要请我喝一杯奶茶?
版权声明:自由转载-非商用-保持署名和原文链接。
本站文章均为本人原创,参考文章我都会在文中进行声明,也请您转载时附上署名。