授权登录【难点、重点】
条件编译【理解】
小程序分包【理解】
我的模块其实是两个组件,一个是登录组件,一个是用户信息组件,根据用户的登录状态判断是否要显示那个组件
为登录绑定单击事件,调用getUserProfile方法,获取用户基本信息
在 methods
节点中声明getUserProfile
事件处理函数如下
methods: {
getUserProfile(){
uni.getUserProfile({
desc:'用户完善会员信息',
success(res) {
console.log('res',res.userInfo);
}
})
}
}
在 store/user.js
模块的 state 节点中,声明 userinfo
的信息对象如下:
state:{
userinfo:JSON.parse(uni.getStorageSync('userinfo')||'{}')
}
在 store/user.js
模块的 mutations 节点中声明updateUserInfo()
mutations:{
updateUserInfo(state,userinfo){
state.userinfo=userinfo
uni.setStorageSync('userinfo',JSON.stringify(userinfo))
}
}
修改methods节点中的getUserProfile
方法,添加调用调用Mutations中的updateUserInfo
实现保存数据到vuex中
import {createNamespacedHelpers} from 'vuex'
const {mapMutations}=createNamespacedHelpers('user')
export default {
methods: {
...mapMutations(['updateUserInfo']),
getUserProfile(){
uni.getUserProfile({
desc:'用户完善会员信息',
success:(res) =>{
//将用户基本信息存储到vuex中
this.updateUserInfo(res.userInfo)
//调用getCode()方法完成code生成
this.getToken(res)
}
})
}
}
}
执行如上代码,用户基本信息已经存储到了本地,如下图所示
当用户同意授权后,我们可以调用 uni.login()
方法来获取 code
async getToken(info){
let [err,res]=await uni.login()
const query = {
code: res.code,
userInfo:this.userinfo,
appId: 'wx2b9aa7f72b4f34e2',
appSecret: '29c37378cf09ecf6fc44466bf5612ecd'
}
uni.request({
method:"POST",
url:'http://47.98.128.191:3001/users/wxLogin',
data:query,
success: (res) => {
console.log(res.data.token);
}
})
}
参数说明:
code:必传参数,后端需要通过 code 去获取用户的 openid;
userInfo:非必选参数,需要根据实际项目需求,看后端是否需要用户信息;
appId 和 appSecret:学习阶段需要传递,实际项目中不需要传递,这两个值会在后端直接设置好;
appId 和 appSecret 在“微信公众平台”中获取
在user.js
模块中state和mutations中分别定义token以及updateToken方法
export default{
namespaced:true,
state:{
token:uni.getStorageSync('token')||''
},
mutations:{
updateToken(state,token){
state.token=token
uni.setStorageSync('token',token)
}
}
}
在页面组件中通过mapMutations将updateToken方法进行映射
import {createNamespacedHelpers} from 'vuex'
const {mapMutations,mapState}=createNamespacedHelpers('user')
export default{
methods: {
...mapMutations(['updateUserInfo','updateToken']),
async getToken(info){
let [err,res]=await uni.login()
const query = {
code: res.code,
userInfo:this.userinfo,
appId: 'wx2b9aa7f72b4f34e2',
appSecret: '29c37378cf09ecf6fc44466bf5612ecd'
}
uni.request({
method:"POST",
url:'http://47.98.128.191:3001/users/wxLogin',
data:query,
success: (res) => {
console.log(res.data.token);
//保存token到vuex中
this.updateToken(res.data.token)
}
})
}
}
}
在components/userinfo/userinfo组件中完成用户的退出登录功能
import {createNamespacedHelpers} from 'vuex'
const {mapMutations}=createNamespacedHelpers('user')
export default {
name:"userinfo",
methods:{
...mapMutations(['updateToken','updateUserInfo']),
async logout(){
const [err, succ] = await uni.showModal({
title: '提示',
content: '确认退出登录吗?'
}).catch(err => err)
if (succ && succ.confirm) {
this.updateToken('')
this.updateUserInfo({})
}
}
}
]}
之前的操作只是判断token有或者无,但是并没有完成token是否有效的判断,下面我们可以完成token是否有效的判断,具体步骤如下
首先在user模块的状态机文件中编写actions的方法getUserInfoAsync来判断用户的token是否有效,如果无效直接跳到登录页面
export default{
namespaced:true,
state:{},
mutations:{},
//完成的是异步操作
actions:{
/*
vuex中的actions选项中的方法是异步方法
该方法的参数有两个
第1个参数是vuex状态机的上下文
第2个参数是payload是参数
*/
getUserInfoAsync(context,payload){
//向后端接口发送请求
console.log('token信息',context.state.token);
uni.request({
url:'http://47.98.128.191:3001/users/getUserInfo',
header:{
'Authorization':context.state.token
},
success: (res) => {
console.log('-----------成功----------');
console.log('res',res.data.code);
if(res.data.code==0){
console.log('999999999999999999');
uni.showToast({
title:'token已经过期请重新登录',
success() {
//调用mutations中的updateUserInfo和updateToken方法
context.commit('updateUserInfo',{})
context.commit('updateToken','')
//跳转到我的页面中来
uni.switchTab({
url:'/pages/mine/index'
})
}
})
}
},
fail: (err) => {
console.log('-----------失败----------');
}
})
}
}
}
在需要认证的页面组件的onShow方法中调用状态机中的这个方法
比如mine模块中和购物车模块中有需要,则在onShow方法中添加
import {createNamespacedHelpers} from 'vuex'
const {mapActions:mapUserActions}=createNamespacedHelpers('user')
export default {
methods: {
...mapUserActions(['getUserInfoAsync']),
},
//当页面组件显示在前台的时候调用
onShow() {
console.log('-------购物车模块的onShow---------');
this.getUserInfoAsync()
}
}
注意:在HBuilder中生成条件编译的代码的快捷键是CTRL+ALT+/
uniapp已将常用的组件、JS API 封装到框架中,开发者按照 uni-app 规范开发即可保证多平台兼容,大部分业务均可直接满足。
但每个平台有自己的一些特性,因此会存在一些无法跨平台的情况。
大量写 if else,会造成代码执行性能低下和管理混乱。
编译到不同的工程后二次修改,会让后续升级变的很麻烦。
在 C 语言中,通过 #ifdef、#ifndef 的方式,为 windows、mac 等不同 os 编译不同的代码。 uni-app 参考这个思路,为 uni-app 提供了条件编译手段,在一个工程里优雅的完成了平台个性化实现。
条件编译是用特殊的注释作为标记,在编译时根据这些特殊的注释,将注释里面的代码编译到不同平台。
写法:以 #ifdef 或 #ifndef 加 %PLATFORM% 开头,以 #endif 结尾。
#ifdef:if defined 仅在某平台存在
#ifndef:if not defined 除了某平台均存在
%PLATFORM%:平台名称
条件编译写法 | 说明 |
---|---|
#ifdef APP-PLUS 需条件编译的代码 #endif | 仅出现在 App 平台下的代码 |
#ifndef H5 需条件编译的代码 #endif | 除了 H5 平台,其它平台均存在的代码 |
#ifdef H5 || MP-WEIXIN 需条件编译的代码 #endif | 在 H5 平台或微信小程序平台存在的代码(这里只有||,不可能出现&&,因为没有交集) |
%PLATFORM% 可取值如下:
值 | 平台 |
---|---|
APP-PLUS | App |
APP-PLUS-NVUE | App nvue |
H5 | H5 |
MP-WEIXIN | 微信小程序 |
MP-ALIPAY | 支付宝小程序 |
MP-BAIDU | 百度小程序 |
MP-TOUTIAO | 字节跳动小程序 |
MP-QQ | QQ小程序 |
MP | 微信小程序/支付宝小程序/百度小程序/字节跳动小程序/QQ小程序 |
支持的文件
.vue
.js
.css
pages.json
各预编译语言文件,如:.scss、.less、.stylus、.ts、.pug
// #ifdef %PLATFORM%
平台特有的API实现
// #endif
示例,如下代码仅在 App 下出现:
//#ifdef APP_PLUS
plus.push.addEventListener('click',function(msg){
var payload = null
var action = '';
if(msg.payload){
if(typeof msg.payload === 'string'){
payload = JSON.parse(msg.payload)
}
action = payload.action;
if(action === 'open'){
plus.webview.open(payload.url)
}
}
})
//#endif
示例,如下代码不会在 H5 平台上出现:
//#ifndef H5
uni.scanCode({
success:(res)=>{
console.log(res.result);
}
});
//#endif
除了支持单个平台的条件编译外,还支持多平台同时编译,使用 || 来分隔平台名称。
示例,如下代码会在 App 和 H5 平台上出现:
//#ifdef APP-PLUS || H5
uni.chooseVideo({
success:(res)=>{
console.log(res.result);
}
});
//#endif
平台特有的组件
示例,如下广告组件仅会在微信小程序中出现:
/*#ifdef %PLATFORM% */
平台特有样式
/* #endif */
注意: 样式的条件编译,无论是 css 还是 sass/scss/less/stylus 等预编译语言中,必须使用 /注释/ 的写法。
正确写法
/* #ifdef MP-WEIXIN */
.wx-color{
color:#fff000;
}
/* #endif */
错误写法
// #ifdef MP-WEIXIN
.wx-color{
color:#fff000
}
// #endif
下面的页面,只有运行至 App 时才会编译进去。
// #ifdef APP-PLUS
{
"path":"pages/api/speech/speech",
"style":{
"navigationBarTitleText":"语音识别"
}
}
//#endif
不同平台下的特有功能,以及小程序平台的分包,都可以通过 pages.json 的条件编译来更好地实现。这样,就不会在其它平台产生多余的资源,进而减小包体积。
json的条件编译,如不同平台的key名称相同,cli项目下开发者自己安装的校验器会报错,需自行关闭这些校验器对json相同key的校验规则。如果使用HBuilderX的校验器,无需在意此问题,HBuilderX的语法校验器为此优化过
在不同平台,引用的静态资源可能也存在差异,通过 static 的的条件编译可以解决此问题,static 目录下新建不同平台的专有目录(目录名称同 %PLATFORM% 值域,但字母均为小写),专有目录下的静态资源只有在特定平台才会编译进去。
如以下目录结构,a.png 只有在微信小程序平台才会编译进去,b.png 在所有平台都会被编译。
┌─static
│ ├─mp-weixin
│ │ └─a.png
│ └─b.png
├─main.js
├─App.vue
├─manifest.json
└─pages.json
分包是指将一个完整的小程序项目,按照需求划分成不同的子包,在构建时打包成不同的分包,用户在使用时按需进行加载。
可以优化小程序首次启动的下载时间
分包前,小程序项目中所有的页面和资源都被打包到了一起,导致真个项目体积过大,影响小程序首次启动的下载时间
分包后,小程序项目由1个主包+多个分包组成
主包:一般只包含项目的启动页面或Tabbar页面,以及所有分包都需要用到的一些公共资源
分包:只包含和当前分包有关的页面和私有资源
在多团队共同开发时可以更好的解耦协作
在小程序启动时,默认会下载主包并启动主包内页面
tabBar页面需要放在主包中
当用户进入分包内某个页面时,客户端会把对应分包下载下来,下载完成后再进行展示
非tabBar页面可以按照功能的不同,划分为不同的分包之后,进行按需下载
目前,小程序分包的大小有以下两个限制
整个小程序所有分包大小不超过16M
(主包+所有分包)
单个分包/主包大小不能超过2M
在pages.json中配置subPackages实现分包,下面对象的常见属性
root:分包的根目录
name:分包的别名
pages:分包目录下,页面的相对存放路径
"subPackages": [
{
"root": "productPkg",
"name": "product",
"pages": [
"pages/product/product-list",
"pages/product/product-detail"
]
},
{
"root": "payPkg",
"name": "pay",
"pages": [
"pages/pay/apy-address",
"pages/pay/apy-detail"
]
}
],
主包无法引用分包内的私有资源
分包之间不能相互引用私有资源
分包可以引用主包内的公共资源
独立分包本质上也是分包,只不过它比较特殊,可以独立于主包和其他分包而单独运行。
最主要的区别:是否依赖于主包才能运行
普通分包必须依赖于主包才能运行
独立分包可以在不下载主包的情况下,独立运行
当小程序从普通的分包页面启动时,需要首先下载主包
而独立分包不依赖主包即可运行,可以很大程度上提升分包页面的启动速度
"subPackages": [
{
"root": "productPkg",
"name": "product",
"pages": [
"pages/product/product-list",
"pages/product/product-detail"
]
},
{
"root": "payPkg",
"name": "pay",
"pages": [
"pages/pay/apy-address",
"pages/pay/apy-detail"
],
"independent":true
}
],
独立分包和普通分包以及主包之间,是相互隔绝的,不能相互引用彼此的资源!例如:
主包无法引用独立分包内的私有资源
独立分包之间,不能相互引用私有资源
独立分包和普通分包之间,不能相互引用私有资源
特别注意:独立分包中不能引用主包内的公共资源
分包预下载指的是:在进入小程序的某个页面时,由框架自动预下载可能需要的分包,从而提升进入后续分包页面时的启动速度。
预下载分包的行为,会在进入指定的页面时触发。在 app.json 中,使用 preloadRule 节点定义分包的预下载 规则,示例代码如下:
"preloadRule": {
"pages/contact/contact": {
"packages": [
"p1"
],
"network": "wifi"
}
},