小程序的组件化开发
小程序的组件存储在components
文件夹,然后创建的文件是:Component
但是在组件中,是通过Component
实例化,普通页面是通过Page
实例化,其他没怎么变;
哪个页面要用到这个组件,就需要在对应页面的.json
文件中,书写路径:组件名可以自定义名字,但是最好见名知意
{
"usingComponents":{
"组件名":"路径/components/...js"
}
}
- 不要用
wx-
来命名组件 - 如果在app.json中使用
usingComponents
声明的组件,则是全局组件!之前我们仅仅是在需要使用到的文件中的.json
文件书写的!
{
"usingComponents":{
"组件名":"路径/components/...js"
}
}
组件的样式细节
组件内的样式,对外部样式的影响
- 组件内的class样式,只对组件wxml内的节点生效,对于引用组件的Page页面不生效;
- 组件内不能使用id选择器、属性选择器、标签选择器;
外部样式对组件内样式的影响
- 外部使用class样式,只对外部的wxml的class生效,对组件内是不生效的;
- 外部使用了id选择器、属性选择器不会对组件内产生影响;
- 外部使用了标签选择器,会对组件内产生影响;
如何让class可以相互影响
- 在Component对象中,可以传入一个options属性,其中options属性中有一个
stylesolation
隔离属性; - stylesolation有3个取值:
- isolated:表示启用样式隔离,在自定义组件内外,使用class指定的样式将不会相互影响(默认,推荐)
- apply-shared:表示页面wxss样式影响到自定义组件,但自定义组件wxss中指定的样式不会影响页面;
- shared:表示页面wxss样式将会影响到自定义组件,自定义组件wxss中指定的样式也会影响页面和其他设置;
组件通信-Properties
页面给组件:
- 数据:properties
- 样式:externalClasses
- 标签:slot
如果组件想要给页面:通过自定义事件
//自定义组件
<section-info title="哈哈哈" content="内容哈哈"/>
组件:section-info.js
Component({
properties:{
title:{
type:Stirng,
value:"默认标题"
},
content:{
type:Stirng,
value:"默认内容"
},
}
})
section-info.wxml
<view>{{title}}</view>
<view>{{content}}</view>
vue中接收默认数据的字段是:default
组件传递样式(了解)
section-info.wxml:
//页面传递一个info
<view class="info"></view>
section-info.js:
Component({
properties:{
},
//外部传进来一个叫info的class
externalClasses:["info"]
})
页面:将写好的样式传递给info属性
<section-info info="abc"/>
.wxss:
.abc{
background-color:#0f0
}
组件向外传递事件
Component:{
properties:{},
externalClasses:['info'],
methods:{
onTitleTap(){
//将事件传递出去:通过传递事件名和参数
this.triggerEvent("titleClick","aaa")
}
}
}
...
<section-info
info="abc"
title="我与地坛"
bind:titleclick="onSectionInfoTitle"
/>
...
Page({
onSectionInfoTitle(){
}
})
页面调用组件中的方法
在Vue中,我们是通过ref
拿到组件实例,然后通过.xxx
的方式拿到组件中的方法
要给组件加上一个class选择器
,然后通过selectComponent('选择器.tab-control')
wxml:
<tab-control
class="tab-control"
/>
<button bindtap="onExecTCMethod">调用TC方法</button>
js:
Page({
onExecTCMethod(){
//获取对应的组件实例
const tabControl = this.selectComponent()
//调用组件实例的方法
tabControl.test()
}
})
插槽
所有的自定义组件插槽,都需要先组件注册;
//my-slot.wxml
<view class="my-slot">
<view class="content">
<slot></slot>
</view>
</view>
//页面.wxml
<my-slot>
//页面自行插入
<button>我是按钮</button>
</my-slot>
在vue中,可以在组件的插槽中书写默认值的,但是小程序中,插槽是不支持默认值的!!所以<slot>
中间不能插入值,但是我又想要默认值怎么办?
.my-slot{
margin:20px 0;
}
.default{
display:none;
}
//当里面的内容为空,就找到兄弟选择器,是伪类
.content:empty + .default{
display:block
}
<view class="my-slot">
<view class="content">
<slot></slot>
</view>
//借助css来实现
<view class="default">哈哈哈</view>
</view>
//mul-slot.wxml
<view class="mul-slot">
<view class="left">
<slot name="left"></slot>
</view>
<view class="right">
<slot name="right"></slot>
</view>
<view class="center">
<slot name="center"></slot>
</view>
</view>
//mul-slot.js
Component({
options:{
//允许多个插槽
multipleSlots:true
}
})
//多个插槽使用
<mul-slot>
<view slot="left"></view>
<view slot="right"></view>
<view slot="center"></view>
</mul-slot>
组件的代码复用-Behavior
类似于Vue中的mixins
在最外层建一个:behaviors
的文件夹,里面建一个.js
文件:
export const counterBehavior = Behavior({
data:{
counter:100
},
methods:{
increment(){
this.setData({
//最好+1,不要写成++
counter:this.data.counter + 1
})
}
}
})
具体组件.js使用behavior中的数据:
import {counterBehavior} from '../../behavior'
Component({
behaviors:[counterBehavior]
})
组件的.wxml:由于已经在.js文件中引入behaviors中的数据,所以这里可以直接使用
<view>{{counter}}</view>
<button bindtap="increment"></button>
组件的生命周期函数
attached
:还没被渲染出来,但是已经被挂载到节点树了;
ready
:不仅被挂载到节点树了,而且也被渲染出来(了解一下)
只要是组件,就不要忘记注册!!
// c-lifetime.js
Component({
lifetimes:{
created(){
created:组件被创建
},
attached(){
attached:组件被添加到组件树中
},
detached(){
detached:组件从组件树中被移除
}
}
})
// index.wxml
<button bindtap="onChange">切换</button>
<c-lifetime wx:if="{{isShowLiftTime}}"/>
// index.js
Page({
data:{
isShowLiftTime:true
},
onChangeTap(){
this.setData({isShowLiftTime:!this.data.isShowLiftTime})
}
})
wx:if
如果是false,就直接移除,通过组件的生命周期就能很明显的发现;
组件自己想监听自己所在页面的变化:用的较少
Component({
lifetimes:{
},
pageLifetimes:{
show(){
},
hide(){
},
resize(){
}
}
})
Component构造器
Component
可以传入哪些属性呢?
properties
:定义页面传入到该组件的属性
data
:定义该组件自己的内部数据
methods
:定义该组件中的方法(监听、执行)
options
:配置选项:styleIsolation
(组件与页面的样式隔离)、multipleSlots
(是否匹配多个插槽)
externalClasses
:引用外部样式:externalClasses:['外部样式名']
observers
:属性和数据的监听:
pageLifetimes
:页面本身生命周期
lifetimes
:组件生命周期
常见API
网络请求-API参数
微信给我们提供了专属的API接口:wx.request(Object object)
,其中url
参数是必传参数;
// index.js
Page({
data:{
allCities:{}
},
onLoad(){
//网络请求的基本使用
wx.request({
url:'http://123.207.32.32:1888/api/city/all'
success:(res)=>{
const data = res.data.data
this.setData({allCities:data})
},
fail:(err)=>{
console.log(err)
}
})
wx.rquest({
url:'',
data:{
page:1
},
success:(res)=>{
const data = res.data.data
this.setData({houselist:data})
}
})
}
})
//index.wxml
<view>
<block wx:for="{{houselist}}" wx:key="{{item.data.id}}">
...
</block>
</view>
网络请求域名的后台配置
微信会对域名进行验证,查看有没有备案,是否合法,并且域名不能使用IP地址或localhost!
所以在我们测试的时候,把这个选项勾上,就可以暂时跳过检查了
网络请求的函数封装
// index.js封装成函数
export function hyRequest(options){
return new Promise((resolve,reject)=>{
wx.request({
...options,
success:(res)=>{
resolve(res.data)
},
fail:reject
})
})
}
//使用封装后的wx.request
import {request} from '...'
Page({
data:{
allCities:{}
},
//普通使用:
onLoad(){
hyRequest({
url:'...',
}).then(res=>{
this.setData({allCities:res.data})
})
}
//使用await/async
async onLoad(){
const res = await hyRequest({url:'...'})
}
this.setData({allCities:res.data})
const houseRes = await hyRequest({
url:'...',
data:{
page:1
}
})
this.setData({houseList:houseRes.data})
})
但是await存在一个问题:必须要在await返回正确结果后,才能执行后面的代码,所以第二个await可能就不会执行到;
更推荐的写法:
将请求封装到单独的一个函数中:由于本身调用的方法是一个异步的,所以不管这个方法有没有返回结果,都会执行下面的方法;
Page({
data:{
...
},
onLoad(){
this.getCityData()
},
async getCityData(){
const cityRes = await hyRequest({
url:''
})
this.setData({allCities:cityRes.data})
}
})
使用微信小程序提供的上拉加载更多方法:onReachBottom(){}
Page({
data:{
currentPage:1
}
onLoad:{
onReachBottom();
},
async getHouselistData(){
const houseRes = await hyRequest({
url:'',
data:{
page:1
}
})
this.setData({...})
//为什么这里不需要setData?
//因为使用setData是为了让页面实现响应式,实际上currentPage不需要响应式
//只需要他本身数值发生改变就行了,不需要它显示在页面上
this.data.currentPage++
}
onReachBottom(){
this.getHouselistData()
}
})
网络请求的类封装
class HYRequest{
constructor(baseURL){
this.baseURL = baseURL
}
request(options){
const {url} = options
return new Promise((resolve,reject)=>{
wx.request({
...options,
//拼接并覆盖
url:this.baseURL+url,
success:(res)=>{
resolve(res.data)
},
fail:reject
})
})
}
get(options){
return this.request({...options,method:'get'})
}
post(options){
return this.request({...options,method:'post'})
}
}
export const hyRequestInstance = new HYRequest('http://123...')
hyRequestInstance.get({
url:'/city/all'
}).then(res=>{
console.log(res)
})
弹窗相关API
这里没有写showLoading
,因为和showToast
类似;
展示弹窗:
<button size="mini" bindtap="onShowToast"></button>
<button size="mini" bindtap="onShowModal"></button>
<button size="mini" bindtap="onShowAction"></button>
.js
Page({
onShowToast(){
wx.showToast({
title:'购买成功!',
icon:'loading',
duration:3000,
success:(res)=>{
},
fail:(err)=>{
}
})
wx.showLoading({
title:'加载中ing',
})
}
})
developers.weixin.qq.com/miniprogram…
Page({
onShowModal(){
title:"确定购买吗?",
content:"确定购买的话,请确定您的微信有钱!",
success:(res)=>{
}
},
onShowAction(){
wx.showActionSheet({
//从底部弹出
itemList:['衣服','鞋子','裤子'],
success:(res)=>{
//通过tapIndex知道选择的是第几项数据
console.log(res.tapIndex)
}
})
}
})
分享API回调函数
通过onShareAppMessage
决定信息的展示,监听用户点击页面内转发按钮button组件open-type="share"
或右上角菜单 “转发”按钮的行为
,并自定义转发内容;
.js文件
Page({
//这个就是微信小程序提供的api,直接写在js文件中即可
onShareAppMessage(){
return{
title:"分享的标题",
path:'要分享页面的这个路径',
imageUrl:"分享出去的封面图片"
}
}
})
设备信息和位置信息
获取用户的设备信息
<button bindtap="onGetSystemInfo">设备信息</button>
onGetSystemInfo(){
wx.getSystemInfo({
success:(res)=>{
}
})
}
onGetSystemInfo(){
wx.getLocation({
success:(res)=>{
}
})
}
小程序想要访问用户信息的时候,需要得到用户的授权,如何得到用户的授权?
调用api后,去app.json中书写:
{
"permission":{
"scope.userLocation":{
"desc":"需要获取您的位置信息"
}
}
}
developers.weixin.qq.com/miniprogram…
本地存储Storage的API
Sync是同步,async是异步,异步就意味着我可能一开始就书写了setStorage
存储数据,但是我getStorage
取数据的时候,是有可能存在取不到的,因为数据还没存进去;
Page({
//存储一些键值对
wx.setStorageSync('name','why')
wx.setStorageSync('age',18)
wx.setStorageSync('friends',['abc','cba'])
//获取storage中的内容
const name = wx.getStorageSync('name')
const age = wx.getStorageSync('age')
const friends = wx.getStorageSync('friends')
//删除storage中内容
wx.removeStorageSync('name')
//清空storage中的内容
wx.clearStorageSync()
---------------------------------------
异步操作:
wx.setStorage({
key:'books',
data:['..','..','..'],
//加密:
encrypt:true,
success:(res)=>{
wx.getStorage({
key:'books',
encrypt:true,
success(res)=>{
console.log(res)
}
})
}
})
})
页面跳转
用的最多的是第四个方法:wx.navigateTo()
navigateTo
保留当前页面,跳转到应用内的某个页面;
<button bindtap="onNavTap">跳转</button>
--------------------------------------
Page({
onNavTap(){
//页面导航操作
wx.navigateTo({
//跳转的过程,传递一些参数过去
url:'/pages2/detail/detail.js?name=why&age=18',
})
}
})
--------------------------------------
详情页拿数据:
Page({
data:{
name:'',
age:0
},
//通过options接收到参数
onLoad(options)
const name = options.name
const age = options.age
this.setData({name,age})
})
详情页展示数据:
<view>{{name}}--{{age}}</view>
只有跳转后,数据才会传递
Page({
data:{
name:why,
age:18
},
onNavTap(){
const name = this.data.name
const age = this.data.age
wx.navigateTo({
//跳转的过程,传递一些参数过去
url:`/pages2/detail/detail.js?name=${name}&age=${age}`,
})
}
})
页面返回navigateBack
<button size="mini" bindtap="onBackTap"></button>
--------------------------------------------
onBackTap(){
wx.navigateBack({
//通过设置delta来确定返回的级数
delta:1
})
}
onBackTap(){
//返回导航
wx.navigateBack()
//给上一级的页面传递数据
1.获取到上一个页面的实例
const pages = getCurrentPages()
//上一个一定是-2,当前的是-1,因为数组会越界
const prePage = pages[page.length-2]
//间接修改上一个实例的数据
prePage.setData({message:"hhe"})
}
//写在onUnload中(就是后退键,之前我们是写在button上的方法),让后退键也有方法
onUnload(){
//给上一级的页面传递数据
1.获取到上一个页面的实例
const pages = getCurrentPages()
//上一个一定是-2,当前的是-1,因为数组会越界
const prePage = pages[page.length-2]
//间接修改上一个实例的数据
prePage.setData({message:"hhe"})
}
getCurrentPages()
可以拿到当前栈中所有的页面实例
第二种页面的返回数据传递方式:
onNavTap(){
wx.navigateTo({
url:``
events:{
backEvent(data){
},
coderwhy(data){
}
}
})
}
跳转到的页面:拿到eventChannel,将数据传递出去
const eventChannel = this.getOpenerEventChannel()
eventChannel.emit('backEvent',{name:...})
和事件总线很像
登录中身份的问题
静默登录:用户不用操作就自动登陆成功了,即使换手机了,我们产品也要知道这个还是之前的用户,然后将之前的数据全部调出来;
只要保证是同一个微信登录的,静默登录后,微信功能登录会给一个openid
,用来识别同一个人的标识符;所以就算你换手机了,只要是同一个openid就行;然后openid中存储这个用户的所有数据信息,在小程序中展示出来即可;
张三除了利用微信在小程序登录,还在公众号里也登录了,但是公众号和小程序中登录存储的openid
是不同的,但是unionid
是相同的,unionid
用来在微信不同产品中,能够识别同一个用户的;
现在小程序已经不给你openid了,因为客户端直接给openid有点不安全;我们现在需要先获取code
,然后用来换取authToken
;
用户身份多平台共享:openid将手机号和用户名互相关联起来,在其他平台上,通过手机号也能找到用户的数据;
小程序用户登录的流程
在一进入页面的时候,完成登录的流程
Page({
//onLoad登录的流程
onLoad(){
//3.获取token,判断token是否有值
const token = wx.getStorageSync('token')
if(token){
//如果有token,请求其他数据
}else{
this.handleLogin()
}
}
handleLogin(){
//1.获取code,使用系统提供的API:wx.login
wx.login({
success:(res)=>{
const code = res.code//拿到code
//2.将这个code发送到自己的服务器(后端)
wx.reqeust({
url:'',
data:{
code
},
method:"post",
//接收服务器返回的token
success:(res)=>{
const token = res.data.token
wx.setStorageSync('token',token)
}
})
}
})
}
})
将code发送给后端后,后端拿到code后,还需要拿到appid(小程序ID)
和appsecret(小程序密钥)
去微信的服务器中,向微信服务器发送请求,换取openid
和session_key
,然后作为身份标识,存储在数据库中,生成一个自定义登录态(token)与openid、session_key关联;然后将token返回给前端,我们存到storage中,wx.request()
每次发起业务请求的时候,会携带token,然后后端根据比对,将数据返回出去;
改进写法:
export function getCode(){
return new Promise((resolve,reject)=>{
wx.login({
success:(res)=>{
resolve(res.code)
}
})
})
}
-------------------------------------------
Page({
async onLoad(){
const token = wx.getStorageSync('token') || ''
//判断token是否过期
const res = await hyRequestInstance.post({
url:'/auth',
header:{
token
}
})
if(token){
请求其他数据
}else{
this.handleLogin()
}
}
async handleLogin(){
//获取code
const code await getCode()
//换取token
const res = await hyRequestInstance.post({
url:'/login',
data:{
code
}
})
//保存token
wx.setStorageSync('token',res.token)
}
})