一些微信小程序和支付宝小程序对应的差异

3,734 阅读11分钟

记录将微信小程序代码挪到支付宝小程序的过程中遇到的一些支付宝小程序和微信小程序的差异,以免每次都去官方文档查。

1.文件后缀名

微信小程序的四个文件后缀为.js、.json、.wxml、.wxss,支付宝小程序的四个文件后缀为.js、.json、.axml、.acss。

使用命令将当前目录下后缀名为wxml的文件全部替换为axml,后缀名为wxss的文件换为acss:

rename 's/\.wxml/\.axml/' * && rename 's/\.wxss/\.acss/' *

使用man rename查看更多rename用法。参考地址:linux 批量修改文件名后缀名命令rename。执行命令时如果提示zsh: command not found: rename,就用brew install rename安装一下rename,如果没有安装brew,使用brew首页的安装命令安装一下即可。Mac下载brew时报错的处理

2.控制属性

微信小程序支付宝小程序
wx:ifa:if
wx:fora:for
wx:keya:key

在axml文件中的a:对应wxml文件中的wx:

3.标签

微信小程序支付宝小程序
<i><icon><icon>
<span><text><text>

微信小程序即支持<icon>标签,又支持<i>,但是支付宝小程序只支持<icon>标签,写HTML5标签习惯之后比较偏向于写<i><span>,但是如果是同时要写支付宝小程序和微信小程序,还是直接使用小程序标签<icon><text>比较好。

4.事件属性

微信小程序支付宝小程序
bindtaponTap
catchtapcatchTap

微信小程序使用小写的事件属性名称,支付宝小程序使用的是小驼峰的事件属性名称。需要注意的是微信小程序比支付宝小程序健壮许多,一些微信小程序支持的事件支付宝小程序是不支持的。

微信小程序事件支付宝小程序事件

5.数据绑定

微信小程序和支付宝小程序都是在axml/wxml中使用{{}}绑定js中的数据。需要注意的是,支付宝小程序的axml中不支持相对复杂处理。

比如微信小程序中使用如下判断是有效的:

<view class="common-button" wx:if="{{detail.show['a_1'] || detail.show['a_2']}}" bindtap="handleA">A按钮</view>

但是在支付宝中直接使用a:if="{{detail.show['a_1'] || detail.show['a_2']}}"无效,需要将判断简化。先在data中定义一个变量,再在axml中直接使用

<view a:if="{{showButtonA}}">A按钮</view>

js中代码如下:

{
  data: {
  	showButtonA: false,
  },
  methods: {
    init () {
  	  const { show = {} } = detail;
  	  const { a_1, a_2 } = show;
  	  this.setData({
  	    showButtonA: a_1 || a_2,
  	  });
    },
  }
}

微信wxml支付宝axml

6.生命周期

这部分直接引用官方文档里的描述文本以及图片。更多生命周期相关内容,查看:微信小程序App微信小程序页面生命周期微信小程序注册页面支付宝小程序页面运行机制

首先看看官网给出的小程序的生命周期示意图:

左图为微信小程序生命周期,右图为支付宝小程序生命周期:

APP生命周期

微信小程序&支付宝小程序

App({
  onLaunch (options) {}, // 小程序初始化完成时触发,全局只触发一次
  onShow (options) {}, // 小程序启动,或者从后台进入前台时触发
  onHide () {}, // 小程序从前台进入后台时触发
  onError (msg) {}, // 小程序发生脚本错误或 API 调用报错时触发。
  globalData: '全局数据'
});

这里的前台和后台指的是用户是否在使用小程序,当用户在使用小程序的时候,相当于在前台,当用户点击右上角的退出按钮退出小程序时相当于进入后台。

虽然微信小程序和支付宝小程序生命周期回调函数参数的属性略有不同,但常用的几个基本是一样的,比如onLaunch和onShow的options参数的以下属性:

options的属性属性值的类型代表的意义
pathstring启动小程序的路径
scenenumber启动小程序的场景值
referrerInfoObject来源信息

页面的生命周期

微信小程序和支付宝小程序注册页面时常用的几个生命周期基本相同。

微信小程序&支付宝小程序

Page({
  onLoad: function(options) {},  // 页面创建时执行
  onShow: function() {}, // 页面出现在前台时执行
  onReady: function() {}, // 页面首次渲染完毕时执行
  onHide: function() {}, // 页面从前台变为后台时执行
  onUnload: function() {},  // 页面销毁时执行
});

这里的前台和后台指的是否是当前页面。比如从页面A跳转到页面B,那么B页面进入前台(onLoad、onShow、onReady),A页面进入后台(onHide)。如果按A页面的左上角的返回按钮,那么A页面被销毁(onUnload)。

微信小程序页面路由

组件的生命周期

微信小程序App的生命周期和页面的生命周期基本相同,但是组件的生命周期的声明方式有很大不同。

微信小程序组件
Component({
  behaviors: [], // behaviors用于引入组件间共享的属性、数据、方法、生命周期函数
  properties: { // 从父组件中传入组件的属性
    contactInfo: {
      type: Object,
      value: null,
      observer(newVal, oldVal) { // 监听从父组件中传入的属性的变化
        if (newVal) {
          this.doSomthing();
        }
      },
    },
  },
  data: {}, // 组件的数据
  lifetimes: { // 生命周期函数
    created: function () {}, // 组件实例刚刚被创建时执行
    attached: function () {}, // 组件实例进入页面节点树时执行
    ready: function () {}, // 组件在视图层布局完毕执行
    moved: function () { }, // 组件实例被移动到节点树的另一个位置时执行
    detached: function () {}, // 组件实例从页面节点树移除时执行
    error: function (err) {}, // 组件方法抛出错误时执行
  },
  // 小程序基础版本库2.2.3之前的生命周期函数在这里声明
  attached: function () { }, 
  ready: function() { },
  pageLifetimes: { // 组件所在页面的生命周期函数
    show: function () { }, // 组件所在的页面被展示时执行
    hide: function () { }, // 组件所在的页面被隐藏时执行
    resize: function () { }, // 组件所在的页面尺寸变化时执行
  },
  methods: {
    doSomething () {
      console.log('doSomething');
    },
  }
})

支付宝小程序组件
Component({
  mixins:[ // mixins用于引入组件间共享的属性、数据、方法、生命周期函数

  ],
  props:{ // 外部传入的属性

  },
  data: {  // 组件内的数据

  },
  onInit(){ // 组件创建时触发

  },
  didMount(){ // 组件创建完毕触发

  },
  didUpdate(prevProps,prevData){ // 组件更新完毕触发

  },
  didUnmount(){ // 组件删除时触发

  },
  methods:{
    doSomething(){
      console.log('doSomething');
    },
  },
});

微信小程序组件的生命周期支付宝小程序组件的生命周期

7.监听父组件传递给子组件的属性值的变化

微信小程序

微信小程序直接使用observer属性,在传入子组件的属性变化的时候会触发observer对应的函数,newVal是属性的最新值。

Component({
  properties: {
    currentStatus: { 
      type: String,
      value: 'all',
      observer: function (newVal, oldVal) {
        this.doSomething();
      },
    },
  },
  ...

父组件的wxml中使用组件:

<component-a currentStatus="{{currentStatus}}" bind:changeStatus="changeStatus"/>

当传入的属性currentStatus变化的时候,就会触发observer对应的函数。

支付宝小程序

根据支付宝小程序文档中的问答,目前还没有像observer那样直接的监听父组件传入子组件的属性值变化的方法。支付宝中可以使用didUpdate代替。

didUpdate 为自定义组件数据更新后的回调,每次组件数据变更的时候都会调用。

要注意的是不论是props改变还是data改变 ,都会触发didUpdate,所以在使用didUpdate根据属性值的变化做一些处理的时候一定要小心,及时返回。

didUpdate (prevProps) {
  const { currentStatus } = this.props;
  const prevCurrentStatus = prevProps.currentStatus;
  if (currentStatus === prevCurrentStatus) return;
  this.doSomething();
},

如果只需要初始化一次的话,可以设置标志位表明是否初始化过:

Component({
  props:{ // 外部传入的属性
    selectSkuData: {},
  },
  initialized: false,
  didUpdate(prevProps,prevData){ // 组件更新完毕触发
    const { selectSkuData } = this.props;
    if (this.initialized) return;
    if (selectSkuData && Object.keys(selectSkuData).length > 0) {
      this.init();
    }
  },
  methods:{
    init() {
       ...
       this.initialized = true; 
    }
});

8.子组件中的事件改变父组件中的数据

微信小程序

子组件中的wxml部分:

<view wx:for="{{statusOptions}}" wx:key="index" data-value="{{item.value}}" data-index="{{index}}"
  class="list-item {{currentStatus === item.value ? 'selected' : ''}}"
  bindtap="changeStatus"
 >{{item.label}}</view>

子组件中的js部分:

methods: {
  changeStatus(event) {
    const { value } = event.target.dataset;
    ...
    this.triggerEvent('changeStatus', { status: value });
  },
},

父组件中的wxml部分:

<component-a currentStatus="{{currentStatus}}" bind:changeStatus="changeStatus"/>

父组件中的事件,为了方便,绑定的事件名称和调用的事件名称我使用了一样的,这里的事件名称也可以用别的,比如bind:changeStatus="handleA"

父组件中的js部分:

changeStatus(event) {
  const { status } = event.detail;
  this.setData({
    currentStatus: status,
  });
  ...
},

支付宝小程序

子组件中的axml部分:

<view a:for="{{statusOptions}}" a:key="index" data-value="{{item.value}}"
  class="list-item {{currentStatus === item.value ? 'selected' : ''}}"
  onTap="changeStatus"
 >{{item.label}}</view>

子组件中的js部分:

changeStatus(event) {
  const { value } = event.target.dataset;
  ...
  this.props.onChangeStatus({ status: value });
},

父组件中的axml部分:

<component-a currentStatus="{{currentStatus}}" onChangeStatus="changeStatus"/>

父组件中的js部分:

changeStatus(data) {
  const { status } = data;
  this.setData({
    currentStatus: status,
  });
  ...
},

注意:

  1. 子组件中调用方法this.props.onChangeStatus({ status: value });相当于间接调用父组件中定义的方法,函数拿到的参数直接是子组件调用时传的参数,但是微信小程序中父组件绑定的事件拿到的参数是微信小程序的event对象,要通过event.detail才能拿到传递的数据。 2.给子组件绑定的事件属性一定要以on开头,比如上文的onChangeStatus,我试了下换成以handle开头的话,子组件件中拿到的数据是一个字符串而不是一个函数。

不论支付宝小程序还是微信小程序,如果组件是嵌套的,遇到如下这种情况: 页面A包含组件a, 组件a中包含组件b,当点击组件b的时候,需要触发页面A中的某个事件,那在组件a中也要定义方法。以支付宝小程序为例:

页面A

changeData () {
  console.log('doSomething');
}

在页面A中使用组件a:

<component-a 
  onChangeData="changeData"
/>

组件a

Component({
  props: {
    onChangeData: () => {}
  },
  methods: {
    onChangeData () {
      console.log('组件a中定义的事件');
      this.props.onChangeData();
    }
  }
})

组件a中使用组件b

<component-b
 onChangeData="onChangeData"

这里从组件a传递到它的子组件b中的属性onChangeData是在组件a的methods中定义的方法。

在组件b中定义方法

  props: {
    onChangeData: () => {}
  },
  methods: {
    onChangeData () {
      console.log('组件b中定义的事件');
      this.props.onChangeData();
    }
  }

当触发组件b中的onChangeData方法时,就会触发页面A中的changeData方法。

只使用

(1)在页面A中使用入组件a:

<component-a 
  onChangeData="changeData"
/>

(2)在组件a中使用组件b:

<component-b
 onChangeData="onChangeData"

不在组件a中定义方法的话,(2)中传递给onChangeData的值是null,而不是changeData。

9.获取组件实例

微信小程序

父组件的wxml部分:

<component-a id="componentA" />

父组件的js部分:

this.componentA = this.selectComponent('#componentA');

支付宝小程序

父组件的axml部分:

<component-a ref="saveComponentA" />

父组件的js部分:

saveComponentA(ref) {
  this.componentA= ref;
},

10.API

微信的API都放在wx下,比如wx.canIUse,支付宝的API都放在my下,比如my.canIUse。以下是这次项目用到的API的差异:

1.请求

微信小程序
wx.request({
  url: 'https://www.test.com/test/',
  data: {
    a: 'valueA',
    b: 'valueB',
  },
  header: {
    'content-type': 'application/json',
  },
  success: (res) => {},
  fail: (err) => {},
  complete: () => {},
})

wx.request中的method可以是GET、HEAD、POST、PUT、DELETE、TRACE、CONNECT,里面没有PATCH方法,因为目前项目中没有使用到PATCH请求,所以只简单放一个在微信开放社区中的不支持PATCH请求的解决方案:wx.request()不支持PATCH请求

支付宝小程序

my.request 目前支持 GET/POST/PUT(其中 PUT 请求在支付宝客户端 10.1.92 或更高版本支持)。

如上所示支付宝小程序支持的请求方式比较少,但是项目中用到了PUT、DELETE请求,无法避免。由后端解决了这个问题。前端传递参数的时候传递一个my.request的headers中加一个原本不存在的属性,当后端收到这个属性的时候,以这个属性值作为真正的请求方式。

my.request({
  url: 'https://www.test.com/test/',
  method: 'POST',
  data: {
    a: 'valueA',
    b: 'valueB',
  },
  headers:{
    'content-type':'application/json',
    'useMethod': 'DELETE',
  },
  dataType: 'json',
  success: (res) => {},
  fail: (res) => {},
  complete: (res) => {},
});

当后端接收到useMethod属性时,会以useMethod的值作为真正的请求方式。

还要注意的是wx.request()无论请求的状态码是什么(200也好401也好),只要请求本身是成功了的,就会触发success函数,但是my.request()在状态码为200-300之间才会触发success,请求返回代表失败的状态码(比如401)时,会触发fail。

微信小程序request微信小程序网络支付宝小程序request

2.拨打电话

微信小程序:

wx.makePhoneCall({
  phoneNumber: '12345',
  success: (res) => {},
  fail: (err) => {},
  complete: () => {},
});

支付宝小程序:

my.makePhoneCall({
  number: '12345' 
});

3.复制文本到剪切板

微信小程序

设置系统剪切板的内容:

wx.setClipboardData({
  data: '被复制的数据',
  success: () => {},
  fail: () => {},
  complete: () => {}
})

获取系统剪切板的内容:

wx.getClipboardData({
  success ({ data }) {
    console.log(data); // data是拿到的系统剪切板的内容
  },
})
支付宝小程序

设置系统剪切板的内容:

my.setClipboard({
  text: '被复制的内容',
  success: () => {},
  fail: () => {},
  complete: () => {},
});

获取系统剪切板的内容:

my.getClipboard({
  success: ({ text }) => {
    console.log(text); // text是拿到的系统剪切板的内容
  },
});

4.交互

项目中经常会用到交互API,这里不一一列举出来了,不记得的时候去下面两个路径中查找。

微信小程序界面交互支付宝小程序交互反馈

5.其他

和支付宝小程序中文件的导入导出和微信小程序有不同。按照项目中的代码简化如下:

有文件a.js:

let a = 111;

export function getA() {
  if (a === 111) {
    a = 222;
  }
  if (a === 222) {
    a = 111; 
  }
  return a;
}

在页面1和页面2中都引用了a.js文件 使用方式如下:

import { getA } from '相对路径/a.js';

Page({
    onLoad() {
       const a = getA(); 
       console.log(a);
    }
})

用户首先进入页面1,然后进入页面2。 在微信小程序中,页面1打印出的内容是222,页面2打印出的内容是111。因为a.js中的代码只在第一次import的时候执行了一次。 但是在支付宝小程序中(在开发者工具上是和微信小程序一样的效果,但是在真机上不同),真机上进入页面1和页面2的时候,都会执行a.js中的代码,都会初始化a为111,所以两次打印出的内容都是222。