上篇文章介绍了微信小程序的基本使用,如果看完了我列出的所有教程,我相信应该能熟练的玩转一个小程序了,如果遇到一些新的功能,可以去微信的官网上去查找相应功能或者控件的使用,应该都不是很难的事情。这篇文章在此基础上,再介绍一些进阶知识以及在小程序开发中我遇到的一些坑。主要内容包括如下:
- 自定义组件开发,避免重复代码
- 组件间如何共享数据和方法
- 组件间如何进行状态管理
这些内容是一些进阶知识,主要是尽量减少代码的复制和粘贴,比如把一些相同的组件进行封装在不同的页面进行调用,减少页面的重复代码开发。把一些相同的数据和方法进行封装,减少不同组件间拷贝相同的代码。把组件间共享的数据状态抽象出来进行统一管理,减少数据的重复。我将以一个小程序中出现率最高的列表导航功能为实例,详细的介绍如何保证这些组件之间有效的进行数据交互。
实例原型
页面导航功能是在小程序中出现频率最高的组件,以一个实际的例子为原型(请忽略界面的外观),需要实现的是俱乐部有关的活动和俱乐部的介绍。
1)页面导航组件的开发
2)俱乐部活动会在很多的页面出现,会出现在俱乐部首页,也会出现在某一个俱乐部的首页上,因此需要设计成组件,在多个页面间重用。
3)点击俱乐部介绍,会进入到俱乐部介绍的详细信息,因为在第一个页面已经获取到了俱乐部的详细信息,在俱乐部介绍详情页就不需要再次获取数据,减少网络请求次数,因此两个页面之间需要进行数据交互。
4)关注和取消关注着两个功能在很多页面出现,在每个页面也有相同的behavior,因此可以使用behavior来减少代码的重复。
![](https://img.haomeiwen.com/i23610677/b3e4bddc5fd2eecf.png)
开发自定义组件
创建自定义组件,需要以下几步:
- 第一步,使用小程序开发工具,在Components文件夹下创建一个组件文件夹,比如,我的实例中是activityList
- 第二步,在此文件夹下右键新建组件,输入组件名activityList,工具自动会创建activitList.wxml,activityList.js,activityList.wxss,activityList.json四个文件。
- 第三步,在json文件中设置为组件,如下:
{
"component": true
}
- 第四步,在组件的js中,需要定义properties,data和methods。其中properties是不同于页面定义,是组件特有的,在使用组件时,通过在wxml里进行设置。需要指定类型和默认值,如下:
/**
* 组件的属性列表
*/
properties: {
title: {
type: String,
value: '近期活动'
},
baseUrl:{
type:String,
value:''
}
},
- 第四步,定义组件的data,这个和page的开发没有差别,也就是定义组件需要的数据。
/**
* 组件的初始数据
*/
data: {
activities: []
},
- 第五步,定义组件的方法,这个也跟page没有差别,每个组件也不同。
loadData(data) {
console.log(data)
if (data && data.length) {
this.setData({
activities: data
})
}
},
- 第六步,开发组件的页面
<!--components/activityList/activityList.wxml-->
<view class="page">
<view class="page__bd">
<view>
<text class="text-prompt" bindtap=”_tap“>{{title}}</text>
</view>
<view>
<view wx:if="{{activities.length > 0}}" class="weui-cells weui-cells_after-title">
<block wx:for-items="{{activities}}" wx:for-item="item" wx:key="_id">
<navigator url ="{{baseUrl}}clubActivity/clubActivity?id={{item._id}}&needRegister={{item.activityItems.needRegister}}&needCheckIn={{item.activityItems.needCheckIn}}&activityId={{item.activityItems._id}}" class="weui-cell weui-cell_access" hover-class="weui-cell_active">
<view class="weui-cell__hd">
<image src="{{item.thumbnail}}" class="activity-image">
</image>
<mp-badge wx:if="{{item.activityItems.isTop}}" content="置顶" class="top-text" />
</view>
<view class="weui-cell__bd activity-list-item">
<view class="activity-title">
<text>{{item.title}}</text>
</view>
<view class="activity-subTitle">
<view><text>时间:{{item.activityItems.occurence}}</text></view>
<view><text>地点:{{item.activityItems.address}}</text></view>
</view>
</view>
<view class="weui-cell__ft weui-cell__ft_in-access"></view>
</navigator>
</block>
</view>
</view>
</view>
</view>
使用自定义组件
- 第一步,要在页面的 json 文件中进行引用声明。此时需要提供每个自定义组件的标签名和对应的自定义组件文件路径。
{
"usingComponents": {
"activityList": "../../components/activityList/activityList"
}
}
注意:
必须要使用双引号,单引号会报错。
组件名字在这里也可以定义,不需要与文件名一致
- 第二步,在页面的wxml中使用自定义组件。如下:
<view>
<activityList id="activityList" title="近期活动" baseUrl="../../pages/club/"></activityList>
</view>
页面和组件之间交互
页面和组件之间肯定需要进行交互,它们之间的交互包括页面向子组件传递数据和调用子组件的方法。子组件向页面传递数据两个部分,方式也不同。
1) 页面访问子组件
页面访问子组件,可以调用组件的方法,可以通过方法里传递数据。也可以使用子组件里定义的properties来设置值,但properties不能多次动态设置,使用方法可以来操作子组件的数据。比如,我的实例中,导航列表里的数据是通过page传递的,page获取到数据后,通过调用子组件的loadData方法来设置子组件的data数据,进而渲染自组件的导航列表。
- 第一步,获取子组件对象,使用以下方法:
if(!this.activityList)
this.activityList = this.selectComponent("#activityList");
其中,activitylist是在wxml中定义的id,有点类似于javascript中获取dom对象。但也有可能出现获取对象为null的情况。
注意:
-
不要在页面的onLoad,onReady,onShow中直接获取对象,这样会经常会返回null,我一般在onload的数据加载完后的回调中或者在某一个页面事件里去获取对象。
-
不要使用html标签来获取对象,使用class或者id可以获取到。
-
第二步,调用子组件的方法。这个就比较简单,获取到子组件对象后,就可以直接调用。
this.activityList.loadData(res.result);
子组件里有相应的loadData方法即可
loadData(data) {
if (data && data.length) {
this.setData({
activities: data
})
}
},
activities是组件定义的data对象,这样就可以通过父组件调用子组件的方法,进而设置子组件的data来渲染子组件。
2)子组件访问页面
子组件也需要来访问页面,这时通过获取页面对象的方式肯定是不行的。小程序提供了事件的机制来进行通讯。通过在子组件里触发事件,在页面来监听此事件达到交互的目的。
- 第一步,在子组件里定义触发事件的方法,比如:
Component({
options: {
addGlobalClass: true // 组件外部定义的class可以影响到组件里面样式
},
/**
* 组件的属性列表
*/
properties: {
title: {
type: String,
value: '近期活动'
},
baseUrl:{
type:String,
value:''
}
},
/**
* 组件的初始数据
*/
data: {
activities: []
},
/**
* 组件的方法列表
*/
methods: {
/*
* 内部私有方法建议以下划线开头
* triggerEvent 用于触发事件
*/
_tapEvent(){
//触发取消回调
this.triggerEvent("tapEvent")
}
}
})
上面是一个为了子组件向父组件发送事件的一个虚拟例子,当导航列表的标题被点击时,向页面组件发送一个tapEvent。
- 第二步,在子组件中绑定此私有方法。
<view class="page">
<view class="page__bd">
<view>
<text class="text-prompt" bindtap=”_tapEvent“>{{title}}</text>
</view>
</view>
</view>
- 第三步,在页面组件中相应此事件。
首先,需要定义此相应事件的方法
const app = getApp()
Page({
//响应子组件里的事件
_tapEvent(){
console.log('你点击了导航标题');
},
})
然后,在页面的子组件wxml中指定此方法。
<view>
<activityList id="activityList" title="近期活动" baseUrl="../../pages/club/" bind:tapEvent="_tapEvent">
</activityList>
</view>
这样,就实现了子组件点击标题,发送事件到页面,页面里的响应事件的方法就会被执行。在我这个例子中,是虚拟的,没有实际意义,只是为了演示这种交互方式。
插槽的使用
子组件里的某些内容可能在每个页面中不相同,这是就可以使用插槽(slot)来由页面组件传入内容到子组件,可以支持多个插槽。使用方法比较简单,我的例子里没有实际使用,我就用一个简单的例子来演示。
- 第一步,在子组件里定义slot,需要指定name。
<view class="content">
<view class="title-box">标题1</view>
<slot class="contant-box" name="slotName1"></slot>
</view>
- 第二步,在页面组件里,加入slot的内容
<!--卡片-->
<activityList> // 自定义组件的名称
<view slot="slotName1">插入组件里面的东西1</view>
<view slot="slotName2">插入组件里面的东西2</view>
</activityList>
- 第三步,如果是多slot,需要在组件里需要设定multipleSlots为true
Component({
options: {
multipleSlots: true // 在组件定义时的选项中启用多slot支持
},
})
写在最后
本来以为用一篇文章将所有的组件和数据共享的全部内容都演示完,写着发现自定义组件内容已经很多了,后面的内容再继续演示组件和页面,页面和页面之间数据共享和交互。此外,自定义组件开发除了在上面用注释的方式中列的一些注意的地方,还需要有两个点注意。
- 组件内如果需要使用页面的样式,需要有下面的定义,否则样式不生效。
addGlobalClass: true // 组件外部定义的class可以影响到组件里面样式
- 组件里的url的相对路径是page页面的相对路径,而不是组件本身的相对路径,这个需要特别注意,否则会出现url不能访问的问题。在我的activityList自定义组件中,加入了baseUrl属性就是这个作用,在Navigotor中指定的url设定了baseUrl,因为调用子组件的页面可能相对路径不同,由页面组件传入到子组件中就很好的解决了这个问题,否则很有可能出现导航不能生效。
网友评论