Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

小程序开发记录 #19

Open
chiwent opened this issue Dec 29, 2019 · 1 comment
Open

小程序开发记录 #19

chiwent opened this issue Dec 29, 2019 · 1 comment

Comments

@chiwent
Copy link
Owner

chiwent commented Dec 29, 2019

小程序开发记录

持续更新中...

微信体系的一些基本概念

  • 临时code:用户登录凭证,有效期5分钟,可以通过wx.login获取。可以在开发者服务器后台调用auth.code2Session,上送临时code获取openid和session_key等信息。
  • openid:是微信号与公众平台APPID加密后得到的用户唯一标识,可以按那样理解:openid = hash(uid + app_id)
    在未关注公众号时,访问公众号网页也会产生一个用户和对应的公众号唯一openid。它可以用来标识用户身份
  • unionid:是微信号与开放平台APPID加密后得到的用户唯一标识,前提是各个公众平台需要先绑定到同一个开放平台,才能从各平台中获取到同一个unionid。可以这样理解:unionid = hash(uid + 开放平台id)

    unionid可以看作是各个openid的集合,如果开发者拥有多个公众号或小程序,那么用户在各个公众号或小程序中都有一个openid,如果要在多个产品中进行账号互通,可以使用unionid来实现。只要在同一个微信开放平台账号下的公众号,用户的unionid就是唯一的。

关于unionid机制下如何获取用户基本信息,可以参考官方文档:获取用户基本信息(UnionID机制)UnionID 机制说明

微信小程序的登录流程:

image

微信授权和开发功能

  • 微信用户登录: wx.login,该api可以获取用户的临时code,配合后台code2Session可以拿到openid
  • 微信用户信息获取:wx.getUserInfo,该api可以获取用户的信息,如昵称、头像等。注意参数withCredentials要设置为true才可以获取加密数据encryptedData和加密算法的初始向量iv
  • 检查登录状态:wx.checkSession,通过wx.login + cide2Session可以生成临时code和session_key(会话密钥),会话密钥是检测用户登录状态的标志,在success回调方法是未过期的回调,fail是过期的回调。
    注意:为了维护系统的安全,临时code可以返回给前端,但是session_key是不可以对外的

关于如何获取openid和unionid

openid的获取方式有两种:

  • 1.通过wx.login获取临时code,将code、appid、secret_key上送到后台获取openid,不过,appid和secret_key是不允许在前后端交互的,所以,一般和后端开发协商后,前端只上送临时code。
  • 2.通过调用wx.getUserInfo获取微信返回的关键参数,再上送到后台解密获取openid。不需要调用wx.login???

unionid的获取机制可以参考上一节提到的官方文档,下面做一些简要补充。

一般来说,获取unionid可以有两种方式来实现:

  • 1.通过wx.login + wx.getUserInfo获取unionid,通过调用Wx.login获取临时code,调用wx.getUserInfo获取encryptedData和iv,接着将这三个参数上送给后端处理,可以得到openid和unionid。为什么和前面openid不同,还需要调用wx.login???
  • 2.用户登录过关联的其他公众号或小程序
    前面提到过,同一个开发平台下注册的公众号和小程序的单个用户的unionid是一样的,所以,假如用户已经在其他同主体的公众号或小程序登录了,那么可以直接通过wx.login + code2Session方式获取该用户的unionid

用户在首次加载小程序后完成授权,后续再次进入小程序就是静默授权(保有缓存),不会有弹窗授权

开发者如何正确调用授权

目前,在用户首次授权时,微信强制开发者使用调出按钮的方式来引导用户授权。所以,正确的授权方式应该是在页面中定义一个授权弹窗,弹窗上的授权按钮是带有授权功能的,点击后即可完成授权:

授权窗口

<view class="authorize" hidden="{{isShowAuth}}">
    <view class="box">
      <view class="title">授权登录</view>
      <view class="content">是否授权获取用户信息</view>
      <view class="footer">
        <button bindtap="cancelAuthroize">取消</button>
        <button open-type="getUserInfo" bindgetuserinfo="getAuthorize">确定</button>
      </view>
    </view>
  </view> 
isHiddenAuth: false

getAuthorize() {
    if (this.data.isShowAuth) {
        wx.getSetting({
            success: res => {
                if (res.authSetting['scope.userInfo']) {
                    // 已经授权,可以直接调用getUserInfo静默授权
                    this.getUserInfoFromServer();
                    this.setData({
                        isShowAuth: false
                    });
                } else {
                    // 还未授权,调出弹窗引导用户授权
                    this.setData({
                        isShowAuth: true
                    });
                }
            }
        })
    } else {
        // 如果已经授权就直接登录,在这里可以保存用户信息
    }
}
getUserInfoFromServer() {
    wx.getUserInfo({
        withCredentials: true,
        success: async res => {
            // 向后端发出请求,获取openid和unionid
            const data = await request({
                encryptedData: res.encryptedData,
                iv: res.iv
            });
            // 保存用户信息
            app.globalData.userInfo = data;
        }
    });
},
cancelAuthroize() {
    this.setData({
        isShowAuth: false,

    });
    app.globalData.userInfo = null;
}

微信小程序生命周期

微信小程序的生命周期可以大致分为3类,App入口的生命周期,页面的生命周期以及组件的生命周期。

  • App()
    App函数是整个小程序的入口,它的生命周期如下:
  • onLaunch:监听小程序初始化,在注册时执行,只在最开始的时候执行一次
  • onShow:监听小程序显示,在启动或者从后台进入前台展示时执行
  • onHide:监听小程序隐藏,在切换到后台时执行
  • onError:监听全局错误,在代码执行出错时执行
  • onPageNotFound:页面不存在时触发

关于onLaunch和onShow的说明:

小程序的启动方式有两种:冷启动和热启动,在用户首次打开小程序的时候,小程序的启动方式就是冷启动,此时会触发onLaunch和onShow回调;当用户将该小程序切至后台,但是并没有被销毁,然后再次打开,小程序的加载时间一般会首次打开的时间更短,此时不会触发onLaunch,但是会触发onShow

  • Page()

  • onLoad:监听页面加载
    适用场景:页面加载时的初始化操作、获取路由参数。一个页面只会调用一次

  • onShow:监听页面显示
    适用场景:页面显示时的操作。每次页面打开时都会调用一次

  • onReady:监听页面初次渲染完成
    适用场景:页面已经准备妥当,可以和view层进行交互、对页面进行设置(比如设置标题)。一个页面只会调用一次

  • onHide:监听页面隐藏
    适用场景:路由跳转或者底部导航切换时调用、退出页面时关闭一些操作(比如定时器)

  • onUnload:监听页面卸载
    适用场景:底部导航切换时不会触发,路由跳转会触发,退出页面时关闭一些操作(比如定时器)

  • Component()
    组件生命周期可以分为两类:组件内自身的生命周期(在lifetimes属性中定义)和组件在引用页的生命周期(在pageLifetimes)中定义

1.lifetimes:

  • created:组件刚刚完成实例化,但是节点树还未导入,不可以setData。所以在这个阶段只能给组件的this指针添加一些自定义属性
  • attached:节点树构建完成,可以setData,但是还无法操作节点,无法使用getCurrentPages。在这个阶段可以完成一些数据初始化工作
  • ready:组件布局完成,此时可以获取节点信息,也可以操作节点。
  • moved:微信官方的解释是“在组件实例被移动到节点树另一个位置时执行”,在组件重排序的时候会执行,比如<component-a wx:for="{{array}}" wx:key="{{item.key}}"></component-a>里面的顺序修改
  • detached:组件实例被移除时执行。在这个阶段,可以做一些销毁收尾的操作。
  • error:组件方法报错时执行

2.pageLifetimes:

  • show:组件在引用的页面展示时执行
  • hide:组件在引用的页面隐藏时执行
  • resize:组件在引用的页面尺寸改变时执行

请求Promise化

var wxPromise = function (fn) {
    return function (opt = {}) {
        return new Promise((resolve, reject) => {
            opt.success = function(res) {
                resolve(res);
            };
            opt.fail = function(res) {
                reject(res);
            }
            fn(opt);
        })
    }
}


// 使用
var wxRequest = wxPromise(wx.request);
wxRequest({ url, param }).then(res => {
    console.log(res.data);
});

监控或埋点

拦截请求

在web开发中,我们可以对原生ajax方法或者axios拦截器做一些修改,来实现网络请求的监听。如下:

window.XMLHttpRequest.prototype.open = (originMethod => {
    return function (method, url, async) {
        console.log('发送请求,接口是:', url);
        return originMethod.apply(this, arguments);
    };
})(window.XMLHttpRequest.prototype.open);

在微信小程序中,我们同样可以通过拦截wx.request方法来实现请求的监听。不过,和上面的方式不一样,我们不能直接对wx.request进行赋值来改写,它只有get方法而没有set方法。所以,我们要用Object.defineProperty来修改:

const originRequest = wx.request;
Object.defineProperty(wx, 'request', {
    configurable: true,
    enumerable: true,
    writable: true,
    valud: function() {
        const config = arguments[0] || {};
        const url = config.url;
        console.log('发送请求,接口是:', url);
        return originRequest.apply(this, arguments);
    }
});

使用发布订阅模型来进行全局事件通信

function PubSub() {
    // 订阅列表
    this.eventList = null;
}
// 订阅事件
PubSub.prototype.subscribe = function(eventName, func) {
    this.initEventList(eventName);
    this.eventList[eventName].push(func);
};
// 移除订阅
PubSub.prototype.unsubscribe = function(eventName,  func) {
    this.initEventlist(eventName);
    this.eventList[eventName] = this.eventList[eventName].filter(function (item) {
        return item != func;
    });
    if (!this.eventList[eventName].length) {
        delete this.eventList[eventName];
    }
};
// 发布事件
PubSub.prototype.publish = function(eventName, data) {
    this.initEventList();
    if (this.eventName[eventName]) {
        for (var i = 0, event = this.eventList[eventName]; i < eventList.length; i += 1) {
            var func = event[i];
            func(data);
        }
    }
};
// 初始化事件列表
PubSub.prototype.initEventList = function(eventName) {
    if (!this.eventList) {
        this.eventList = {};
    }
    if (eventName && !this.eventList[eventName]) {
        this.eventList[eventName] = [];
    }
};

module.exports = PubSub;

使用:

var PubSub = require('../utils/pubSub');
App({
  onLaunch: function(e) {
    //  注册发布订阅模式
    event: new PubSub(),
  }
});

// 页面A - 发布
var app = getApp()
Page({
    publish: function() {
        app.event.publish('test',  'test')
    },
})

// 页面B - 订阅
var app = getApp()
Page({
    onLoad: function(){
        app.event.subscribe('test', this.subFunc.bind(this))
    },
    onUnload: function() {
       // 移除所有订阅事件
       app.event.unsubscribe('test');
       // 移除单个订阅事件
        app.event.unsubscribe('test', this.subFunc.bind(this))
    },
    subFunc: function(param) {
        // 监听事件
       console.log(param)
    },
})

小程序设置缓存有效期

微信小程序通过自带API设置的客户端缓存是不存在有效期的概念的,长期有效,如果需要设置带有效期的缓存,就需要同时对一个目标同时设置目标本身的数据,以及对应自身的设置时间:

// 基本原理:通过微信官方的API分别对数据进行两份缓存,一份是原数据的缓存,另外一份是对应的时间进行缓存,时间缓存中带有原数据的key值
class Storage {
    constructor(props) {
        this.props = props || {};
        this.source = wx || this.props.source;
    }
    /**
     * 获取缓存
     * @param {String} key key值
     * @return 缓存内容
     */
    getStorage(key) {
        const timeout = parseInt(this.source.getStorageSync(`${key}__time`) || 0, 10);
        // 有对应的时间缓存
        if (timeout) {
            // 如果超出时间
            if (Date.now() > timeout) {
                this.removeStorage(key);
                return;
            }
        }
        const value = this.source.getStorageSync(key);
        return value;
    }
    /**
     * 设置缓存
     * @param {String} key key值
     * @param value value值
     * @param {Number} timeout 过期时间
     * @return value
     */
    setStorage(key, value, timeout = 0) {
        const _timeout = parseInt(timeout, 10);
        this.source.setStorageSync(key, value);
        if (_timeout) {
            this.source.setStorageSync(`${key}__time`, Date.now() + 1000 * 60 * _timeout);
        } else {
            this.source.removeStorageSync(`${key}__time`);
        }
        return value;
    }
    removeStorageSync(key) {
        this.source.removeStorageSync(key);
        this.source.removeStorageSync(`${key}__time`);
        return undefined;
    }
}
const storage = new Storage();
wx.$storage = storage;
export default storage;




参考:

@chiwent
Copy link
Owner Author

chiwent commented May 20, 2020

小程序优化

一些在前端开发中通用的优化策略就不谈了,主要是针对小程序这种特殊情况的。

分包加载

小程序主包的大小限制为2M,超过该大小后无法上传。所以,对于一些较为独立的业务功能,应该考虑分包开发,分包的文件不会占用主包的空间。

不要滥用setData

通过setData设置的数据,是小程序数据层和视图层的中间介质。微信开发者文档上有这么一句话:

  小程序的视图层目前使用 WebView 作为渲染载体,而逻辑层是由独立的 JavascriptCore 作为运行环境。在架构上,WebView 和 JavascriptCore 都是独立的模块,并不具备数据直接共享的通道。当前,视图层和逻辑层的数据传输,实际上通过两边提供的 evaluateJavascript 所实现。即用户传输的数据,需要将其转换为字符串形式传递,同时把转换后的数据内容拼接成一份 JS 脚本,再通过执行 JS 脚本的形式传递到两边独立环境。
而 evaluateJavascript 的执行会受很多方面的影响,数据到达视图层并不是实时的。

大量数据、频繁地setData,会增加数据层和视图层通信的压力,影响小程序的性能。所以,对于setData的操作,我们应该要有以下的基本准则:

  • 优先考虑减少setData的频率,合并多个setData的操作
  • 如果setData的数据目标是一个复杂对象内的属性,应该考虑设置需要改变的最小单位数据
  • 与页面渲染无关的数据,不要放在data中(直接绑定在this,或者使用纯数据字段

官方文档上也提供了一些关于setData的优化tips,可以参考:
https://developers.weixin.qq.com/miniprogram/dev/framework/performance/tips.html

补充说明:
不要用setData将data值设置为undefined,否则可能会出现一些问题(控制台也会警告)

前面提到了数据层和视图层的通信问题,实际上可以展开为,不仅仅是setData,一切涉及到此类通信的问题都需要谨慎考虑数据的大小和设置频率。比如,wxml中的dataset属性,最好也是不要绑定数据大的复杂对象;减少事件绑定......

做好防抖和节流

对于频繁触发的事件,要做好防抖或节流的控制,比如在onPageScroll中,每次滚动都会触发该事件,如果在里面直接进行setData,是一件恐怖的事。

用好wxs

wxs的执行效率比js高,所以data中的一些数据操作可以考虑在wxs中处理。





参考:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant