【小程序】实现复用及造轮子入门
小程序实现可以通过 template 和 component 两种方式实现代码的复用,以减少不必要的工作量。template 被官方称为模块,在 xml
和 css
中定义代码片段,然后可以在其他页面使用,事实上,template 像是复制粘贴的另一种形式,只是把复制粘贴的操作使用<template>
标签来代替,减少了页面的简洁程度,方便维护(有点像 android 中的 <include>
)。比起template, component 实现的自定义组件,可以进行自己的逻辑处理,并像官方提供的组件一样去使用。
template 和 component 的不同
component 和普通的 page 一样有 xml、css、json 和 js 文件,template 只有 xml 和 css 文件
component 可以处理点击事件,tempalte 需要在使用者的 js 中定义
template 的动态数据均来自使用者的传递,component 不是
对于 template,在使用者的 css 文件中定义的样式会覆盖模块中定义的样式,而对于 component,app 的样式和使用者的样式均不会影响到组件内部(除非使用
externalClasses
)component 只能使用类选择器 ,template 没有限制
……
template 复用弹窗
template 通常在复用的部分逻辑处理比较少的情况下使用,如在应用中有一些 UI 一样、但文字内容不一样的提示弹框,这时可以使用 template 来达到复用的目的。
1. 定义
建立一个目录,在目录下建立 xml 和 css 文件,编辑弹框的 xml 和 css 文件和编辑普通的 page 基本没有什么差别,唯一不同的是在 xml 的最外层需要套上一个 <template>
标签,在该标签中定义 template 的 name 属性,这个 name 属性将成为该弹框的标识。另外 template 中动态数据通过引用者传入。
<!--tip-dialog.wxml-->
<template name="tip-dialog">
<view class='container'>
<view class='tip-content'>
<image class='content-icon' src="{{icon}}"></image>
<text class='content-text'>{{text}}</text>
</view>
<view class='tip-btn' bindtap='clickedBtn'>哦~</view>
</view>
</template>
2. 使用
xml 文件中使用
<import>
标签引入模板<import src="/pages/tip-dialog/tip-dialog"/>
使用
<template>
标签指明模板,并传入动态数据,data 属性中的 … 是展开符号,将 js 中的对象属性展开传递到模板,使模板中可以直接通过属性名称使用,而不是对象.属性<template is="tip-dialog" data="{{...tipData}}" wx:if="{{showTip}}"/>
css 文件中使用
@import
引入模板的 css 文件,完成@import "/pages/tip-dialog/tip-dialog";
component 造简单的轮子
微信从 1.6.3开始支持自定义组件,自定义组件拥有拥有和 page 一样的四个文件,其中 js 文件的结构有一些差异,不过写法基本没什么变化。创建自定义组件的步骤如下:
- 新建目录,右键建立 component
- component.json
{
"component": true
}
和编辑普通的页面一样编辑组件,注意是 js 文件的一些差异
- properties:是组件对外开放的属性,当在使用者 xml 文件中使用该组件时,可以为这些属性传值达到改变组件的目的
- data:内部数据,和 page 的 data 一样,和 properties 不同的是它是对内的
- mthods:组件的自定义方法都定义在其内部,亲测定义在外部无法识别
- externalClasses:组件外部样式,组件内部的样式是不受 app 和 使用者的 css 影响的,如果有组件内部有一些样式希望在使用者使用时才决定,那么久可以通过 externalClasses 去实现
- behaviors:用于组件间的引用(详情:文档-组件间的关系)
- 生命周期 :created()、attached()、ready()、moved()、dettach()
Component({ //选项 options: { multipleSlots: true // 启用多slot(插槽)支持 }, properties: { //外部属性 text: { type: String, //类型 value: 'default value', //默认值 observer: '方法名' //当数据发生变化时调用 } }, //外部样式 externalClasses: ['text-class'], data: { // 内部数据 someData: {} }, //生命周期 attached: function(){}, moved: function(){}, detached: function(){}, methods: { // 自定义方法 customMethod: function(){} } })
使用者 json
{ "usingComponents": { //组件名-路径 "component-name": "component-path", } }
使用者 xml
<component-name property="" />
官方文档:文档-自定义组件
自定义导航栏
小程序官方提供的导航栏可以控制的部分太少,很多时候并不能满足项目的需求,从 1.9.1 开始,微信开始支持自定义导航栏,这里使用 component 实现一个导航栏。功能如下:
- 导航栏有标题,返回键,分割线
- 导航栏高度自适应,标题和返回键始终垂直居中
- 导航栏可由使用者指定背景颜色或图片
- 标题的内容和样式对使用者开放
- 返回键可显示和不显示,按钮的 icon 可由使用者提供,且提供默认 icon,点击可返回上一个界面,提供返回按钮的监听事件
- 分割线可显示和不显示,使用者可决定颜色
<!--nav-bar.wxml-->
<view class='nav-container' style='height:{{customeHeight? navHeight:height}}px;background:{{bgColor}};'>
<image class="nav-background-img" src='{{imageBg}}' style='height:{{height}}px;' mode='top' wx:if="{{useImageBg}}"></image>
<image class='nav-back' src='{{backIcon}}' wx:if='{{showBack}}' bindtap='clickedNavBarBack'/>
<view class='nav-title-view' style='margin-bottom: {{marginB}}rpx;'>
<view class='nav-title-class'>{{title}}</view>
</view>
<view class='nav-line' style='background-color:{{lineColor}};' wx:if="{{showLine}}"></view>
</view>
tips:
- 不使用 externalClasses 的情况下,组件使用者 css 不能覆盖组件中的 css
- 在使用 externalClasses 的情况下,组件内部 xss 中对于该 class 样式失效,但是 wxml 中 style 属性指定的样式仍有效
//nav-bar.js
Component({
externalClasses: ['nav-title-class'],
properties: {
//指定导航栏高度,不指定将使用默认的计算方式
navHeight: {
type: Number,
value: -1,
observer: 'navHeightChange'
},
//导航栏标题
title: {
type: String,
value: "VUI"
},
//是否显示返回键
showBack: {
type: Boolean,
value: true
},
//背景颜色,默认 #fff
bgColor: {
type: String,
value: ''
},
//背景图片,默认不使用,使用后将覆盖掉背景颜色
imageBg: {
type: String,
value: '',
observer: 'imageBgChange'
},
//指定返回按钮的 Icon
backIcon: {
type: String,
value: DEFAULT_BACKICON
},
//导航栏底部分割线颜色
lineColor: {
type: String,
value: "#ccc"
},
//是否显示底部分割线
showLine: {
type: Boolean,
value: true
}
},
data: {
height: 64,
useImageBg: false,
customHeight: false,
marginB: 10
},
created: function() {
_this = this
},
/**
* ready 开始可以获得控件信息
*/
ready: function() {
let height = _this.getNavBarHeight()
_this.calculatePosiontion(height)
},
methods: {
clickedNavBarBack: function() {
console.log("navBar.clikedBack()")
wx.navigateBack({})
this.triggerEvent("tapBack") //使用者可以绑定这个方法来监听返回键点击事件(bindtapBack)
},
calculatePosiontion: function (height) {
let sys = wx.getSystemInfoSync()
let query = wx.createSelectorQuery().in(this)
query.select(".nav-title-view").boundingClientRect()
query.exec(function (rects) {
let titleH = rects[0].height
let realH = height - sys.statusBarHeight
let marginB = (realH - titleH) / 2
_this.setData({
height: height,
marginB: marginB
})
})
},
getNavBarHeight: function () {
let height = 64
let res = wx.getSystemInfoSync();
let system = res.system
if(system.search('android') != -1) {
height = 68
} else {
let model = res.model
if (model.search('iPhone X') != -1) {
height = 88
} else if (model.search('iPad') != -1) {
height = 100
}
}
return height
},
navHeightChange: function (newVal, oldVal) {
_this.setData({
customeHeight: true
})
},
imageBgChange: function(newVal, oldVal) {
if(newVal != '') {
_this.setData({
useImageBg: true
})
}
}
}
})
对于标题栏高度,使用 px 作为单位,android 统一为 68,iPhone X 为 88,iPhone 其他为 64。对于居中,首先将标题和返回按钮固定在导航栏底部,然后计算 margin-bottom 的值(导航栏高度包括了状态栏的高度)。
<!-- index.wxml -->
<nav-bar show-back="{{true}}" title="VUI" nav-title-class="nav-title-class" line-color="#aaa" showLine="{{showNavLine}}" bindtapBack="tapBack"></nav-bar>
效果: