微信小程序 Page,Component 二次封装(符合 vue2 的开发习惯)

Posted 在厕所喝茶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了微信小程序 Page,Component 二次封装(符合 vue2 的开发习惯)相关的知识,希望对你有一定的参考价值。

微信小程序 Page,Component 二次封装

前言

相信很多开发过微信小程序的开发者都应该知道,微信小程序在语法上面跟vue很像,但是又有点区别。本文将讲述如何对微信小程序的Page页面构造器和Component构造器进行二次封装,抹平一些微信小程序与vue之间差别

目的

很多人可能会有疑问,为什么需要对PageComponent构造器进行二次封装。原因如下:

  • 对于不是很熟悉微信小程序开发,但是熟悉vue开发的开发者来说,将PageComponent封装成更加符合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

  • computedwatch微信小程序需要结合miniprogram-computed才能使用,我们在接下来会封装进去

  • onShowonReachBottom等字段是Page构造器特有的,需要在封装的时候保留下来

  • vue中,页面上的查询参数是存放在this.$route.query中的,但是微信小程序是在onLoad的回调参数中获取的,我们把微信小程序的页面查询参数挂载到this.$query中。

封装效果

最终我们封装出来的形式如下:

MyPage(
  mixins: [],
  data() 
    return ;
  ,
//   也可以是
//   data:,
  created() ,
  mounted() ,
  methods() 
    onClick()
  ,
  computed: ,
  watch: ,
  destroyed() ,
  onShow(),
  onReachBottom()
);

流程

实际上,很多字段都还是做了个映射而已,实际上并没有很大的改动。流程如下:

  • 检查data字段是函数还是对象,如果是函数,就需要执行这个函数,获取返回的对象

  • mixinsdatacreatedmountedcomputedwatchdestroyedonShowonReachBottom等字段映射到微信小程序对应的字段上面

  • methods需要扁平特殊处理,因为微信小程序的自定义函数方法是跟dataonShow等字段同级的,是兄弟关系

  • 检查是否使用了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.jsonLaunch函数执行完毕之后被调用

改造 app.js

这个生命周期函数需要对app.js进行改造才能生效,改造点如下:

  • 添加一个isLoad属性,用来标识app.jsonLaunch的函数是否执行完毕

  • 添加一个taskList异步任务队列,存放需要在onLaunch函数执行完毕之后所以需要执行的任务

  • onLaunch函数中如果有异步任务,需要使用asyncawait等待异步任务执行完毕。

  • 使用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字段是函数还是对象,如果是函数,就需要执行这个函数,获取返回的对象

  • datapropsmixinsmethodsbeforeCreatecreatedmounteddestroyedwatchcomputedrelationsclasses等字段映射到微信小程序对应的字段上面

  • 检查是否使用了computed等字段,如果使用了,就自动给behaviors字段添加一个computedBehavior(通过miniprogram-computed引进来的)

  • options字段默认开启multipleSlots:trueaddGlobalClass: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 = ;

  微信小程序组件封装及调用-原生

在微信小程序中封装一个网络请求方法文件

微信小程序父组件使用子组件并传参

微信小程序网络封装-简单高效

#yyds干货盘点#愚公系列2023年02月 微信小程序-Page页面扩展

望正接收器用微信啥小程序