理论
小程序的双线程模型
- 小程序的宿主环境是:微信客户端
- 小程序考虑到性能与安全的问题,采用了「双线程模型」的架构
- WXML模块和WXSS样式运行于 渲染层,渲染层使用 WebView线程渲染(一个程序有多个页面,会使用多个 WebView的线程)。
- JS脚本(app.js/home.js等)运行于 逻辑层,逻辑层使 用JsCore运行JS脚本。
- 这两个线程都会经由微信客户端(Native)进行中转交互。
小程序的配置文件以及目录结构
- pages 页面文件夹
- index 页面文件
- index.js 页面逻辑
- index.json 页面配置
- index.wxml 页面结构
- index.wxss 页面样式表
- index 页面文件
- utils 工具文件夹
- app.js 首次进入小程序执行的逻辑
- app.json 项目全局配置
- app.wxss 小程序全局样式表
- project.config.json 项目配置文件(公有)
- project.private.config.json 项目配置文件(私有)
- 一般本地的配置数据在这里配置(这个文件不会被上传到服务器或共享给其他开发者)
- 这个文件中设置的内容会覆盖掉project.config.json文件中的相同设置
- sitemap.json 小程序搜索相关
- 微信会爬取你的页面内容, 当用户在自己的微信中搜索时可以搜索到你开发的小程序
小程序的核心技术主要是三个
- 页面布局:WXML,类似HTML;
- 页面样式:WXSS,几乎就是CSS(某些不支持,某些进行了增强,但是基本是一致的);
- 页面脚本:JavaScript+WXS(WeixinScript);
app.js 注册小程序
- 每个小程序都需要在 app.js 中调用 App 函数 注册小程序实例
- 在注册时,可以绑定对应的生命周期函数,
- 在生命周期函数中,执行对应的代码
- 注册app的时候,一般会做什么?
- 判断小程序的进入场景
- 在onLaunch和onShow生命周期回调函数中,会有options参数,其中有scene值
- 监听生命周期函数,在生命周期中执行对应的业务逻辑,比如在某个生命周期函数中进行登录操作或者请求网络数据;
- 因为App()实例只有一个,并且是全局共享的(单例对象),所以我们可以将一些共享数据放在这里;(globaldata)
- 判断小程序的进入场景
注册页面-page函数
小程序中的每个页面,都有一个对应的js文件,其中调用Page函数注册页面实例
在注册时,可以绑定初始化数据、!生命周期回调、事件处理函数等。
- 注册一个Page页面时,我们一般需要做什么呢?
- 在生命周期函数中发送网络请求,从服务器获取数据
- 初始化一些数据,以方便被wxml引用展示,
- 监听wxml中的事件,绑定对应的事件函数;
- 其他一些监听(比如页面滚动、上拉刷新、下拉加载更多等)
wxs语法
为什么要设计WXS语言呢?
- 在WXML中是不能直接调用Page/Component中定义的函数的,
- Mustache语法中默认无法调用函数因为双线程模型导致不在同一线程内
- 但是某些情况,我们可以希望使用函数来处理WXML中的数据(类似于Vue中的过滤器),这个时候就使用WXS了
WXS使用的限制和特点:
- WXS 不依赖于运行时的基础库版本,可以在所有版本的小程序中运行
- WXS 的运行环境和其他,JavaScript 代码是隔离的,WXS 中不能调用其他JavaScript 文件中定义的函数,也不能调用小程序提供的API;
- 由于运行环境的差异,在 ios 设备上小程序内的 WXS 会比 JavaScript 代码快2~ 20 倍。在 android 设备 上二者运行效率无差异;
wxs使用方法
- wxs不支持 ECMAScript 6+
- 每个模块都有自己独立的作用域。即在一个模块里面定义的变量与函数,默认为私有的,对其他模块不可见;
- 一个模块要想对外暴露其内部的私有变量与函数,只能通过module.exports实现;
方式一:wxs标签 - 在页面内
<wxs module="format">
function formatPrice(price){
return "$"+price
}
//必须导出后,才能被其它地方调用,必须使用commonJS导出
module.exports = {
formatPrice:formatPrice
}
</wxs>
<!-- 使用 -->
<view class="books">
<block wx:for="{{books}}" wx:key="id">
<view> name:{{item.name}} - price: {{format.formatPrice(item.price)}}</view>
</block>
</view>
方式二:外部文件引用
- 写在以.wxs结尾的文件中
<wxs module="format" src="/utils/format.wxs"></wxs>
<view class="books">
<block wx:for="{{books}}" wx:key="id">
<view> name:{{item.name}} - price:{{format.formatPrice(item.price)}}</view>
</block>
</view>
```
```JAVASCRIPT
function formatPrice(price) {
return "$" + price
}
//必须导出后,才能被其它地方调用,必须使用commonJS导出
module.exports = {
formatPrice: formatPrice,
}
touches和changedTouches的区别
- changedTouches :触发事件时改变的触摸点的集合
- 如刚开始点了3个点然后松开1个changedTouches里面会有离开的那个触摸点
- touches :当前屏幕上所有触摸点的列表
touches
数组将包含所有活跃的触摸点(即所有正在触摸屏幕的手指)
功能
定义全局App的数据
- 在app.js里面有一个配置选项globaldata其定义的数据可在app内共享
定义:
App({
//数据不是响应式的,这里共享的数据通常是一些固定的数据
globaldata:{
token:"coderwhyToken",
userInfo:{
nickname:"XXX",
level:"99"
}
},
onLaunch(options) {
console.log("小程序已经启动了",options);
// 1.进行登录操作(判断逻辑)保存到storage
wx.setStorageSync('key', "kobtoekn")
},
onShow(options){
console.log("onShow",options);
},
onHide(){
console.log("onHide");
}
})
使用:
//获取全局数据
//1.获取app实例对象
const app = getApp()
//2.从app实例对象获取数据
const token = app.globaldata.token
const userInfo = app.globaldata.userInfo
console.log(token, userInfo);
//3.拿到token目的发送网络请求
//4.讲数据展示到页面上去
this.setData({userInfo})
}
下拉刷新上拉加载和监听页面的滚动
下拉刷新
- 在需要开启下拉刷新的页面配置开启下拉刷新
onPullDownRefresh:true
- 在页面page函数内编写事件的处理函数
//下拉刷新
onPullDownRefresh() {
console.log("用户进行了下拉刷新");
//模拟网络请求:定时器
setTimeout(() => {
this.setData({
listCount: 20
})
//停止下拉刷新
wx.stopPullDownRefresh({
success: (res) => {
console.log("下拉刷新成功", res);
},
fail: (err) => {
console.log("下拉刷新失败", err);
}
})
}, 1000)
},
上拉加载
- 在页面的json文件内有一个配置选项
- 页面上拉触底事件触发时距页面底部距离,单位为px
"onReachBottomDistance":100
- 在页面编写触底事件的处理函数
//监听滚动到底部
onReachBottom() {
console.log("滚动到了底部");
this.setData({
listCount: this.data.listCount + 30
}, (res) => {
console.log("数据处理完成", res);
})
}
监听页面的滚动
- 在页面编写页面滚动事件的处理函数
onPageScroll(event){
console.log("页面滚动",event);
},
获取用户信息
- 必须依靠点击操作动作才能触发
获取用户头像
<button type="primary" open-type="chooseAvatar" bindchooseavatar="getUserInfo3">获取用户头像</button>
getUserInfo3(event){
console.log("获取到用户头像",event);
},
获取用户昵称
- type=”nickname” 获取昵称
<input type="nickname" class="weui-input" placeholder="请输入昵称"/>
获取手机号
- 通过按钮用户主动触发获取code码(有效期为5分钟)
- 将code码发送给后台服务器,后台服务器携带access_token,code,openid(非必传)调用微信后台提供的 phonenumber.getPhoneNumber接口来获取手机号并存入数据库,并返回给前台对应的结果
- 不需要提前调用
wx.login
进行登录 - 前台拿到结果后做对应的逻辑处理
<button open-type="getPhoneNumber" bindgetphonenumber="getphonenumber">获取手机号</button>
getphonenumber(event){
console.log("获取用户手机号",event);
},
选择本地图片以及image组件
- 选择本地图片
- wx.chooseMedia :拍摄或从手机相册中选择图片或视频
<button bindtap="onChooseImage">选择本地图片</button>
onChooseImage(){
wx.chooseMedia({
mediaType:"image"
}).then(res=>{
console.log(res);
//本地临时文件路径 (本地路径)
const imagePath = res.tempFiles[0].tempFilePath
this.setData({
chooseImgae:imagePath
})
})
},
image组件
- mode属性
- widthFix (常用)
- 缩放模式,宽度不变,高度自动变化,保持原图宽高比不变
- widthFix (常用)
双向绑定
- 在 WXML 中,普通的属性的绑定是单向的
- 如果需要在用户输入的同时改变
this.data.value
,需要借助简易双向绑定机制。此时,可以在对应项目之前加入model:
前缀:
<!-- input的双向绑定 -->
<input type="text" model:value="{{message}}"/>
- 这样,如果输入框的值被改变了,
this.data.value
也会同时改变。同时, WXML 中所有绑定了value
的位置也会被一同更新, 数据监听器 也会被正常触发。
自定义组件中传递双向绑定
- 组件内绑定的数据变更时
- 使用组件页面的
this.data.faValue也会同时变更,页面 WXML
中所有绑定了faValue
的位置也会被一同更新
组件内
<view>{{faValue}}</view>
<input type="text" model:value="{{faValue}}"/>
properties:{
faValue:{
type:String,
value:"默认的数据"
}
}
页面
<text-style model:fa-value="{{faValue}}"/>
hidden属性:
- hidden是所有的组件都默认拥有的属性;
- 当hidden属性为true时, 组件会被隐藏;
- 当hidden属性为false时, 组件会显示出来;
hidden和wx:if的区别
- hidden控制隐藏和显示是控制是否添加hidden属性
- wx:if是控制组件是否渲染的
wx:for注意事项
- 默认情况下,item –index的名字是固定的
- 指定item和index的名称:
wx:for-item="item名称" wx:for-index="index名称"
<view class="books">
<block wx:for="{{books}}" wx:key="id" wx:for-item="book" wx:for-index="ii">
<view>{{book.name}}-{{book.price}} - {{ii}}</view>
</block>
</view>
key作用
wx:key 的值以两种形式提供
- 字符串,代表在for 循环的array 中item 的某个property,该 property 的值需要是列表中唯一的字符串或数字,且不能 动态改变。
- 保留关键字*this 代表在for 循环中的item 本身,这种表示需要item 本身是一个唯一的字符串或者数字。
内置组件的两种传参方式
currentTarget和target的区别
- target触发事件的元素
- currentTarget事件处理的元素
data-属性的名称
- 当存在元素嵌套关系时候需要判断当前触发事件的元素和带参元素以及事件处理的元素的对应关系
<view class="arguments" bindtap="onArguments" data-name="XXX" data-age="18">参数传递</view>
onArguments(event){
const {age,name} = event.currentTarget.dataset
console.log(age,name);
},
mark数据绑定
- mark数据绑定 会拿到所有的属性(父子元素)
<view class="mark" bindtap="onMarkTap" mark:name="XXX">
<text class="title" mark:age="18">mark数据绑定 会拿到所有的属性(父子元素)</text>
</view>
//mark数据绑定
onMarkTap(event){
console.log(event);
}
自定义组件的注意事项
- 需要在json文件中进行自定义组件声明(将component 字段设 为true 可这一组文件设为自定义组件):
- 在使用组件的页面需要json文件中进行声明导入(usingComponents字段)
- 自定义组件也是可以引用自定义组件的,引用方法类似于页面引用自定义组件的方式(使用usingComponents字段)
- 自定义组件和页面所在项目根目录名不能以“wx-”为前缀,否则会报错
- 如果在app.json的usingComponents声明某个组件,那么所有页面和组件可以直接使用该组件(全局组件)
组件内样式的细节
- 组件内不能使用id选择器、属性选择器、标签选择器
- 外部使用class的样式,只对外部wxml的class生效,对组件内是不生效的
- 外部使用了标签选择器,会对组件内产生影响
如何让class可以相互影响(组件和页面)
- 在Component对象中,可以传入一个options属性,其中options属性中有一个styleIsolation(隔离)属性
- styleIsolation有三个取值:
- isolated 表示启用样式隔离,在自定义组件内外,使用class 指定的样式将不会相互影响(默认取值);
- apply-shared 表示页面 wxss 样式将影响到自定义组件,但自定义组件 wxss中指定的样式不会影响页面;
- shared 表示页面wxss样式将影响到自定义组件,自定义组件wxss中指定的样式也会影响页面和其他设置了
Component({
options:{
styleIsolation:"apply-shared"
}
})
组件的通信细节
- 组件使用properties属性接收参数
- 可以通过value设置默认值
- 组件向外传递事件–自定义事件
- 组件内
this.triggerEvent("titleclick","传递的参数")
- 页面:
<section-info ;bind:titleclick="titleclick"/>
- 组件内
- 页面调用组件的方法
- 可在父组件里调用this.selectComponent,获取子组件的实例对象
<tab-control titles="{{titles}}" bind:indexChange="indexChange" class="tabControl"></tab-control>
<button bindtap="onExecTab">调用子组件的方法</button>
onExecTab(){
//1.获取组件实例对象
const tabControl = this.selectComponent(".tabControl")
//2.调用实例对象的方法
tabControl.test()
},
向组件传递样式
- 样式在组件内可以固定不变,外部可以决定组件的样式
方法
//-info
Component({
externalClasses:["info"],
})
- 在组件内的wxml中使用externalClasses属性中的class
<view class="contont info">{{content}}</view>
- 在页面中传入对应的class,并且给这个class设置样式
- class的样式是在页面中定义的
- abc可以是任意定义的class
<section-info info="abc"/>
.abc{
background-color: rgb(119, 15, 15);
}
自定义组件的多插槽
- 如果多插槽报错可以尝试更换组件命名解决
- 多插槽需要配置组件的options选项
组件内:
<view class="mu-slot">
<view class="left">
<slot name="left"></slot>
</view>
<view class="center">
<slot name="center"></slot>
</view>
<view class="right">
<slot name="right"></slot>
</view>
</view>
Component({
options:{
//开启多插槽
multipleSlots:true
}
})
页面:
<mu-slot>
<button class="right" size="mini" slot="left">left</button>
<button class="right" size="mini" slot="center">center</button>
<button class="right" size="mini" slot="right">right</button>
</mu-slot>
组件的生命周期
组件一共有两类生命周期
- 组件自身的生命周期
- 指的是组件自身的一些函数,这些函数在特殊的时间点或遇到一些特殊的框架事件时被自动触发
- 在lifetimes里面定义
- 组件所在页面的生命周期
- 在pageLifetimes里面定义
- 执行流程
- 组件被创建created – 组件被添加到组件树中attached-页面显示了show
Component({
lifetimes:{
created(){
console.log("组件被创建created");
},
attached(){
console.log("组件被添加到组件树中attached");
},
detached(){
console.log("组件从组件树中被移除detached");
}
},
pageLifetimes:{
show(){
console.log("页面显示了show");
},
hide(){
console.log("页面隐藏了");
},
resize(){
console.log("页面尺寸发生变化");
}
})
获取位置信息
- wx.getLocation()
- 需要在app.json中配置permission和requiredPrivateInfos才可以调用
"permission": {
"scope.userLocation": {
"desc": "描述使用位置信息的用途"
}
},
"requiredPrivateInfos": [
"getLocation"
],
页面跳转传参
- 两种方式
- 使用URL中的query字段
- events参数
页面返回传参的方式
方式一:获取页面实例
- 获取到上一个页面的实例
- 通过setData给上一个页面设置数据
- 这个操作一般在页面的onUnload生命周期中操作
- 这样可以点击左上角返回按钮的时候也可以执行
onUnload(){
//2.给上一级的页面传递数据
//2.1获取到上一个页面的实例
const pages = getCurrentPages()
const prePage = pages[pages.length-2]
console.log(pages);
//2.2通过setData给上一个页面设置数据
prePage.setData({message:this.data.message})
}
方式二:使用events参数
- 在跳转函数中定义events,并编写回调函数
- 在下一页面中拿到EventChannel函数
- 通过EventChannel的回调函数拿到 返回页面传递的数据
//跳转函数
wx.navigateTo({
url: '/pages/inpage/inde',
events:{
backEvent(data){
console.log(data);
}
}
})
//下一页面
onBack(){
//1.返回导航
wx.navigateBack()
//方式二,回调event回调函数
//1.拿到EventChannel函数
const eventChannel = this.getOpenerEventChannel()
//2.通过EventChannel的回调函数
eventChannel.emit("backEvent",{message:"eventChannel返回传参"})
},
小程序登录流程
- openid和unionid
- unionid是在不同产品之间识别用户身份的id
- 用户多平台共享
- 账号绑定
- 手机号绑定
小程序静默登录
- 调用wx.login方法获取code
- 把code发送给后端服务器后端服务器携带code+appid+appsecret,调用微信接口换取session_key和openid
- 自定义登录状态并与session_key和openid关联生成token,并返回给前端
- 前端拿到token存入本地
//1.获取token,判断token是否有值
const token = wx.getStorageSync("token")
//1.判断是否有token
//1.1判断token是否过期(也是发请求)
if(token&&是否过期){
console.log("请求其它数据");
}else{
this.handleLogin()
}
async handleLogin(){
const code = await getCode()
console.log(code);
//使用code向后端换token
}
//单独定义一个文件 //获取code
export function getCode(){
return new Promise((resolve,reject)=>{
wx.login({
success: (res) => {
//2.把code发给后端换取opid
resolve(res.code)
},
})
})
}