• 学习微信小程序 Westore


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

    1、初见Westore

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

    export default function create(store, option) {
        let updatePath = null
        if (arguments.length === 2) {   
           ...
            Page(option)
        } else {
            ...
            Component(store)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

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

    2、按文档理解

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

    1. 经过改造后,相比小程序更新视图的setData,Westore的update性能更好,为啥呢?update底层实质还是调用的setData,只是再调用直接走了一次diff,只更新变动的,举个栗子:
    data: {
    	info: {
    		a: 'xxx',
    		b: 'xxx'
    		...
    	}
    }
    
    我们一般在更新一些数据时,可能会直接 setData({ info: newInfo }),而实际 newInfo 只是某个属性改变了
    当使用 update() 时,会直接找出不同,差量的去更新
    update()-------> setData({ 'info.a': '改变了哦' })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    1. 通过 store ,可以设置一些函数属性,这个就类似vue计算属性
    2. 剩下的就是关于 store 全局数据的一个概念,就不累赘了
      当然,这里只是简单说下体会最明显的几点

    3、简单分析下流程

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

    3.1 浅析create

    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 {
            组件逻辑,先不看
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    create,接收两个参数
    store ---- 可以理解为页面的数据(或者共享时公有的)
    option — 则是小程序原有选项
    代码开头则通过实参个数,区分了当前是组件,还是页面(这里以页面为例),同时记录下页面data路径,也就是 getUpdatePath 函数

    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)
    		}
    	})
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

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

    data: {
     a: '123',
     b: { c: '456' }
    }
    getUpdatePath(data){
     a: true,
     'b.c': true
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

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

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

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

    来到 walk 函数

    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!')
            }
        })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

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

    data: {
        vipInfo: {
          age: '25',
          getAge(){
            return this.vipInfo.age
          }
        }
    }
    经过 walk 后
    vipInfo: {
      age: '25',
      getAge: undefined
    }
    // getAge 变成了一个属性,并且通过拦截的方式,当get的时候再执行开始定义的函数
    // 这也就能解释如何实现 函数属性的了
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

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

    以上步骤,可以知道主要是
    1、同步 store 中的值到 小程序 data 中
    2、记录每个属性的路径
    3、当 store 中有函数属性时,通过响应拦截方式,将其转变为 属性(同时再次同步一次值)
    4、给store添加一些api
    5、对 onLoad 方法进行改写,包括 onUnload
    6、走小程序 Page 过程

    3.2 那就看看 update

    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)
            })
        })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

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

    4、总结

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

  • 相关阅读:
    爱奇艺:基于龙蜥与 Koordinator 在离线混部的实践解析
    用微服务平台框架,实现高效的流程化办公!
    uniapp 基础
    新旧iphone短信转移,苹果旧手机短信导入新手机
    在playfab server上部署unreal linux server
    【无标题】近几年攻防演练攻击队典型突破的例子
    不单独部署注册中心,又要具备注册中心的功能,咋不让我上天?
    第三章 内存管理 六、基本分页存储管理
    科技驱动固定资产管理变革:RFID技术的前沿应用
    Mysql进阶优化篇06——分组查询优化、分页查询优化、覆盖索引
  • 原文地址:https://blog.csdn.net/qq_45219069/article/details/132642497