学习微信小程序 Westore

最近,接到小程序需求,并且是在以前公司老项目上改造,打开项目,发现却不是我想象中的那样,不是上来就是 Page({}) ,而是create(store,{}),纳尼???这什么玩意,怎么没见过

1、初见Westore

接上,于是乎打开了create函数(后面得知本项目引用的1.0版本)如下

javascript 复制代码
export default function create(store, option) {
    let updatePath = null
    if (arguments.length === 2) {   
       ...
        Page(option)
    } else {
        ...
        Component(store)
    }
}

咋眼一看,难不成是自己写了一套状态管理?直觉告诉我,这应该不是前辈写的,应该是某个三方库,于是乎去搜索了一番,果然是腾讯官网针对小程序优化而出的,链接在这里,感兴趣的小伙伴可以去看看哦

2、按文档理解

大概是说,以store去驱动视图,性能有所提高,能解决小程序跨页面通讯,传值等问题,反正巴拉巴拉一大堆(小程序不是有globalData吗,说实话,我还没理解它这个的好处),结合自己理解再简单总结下吧

  1. 经过改造后,相比小程序更新视图的setData ,Westore的update 性能更好,为啥呢?update底层实质还是调用的setData,只是再调用直接走了一次diff,只更新变动的,举个栗子:
javascript 复制代码
data: {
	info: {
		a: 'xxx',
		b: 'xxx'
		...
	}
}

我们一般在更新一些数据时,可能会直接 setData({ info: newInfo }),而实际 newInfo 只是某个属性改变了
当使用 update() 时,会直接找出不同,差量的去更新
update()-------> setData({ 'info.a': '改变了哦' })
  1. 通过 store ,可以设置一些函数属性,这个就类似vue计算属性
  2. 剩下的就是关于 store 全局数据的一个概念,就不累赘了
    当然,这里只是简单说下体会最明显的几点

3、简单分析下流程

  1. 映入眼帘的是 create ,那么需要知道它干了啥
  2. 函数属性是怎么实现的
  3. 凭什么 update 就比 setData好

3.1 浅析create

javascript 复制代码
export default function create(store, option) {
    let updatePath = null
    // 在这一步,区分是组件还是页面
    if (arguments.length === 2) {   
        if (option.data && Object.keys(option.data).length > 0) {
          // 记录data中的key
            updatePath = getUpdatePath(option.data)
            // 页面data的值初始值替换为 store中的值
            syncValues(store.data, option.data)
            
        }
        // 保留store源数据,同时给当前store新增几个方法,尤其是update
        if (!originData) {
            originData = JSON.parse(JSON.stringify(store.data))
            globalStore = store
            store.instances = {}
            store.update = update
            ...
            // 给全局的 globalStore 添加一个 method
            extendStoreMethod(store)
        }
        getApp().globalData && (getApp().globalData.store = store)
        //option.data = store.data
        const onLoad = option.onLoad
        // walk 为了解决当定义在store里面属性是一个方法时
        // 会通过 Object.defineProperty 拦截一下该属性的get过程(也就是缓存下函数,改变this环境执行一下)
        walk(store.data)
        // 解决函数属性初始化不能显示的问题,要求必须在data中声明使用
        // 这段代码是同步store.data到option.data,只有经过walk方法后store.data中的函数才能变成属性,才能被小程序page方法渲染
        if (option.data && Object.keys(option.data).length > 0) {
            updatePath = getUpdatePath(option.data)
            console.log('updatePath',updatePath)
            syncValues(store.data, option.data)
        }
        option.onLoad = function (e) {
          // 给当前实例添加 store 、更新路径、update方法、执行onLoad、同步data、
          // 走小程序 setData
            this.store = store
            this._updatePath = updatePath
            ...
            this.setData(this.data)
        }

	// 解决执行navigateBack或reLaunch时清除store.instances对应页面的实例
	const onUnload = option.onUnload
    option.onUnload = function () {
        onUnload && onUnload.call(this)
        store.instances[this.route] = []
    }
        Page(option)
    } else {
        组件逻辑,先不看
    }
}

create ,接收两个参数

store ---- 可以理解为页面的数据(或者共享时公有的)

option --- 则是小程序原有选项

代码开头则通过实参个数,区分了当前是组件,还是页面(这里以页面为例),同时记录下页面data 路径,也就是 getUpdatePath 函数

javascript 复制代码
function getUpdatePath(data) {
	const result = {}
    dataToPath(data, result)
	return result
}

function dataToPath(data, result) {
	Object.keys(data).forEach(key => {
    result[key] = true
		const type = Object.prototype.toString.call(data[key])
		if (type === OBJECTTYPE) {
			_objToPath(data[key], key, result)
		} else if (type === ARRAYTYPE) {
			_arrayToPath(data[key], key, result)
		}
	})
}

如上,getUpdatePath 目的就是把各个属性记录下来,如

javascript 复制代码
data: {
 a: '123',
 b: { c: '456' }
}
getUpdatePath(data) 后
{
 a: true,
 'b.c': true
}

有了这个后,是为了方便后续判断要更新的key在不在data中

还有一步 syncValues ,这个函数就是把store中的值,同步到 data 中,这就是为什么页面需要列出store中的属性的原因(这里是v1,貌似proxy那个版本不需要了)

接着就是给store添加一些方法(如update),以及源数据保留等

来到 walk 函数

javascript 复制代码
function walk(data) {
    Object.keys(data).forEach(key => {
        const obj = data[key]
        const tp = type(obj)
        if (tp == FUNCTIONTYPE) {
            setProp(key, obj)
        } else if (tp == OBJECTTYPE) {
            Object.keys(obj).forEach(subKey => {
              // 值,key vipInfo.age
                _walk(obj[subKey], key + '.' + subKey)
            })

        } else if (tp == ARRAYTYPE) {
            obj.forEach((item, index) => {
                _walk(item, key + '[' + index + ']')
            })

        }
    })
}

function _walk(obj, path) {
    const tp = type(obj)
    if (tp == FUNCTIONTYPE) {
        setProp(path, obj)
    } else if (tp == OBJECTTYPE) {
        Object.keys(obj).forEach(subKey => {
            _walk(obj[subKey], path + '.' + subKey)
        })

    } else if (tp == ARRAYTYPE) {
        obj.forEach((item, index) => {
            _walk(item, path + '[' + index + ']')
        })

    }
}

function setProp(path, fn) {
    const ok = getObjByPath(path)
    fnMapping[path] = fn
    Object.defineProperty(ok.obj, ok.key, {
        enumerable: true,
        get: () => {
            return fnMapping[path].call(globalStore.data)
        },
        set: () => {
            console.warn('Please using store.method to set method prop of data!')
        }
    })
}

看到这种名字的函数,第一反应就是逐个遍历 的过程,这个函数虽然拆成了几个函数,但目的其实很简单,只有当 tp == FUNCTIONTYPE 时,才会跳出这个过程,走 setProp 函数,看到这里可能还是有点迷糊,那就加一个函数属性,豁然开朗

javascript 复制代码
data: {
    vipInfo: {
      age: '25',
      getAge(){
        return this.vipInfo.age
      }
    }
}
经过 walk 后
vipInfo: {
  age: '25',
  getAge: undefined
}
// getAge 变成了一个属性,并且通过拦截的方式,当get的时候再执行开始定义的函数
// 这也就能解释如何实现 函数属性的了

剩下 几部就是对onLoad函数的改写,以及一些页面卸载,实例销户的过程,最终还是走的小程序Page函数

以上步骤 ,可以知道主要是

1、同步 store 中的值到 小程序 data 中

2、记录每个属性的路径

3、当 store 中有函数属性时,通过响应拦截方式,将其转变为 属性(同时再次同步一次值)

4、给store添加一些api

5、对 onLoad 方法进行改写,包括 onUnload

6、走小程序 Page 过程

3.2 那就看看 update

javascript 复制代码
function update(patch) {
    return new Promise(resolve => {
        //defineFnProp(globalStore.data)
        // 可以传路径,也可以不传
        if (patch) {
            for (let key in patch) {
                updateByPath(globalStore.data, key, patch[key])
            }
        }
        // diff 后直接找出差异的数据
        let diffResult = diff(globalStore.data, originData)
        if (Object.keys(diffResult)[0] == '') {
            diffResult = diffResult['']
        }
        // 是否是全局数据
        const updateAll = matchGlobalData(diffResult)
        let array = []
        if (Object.keys(diffResult).length > 0) {
            for (let key in globalStore.instances) {
                globalStore.instances[key].forEach(ins => {
                    if(updateAll || globalStore.updateAll || ins._updatePath){
                        // 获取需要更新的字段
                        const needUpdatePathList = getNeedUpdatePathList(diffResult, ins._updatePath)
                        console.log('needUpdatePathList',needUpdatePathList)
                        if (needUpdatePathList.length) {
                         ...
                            // 值差量更新,并且包装成 数组 Promise 形式
                            array.push( new Promise(cb => {
                                ins.setData.call(ins, _diffResult, cb)
                            }) )
                        }
                    }
                })
            }
            // 数据更新的回调
            globalStore.onChange && globalStore.onChange(diffResult)
          ...
        Promise.all(array).then(e=>{
            resolve(diffResult)
        })
    })
}

可以看到,update 就比较残暴了,通过 diff ,找出变动的数据,接着是对应实例更新问题,最后把需要更新的数据包装成 Promise 的形式,最终通过 setData 实现

4、总结

以上就是笔者对整个过程的分析,从简单来看,可以理解为重点对 setData 进行了 diff 的优化,用法是上显得直观,官方也给出了 多页面时几种情况 store 的拆分,不过笔者还没想好应该怎么写,跟优雅

相关推荐
我要吐泡泡了哦1 小时前
GAMES104:15 游戏引擎的玩法系统基础-学习笔记
笔记·学习·游戏引擎
骑鱼过海的猫1231 小时前
【tomcat】tomcat学习笔记
笔记·学习·tomcat
贾saisai3 小时前
Xilinx系FPGA学习笔记(九)DDR3学习
笔记·学习·fpga开发
北岛寒沫3 小时前
JavaScript(JS)学习笔记 1(简单介绍 注释和输入输出语句 变量 数据类型 运算符 流程控制 数组)
javascript·笔记·学习
铁匠匠匠5 小时前
从零开始学数据结构系列之第六章《排序简介》
c语言·数据结构·经验分享·笔记·学习·开源·课程设计
架构文摘JGWZ6 小时前
Java 23 的12 个新特性!!
java·开发语言·学习
小齿轮lsl6 小时前
PFC理论基础与Matlab仿真模型学习笔记(1)--PFC电路概述
笔记·学习·matlab
Aic山鱼7 小时前
【如何高效学习数据结构:构建编程的坚实基石】
数据结构·学习·算法
qq11561487077 小时前
Java学习第八天
学习
天玑y7 小时前
算法设计与分析(背包问题
c++·经验分享·笔记·学习·算法·leetcode·蓝桥杯