微信小程序 Page,Component 二次封装(符合 vue2 的开发习惯)
Posted 在厕所喝茶
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了微信小程序 Page,Component 二次封装(符合 vue2 的开发习惯)相关的知识,希望对你有一定的参考价值。
微信小程序 Page,Component 二次封装
前言
相信很多开发过微信小程序的开发者都应该知道,微信小程序在语法上面跟vue
很像,但是又有点区别。本文将讲述如何对微信小程序的Page
页面构造器和Component
构造器进行二次封装,抹平一些微信小程序与vue
之间差别
目的
很多人可能会有疑问,为什么需要对Page
和Component
构造器进行二次封装。原因如下:
-
对于不是很熟悉微信小程序开发,但是熟悉
vue
开发的开发者来说,将Page
和Component
封装成更加符合vue
开发习惯,能够让他们更加快速上手小程序。对于新手来说是很友好的。 -
可自定义钩子函数(生命周期函数)。在实际的项目开发者,很多微信小程序都是需要获取用户信息的,而且获取用户信息是在
app.js
中的,但是页面的渲染跟app.js
的执行是异步的,也就是说,页面的onLoad
生命周期函数执行完毕了,app.js
可能还没获取到用户信息,导致页面获取不到用户信息。这也是很多人遇见的问题,网上也有很多解决的方案,但是比较麻烦。我们可以给二次封装的Page
构造器自定义一个onAppLoad
生命周期函数,该函数执行时间是在app.js
获取完用户信息之后执行,这样子页面就可以获取得到用户的信息了。 -
扩展自定义功能。我们知道
vue
是可以使用watch
监听属性和computed
计算属性。微信小程序也可以支持的,但是需要第三方npm包
,使用起来比较麻烦,我们也可以在二次封装中,把watch
监听属性和computed
计算属性添加进去,简化一些流程(引包,设置behaviors
等) -
统一管理。对于项目上来说,有些页面可能用户需要某种权限才能进来页面。基于这个需求,我们可以在二次封装好的
Page
构造器中,统一检查用户权限,而不是在每个页面中写一次检查权限。
Page 封装
Page 常用字段
我们先来看看 Page
常用的字段有哪些,不常用的我就不列举出来了,可自行查看文档。点击这里查看文档
Page(
// 混入
behaviors: [],
// 页面数据
data: ,
// 页面加载完毕
onLoad() ,
// 页面显示出来
onShow() ,
// 页面渲染完毕
onReady() ,
// 页面卸载
onUnload() ,
// 上拉触底
onReachBottom() ,
// 自定义函数
onClick() ,
);
vue 常用字段
我们再看看vue
有哪些常用的字段
export default
mixins: [],
data()
return ;
,
created() ,
mounted() ,
methods() ,
computed: ,
watch: ,
destroyed() ,
;
字段对应关系
从上面对比,我们不难发现微信小程序和vue
字段的对应关系
-
Page.data
->vue.data
-
Page.onLoad
->vue.created
-
Page.onReady
->vue.mounted
-
Page.onUnload
->vue.destroyed
-
Page.onClick
->vue.methods.onClick
-
Page.behaviors
->vue.mixins
-
computed
和watch
微信小程序需要结合miniprogram-computed
才能使用,我们在接下来会封装进去 -
onShow
,onReachBottom
等字段是Page
构造器特有的,需要在封装的时候保留下来 -
在
vue
中,页面上的查询参数是存放在this.$route.query
中的,但是微信小程序是在onLoad
的回调参数中获取的,我们把微信小程序的页面查询参数挂载到this.$query
中。
封装效果
最终我们封装出来的形式如下:
MyPage(
mixins: [],
data()
return ;
,
// 也可以是
// data:,
created() ,
mounted() ,
methods()
onClick()
,
computed: ,
watch: ,
destroyed() ,
onShow(),
onReachBottom()
);
流程
实际上,很多字段都还是做了个映射而已,实际上并没有很大的改动。流程如下:
-
检查
data
字段是函数还是对象,如果是函数,就需要执行这个函数,获取返回的对象 -
将
mixins
,data
,created
,mounted
,computed
,watch
,destroyed
,onShow
,onReachBottom
等字段映射到微信小程序对应的字段上面 -
methods
需要扁平特殊处理,因为微信小程序的自定义函数方法是跟data
,onShow
等字段同级的,是兄弟关系 -
检查是否使用了
computed
或者watch
等字段,如果使用了,就自动给behaviors
字段添加一个computedBehavior
(通过miniprogram-computed
引进来的) -
重写
onLoad
生命周期函数,将页面地址的查询参数挂载到this.$query
上
源码
最终代码如下:
import isFunction from "./is";
import behavior as computedBehavior from "miniprogram-computed";
function mapKeys(fromTarget, toTarget, map)
Object.keys(map).forEach((key) =>
if (fromTarget[key])
toTarget[map[key]] = fromTarget[key];
);
function proxyMethods(fromTarget, toTarget)
if (fromTarget)
Object.keys(fromTarget).forEach((key) =>
toTarget[key] = fromTarget[key];
);
function proxyOnLoad(MyOptions, options)
// 保存原有的onLoad函数
const oldLoad = options.onLoad;
options.onLoad = function (query)
// 挂载查询参数
this.$query = query;
if (isFunction(oldLoad))
// 执行原有的onLoad函数
oldLoad.call(this, query);
;
function proxyComputedAndWatch(MyOptions, options)
if (MyOptions.computed || MyOptions.watch)
options.behaviors = options.behaviors || [];
// 如果使用到了`computed`或者`watch`,就需要添加对应的`behaviors`,否则无效
options.behaviors.push(computedBehavior);
function MyPage(MyOptions)
const options = ;
// 检查`data`字段是否为函数
if (isFunction(MyOptions.data))
MyOptions.data = MyOptions.data();
// 字段映射
mapKeys(MyOptions, options,
data: "data",
onReachBottom: "onReachBottom",
onShow: "onShow",
mounted: "onReady",
created: "onLoad",
mixins: "behaviors",
computed: "computed",
watch: "watch",
destroyed: "onUnload",
);
// 扁平methods字段中的数据
proxyMethods(MyOptions.methods, options);
// 检查是否使用了`computed`或者`watch`字段
proxyComputedAndWatch(MyOptions, options);
// 重写`onLoad`生命周期函数,将页面地址的查询参数挂载到`this.$query`上
proxyOnLoad(MyOptions, options);
Page(options);
export default MyPage;
扩展自定义功能或者生命周期函数
我们以扩展一个onAppLoad
生命周期函数为例,这个生命周期函数是在app.js
的onLaunch
函数执行完毕之后被调用
改造 app.js
这个生命周期函数需要对app.js
进行改造才能生效,改造点如下:
-
添加一个
isLoad
属性,用来标识app.js
的onLaunch
的函数是否执行完毕 -
添加一个
taskList
异步任务队列,存放需要在onLaunch
函数执行完毕之后所以需要执行的任务 -
onLaunch
函数中如果有异步任务,需要使用async
和await
等待异步任务执行完毕。 -
使用
try-catch
包裹函数体,在finally
中,将isLoad
属性标记为true
,然后执行taskList
中的任务
最终改造代码如下:
// app.js
App(
// 异步任务队列
taskList: [],
// 加载完毕
isLoad: false,
async onLaunch()
// 加个try-catch,防止请求爆炸,taskList无法执行
try
await this.getUserInfo();
// ...
catch (error)
console.log("首页出现的错误", error);
finally
this.isLoad = true;
// 加载完毕就执行任务队列
this.runTask();
,
// 获取用户信息
async getUserInfo(params)
// ...
,
runTask()
const taskList = this.taskList.slice();
if (taskList.length > 0)
taskList.forEach((task) =>
task();
);
this.taskList = [];
,
);
实现 onAppLoad 函数
app.js
改造完成之后,我们需要准备一个函数,这个函数的作用就是通过getApp()
获取小程序全局唯一的app
实例(实际上就是app.js
中的this
),然后通过app.isLoad
判断app.js
中的onLaunch
函数是否执行完毕了,最终返回一个Promise
代码如下:
// on-app-load.js
const app = getApp();
function onAppLoad()
return new Promise((resolve, reject) =>
try
if (!app.isLoad)
app.taskList.push(() =>
resolve();
);
else
resolve();
catch (error)
reject(error);
);
export default onAppLoad;
重新改造 onLoad 生命周期函数
最后回来到我们的proxyOnLoad
函数中,对onLoad
函数重新进行改造,改造如下:
import onAppLoad from "./on-app-load";
function proxyOnLoad(MyOptions, options)
const oldLoad = options.onLoad;
options.onLoad = function (query)
this.$query = query;
// 检查是否使用了onAppLoad这个生命周期函数
if (isFunction(MyOptions.onAppLoad))
// 调用封装好的onAppLoad函数,执行`MyOptions.onAppLoad`这个生命周期函数
onAppLoad().then(() =>
MyOptions.onAppLoad.call(this, query);
);
if (isFunction(oldLoad))
oldLoad.call(this, query);
;
最终效果
最终使用效果如下:
MyPage(
onAppLoad()
// todo,在这里就可以确保获取到用户信息
,
);
Component 封装
Component
的封装跟Page
的封装差不多,只是字段上面会有点差别,还有相比于Page
构造器来说,Component
会多了一些功能
Component 常用字段
我们先来看看 Component
常用的字段有哪些,不常用的我就不列举出来了,可自行查看文档
Component(
// 一些选项
options: ,
// 组件接受的外部样式类
externalClasses: [],
// 混入
behaviors: [],
// 组件的对外属性
properties: ,
// 组件的内部数据
data: ,
// 组件自定义方法
methods: ,
// 组件实例刚刚被创建时执行
created() ,
// 组件实例进入页面节点树时执行
attached() ,
// 在组件布局完成后执行
ready() ,
// 组件间关系定义
relations: ,
// 组件实例被从页面节点树移除时执行
detached() ,
// 组件数据字段监听器
observers: ,
);
vue 常用字段
我们再看看vue
有哪些常用的字段
export default
mixins: [],
data()
return ;
,
props: ,
beforeCreate() ,
created() ,
mounted() ,
methods() ,
computed: ,
watch: ,
destroyed() ,
;
字段对应关系
从上面对比,我们不难发现微信小程序和vue
字段的对应关系
-
Page.data
->vue.data
-
Page.properties
->vue.props
-
Page.behaviors
->vue.mixins
-
Page.methods
->vue.methods
-
Page.created
->vue.beforeCreate
-
Page.attached
->vue.created
-
Page.ready
->vue.mounted
-
Page.detached
->vue.destroyed
-
Page.observers
->vue.watch
-
computed
需要结合miniprogram-computed
才能使用 -
watch
字段因为Component
构造器已经有对应的字段observers
实现了,所以watch
字段不需要结合miniprogram-computed
-
relations
用来定义组件之间的关系,详情可点击这里查看。Component
组件构造器特有字段,需要保留 -
options
用来定义一些选项,比如是否开启全局样式(addGlobalClass:true
),是否允许多插槽(微信小程序的组件默认只能允许有一个插槽,如果需要具名插槽,需要在该字段中声明multipleSlots:true
)。Component
组件构造器特有字段,需要保留 -
externalClasses
组件接受的外部样式类。微信小程序组件的样式默认是隔离的(即页面样式不影响组件样式,组件样式也不影响页面样式)。我们在封装的过程中,会默认添加options.addGlobalClass=true
,这样子组件就可以受页面样式的影响,方便修改组件的样式。但是如果是基于某个组件在进行二次封装组件的时候,组件样式是影响不了其他组件的样式的,需要通过externalClasses
来指定样式类。Component
组件构造器特有字段,对应封装后的是classes
字段 -
跟
Page
构造器封装一样,我们同样把页面的地址查询参数也挂载到this.$query
上,但是Component
获取页面地址的查询参数会跟Page
获取的方式不一样,Component
是在ready
生命周期函数中,通过获取组件所在页面的实例,然后在获取得到页面的查询参数
封装效果
MyComponent(
mixins: [],
data()
return ;
,
// 也可以是
// data:,
props: ,
methods() ,
beforeCreate() ,
created() ,
mounted: ,
relations: ,
destroyed() ,
classes: [],
watch: ,
computed: ,
);
流程
-
检查
data
字段是函数还是对象,如果是函数,就需要执行这个函数,获取返回的对象 -
将
data
,props
,mixins
,methods
,beforeCreate
,created
,mounted
,destroyed
,watch
,computed
,relations
,classes
等字段映射到微信小程序对应的字段上面 -
检查是否使用了
computed
等字段,如果使用了,就自动给behaviors
字段添加一个computedBehavior
(通过miniprogram-computed
引进来的) -
options
字段默认开启multipleSlots:true
和addGlobalClass:true
-
重写
ready
生命周期函数,将页面地址的查询参数挂载到this.$query
上
源码
import behavior as computedBehavior from "miniprogram-computed";
import isFunction from "./is";
function mapKeys(source, target, map)
Object.keys(map).forEach((key) =>
if (source[key])
target[map[key]] = source[key];
);
function getCurrentPageParam()
// 获取加载的页面
const pages = getCurrentPages();
//获取当前页面的对象
const currentPage = pages[pages.length - 1];
//如果要获取url中所带的参数可以查看options
const options = currentPage.options;
return options;
function proxyReady(MyOptions, options)
// 保存原有的ready函数
const ready = options.ready;
options.ready = function ()
// 挂载查询参数
this.$query = getCurrentPageParam();
if (isFunction(ready))
// 执行原有的onLoad函数
ready.call(this);
;
function proxyComputed(MyOptions, options)
// 如果使用到了`computed`,就需要添加对应的`behaviors`,否则无效
if (MyOptions.computed)
options.behaviors = options.behaviors || [];
options.behaviors.push(computedBehavior);
function proxyProps(MyOptions, options)
// vue的props写法和微信小程序的写法略有不同,需要特殊处理一下某些字段
if (options.properties)
Object.keys(options.properties).forEach((name) =>
if (Array.isArray(options.properties[name]))
options.properties[name] = null;
);
function MyComponent(MyOptions)
const options = ;
微信小程序组件封装及调用-原生