学习微信小程序 Westore

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

1、初见Westore

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

export default function create(store, option) {let updatePath = nullif (arguments.length === 2) {   ...Page(option)} else {...Component(store)}
}

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

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. 通过 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中的keyupdatePath = getUpdatePath(option.data)// 页面data的值初始值替换为 store中的值syncValues(store.data, option.data)}// 保留store源数据,同时给当前store新增几个方法,尤其是updateif (!originData) {originData = JSON.parse(JSON.stringify(store.data))globalStore = storestore.instances = {}store.update = update...// 给全局的 globalStore 添加一个 methodextendStoreMethod(store)}getApp().globalData && (getApp().globalData.store = store)//option.data = store.dataconst 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、// 走小程序 setDatathis.store = storethis._updatePath = updatePath...this.setData(this.data)}// 解决执行navigateBack或reLaunch时清除store.instances对应页面的实例const onUnload = option.onUnloadoption.onUnload = function () {onUnload && onUnload.call(this)store.instances[this.route] = []}Page(option)} else {组件逻辑,先不看}
}

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] = trueconst 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 目的就是把各个属性记录下来,如

data: {a: '123',b: { c: '456' }
}
getUpdatePath(data){a: true,'b.c': true
}

有了这个后,是为了方便后续判断要更新的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] = fnObject.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 函数,看到这里可能还是有点迷糊,那就加一个函数属性,豁然开朗

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/71960.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

go语言的高级特性

go语言调用C语言 go tool cgo main.go

封闭岛屿数量 -- 二维矩阵的dfs算法

1254. 统计封闭岛屿的数目 这道题和 岛屿数量 – 二维矩阵的dfs算法 类似,区别在于不算边缘部分的岛屿,那其实很简单,把上⼀题中那些靠边的岛屿排除掉,剩下的就是「封闭岛屿」了。 关于岛屿的相似题目: 岛屿数量 –…

Chrome 108版(64-bit 108.0.5359.125)网盘下载

还在用Selenium的朋友们注意了,目前Chrome的最新版是116,而官方的Chromedriver只支持到115版。 可惜Google不提供旧版Chrome的下载方式,需要旧版的很难回去了。如果真的想要旧版的Chrome,只能民间自救。 我在2022年12月备份了C盘…

2023-09-07 monetdb-mvc的实现与并发控制-思考

摘要: monetdb用mvc模块实现了并发控制中对于事务的调度管理, mvc可以看作一次执行的完整的抽象. 并发控制中一个比较需要注意的地方就是调度管理以及锁的使用和管理, 是的, 事务对于资源的使用的串行会使用到锁, 但是申请锁和释放锁却不能由事务本身来进行, 而是调度事务的模…

pyspark 系统找不到指定的路径; \Java\jdk1.8.0_172\bin\java

使用用具PyCharm 2023.2.1 1:pyspark 系统找不到指定的路径, Java not found and JAVA_HOME environment variable is not set. Install Java and set JAVA_HOME to point to the Java installation directory. 解决方法:配置正确环境变量…

VR农学虚拟仿真情景实训教学演示

首先,VR农学虚拟仿真情景实训教学提供了更为真实的实践环境。传统的农学实训往往受制于时间、空间和资源的限制,学生只能通过观察或简单的模拟来学习农业知识和技能。而借助虚拟现实技术,学生可以进入虚拟农场,与各种农作物、工具…

Spring手动构建BeanDefinition的几种方法

Spring手动构建BeanDefinition的几种方法 GenericBeanDefinitionRootBeanDefinitionChildBeanDefinitionBeanDefinitionBuilder GenericBeanDefinition Data public class Student {String name;int age; }public void test() {AnnotationConfigApplicationContext context …

数据库分析工具explain

1.id:查询语句的编号 2.select_type:查询类型,有三种,simple简单查询,primary,subquery等 3.table:查询的表 4.type:查询性能,system > const > eq_ref > ref > range > index > ALL system&…

Redis 高可用及持久化

Redis 高可用 在web服务器中,高可用是指服务器可以正常访问的时间,衡量的标准是在多长时间内可以提供正常服务(99.9%、99.99%、99.999%等等)。但是在Redis语境中,高可用的含义似乎要宽泛一些,除了保证提供…

一个集成的BurpSuite漏洞探测插件1.1

免责声明 本文发布的工具和脚本,仅用作测试和学习研究,禁止用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断。如果任何单位或个人认为该项目的脚本可能涉嫌侵犯其权利&#xff0c…

Linux查看当前文件夹的大小

在Linux中,可以使用du(disk usage)命令来查看当前文件夹的大小。以下是一些使用du的方法: 查看当前文件夹的大小: 为了查看当前文件夹的总大小,可以在文件夹中运行: du -sh .这里: -…

最近读书了吗?林曦老师与你分享来自暄桐课堂的读书方法

近来,大家有在开心读书吗?对于读书,有一个很生动的说法:“无事常读书,一日是四日。若活七十年,便二百八十。”读书帮助我们超越个体生命经验的限制,此时此地的我们,也可借由书本&…

Linux 服务器运维管理面板1Panel体验

地址 https://github.com/1Panel-dev/1Panel 安装 根据GitHub提示运行即可 curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_start.sh && sudo bash quick_start.sh安装成功,期间会安装docker 、docker-compose

国产信创服务器如何进行安全可靠的文件传输?

信创,即信息技术应用创新,2018年以来,受“华为、中兴事件”影响,国家将信创产业纳入国家战略,并提出了“28n”发展体系。从产业链角度,信创产业生态体系较为庞大,主要包括基础硬件、基础软件、应…

aarch64 arm64 部署 stable diffusion webui 笔记 【3】

接上篇 aarch64 arm64 部署 stable diffusion webui 笔记 【2】继续安装其他依赖 gfpgan-CSDN博客 source venv/bin/activate export LD_LIBRARY_PATH/usr/local/lib64:/usr/local/lib:/usr/lib64:/usr/lib ./webui.sh 离线安装记录 (venv) [yeqiangceph3 stable-diffusio…

线性代数(六) 线性变换

前言 《线性空间》定义了空间,这章节来研究空间与空间的关联性 函数 函数是一个规则或映射,将一个集合中的每个元素(称为自变量)映射到另一个集合中的唯一元素(称为因变量)。 一般函数从 “A” 的每个元…

YMatrix 5.0 与天翼云完成产品兼容性认证

近日,北京四维纵横数据技术有限公司与天翼云宣布完成产品兼容性认证。经过双方严格的测试验证,超融合数据库 YMatrix 5.0 与天翼云兼容性良好,可基于天翼云稳定运行。 数据库系统作为基础软件的核心,自主可控势在必行。在此背景下…

【Sentinel】Sentinel与gateway的限流算法

文章目录 1、Sentinel与Hystrix的区别2、限流算法3、限流算法对比4、Sentinel限流与Gateway限流 1、Sentinel与Hystrix的区别 线程隔离有两种方式实现: 线程池隔离(Hystrix默认采用)信号量隔离(Sentinel默认采用) 服…

Java基础学习笔记-5

前言 Java编程语言是一门广泛应用于软件开发领域的高级编程语言。它的强大特性和跨平台性使其成为许多开发者的首选语言。本文将介绍一些Java编程的关键概念,包括函数重载、可变参数、值传递、递归等,这些概念是Java编程的基础,对于理解和掌…