# 小程序自定义组件支持

uni-app在支持 vue 组件之外,也实现了对小程序自定义组件的兼容。

小程序组件不是 vue 组件,并且每家小程序都有自己的组件规范,比如微信小程序的组件是 wxml 格式。

小程序组件不是全端可用,支持度最广的微信小程序自定义组件,也只能支持微信小程序、app-vue、web,其他平台无法兼容。

如果需求上只需兼容有限平台,也可以使用小程序组件。否则仍然推荐使用 vue 组件。

平台差异说明

平台 支持情况 小程序组件存放目录
H5 vue2 项目支持微信小程序组件(2.4.7+),vue3 不支持 wxcomponents
App(不含 nvue) vue2 项目支持微信小程序组件,vue3 不支持 wxcomponents
微信小程序 支持微信小程序组件 wxcomponents
支付宝小程序 支持支付宝小程序组件 mycomponents
百度小程序 支持百度小程序组件 swancomponents
抖音小程序、飞书小程序 支持抖音小程序、飞书小程序组件 ttcomponents
QQ 小程序 支持 QQ 小程序组件 wxcomponents
快手小程序 支持快手小程序组件 kscomponents
京东小程序 支持京东小程序组件 jdcomponents
鸿蒙元服务 支持鸿蒙元服务组件 hascomponents
小红书小程序 支持小红书小程序组件 xhscomponents

此文档要求开发者对各端小程序的自定义组件有一定了解,没接触过小程序自定义组件的可以参考:

目录结构

	
┌─wxcomponents                  微信小程序自定义组件存放目录
│   └──custom                   微信小程序自定义组件
│		├─index.js
│		├─index.wxml
│		├─index.json
│		└─index.wxss
├─mycomponents                  支付宝小程序自定义组件存放目录
│   └──custom                   支付宝小程序自定义组件
│		├─index.js
│		├─index.axml
│		├─index.json
│		└─index.acss
├─swancomponents                百度小程序自定义组件存放目录
│   └──custom                   百度小程序自定义组件
│		├─index.js
│		├─index.swan
│		├─index.json
│		└─index.css
├─pages
│  └─index
│		└─index.vue
│
├─static
├─main.js
├─App.vue
├─manifest.json
└─pages.json
	

使用方式

pages.json 对应页面的 style -> usingComponents 引入组件:

{
	"pages": [{
		"path": "index/index",
		"style": {
			// #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-QQ
			"usingComponents": {
				"custom": "/wxcomponents/custom/index"
			},
			// #endif
			// #ifdef MP-BAIDU
			"usingComponents": {
				"custom": "/swancomponents/custom/index"
			},
			// #endif
			// #ifdef MP-ALIPAY
			"usingComponents": {
				"custom": "/mycomponents/custom/index"
			},
			// #endif
			"navigationBarTitleText": "uni-app"
		}
	}]
}

在页面中使用

<!-- 页面模板 (index.vue) -->
<view>
  <!-- 在页面中对自定义组件进行引用 -->
  <custom name="uni-app"></custom>
</view>

代码示例

下面以微信小程序官方自定义组件示例 miniprogram-slide-view 为例演示小程序自定义组件的使用方式。 其他组件使用示例见 GitHub:wxcomponents-template。 插件市场有一个完整的vant weapp 引用好的示例工程,详见https://ext.dcloud.net.cn/plugin?id=302

目录结构

	
┌─components
├─wxcomponents
│   └──miniprogram-slide-view
│		├─index.js
│		├─index.wxml
│		├─index.json
│		└─index.wxss
│
├─pages
│  └─slide-view
│		└─slide-view.vue
│
├─static
├─main.js
├─App.vue
├─manifest.json
└─pages.json
	

pages.json

{
  "pages": [
    {
      "path": "slide-view/slide-view",
      "style": {
        "navigationBarTitleText": "slide-view",
        "usingComponents": {
          "slide-view": "/wxcomponents/miniprogram-slide-view/index"
        }
      }
    }
  ]
}

slide-view.vue

<template>
  <view class="slide">
    <slide-view width="750" height="110" slide-width="500">
      <view slot="left" class="l">
        <image src="/static/file_transfer.jpg" class="img"></image>
        <view class="text">
          <view class="title">文件传输助手</view>
          <view class="time">7:00 PM</view>
        </view>
      </view>
      <view slot="right" class="r">
        <view class="read">标为已读</view>
        <view class="delete">删除</view>
      </view>
    </slide-view>
  </view>
</template>
<script>
  export default {};
</script>
<style>
  .slide {
    border-bottom: 1px solid #dedede;
  }
  .l {
    background-color: white;
    height: 110rpx;
    width: 750rpx;
    display: flex;
    flex-direction: row;
  }
  .r {
    height: 110rpx;
    display: flex;
    direction: row;
    text-align: center;
    vertical-align: middle;
    line-height: 110rpx;
  }
  .read {
    background-color: #ccc;
    color: #fff;
    width: 350rpx;
  }
  .delete {
    background-color: red;
    color: #fff;
    width: 150rpx;
  }
  .img {
    width: 90rpx;
    height: 90rpx;
    border-radius: 10rpx;
    margin: 10rpx 15rpx;
  }
  .text {
    display: flex;
    flex-direction: row;
  }

  .title {
    margin-top: 15rpx;
    font-size: 33rpx;
  }
  .time {
    margin-top: 15rpx;
    color: #ccc;
    font-size: 25rpx;
    margin-left: 330rpx;
  }
</style>

注意事项

  • 页面路由中的文件不能引用项目特殊文件夹 wxcomponents(或 mycomponents、swancomponents)下的 js 文件,否则这些文件会被编译。
  • 小程序组件需要放在项目特殊文件夹 wxcomponents(或 mycomponents、swancomponents)。HBuilderX 建立的工程 wxcomponents 文件夹在 项目根目录下。vue-cli 建立的工程 wxcomponents 文件夹在 src 目录下。可以在 vue.config.js 中自定义其他目录
  • 小程序组件的性能,不如 vue 组件。使用小程序组件,需要自己手动 setData,很难自动管理差量数据更新。而使用 vue 组件会自动 diff 更新差量数据。所以如无明显必要,建议使用 vue 组件而不是小程序组件。比如某些小程序 ui 组件,完全可以用更高性能的 uni ui 替代。
  • 当需要在 vue 组件中使用小程序组件时,注意在 pages.jsonglobalStyle 中配置 usingComponents,而不是页面级配置。
  • 注意数据和事件绑定的差异,组件使用时应按照 vue 的数据和事件绑定方式
    • 属性绑定从 attr="",改为 :attr="a";从 title="复选框" 改为 :title="'复选框' + item"
    • 事件绑定从 bind:click="toggleActionSheet1" 改为 @click="toggleActionSheet1",目前支付宝小程序不支持 vue 的事件绑定方式,具体参考:支付宝小程序组件事件监听示例
    • 阻止事件冒泡 从 catch:tap="xx" 改为 @tap.native.stop="xx"
    • wx:if 改为 v-if
    • wx:for="" wx:key="" 改为v-for="(item,index) in list"

详细的小程序转 uni-app 语法差异可参考文档https://ask.dcloud.net.cn/article/35786

# WXS

WXS 是一套运行在视图层的脚本语言,微信端的规范详见

它的特点是运行在视图层。当需要避免逻辑层和渲染层交互通信折损时,可采用 wxs。

uni-app 可以将 wxs 代码编译到微信小程序、QQ 小程序、app-vue、H5 上(uni-app 2.2.5及以上版本)

与 wxs 类似,百度小程序提供了 Filter,阿里小程序和抖音小程序提供了 SJS,uni-app 也支持使用这些功能,并将它们编译到百度和阿里的小程序端。不过它们的功能还不如 wxs 强大。

平台差异说明

App H5 微信小程序 支付宝小程序 百度小程序 抖音小程序、飞书小程序 QQ 小程序 小红书小程序 京东小程序
√(不支持 nvue) SJS Filter SJS SJS JDS

App 端 nvue 解决此类需求,不应该使用 wxs,而是使用 bindingx。

wxs 示例

以下是一些使用 WXS 的简单示例,要完整了解 WXS 语法,请参考WXS 语法参考。本示例使用 wxs 响应touchmove事件,减少视图层与逻辑层通信,使滑动更加丝滑。

<template>
  <view>
    <view class="area">
      <view
        @touchstart="test.touchstart"
        @touchmove="test.touchmove"
        class="movable"
        >{{test.msg}}</view
      >
    </view>
  </view>
</template>
<script module="test" lang="wxs">
  var startX = 0
  var startY = 0
  var lastLeft = 50; var lastTop = 50
  function touchstart(event, ins) {
  	console.log("touchstart")
    var touch = event.touches[0] || event.changedTouches[0]
    startX = touch.pageX
    startY = touch.pageY
  }
  function touchmove(event, ins) {
    var touch = event.touches[0] || event.changedTouches[0]
    var pageX = touch.pageX
    var pageY = touch.pageY
    var left = pageX - startX + lastLeft
    var top = pageY - startY + lastTop
    startX = pageX
    startY = pageY
    lastLeft = left
    lastTop = top
    ins.selectComponent('.movable').setStyle({
      left: left + 'px',
      top: top + 'px'
    })
  	return false
  }
  module.exports = {
  	msg: 'Hello',
    touchstart: touchstart,
    touchmove: touchmove
  }
</script>

<script>
  export default {
    data() {
      return {};
    },
    methods: {},
  };
</script>

<style>
  .area {
    position: absolute;
    width: 100%;
    height: 100%;
  }
  .movable {
    position: absolute;
    width: 100px;
    height: 100px;
    left: 50px;
    top: 50px;
    color: white;
    text-align: center;
    line-height: 100px;
    background-color: red;
  }
</style>

支付宝小程序,百度小程序官方暂未支持事件响应,不过也可以使用对应的 SJS、Filter 过滤器实现一些数据处理的操作,以下代码展示了一个时间格式化的小功能

index.vue

<template>
  <view>
    <view> {{timestr}} 是 </view>
    <view> {{utils.friendlyDate(timestamp)}} </view>
  </view>
</template>
<script module="utils" lang="filter" src="./utils.filter.js"></script>
<script module="utils" lang="sjs" src="./utils.sjs"></script>

<script>
  export default {
    data() {
      return {
        timestr: "2019/08/22 10:10:10",
        timestamp: 0,
      };
    },
    created() {
      this.timestamp = new Date(this.timestr).getTime();
    },
    methods: {},
  };
</script>

utils.sjsutils.filter.js 内容一致

export default {
  friendlyDate: (timestamp) => {
    var formats = {
      year: "%n% 年前",
      month: "%n% 月前",
      day: "%n% 天前",
      hour: "%n% 小时前",
      minute: "%n% 分钟前",
      second: "%n% 秒前",
    };
    var now = Date.now();
    var seconds = Math.floor((now - parseInt(timestamp)) / 1000);
    var minutes = Math.floor(seconds / 60);
    var hours = Math.floor(minutes / 60);
    var days = Math.floor(hours / 24);
    var months = Math.floor(days / 30);
    var years = Math.floor(months / 12);

    var diffType = "";
    var diffValue = 0;
    if (years > 0) {
      diffType = "year";
      diffValue = years;
    } else {
      if (months > 0) {
        diffType = "month";
        diffValue = months;
      } else {
        if (days > 0) {
          diffType = "day";
          diffValue = days;
        } else {
          if (hours > 0) {
            diffType = "hour";
            diffValue = hours;
          } else {
            if (minutes > 0) {
              diffType = "minute";
              diffValue = minutes;
            } else {
              diffType = "second";
              diffValue = seconds === 0 ? (seconds = 1) : seconds;
            }
          }
        }
      }
    }
    return formats[diffType].replace("%n%", diffValue);
  },
};

注意

引入方式

<!-- 内联 -->
<script module="test" lang="wxs">
  //...code
</script>
<script module="utils" lang="filter">
  //...code
</script>

<!-- 外部引入 -->
<script module="utils" lang="wxs" src="./utils.wxs"></script>
<script module="utils" lang="filter" src="./utils.filter.js"></script>
<script module="utils" lang="sjs" src="./utils.sjs"></script>
  • 【重要】 编写 wxs、sjs、filter.js、jds 内容时必须遵循相应语法规范
  • 【重要】 module所指定的模块名不可与datamethodscomputed内的属性重名
  • vue3 项目不支持 <script setup> 用法
  • 目前各个小程序正在完善相关规范,可能会有较大改动,请务必仔细阅读相应平台的文档
  • 支付宝小程序请使用 sjs 规范,详见
  • 支付宝小程序 sjs 只能定义在.sjs 文件中,然后使用<script>标签引入
  • 支付宝小程序script的标签属性namefrom被统一为了modulesrc以便后续实现多平台统一写法
  • 百度小程序中请使用 Filter 规范,详见
  • 百度小程序 Filter 只能导出function函数
  • 小红书小程序 src 不能为空,必须设置 静态资源地址
  • 暂不支持在 wxs、sjs、filter.js 中调用其他同类型文件
  • wxs、filter.js 既能内联使用又可以外部引入,sjs 只能外部引入
  • QQ 小程序目前对内联的 wxs 支持不好,部分写法可能会导致编译出错,尽量使用外部引入的方式
  • 在微信自定义组件中wxcomponents也可以使用 wxs
  • nvue页面暂不支持 wxs、sjs、filter.js
  • 各个script标签会分别被打包至对应支持平台,不需要额外写条件编译
  • HBuilderX 2.2.5开始,不推荐使用各个小程序自有的引入方式,推荐使用script标签引入
  • App 和 H5 端,提供了 wxs 的升级版,更加强大,见下面的 renderjs 章节