前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >微信小程序----全局状态管理 (便于全局埋点等操作)

微信小程序----全局状态管理 (便于全局埋点等操作)

作者头像
Rattenking
发布2021-02-01 11:03:33
2.9K0
发布2021-02-01 11:03:33
举报
文章被收录于专栏:Rattenking

说明

由于我是一个有着一颗玻璃心的博主,导致在2018年后博客很少更新。原因是由于我的分享并没有解决到部分人的问题,而导致被骂了。当时这颗玻璃心就碎了,所以这两年以来很是消极,博客很少更新。这里给那些关注我,支持我的朋友说声【对不起】!前段时间,看了一个工作两年时间博主的 2021 年 flag,突然回首,还有很多记忆。所以,我决定以后每周最少一篇博客,记录我的学习和成长。谢谢!

需求场景

小程序开发完成,接到需求:需要对小程序的所有页面【onLoad】生命周期进行埋点,对页面中的点击事件进行埋点。

需求分析

  1. 全部页面生命周期和点击事件的埋点,埋点多;
  2. 每个页面引入埋点文件,不利于后期维护。

需求解决

  1. 解决多页面生命周期埋点----重写页面生命周期: 1.1 重写 Page 对象的传入对象,也就是【微信小程序之页面拦截器】的方法; 1.2 重写 Page 对象本身,就是【 微信小程序–页面劫持】的方法;
  2. 解决多页面引入重写文件的方法: 2.1 重写 Page 对象本身,或者重写 App.Page 对象,方案:【 微信小程序全局状态管理库(wxMiniStore)

1. 方案1:劫持 Page 的传入对象

1.1 hijack_page_object.js 代码
代码语言:javascript
复制
/**
 * hijack_page_object 页面对象劫持
 * options 对象传入参数
*/
const hijack_page_object = (options = {}) => {
  const { onLoad, onUnload } = options;
  options = {
    ...options,
    collectClick(opts){
      // 页面点击埋点
      console.log('页面点击埋点')
      // 点击埋点逻辑
    },
    collectPage(opts){
      // 页面生命周期埋点
      console.log('页面生命周期埋点')
      // 生命周期埋点逻辑
    },
    jumpNextPage(url){
      // 全局页面跳转方法
      wx.navigateTo({url})
      // 埋点跳转点击
      this.collectClick({})
    },
    onLoad(opts){
      onLoad && onLoad.call(this, opts)
      console.log('全局页面生命周期!')
      // 埋点
      this.collectPage({
        "lifeCycle": "onLoad",
        "loadTime": +new Date()
      })
    },
    onUnload(){
      onUnload && onUnload.call(this)
      // 埋点
      this.collectPage({
        "lifeCycle": "onUnload",
        "loadTime": +new Date()
      })
    }
  }
  return options;
}

module.exports = hijack_page_object;
1.2 全局引入或者单页面引入
1.2.1 全局引入 app.js
代码语言:javascript
复制
// 引入页面传入对象处理方法
const hijack_page_object = require('./utils/hijack_page_object')
// App 中注册为全局方法
App({
	hijack_page_object 
})
1.2.2 页面使用 hijack_page_object 方法(index.js)
代码语言:javascript
复制
// 引入 hijack_page_object 
const app = getApp();
const { hijack_page_object } = app;

// 使用 hijack_page_object 
Page(hijack_page_object({
  onLoad(){
    console.log('当前页面生命周期!')
  }
}))
1.2.3 单页面对 hijack_page_object.js 的引入和使用(index.js)
代码语言:javascript
复制
// 引入 hijack_page_object.js
const hijack_page_object = require('../utils/hijack_page_object')
// 使用 hijack_page_object 
Page(hijack_page_object({
  onLoad(){
    console.log('当前页面生命周期!')
  }
}))
1.2.4 引入当前代码的输出(index.js)
代码语言:javascript
复制
当前页面生命周期!
全局页面生命周期!
页面生命周期埋点
1.2.5 总结

方案 1 的两种引入方式比较,全局引入比较快捷,一次引入,其他页面直接使用app里的变量访问即可;单页面引入不方便维护,代码冗余!建议多频率使用的方法等直接在app.js中注册!

2. 方案2:重写 Page 对象

2.1 hijack_page.js 代码
代码语言:javascript
复制
let _Page = Page;
Page = (options) => {
  const { onLoad, onUnload } = options;
  options = {
    ...options,
    collectClick(opts){
      // 页面点击埋点
      console.log('页面点击埋点')
      // 点击埋点逻辑
    },
    collectPage(opts){
      // 页面生命周期埋点
      console.log('页面生命周期埋点')
      // 生命周期埋点逻辑
    },
    jumpNextPage(url){
      // 全局页面跳转方法
      wx.navigateTo({url})
      // 埋点跳转点击
      this.collectClick({})
    },
    onLoad(opts){
      onLoad && onLoad.call(this, opts);
      console.log('全局页面生命周期!')
      this.collectPage({
        "lifeCycle": "onLoad",
        "loadTime": +new Date()
      });
    },
    onUnload(){
      onUnload && onUnload.call(this);
      this.collectClick({
        "lifeCycle": "onUnload",
        "stayTime": +new Date() - this._enterTime
      });
    }
  }
  _Page(options)
}
module.exports = {
  Page
}
2.2 hijack_page 的使用
2.2.1 全局引入 hijack_page (app.js)
代码语言:javascript
复制
// 引入 hijack_page
const hijack_page = require('./utils/hijack_page')
// 注册 hijack_page
App({
	hijack_page
})
2.2.2 页面使用 hijack_page (index.js)
代码语言:javascript
复制
// 引入 Page 
const app = getApp();
const { Page } = app.hijack_page;
// 使用 Page 
Page({
	onLoad(){
	    console.log('当前页面生命周期!')
	}
})
2.2.3 当前方案代码输出(index.js)
代码语言:javascript
复制
当前页面生命周期!
全局页面生命周期!
页面生命周期埋点
2.2.4 总结

对比方案1和方案2,发现直接重写 Page 比 劫持传入 Page 的对象在使用时方便很多!

3. 方案3:重写 App.Page

3.1 proxyStore.js 代码
代码语言:javascript
复制
const { 
  TYPE_OBJECT,
  _typeOf,
  _deepClone,
  _isObjEqual
} = require('./util');

let $state =  Symbol('$state'),
    $openPart =  Symbol('$openPart'),
    $behavior =  Symbol('$behavior'),
    $methods =  Symbol('$methods'),
    $pageLife =  Symbol('$pageLife'),
    $pageListener =  Symbol('$pageListener'),
    $nonWritable =  Symbol('$nonWritable'),
    $stack =  Symbol('$stack'),
    $debug =  Symbol('$debug');

class ProxyStore{
  constructor(opts){
    // 初始化数据
    this.initData(opts);
    // 初始化页面周期数组
    this.initPageLife();
    // 重写 Page 对象
    this.rewritePage();
    // 重写 Component 对象
    this.rewriteComponent();
  }
  initData(opts){
    const { 
      openPart = false,
      behavior,
      methods = {},
      pageLisener = {},
      pageListener,
      nonWritable = false,
      debug = true,
    } = opts;
    if(_typeOf(opts.state) === TYPE_OBJECT){
      this[$state] = _deepClone(opts.state);
    }
    this[$openPart] = openPart;
    this[$behavior] = behavior;
    this[$methods] = methods;
    this[$pageListener] = pageListener || pageLisener;
    this[$nonWritable] = nonWritable;
    this[$debug] = debug;
    this[$stack] = [];
  }
  initPageLife(){
    this[$pageLife] = [
      "data",
      "onLoad",
      "onShow",
      "onReady",
      "onHide",
      "onUnload",
      "onPullDownRefresh",
      "onReachBottom",
      "onShareAppMessage",
      "onPageScroll",
      "onTabItemTap",
    ]
  }
  created(page){
    !this[$stack].some(cur => cur === page) && this[$stack].push(page);
    page.watch && this.watch(page)
    if(!_isObjEqual(page.data.$state, this[$state])){
      page.setData({$state: this[$state]})
    }
  }
  destroy(page){
    let index = this[$stack].findIndex(cur => cur === page);
    ~index && this[$stack].splice(index, 1);
  }
  watch(page){
    page.data = new Proxy(page.data,{
      set(target, key, value, receiver){
        page.watch && page.watch[key] && page.watch[key].call(page, value);
        return Reflect.set(target, key, value, receiver);
      },
      get(target, key, receiver){
        return Reflect.get(target, key, receiver);
      }
    })
  }
  rewritePage(){
    const _Page = Page;
    const _this = this;
    App.Page = (options = {}, ...args) => {
      const { onLoad, onUnload } = options;
      options = {
        ...options,
        data: {
          ...(options.data || {}),
          $state: _this[$state]
        },
        ...(_this[$methods] || {}),
        onLoad(opts){
          _this.created(this)
          onLoad && onLoad.call(this,opts)
        },
        onUnload(){
          _this.destroy(this)
          onUnload && onUnload.call(this)
        }
      }
      Object.keys(_this[$pageListener]).forEach(key => {
        if(typeof _this[$pageListener][key] === "function" && _this[$pageLife].some((item) => item === key)){
          const lifeName = options[key];
          options = {
            ...options,
            [key](opts){
              let globalValue = _this[$pageListener][key].call(this, opts);
              let pageValue = lifeName && lifeName.call(this, opts);
              return pageValue || globalValue;
            }
          }
        }
      })
      _Page(options, ...args)
    }
    if (!this[$nonWritable]) {
      try {
        Page = App.Page;
      } catch (e) {}
    }
  }
  rewriteComponent(){
    const _Component = Component;
    const _this = this;
    App.Component = (options = {}, ...args) => {
      const { lifetimes = {} } = options;
      let attached = lifetimes.attached || options.attached,
          detached = lifetimes.detached || options.detached;
      options = {
        ...options,
        data: {
          ...(options.data || {}),
          $state: _this[$state]
        }
      }
      Object.keys(_this[$methods]).forEach(key => {
        if(typeof _this[$methods][key] === "function" && !_this[$pageLife].some((item) => item === key)){
          options.methods || (options.methods = {})
          const lifeName = options.methods[key];
          options.methods[key] = function(opts){
            _this[$methods][key].call(this, opts);
            lifeName && lifeName.call(this,opts);
          }
        }
      })
      let attachednew = function(){
        _this.created(this)
        attached && attached.call(this)
      }
      let detachednew = function(){
        _this.destroy(this)
        detached && detached.call(this)
      }
      if(options.lifetimes && _typeOf(options.lifetimes) === TYPE_OBJECT){
        options.lifetimes.attached = attachednew;
        options.lifetimes.detached = detachednew;
      } else {
        options.attached = attachednew;
        options.detached = detachednew;
      }
      _Component(options, ...args)
    }
    if (!this[$nonWritable]) {
      try {
        Component = App.Component;
      } catch (e) {}
    }
  }
  getState() {
    return _deepClone(this[$state]);
  }
  setState(obj, fn = () => {}) {
    if (_typeOf(obj) !== TYPE_OBJECT) throw new Error("setState的第一个参数须为object!");
    let prev = this[$state];
    let current = {
      ..._deepClone(prev),
      ..._deepClone(obj)
    };
    this[$state] = current;
    if(this[$stack].length){
      let props = this[$stack].map(page => {
        return new Promise((resolve,reject) => {
          page.setData({$state: current}, resolve)
        })
      })
      Promise.all(props).then(fn);
    }else{
      fn();
    }
  }
}
module.exports = ProxyStore;
3.2 util.js 基础方法js代码
代码语言:javascript
复制
const util = {
  TYPE_ARRAY: "[object Array]",
  TYPE_OBJECT: "[object Object]",
  _typeOf(value){
    return Object.prototype.toString.call(value)
  },
  _deepClone(obj){
    return JSON.parse(JSON.stringify(obj))
  },
  _isEmptyObject(obj){
    if(util._typeOf(obj) !== util.TYPE_OBJECT) throw new Error(`传入值不是对象!`);
    for(let key in  obj){
      return false;
    }
    return true
  },
  _isObjEqual(o1,o2){
    var props1 = Object.getOwnPropertyNames(o1);
    var props2 = Object.getOwnPropertyNames(o2);
    if (props1.length != props2.length) {
      return false;
    }
    for (var i = 0,max = props1.length; i < max; i++) {
      var propName = props1[i];
      if (o1[propName] !== o2[propName]) {
        return false;
      }
    }
    return true;
  }
}
module.exports = util;
3.3 使用 ProxyStore
3.3.1 app.js 注册
代码语言:javascript
复制
// 引入 ProxyStore
const ProxyStore = require('./store/proxyStore');
// 声明
let store = new ProxyStore({
  state: {
    msg: 'Hello World!'
  },
  methods: {
    jumpNextPage(url){
      wx.navigateTo({url})
    }
  },
  pageListener: {
    onLoad(){
      console.log('全局')
    }
  }
})
// app.js注册
App({
	store 
})
3.3.2 index.js 使用
代码语言:javascript
复制
Page({
	onLoad(){
	    console.log('当前页面生命周期!')
	}
})
3.3.3 index.js页面输出
代码语言:javascript
复制
全局
当前页面生命周期!

4. 总结

方案3 采用的是【 微信小程序全局状态管理库——wxMiniStore】的方法,方案可以对全局状态进行管理,同时页面可以使用watch 监听变量的修改!对比三种方案,方案三使用最简单,如果不需要那么多功能,可以删除不需要的代码!

5. 注意

方案三基本使用的是【微信小程序全局状态管理库——wxMiniStore】,但是做了自定义调整,调整如下:

5.1 获取全局状态必须使用 getState() 获取 $state 对象;
代码语言:javascript
复制
// 错误示范【这样是获取不到$state对象的】
let $state = getApp().store.$state

// 正确示范
let $state = getApp().store.getState()
5.2 设置全局状态必须使用setState(Object);
代码语言:javascript
复制
// 错误示范【这样是更新不到$state对象的】
getApp().store.$state.msg = 'Hello Index!'

// 正确示范
getApp().store.setState({msg: 'Hello Index!'})
5.3 watch 监听必须是 this.data 改变的变量;
代码语言:javascript
复制
// 错误示范【使用 this.setData 监听不到修改】
Page({
	onLoad(){
		this.setData({goodsList: [1,2,3,4,5,6]})
	},
	watch: {
		goodsList(val){
			console.log(val)
			this.setData({goodsList: val})
		}
	}
})

// 正确示范
Page({
	onLoad(){
		this.data.goodsList =  [1,2,3,4,5,6]
	},
	watch: {
		goodsList(val){
			console.log(val)
			this.setData({goodsList: val})
		}
	}
})

注意: 如果页面没有 watch 对象,页面并不会执行变量的监听,所以在不需要监听时,尽量不要 watch,减少性能消耗!

5.4 未开发的 wxMiniStore 功能:【开启局部模式、设置范围、useProp 】

6. 参考

  1. 微信小程序之页面拦截器
  2. 微信小程序–页面劫持
  3. 微信小程序全局状态管理库——wxMiniStore

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/01/14 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 说明
  • 需求场景
  • 需求分析
  • 需求解决
  • 1. 方案1:劫持 Page 的传入对象
    • 1.1 hijack_page_object.js 代码
      • 1.2 全局引入或者单页面引入
        • 1.2.1 全局引入 app.js
        • 1.2.2 页面使用 hijack_page_object 方法(index.js)
        • 1.2.3 单页面对 hijack_page_object.js 的引入和使用(index.js)
        • 1.2.4 引入当前代码的输出(index.js)
        • 1.2.5 总结
    • 2. 方案2:重写 Page 对象
      • 2.1 hijack_page.js 代码
        • 2.2 hijack_page 的使用
          • 2.2.1 全局引入 hijack_page (app.js)
          • 2.2.2 页面使用 hijack_page (index.js)
          • 2.2.3 当前方案代码输出(index.js)
          • 2.2.4 总结
      • 3. 方案3:重写 App.Page
        • 3.1 proxyStore.js 代码
          • 3.2 util.js 基础方法js代码
            • 3.3 使用 ProxyStore
              • 3.3.1 app.js 注册
              • 3.3.2 index.js 使用
              • 3.3.3 index.js页面输出
          • 4. 总结
          • 5. 注意
            • 5.1 获取全局状态必须使用 getState() 获取 $state 对象;
              • 5.2 设置全局状态必须使用setState(Object);
                • 5.3 watch 监听必须是 this.data 改变的变量;
                  • 5.4 未开发的 wxMiniStore 功能:【开启局部模式、设置范围、useProp 】
                  • 6. 参考
                  相关产品与服务
                  云开发 CloudBase
                  云开发(Tencent CloudBase,TCB)是腾讯云提供的云原生一体化开发环境和工具平台,为200万+企业和开发者提供高可用、自动弹性扩缩的后端云服务,可用于云端一体化开发多种端应用(小程序、公众号、Web 应用等),避免了应用开发过程中繁琐的服务器搭建及运维,开发者可以专注于业务逻辑的实现,开发门槛更低,效率更高。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档