04小程序

193 阅读11分钟

小程序的组件化开发

image.png

小程序的组件存储在components文件夹,然后创建的文件是:Component

image.png

image.png

但是在组件中,是通过Component实例化,普通页面是通过Page实例化,其他没怎么变;

哪个页面要用到这个组件,就需要在对应页面的.json文件中,书写路径:组件名可以自定义名字,但是最好见名知意

{
    "usingComponents":{
        "组件名":"路径/components/...js"
    }
}

image.png

image.png

  • 不要用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

image.png

页面给组件:

  • 数据: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

image.png

组件传递样式(了解)

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
}

image.png

组件向外传递事件

image.png

Component:{
    properties:{},
    externalClasses:['info'],
    
    methods:{
        onTitleTap(){
            //将事件传递出去:通过传递事件名和参数
            this.triggerEvent("titleClick","aaa")
        }
    }
}

...

<section-info
    info="abc"
    title="我与地坛"
    bind:titleclick="onSectionInfoTitle"
/>

...

Page({
    onSectionInfoTitle(){
        
    }
})

image.png

页面调用组件中的方法

在Vue中,我们是通过ref拿到组件实例,然后通过.xxx的方式拿到组件中的方法

image.png

要给组件加上一个class选择器,然后通过selectComponent('选择器.tab-control')

wxml:
<tab-control
    class="tab-control"
/>
<button bindtap="onExecTCMethod">调用TC方法</button>

js:
Page({
    onExecTCMethod(){
        //获取对应的组件实例
        const tabControl = this.selectComponent()
        //调用组件实例的方法
        tabControl.test()
    }
})

插槽

image.png

image.png

所有的自定义组件插槽,都需要先组件注册;

//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>

image.png

//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>

image.png

组件的代码复用-Behavior

image.png

类似于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>

组件的生命周期函数

image.png

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,就直接移除,通过组件的生命周期就能很明显的发现;

image.png

组件自己想监听自己所在页面的变化:用的较少

Component({
    lifetimes:{
    
    },
    pageLifetimes:{
        show(){
        
        },
        hide(){
        
        },
        resize(){
        
        }
    }
})

Component构造器

Component可以传入哪些属性呢?

properties:定义页面传入到该组件的属性

data:定义该组件自己的内部数据

methods:定义该组件中的方法(监听、执行)

options:配置选项:styleIsolation(组件与页面的样式隔离)、multipleSlots(是否匹配多个插槽)

externalClasses:引用外部样式:externalClasses:['外部样式名']

observers:属性和数据的监听:

image.png

pageLifetimes:页面本身生命周期

lifetimes:组件生命周期

常见API

网络请求-API参数

image.png

微信给我们提供了专属的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>

网络请求域名的后台配置

image.png

微信会对域名进行验证,查看有没有备案,是否合法,并且域名不能使用IP地址或localhost!

image.png

所以在我们测试的时候,把这个选项勾上,就可以暂时跳过检查了

网络请求的函数封装

// 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

image.png

这里没有写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',
            
        })
    }
})

image.png

image.png

developers.weixin.qq.com/miniprogram…

Page({
    onShowModal(){
        title:"确定购买吗?",
        content:"确定购买的话,请确定您的微信有钱!",
        success:(res)=>{
            
        }
    },
    onShowAction(){
        wx.showActionSheet({
            //从底部弹出
            itemList:['衣服','鞋子','裤子'],
            success:(res)=>{
                //通过tapIndex知道选择的是第几项数据
                console.log(res.tapIndex)
            }
        })
    }
})

image.png

image.png

分享API回调函数

image.png

通过onShareAppMessage决定信息的展示,监听用户点击页面内转发按钮button组件open-type="share"右上角菜单 “转发”按钮的行为,并自定义转发内容;

.js文件
Page({
    //这个就是微信小程序提供的api,直接写在js文件中即可
    onShareAppMessage(){
        return{
            title:"分享的标题",
            path:'要分享页面的这个路径',
            imageUrl:"分享出去的封面图片"
        }
    }
})

设备信息和位置信息

获取用户的设备信息

image.png

<button bindtap="onGetSystemInfo">设备信息</button>

onGetSystemInfo(){
    wx.getSystemInfo({
        success:(res)=>{
            
        }
    })
}

image.png

onGetSystemInfo(){
    wx.getLocation({
        success:(res)=>{
            
        }
    })
}

小程序想要访问用户信息的时候,需要得到用户的授权,如何得到用户的授权?

调用api后,去app.json中书写:

{
    "permission":{
        "scope.userLocation":{
            "desc":"需要获取您的位置信息"
        }
    }
}

image.png

developers.weixin.qq.com/miniprogram…

本地存储Storage的API

image.png

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)
                }
            })
        }
    })
})

页面跳转

image.png

用的最多的是第四个方法:wx.navigateTo()

navigateTo

image.png

保留当前页面,跳转到应用内的某个页面;


<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

image.png

<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()可以拿到当前栈中所有的页面实例

image.png

image.png

第二种页面的返回数据传递方式:

onNavTap(){
    wx.navigateTo({
        url:``
        events:{
            backEvent(data){

            },
            coderwhy(data){

            }
        }
    })
}


跳转到的页面:拿到eventChannel,将数据传递出去
const eventChannel = this.getOpenerEventChannel()
eventChannel.emit('backEvent',{name:...})

image.png

和事件总线很像

image.png

登录中身份的问题

image.png

image.png

静默登录:用户不用操作就自动登陆成功了,即使换手机了,我们产品也要知道这个还是之前的用户,然后将之前的数据全部调出来;

只要保证是同一个微信登录的,静默登录后,微信功能登录会给一个openid,用来识别同一个人的标识符;所以就算你换手机了,只要是同一个openid就行;然后openid中存储这个用户的所有数据信息,在小程序中展示出来即可;

张三除了利用微信在小程序登录,还在公众号里也登录了,但是公众号和小程序中登录存储的openid是不同的,但是unionid是相同的,unionid用来在微信不同产品中,能够识别同一个用户的;

现在小程序已经不给你openid了,因为客户端直接给openid有点不安全;我们现在需要先获取code,然后用来换取authToken

用户身份多平台共享:openid将手机号和用户名互相关联起来,在其他平台上,通过手机号也能找到用户的数据;

小程序用户登录的流程

image.png

在一进入页面的时候,完成登录的流程

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(小程序密钥)去微信的服务器中,向微信服务器发送请求,换取openidsession_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)
}
})