当前位置: 首页 > news >正文

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

相关文章:

学习微信小程序 Westore

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

CentOS上使用Docker安装和部署kkFileView

🎈1 参考文档 kkFileView官方文档 🚀2 安装kkFileView 拉取Redis镜像。 docker pull keking/kkfileview启动docker容器。 docker run -it -d -p 8012:8012 keking/kkfileview --restart always解释: docker run redis # 从kkfileview镜像运行…...

Level-based Foraging 多智能体游戏仿真环境

游戏场景测试 参考链接: https://kgithub.com/semitable/lb-foraging...

LeetCode-53-最大子数组和-贪心算法

贪心算法理论基础: 局部最优推全局最优 贪心无套路~ 没有什么规律~ 重点:每个阶段的局部最优是什么? 题目描述: 给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素&#…...

解决gitee仓库中 .git 文件夹过大的问题

最近,许多项目都迁移到gitee。使用的也越来越频繁,但是今天突然收到一个仓库爆满的提示。让我一脸懵逼。本文将详细为你解答,这种情况如何处理。 1、起因 我收到的报错如下: remote: Powered by GITEE.COM [GNK-6.4] remote: T…...

uniapp 开发小程序,封装一个方法,让图片使用线上地址

1.在main.js文件中,添加以下代码: 复制使用: // 图片使用网络地址 Vue.prototype.localImgSrc function(img){//项目的地址域名,例如百度return "https://baidu.cn/static/index/images/" img; }2.在页面中直接使用&…...

Android 12 源码分析 —— 应用层 三(SystemUIFactory及其Dependency解析)

Android 12 源码分析 —— 应用层 三(SystemUIFactory及其Dependency解析) 在上一篇文章中,介绍了SystemUI的启动流程,并且简单提及了Dagger2用来管理各个SystemUI中要用的依赖。而这部分代码就在:mContextAvailableC…...

考前冲刺上岸浙工商MBA的备考经验分享

2023年对于许多人来说都是不平凡的一年,历经三年的抗争,我们终于成功结束了疫情。而我也很幸运的被浙工商MBA项目录取,即将开始全新的学习生活。身为一名已在职工作6年的人,能够重回校园真是一种特别令人激动的体验。今天&#xf…...

XmlDocument.SelectNodes 不起作用

今天采用Xpath读取Xml节点,怎么都读不出。 问题分析: 错误代码如下: XmlDocument xmlD new XmlDocument();xmlD.PreserveWhitespace true;xmlD.LoadXml(xStr);xmlD.SelectNodes("job-scheduling-data/schedule/job");经排查 do…...

部署单点elasticsearch

部署elasticsearch 创建网络 因为我们还需要部署kibana容器,因此需要让es和kibana容器互联。这里先创建一个网络 docker network create es-net 拉取镜像 我们采用elasticsearch的7.12.1版本的镜像 docker pull elasticsearch:7.12.1 运行 运行docker命令&a…...

ElementUI浅尝辄止16:Tag 标签

用于标记和选择。 1.如何使用&#xff1f; 由type属性来选择tag的类型&#xff0c;也可以通过color属性来自定义背景色。<el-tag>标签一</el-tag> <el-tag type"success">标签二</el-tag> <el-tag type"info">标签三</e…...

Java虚拟机(JVM)框架

见&#xff1a;GitHub - eHackyd/Java_JVM: Java虚拟机(JVM)框架的学习笔记...

配置Publisher 的编译规则

步骤 1&#xff1a;创建ROS Package 使用以下命令创建一个新的ROS软件包&#xff1a; catkin_create_pkg my_publisher_package roscpp std_msgs步骤 2&#xff1a;编辑 CMakeLists.txt 文件 打开您的ROS软件包的 CMakeLists.txt 文件&#xff0c;通常位于软件包的根目录。您…...

【SpringBoot】接口实现:SpringBoot实现博客系统的文章列表页接口代码

以下是一个简单的Spring Boot博客系统的文章列表页接口代码示例&#xff1a; java RestController RequestMapping("/articles") public class ArticleController {Autowiredprivate ArticleService articleService;GetMapping("/")public List<Artic…...

如何使用SQL系列 之 如何在SQL中插入数据

简介 结构化查询语言&#xff0c;通常被称为SQL&#xff0c;在允许您将数据插入表中方面提供了极大的灵活性。例如&#xff0c;你可以使用VALUES关键字指定单独的行数据&#xff0c;使用SELECT查询从现有表中复制整组数据&#xff0c;以及以使SQL自动插入数据的方式定义列。 …...

【LeetCode题目详解】1281题 整数的各位积和之差 面试题 01.01. 判定字符是否唯一 python题解(作业一二)

本文章以python为例! 一、力扣第1281题&#xff1a;整数的各位积和之差 问题描述&#xff1a; 1281. 整数的各位积和之差 给你一个整数 n&#xff0c;请你帮忙计算并返回该整数「各位数字之积」与「各位数字之和」的差。 示例 1&#xff1a; 输入&#xff1a;n 234 输出…...

1.12 进程注入ShellCode套接字

在笔者前几篇文章中我们一直在探讨如何利用Metasploit这个渗透工具生成ShellCode以及如何将ShellCode注入到特定进程内&#xff0c;本章我们将自己实现一个正向ShellCodeShell&#xff0c;当进程被注入后&#xff0c;则我们可以通过利用NC等工具连接到被注入进程内&#xff0c;…...

MySQL 日志系统

重要日志模块 日志文件bin logredo log**关于循环写入和擦除的checkpoint 规则**redo log 怎么刷入磁盘的 binlog 和 redo log 有什么区别&#xff1f;undo log 日志文件 错误日志&#xff08;error log&#xff09;&#xff1a; 错误日志文件对 MySQL 的启动、运行、关闭过程进…...

LeetCode刷题---Two Sum(一)

文章目录 &#x1f340;题目&#x1f340;解法一&#x1f340;解法二&#x1f340;哈希表 &#x1f340;题目 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。 你可以假设每…...

算法通关村第十七关——插入区间

LeetCode435,给定一个区间的集合&#xff0c;找到需要移除区间的最小数量&#xff0c;使剩余区间互不重叠。 示例1&#xff1a; 输入&#xff1a;interva1s[[1,3],[6,9]],newInterva1[2,5] 输出&#xff1a;[[1,5]&#xff0c;[6,9]] 解释&#xff1a;新区间[2,5]与[1,3]重…...

浅谈 React Hooks

React Hooks 是 React 16.8 引入的一组 API&#xff0c;用于在函数组件中使用 state 和其他 React 特性&#xff08;例如生命周期方法、context 等&#xff09;。Hooks 通过简洁的函数接口&#xff0c;解决了状态与 UI 的高度解耦&#xff0c;通过函数式编程范式实现更灵活 Rea…...

JavaScript 中的 ES|QL:利用 Apache Arrow 工具

作者&#xff1a;来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗&#xff1f;了解下一期 Elasticsearch Engineer 培训的时间吧&#xff01; Elasticsearch 拥有众多新功能&#xff0c;助你为自己…...

【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表

1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

uniapp微信小程序视频实时流+pc端预览方案

方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度​WebSocket图片帧​定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐​RTMP推流​TRTC/即构SDK推流❌ 付费方案 &#xff08;部分有免费额度&#x…...

【python异步多线程】异步多线程爬虫代码示例

claude生成的python多线程、异步代码示例&#xff0c;模拟20个网页的爬取&#xff0c;每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程&#xff1a;允许程序同时执行多个任务&#xff0c;提高IO密集型任务&#xff08;如网络请求&#xff09;的效率…...

是否存在路径(FIFOBB算法)

题目描述 一个具有 n 个顶点e条边的无向图&#xff0c;该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序&#xff0c;确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数&#xff0c;分别表示n 和 e 的值&#xff08;1…...

什么是Ansible Jinja2

理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具&#xff0c;可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板&#xff0c;允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板&#xff0c;并通…...

Oracle11g安装包

Oracle 11g安装包 适用于windows系统&#xff0c;64位 下载路径 oracle 11g 安装包...

Unity中的transform.up

2025年6月8日&#xff0c;周日下午 在Unity中&#xff0c;transform.up是Transform组件的一个属性&#xff0c;表示游戏对象在世界空间中的“上”方向&#xff08;Y轴正方向&#xff09;&#xff0c;且会随对象旋转动态变化。以下是关键点解析&#xff1a; 基本定义 transfor…...

Copilot for Xcode (iOS的 AI辅助编程)

Copilot for Xcode 简介Copilot下载与安装 体验环境要求下载最新的安装包安装登录系统权限设置 AI辅助编程生成注释代码补全简单需求代码生成辅助编程行间代码生成注释联想 代码生成 总结 简介 尝试使用了Copilot&#xff0c;它能根据上下文补全代码&#xff0c;快速生成常用…...