学习微信小程序 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 的拆分,不过笔者还没想好应该怎么写,跟优雅

相关推荐
中科三方26 分钟前
APP和小程序需要注册域名吗?(国科云)
小程序·apache
genggeng不会代码1 小时前
用于协同显著目标检测的小组协作学习 2021 GCoNet(总结)
学习
搞机小能手2 小时前
六个能够白嫖学习资料的网站
笔记·学习·分类
幽络源小助理3 小时前
微信小程序文章管理系统开发实现
java·微信小程序·springboot
10年前端老司机4 小时前
微信小程序模板语法和事件
前端·javascript·微信小程序
The_cute_cat4 小时前
25.4.22学习总结
学习
冰茶_5 小时前
.NET MAUI 发展历程:从 Xamarin 到现代跨平台应用开发框架
学习·microsoft·微软·c#·.net·xamarin
上趣工作室5 小时前
微信小程序开发1------微信小程序中的消息提示框总结
微信小程序·小程序
帅云毅5 小时前
Web3.0的认知补充(去中心化)
笔记·学习·web3·去中心化·区块链
豆豆5 小时前
day32 学习笔记
图像处理·笔记·opencv·学习·计算机视觉