前端【小程序】06-小程序基础篇【自定义组件】【Vant webapp组件库】
自定义组件
小程序中内置了许多的组件供开发者使用,不仅如此开发者还可根据需要自定义组件。
基本语法
-
创建自定义组件
通过小程序开发者工具可以快速创建组件,新建Component。
创建好的自定义组件从结构上看与页面是完全一致的,由 .wxml、.wxss、.js、.json 构成,也有两点重要的区别:
- .json 文件中必须有 component: true
- .js 文件中调用的是 Component 函数
-
使用组件
组件需要在页面或全局中注册后才可以使用,注册组件会用到配置项
usingComponents
,它的值是对象类型数据,属性名为自定义组件的名称,属性的值为自定义组件的路径
全局注册
1、新增自定义组件,项目根目录下新建components目录,里面在新建my-search目录,在my-search目录右键新建Component,输入my-search,工具就会帮我们在my-search目录下创建组件对应的四个文件my-search.wxml、my-search.wxss、my-search.js、my-search.json
修改my-search.wxml中的内容
1 <text>我是自定义组件</text>
2、在app.json中注册组件
1 { 2 "usingComponents": { 3 // key为注册的组件名, value为组件路径 4 "app-search": "/components/my-search/my-search" 5 }, 6 "entryPagePath": "pages/index/index", 7 "pages": [ 8 "pages/index/index", 9 "pages/logs/logs" 10 ], 11 12 "window": { 13 "navigationBarTextStyle": "black", 14 "navigationBarTitleText": "Weixin", 15 "navigationBarBackgroundColor": "#ffffff" 16 }, 17 "style": "v2", 18 "sitemapLocation": "sitemap.json" 19 }
3、在其他页面这个全局注册的自定义组件
index.wxml
1 <app-search /> // 这里就使用在app.json中注册的自定义组件的名字
局部注册
在需要使用组件的页面的xx.json文件中添加如下内容,进行组件注册
1 { 2 "usingComponents": { 3 "page-search": "/components/my-search/my-search" // key 为自定义的组件名称(随便起)使用的时候是按照这个名称来使用的,value为组件的路径 4 } 5 }
组件样式
文档:https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/wxml-wxss.html#%E7%BB%84%E4%BB%B6%E6%A0%B7%E5%BC%8F
在开发中经常会需要修改自定义组件内部的样式,有两种方式可以实现这个目的。- 样式隔离:默认情况下页面的样式无法影响自定义组件的样式
- 在组件的js文件中,添加options: {addGlobalClass: true}, 来允许在页面中修改自定义组件的样式,但要求必须使用组件本身的类名
- 在组件中定义样式时使用的选择器不能是标签选择器、ID 选择器或属性选择器
- 外部样式类:
- externalClasses: ['xxx-class', 'yyy-class'] 开发自定义的样式类
- xxx-class 和 yyy-class 可以接收外部传入的类名,并应用到组件的布局结构中
- 不要在组件内使用标签选择器、ID选择器、属性选择器,会影响到全局,这个不受样式隔离限制
自定义导航栏组件
1、在项目根目录新增components/my-nav目录,然后在my-nav目录右键新建Component,输入my-nav组件名称回车,会默认创建组件的四个文件my-nav.wxml、my-nav.wxss、my-nav.js、my-nav.json
my-nav.wxml
1 <view class="navigation-bar custom-class"> 2 <view class="navigation-bar-title title-class"> 3 自定义标题 4 </view> 5 </view>
my-nav.wxss
1 /* components/my-nav/my-nav.wxss */ 2 .navigation-bar { 3 background-color: #ffffff; 4 height: 88rpx; 5 /* 顶部刘海预留 */ 6 padding: 100rpx; 7 display: flex; 8 justify-content: center; 9 } 10 11 .navigation-bar-title { 12 font-weight: bold; 13 }
2、在页面使用
在项目根目录新建pages/components,然后在components目录右键新建Page,输入index回车,会生成页面的四个文件index.wxml、index.wxss、index.js、index.json
index.js文件中注册组件,并设置使用自定义导航栏
1 { 2 "usingComponents": { 3 "page-nav": "/components/my-nav/my-nav" // 注册自定义导航栏组件, key为组件注册的名称(在组件使用的时候,按照这个名称使用),value为组件的路径 4 }, 5 "navigationStyle": "custom" // 设置导航栏样式自定义 6 }
index.wxml
1 <!--pages/component/index.wxml--> 2 <!-- 使用自定义导航栏的组件 --> 3 <page-nav />
3、查看效果
4、此时组件my-nav在组件内部的样式中设置了背景色为白色,但是想在页面中动态的修改这个组件的颜色,在当前页面的index.wxss文件中通过类名navigation-bar来进行修改,发现是修改不了的,页面和组件的样式是隔离的
5、如果想在页面修改使用的组件的样式,
方式1:可以在组件的js文件中添加如下配置 : options: {addGlobalClass: true}
1 // components/my-nav/my-nav.js 2 Component({ 3 options: { 4 addGlobalClass: true // 样式默认隔离,为true则表示允许外部修改,一般都设置为true 5 }, 6 /** 7 * 组件的属性列表 8 */ 9 properties: { 10 11 }, 12 13 /** 14 * 组件的初始数据 15 */ 16 data: { 17 18 }, 19 20 /** 21 * 组件的方法列表 22 */ 23 methods: { 24 25 } 26 })
6、此时在使用组件的页面,通过组件的类名,来进行样式的修改,就是可以的
1 /* pages/component/index.wxss */ 2 .navigation-bar { 3 background-color: gold; // 此时在页面的样式表中通过类名来修改组件的样式 4 }
7、方式2:
在组件的js文件中添加如下内容
1 // components/my-nav/my-nav.js // 组件的Js文件 2 Component({ 3 // 方式1: 4 // options: {addGlobalClass: true}, // 样式默认是隔离的,为true则表示允许外部修改 5 6 // 方式2:自定义样式类 7 externalClasses: ['custom-class'], 8 /** 9 * 组件的属性列表 10 */ 11 properties: { 12 13 }, 14 15 /** 16 * 组件的初始数据 17 */ 18 data: { 19 20 }, 21 22 /** 23 * 组件的方法列表 24 */ 25 methods: { 26 27 } 28 })
在页面的样式中定义样式
1 /* pages/component/index.wxss */ 2 .color-pink { 3 color: pink; 4 }
使用组件的页面index.wxml中使用组件并传递样式类名
1 <!--pages/component/index.wxml--> 2 <!-- 使用自定义导航栏的组件 --> 3 <page-nav custom-class="color-pink"></page-nav>
8、查看效果,可以看到传递的color-pink这个类名所带的样式,添加到组件身上的了
slot(插槽)
文档:https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/wxml-wxss.html#%E7%BB%84%E4%BB%B6-wxml-%E7%9A%84-slot
小程序中默认只能使用一个 slot 需要多个插槽时需要传入 options: { multipleSlots: true }。- 创建插槽:在组件的任意位置使用 <slot /> 进行占位
- 默认只能使用 1 个 <slot>
- options: { multipleSlots: true } 启用多插槽
- name 为不同的 <slot /> 命名来区分不同的插槽
- 使用插槽
- 单个插槽的情况下直接在组件中间填充内容即可
- 多外插槽的情况下需要使用
slot
属性来指定插槽位置
单个插槽
在组件的内部,组件my-nav.wxml文件中指定插槽的位置
1 <!--components/my-nav/my-nav.wxml--> 2 <view class="navigation-bar custom-class"> 3 <view class="navigation-bar-title title-class"> 4 <!-- 指定插槽的位置 --> 5 自定义标题 <slot></slot> 6 </view> 7 </view>
在页面使用组件的时候,给插槽传入内容
1 <!--pages/component/index.wxml--> 2 <!-- 使用自定义导航栏的组件 --> 3 <page-nav custom-class="color-pink"> 4 <!-- 向插槽传内容 --> 5 <text>😄</text> <- 这就是传到插槽的内容 整个text标签 6 </page-nav>
多个插槽
1、启用多插槽 组件js文件添加配置:options: { multipleSlots: true }
my-nav.js
1 // components/my-nav/my-nav.js 2 Component({ 3 // 方式1: 4 // options: {addGlobalClass: true}, 5 6 // 方式2:自定义样式类 7 externalClasses: ['custom-class'], 8 9 options: { 10 multipleSlots: true // 在组件定义时的选项中启用多slot支持,一般都设置为true,用一个或者用多个都可以 11 }, 12 13 /** 14 * 组件的属性列表 15 */ 16 properties: { 17 18 }, 19 20 /** 21 * 组件的初始数据 22 */ 23 data: { 24 25 }, 26 27 /** 28 * 组件的方法列表 29 */ 30 methods: { 31 32 } 33 })
2、给组件中的插槽添加name用于区分插槽位置
1 <!--components/my-nav/my-nav.wxml--> 2 <view class="navigation-bar custom-class"> 3 <view class="navigation-bar-title title-class"> 4 <!-- 指定多个插槽 --> 5 <slot name="left"></slot> 自定义标题 <slot name="right"></slot> 6 </view> 7 </view>
3、页面中使用组件的时候,通过slot属性指定插槽的名称,从而将内容传到指定的插槽位置
1 <!--pages/component/index.wxml--> 2 <!-- 使用自定义导航栏的组件 --> 3 <page-nav custom-class="color-pink"> 4 <!-- 向插槽传内容 --> 5 <text slot="left">😄</text> 6 <text slot="right">☆</text> 7 </page-nav>
4、查看效果
组件通信
文档:https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/events.html
组件通信是指将页面或组件的数据传入子组件内部或者将子组件的数据传入父组件或页面当中。
父传子
自定义属性:
组件通过自定义的属性接收来自于组件外部(父组件或页面)的数据
1、组件中定义要接收的属性
1 // components/my-nav/my-nav.js 2 Component({ 3 options: { 4 // 方案1:addGlobalClass: true表示允许外部修改组件样式 5 addGlobalClass: true, 6 multipleSlots: true // 在组件定义时的选项中启用多slot支持 7 }, 8 // 方式2:自定义样式类 9 externalClasses: ['custom-class'], 10 // 组件生命周期 11 lifetimes: { 12 // 相当于vue的created 13 // 14 created() { 15 // 几乎不用,因为created()方法中使用this.setData()无效 16 }, 17 // 相当于vue的mounted 18 attached() { 19 // 获取屏幕刘海的高度 20 const {statusBarHeight} = wx.getSystemInfoSync(); 21 console.log(statusBarHeight); 22 this.setData({ 23 statusBarHeight: statusBarHeight 24 }); 25 } 26 }, 27 /** 28 * 组件的属性列表 - 父传子(外部数据) 29 */ 30 properties: { 31 // 定义接收的属性字段,及类型 32 // back: Boolean // 只指定类型 33 34 // 指定类型并且设置默认值 35 back: { 36 type: Boolean, 37 value: true 38 }, 39 // 回退的层级 40 delta: { 41 type: Number, 42 value: 1 43 } 44 }, 45 46 /** 47 * 组件的初始数据 - (内部数据) 48 */ 49 data: { 50 statusBarHeight: 0 51 }, 52 53 /** 54 * 组件的方法列表 55 */ 56 methods: { 57 58 } 59 })
2、组件中使用这个属性来判断是否显示返回
1 <!--components/my-nav/my-nav.wxml--> 2 <view class="navigation-bar custom-class" style="padding-top: {{ statusBarHeight }}px;"> 3 <view class="navigation-bar-title title-class"> 4 <!-- 使用父组件传来的back,如果为true,则显示该view,否则不显示 --> 5 <!-- 使用navigator并且配合 open-type="navigateBack" 实现点击回退上一个页面--> 6 <!-- 当navigator中open-type为"navigateBack"时,可以设置delta,指定返回的层级 --> 7 <navigator class="left" wx:if="{{ back }}" open-type="navigateBack" delta=" {{ delta }}">返回</navigator> 8 <slot name="right"></slot> 9 </view> 10 </view>
组件的样式
1 /* components/my-nav/my-nav.wxss */ 2 .navigation-bar { 3 display: flex; 4 justify-content: center; 5 align-items: center; 6 position: relative; 7 } 8 9 .left { 10 position: absolute; 11 left: 20rpx; 12 }
3、页面中注册该导航组件,并且设置使用自定义导航,去掉默认的导航样式
1 { 2 "usingComponents": { 3 "page-nav": "/components/my-nav/my-nav" // 注册自定义导航组件 4 }, 5 "navigationStyle": "custom" 6 }
4、页面中使用自定义导航组件,并且通过back属性传值,由于back为Boolean类型,所以通过插值表达式传递boolean类型的值,否则直接"aaa" 这是传字符串,这样就导致back的值永远为true
pages/test/index.wxml
1 <!-- 使用自定义导航栏的组件 --> 2 <page-nav custom-class="color-pink" back="{{ true }}"> 3 <text slot="right">☆</text> 4 </page-nav>
5、编译小程序,可以看到当back传true的时候页面显示返回字样,当传false的时候,没有显示返回
子传父
自定义事件:
组件自定义事件的监听:bind:事件类型(自定义)="事件回调"
组件自定义事件的触发:this.triggerEvent('事件类型(自定义)', 参数)
子组件绑定事件,在触发该事件的时候,向父组件传值
1 <!--components/my-nav/my-nav.wxml--> 2 <view class="navigation-bar custom-class" style="padding-top: {{ statusBarHeight }}px;"> 3 <view class="navigation-bar-title title-class"> 4 <!-- 使用父组件传来的back,如果为true,则显示该view,否则不显示 --> 5 <!-- 使用navigator并且配合 open-type="navigateBack" 实现点击回退上一个页面--> 6 <!-- 当navigator中open-type为"navigateBack"时,可以设置delta,指定返回的层级 --> 7 <navigator class="left" wx:if="{{ back }}" open-type="navigateBack" delta=" {{ delta }}">返回</navigator> 8 <!-- 给插槽绑定事件 --> 9 <slot bind:tap="onTap"></slot> 10 </view> 11 </view>
子组件js中定义事件,并且自定义触发事件
1 // components/my-nav/my-nav.js 2 Component({ 3 options: { 4 // 方案1:addGlobalClass: true表示允许外部修改组件样式 5 addGlobalClass: true, 6 multipleSlots: true // 在组件定义时的选项中启用多slot支持 7 }, 8 // 方式2:自定义样式类 9 externalClasses: ['custom-class'], 10 // 组件生命周期 11 lifetimes: { 12 // 相当于vue的created 13 // 14 created() { 15 // 几乎不用,因为created()方法中使用this.setData()无效 16 }, 17 // 相当于vue的mounted 18 attached() { 19 // 获取屏幕刘海的高度 20 const {statusBarHeight} = wx.getSystemInfoSync(); 21 console.log(statusBarHeight); 22 this.setData({ 23 statusBarHeight: statusBarHeight 24 }); 25 } 26 }, 27 /** 28 * 组件的属性列表 - 父传子(外部数据) 29 */ 30 properties: { 31 // 定义接收的属性字段,及类型 32 // back: Boolean // 只指定类型 33 34 // 指定类型并且设置默认值 35 back: { 36 type: Boolean, 37 value: true 38 }, 39 // 回退的层级 40 delta: { 41 type: Number, 42 value: 1 43 } 44 }, 45 46 /** 47 * 组件的初始数据 - (内部数据) 48 */ 49 data: { 50 statusBarHeight: 0 51 }, 52 53 /** 54 * 组件的方法列表 55 */ 56 methods: { 57 onTap() { 58 // 组件内部访问数据 59 console.log("statusBarHeight: ", this.data.statusBarHeight) 60 // 将组件内部的数据传递给父组件 61 // this.triggerEvent("自定义事件名", 要传递的参数) 62 this.triggerEvent("getHeight", this.data.statusBarHeight) 63 } 64 } 65 })
页面中使用子组件,并且监听子组件自定义的事件
1 <!--pages/test/index.wxml--> 2 <!-- 使用自定义导航栏的组件 --> 3 <!-- bind:getHeight="getHeightFather" --> 4 <!-- bind:getHeight 绑定并监听子组件自定义的事件,监听到之后调用getHeightFather方法 --> 5 <page-nav custom-class="color-pink" back="{{ true }}" bind:getHeight="getHeightFather"> 6 <text>自定义导航</text> 7 </page-nav>
页面(父组件)根据监听到的事件,触发对应的函数
1 // pages/test/index.js 2 Page({ 3 4 /** 5 * 页面的初始数据 6 */ 7 data: { 8 9 }, 10 // 父组件的监听事件 11 // 参数为事件对象 12 getHeightFather(e) { 13 console.log("获取到的子组件传递的值为: ", e.detail) //e.detail就是传递的值 14 wx.showToast({ 15 icon: 'none', 16 title: `子组件状态栏高度为${e.detail}`, 17 }) 18 }, 19 /** 20 * 生命周期函数--监听页面加载 21 */ 22 onLoad(options) { 23 24 }, 25 26 /** 27 * 生命周期函数--监听页面初次渲染完成 28 */ 29 onReady() { 30 31 }, 32 33 /** 34 * 生命周期函数--监听页面显示 35 */ 36 onShow() { 37 38 }, 39 40 /** 41 * 生命周期函数--监听页面隐藏 42 */ 43 onHide() { 44 45 }, 46 47 /** 48 * 生命周期函数--监听页面卸载 49 */ 50 onUnload() { 51 52 }, 53 54 /** 55 * 页面相关事件处理函数--监听用户下拉动作 56 */ 57 onPullDownRefresh() { 58 59 }, 60 61 /** 62 * 页面上拉触底事件的处理函数 63 */ 64 onReachBottom() { 65 66 }, 67 68 /** 69 * 用户点击右上角分享 70 */ 71 onShareAppMessage() { 72 73 } 74 })
Vant 组件库
Vant 组件库有 Vue 和小程序两个版本,在使用时要注意区分!
Vant 组件库小程序版, 官方网站:https://vant-contrib.gitee.io/vant-weapp/#/home
Vant 组件库使用步骤请见 官方文档 - 快速上手
使用JS 基础模板的小程序,加入vant的步骤:
- 打开调试器创建,点击终端,然后执行npm 初始化
1 npm init
- 安装,在小程序的根目录中安装
1 npm i @vant/weapp -S --production
-
修改
app.json
,移除全局配置"style": "v2"
,否则 Vant 组件的样式会受到影响 -
修改
project.config.json
,配置"setting"
选项中的packNpmManually
和packNpmRelationList
1 { 2 "description": "项目配置文件", 3 ...... 4 "setting": { 5 ..... 6 "packNpmManually": true, 7 "enableEngineNative": false, 8 "packNpmRelationList": [ 9 { 10 "packageJsonPath": "./package.json", 11 "miniprogramNpmDistDir": "./" 12 } 13 ], 14 ..... 15 }
-
构建 npm,小程序中凡是通过 npm 下载的模块,都必须经过构建才能使用,构建后的代码会存放在 miniprogram_npm 中。
- 打开微信开发者工具,点击 工具 -> 构建 npm,并勾选 使用 npm 模块 选项,构建完成后,即可引入组件。早期的需要勾选"使用npm模块",现在新本的没有了,只需要在工具菜单中点击构建npm即可,就会在项目目录下生成miniprogram_npm文件夹
Button组件使用示例
在app.json(全局引入在这个文件配置)
或index.json(页面下局部引入在这个文件)
中引入组件
1 "usingComponents": { 2 "van-button": "@vant/weapp/button/index" 3 }
index.wxml页面使用
1 <van-button type="default">默认按钮</van-button> 2 <van-button type="primary">主要按钮</van-button> 3 <van-button type="info">信息按钮</van-button> 4 <van-button type="warning">警告按钮</van-button> 5 <van-button type="danger">危险按钮</van-button>
组件按需注入和用时注入
文档:https://developers.weixin.qq.com/miniprogram/dev/framework/ability/lazyload.html
在小程序启动的过程中,除了代码包下载以外,代码注入也是一个主要的耗时环节。注入代码量的大小与内存占用与注入耗时正相关。利用「按需注入」和「用时注入」的特性,可以优化代码注入环节的耗时和内存占用。
按需注入
通常情况下,在小程序启动时,启动页面依赖的所有代码包(主包、分包、插件包、扩展库等)的所有 JS 代码会全部合并注入,包括其他未访问的页面以及未用到自定义组件,同时所有页面和自定义组件的 JS 代码会被立刻执行。这造成很多没有使用的代码在小程序运行环境中注入执行,影响注入耗时和内存占用。基础库 2.11.1 及以上版本支持,2.11.1 以下兼容但无优化效果。 工具调试请使用 1.05.2111300 及以上版本,基础库选 2.20.1 及以上版本。
1 { 2 "lazyCodeLoading": "requiredComponents" 3 }
用时注入
在开启「按需注入」特性的前提下,「用时注入」可以指定一部分自定义组件不在小程序启动时注入,而是在真正渲染的时候才进行注入。基础库 2.11.2 及以上版本支持,2.11.2 以下和未配置的效果相同。 工具调试请使用 1.05.2111300 及以上版本,基础库选 2.20.1 及以上版本。
lazyCodeLoading
为 requiredComponents
的情况下,为自定义组件配置 占位组件,组件就会自动被视为用时注入组件:- 每个页面内,第一次渲染该组件前,该组件都不会被注入;
- 每个页面内,第一次渲染该组件时,该组件会被渲染为其对应的占位组件,渲染流程结束后开始注入;
- 注入结束后,占位组件被替换回对应组件。
nav-bar组件使用示例
在app.json(全局引入在这个文件配置)
或index.json(页面下局部引入在这个文件)
中引入组件
1 { 2 "usingComponents": { 3 "van-nav-bar": "@vant/weapp/nav-bar/index" // 引入vant中nar-bar组件 4 }, 5 "navigationStyle": "custom" // 使用自定义导航 6 }
在页面index.wxml文件中使用nar-bar
1 <!-- title导航文字,left-text左侧文本,left-arrow是否显示左侧箭头,bind:click-left 绑定左侧点击事件 --> 2 <van-nav-bar 3 title="自定义导航" 4 left-text="返回" 5 left-arrow 6 bind:click-left="onClickLeft" 7 /> 8 9 <!-- 通过插槽修改title --> 10 <van-nav-bar 11 left-text="返回" 12 left-arrow 13 bind:click-left="onClickLeft" 14 > 15 <slot solt="title">自定义导航文本内容</slot> 16 </van-nav-bar>
通过开放的外部样式类,修改组件内部的样式
1 <van-nav-bar 2 title="自定义导航" 3 left-text="返回" 4 left-arrow 5 bind:click-left="onClickLeft" 6 custom-class="bg-color" // 通过开放的外部样式类custom-class类传入外部样式类 7 />
直接通过组件包含的类名修改,直接通过F12查找组件包含哪些类,然后在页面直接根据这个类名来修改,组件内部已经设置了options: {addGlobalClass: true}, 所以外部可以直接根据类名来修改组件的样式