微信小程序 【关键部分】

1. 动机

最近在开发小程序,小程序既需兼顾针对新用户的内容预览,又要为注册用户提供服务,简单梳理下,基本需求如下:

  1. 小程序共三个tab页,所有用户都可以浏览首页内容,了解我们可以提供的优质服务;
  2. 进入其他两个页面之后,如果用户没有登录,那就显示登录按钮,如果登录了,则显示服务内容;
  3. 用户在一个页面登陆之后,全局生效。

就这么个看起来很简单的需求,也经过了如下迭代:

  1. 将登录状态和凭据存储在 App.globalData.authorize 中,每个需要授权的页面 onload 生命周期检查 App.globalData.authorize.authorized ,为 true 时渲染服务内容,为 false 则显示登录按钮;
  2. 但如果打开了需要授权的页面 A 但是没有登录,再打开页面 B 登录,这时候回到 A 页面,登录按钮赫然在眼,这是因为 A 页面的 onload 回调函数只执行了一次;
  3. 为了能在 A 页面及时共享 B 页面登录后的状态,在 A 页面的 onshow 生命周期里再获取了一次登录状态,但这样一来,打开 A 页面的时候,会出现短暂的白屏,用户甚至有可能看到按钮变成服务内容的整个过程。

翻遍小程序 API 文档 ,也没有发现用于监听登录的生命周期,就算有也用不了,因为我们有着自己的账号体系,服务端认证完毕才算真正的登录成功。

所以我决定自己包装原有的 Page 函数,添加一个 onauth 生命周期------

2. 事件总线

首先是自定义登录事件的触发与监听,官方的 EventChannel 需要向后兼容,横竖是个订阅回调,那我还不如自己撸一个得了:

复制代码
/**
 * @file utils/event.js
 */

/**
 * @const EMPTY_HANDLER
 * @desc 空事件回调,被取消事件将被指向此函数
 */
const EMPTY_HANDLER = () => {};

/**
 * @const eventSet - 事件监听函数集
 */
const eventSet = {
  authorize: []
};

/**
 * @function emit - 发送全局事件
 * @param {String} type - 事件类型
 * @param {Object} event - 事件对象
 */
export const emit = (type, event) => (eventSet[type] || []).forEach(item => item(Object.freeze(event)));

/**
 * @function on - 注册全局事件
 * @param {String} type - 事件类型
 * @param {Function} callback - 事件回调函数
 */
export const on = (type, callback) => {
  if (!eventSet[type]) {
    eventSet[type] = [];
  }

  if (!callback instanceof Function) {
    throw new Error('callback must be a Function!');
  }

  return eventSet[type].push(callback)
};

/**
 * @function off - 取消对某事件的监听
 * @param {String} type - 事件类型 
 * @param {Number} id - 需要取消的事件ID,即 registEvent 所返回的值
 */
export const off = (type, id) => {
  if (!eventSet[type]) return

  eventSet[type][id - 1] = EMPTY_HANDLER

  // 如果某类事件已经全被取消的话,将其置为空数组
  const noListener = !eventSet[type].reduce((pre, cur) => (cur && cur === EMPTY_HANDLER) || pre, false);
  if (noListener){
    eventSet[type] = []
  };
}

有关订阅-回调的知识请自行百度,简而言之就是一个杂志亭,订阅者订阅了某款杂志,当发布者发布该款杂志的时候,杂志亭就将杂志送到订阅者手里,双方需要约定的就是杂志名称一致,也就是 on方法和 emit 方法的第一个参数。

3. 定义AuthPage

然后是对 Page 函数的魔改:

复制代码
/**
 * @file utils/auth-page.js
 */

import { on } from '/event.js';

export const AuthPage = function(options){
  const { onAuth, data, onLoad } = options;
  const userInfo = {
    nickName: '',  // 昵称
    account: '',  // 账号
    avatar: {  // 头像
      small: '',
      middle: '',
      large: ''
    },
    title: 'student',  // 头衔
    phoneNumber: 0,  // 电话号码
    gender: 'secret',  // 性别
    'class': ''  // 班级
  }

  if (options.data){
    options.data.authorized = false;
    options.data.userInfo = userInfo
  } else {
    options.data = {
      authorized: false,
      userInfo: userInfo
    }
  }

  /**
   * 仍旧调用原始的 Page 方法
   */
  Page(Object.assign(
    options,
    {
      onLoad: function(...arg) {
        const { authorize, userInfo } = getApp().globalData;

        // 执行开发者期望的 onload 事件
        onLoad instanceof Function && onLoad.bind(this)(...arg);

        // 页面初始化时,若已经授权,直接执行授权回调
        // 否则将授权回调注册为授权事件回调
        if (onAuth instanceof Function){
          if (authorize.authorized){
            onAuth.bind(this)({
              type: 'authorize',
              authorized: true,
              token: authorize.token,
              userInfo: userInfo
            });
          } else {
            on('authorize', onAuth.bind(this));
          }
        }
      }
    }
  ));
}

4. 触发登录事件

自定义的 onAuth 生命周期里订阅了一个 authorize 事件,登录之后需要发布相应的事件来触发其回调,触发的函数需要写在登录组件里:

复制代码
import { emit } from '../../utils/event.js';

wx.login({
    success: res => {
        // ...这里省略了一些复杂的登录流程
        getApp().globalData.authorize = {
            authorized: true
        };
        emit('authorize', res);
    }
})

然后,在两个需要登录的 tab 页引入 AuthPage 替换原有的 Page 函数,并在配置项里写 onAuth 回调,就可以监听登录事件了。


补充:

5.适用范围与最佳实践

5.1 适用范围
  1. 部分页面有权限控制,未登录用户与登录用户可以查看的内容不一样,或者不同级别的用户看到的内容不一样;
  2. 除了触发登录事件的页面,其他页面的权限修改过程不能直接呈现给用户,如果没有这个限制的话,判断登录这事可以在 onShow 函数里完成;
  3. 登录机制仅作为一个组件嵌入需要登录入口的所有页面,而不是单独做一个登录页,因为跳转页面这个操作,肯定是直接呈现给用户的了;
  4. AuthPage 不会过多,且切换权限的时候不会有非常耗时的操作。
5.2 最佳实践
复制代码
    // 页面 A
    AuthPage({
        onLoad: function(){
            const { authorized } = getApp().globalData.authorize;
            let showText = '你还没有登录';
            if(authorized){
                showText = '你已经登陆了';
            }
            this.setState({ showText});
        },
        onAuth: function(){
            this.setState({
                showText: '你已经登陆了'
            })
        }
    });
    
    // 页面 B 
    AuthPage({
        onLoad: function(){
            const { authorized } = getApp().globalData.authorize;
            
            if(authorized){
                this.setState({
                    showLogin: false,  // 隐藏登录组件
                    showContent: true  // 展示内容组件
                })
            } else {
                this.setState({
                    showLogin: true,  // 展示登录组件
                    showContent: false  // 隐藏内容组件
                })
            }
        },
        onAuth: function(option){
            this.setState({
                showLogin: false,  // 隐藏登录组件
                showContent: true  // 展示内容组件
            })
        }
    });

这样写的好处就是:用户未登陆的时候,也可以看到 A 页面的一些内容,使得用户可以初步了解应用内容;用户在 B 页面则会看到登录入口;登录之后 B 页面的登陆组件就被隐藏,取代的是应用内容,而此时 A 页面其实已经在后台悄悄更新了,用户对此是毫无感知的(除非后台更新的页面内容过多导致页面卡顿)。

6 总结

由于先前没有给定用例,需要理解整个流程才能上手编码,导致"懂的人不屑用,不懂的人不会用"的尴尬,所以亡羊补牢,增补了使用范围和用例。

仅供参考!!!

相关推荐
崔庆才丨静觅4 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60615 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了5 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅5 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅5 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅6 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment6 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅6 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊6 小时前
jwt介绍
前端
爱敲代码的小鱼6 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax