- 写一个能自定义尺寸、样式的switch
小程序原生的switch不能灵活的修改宽高、样式,很不方便,我这边参考WeUI的开关,写了一个可以自定义尺寸样式的switch组件。 直接上代码:https://gitee.com/piscdong/wechat-switch 效果如下图,可以自定义宽高,可以做成方角的 [图片] 代码分析 这个switch主要的难点就是点击后背景颜色变换的动画,这里用到了css的transition、transform两个属性来实现动画,以及::before和::after两个伪元素。 wxml基本结构为: [代码]<view class="switch"> <view></view> </view> [代码] 父级view是整个switch容器,会用到::before做背景色切换动画,::after做禁用时的灰色遮罩。内部的一个view是来回切换的白点。未选中时默认class是switch,选中时增加一个class:switch_checked。 选中状态到未选中状态背景有一个从中间变大到全部的白色动画,所以需要给父级view设置一个颜色作为背景色。 [代码].switch { ... background: #00c000; position: relative; } [代码] 未选中时::before覆盖整个容器,选中时::before设置[代码]transform: scale(0);[代码],这样选中时白色区域就会缩放到最小,再加上transition实现动画效果。 [代码].switch::before { display: block; content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; border-radius: 9999rpx; background: #fff; transition: all 0.35s cubic-bezier(0.45, 1, 0.4, 1); } .switch_checked::before { transform: scale(0); } [代码] 来回移动的白点,未选中时通过[代码]left: 0;[代码]定位到左侧,选中时将left设置为100%定位到右侧,但是这样白点会完全移出容器范围,所以还需要加上[代码]transform: translateX(-100%);[代码]将白点向左再一定自身宽度的100%,同样加上transition实现动画效果。 [代码].switch view { position: absolute; top: 0; left: 0; width: 60rpx; height: 60rpx; border-radius: 50%; background: #fff; box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.4); transition: all 0.35s cubic-bezier(0.45, 1, 0.4, 1); } .switch_checked view { left: 100%; transform: translateX(-100%); } [代码] 关于“::” 最后搭车说一下“:”和“::”,“:”是伪类,“::”是伪元素。按照我的理解:伪类不会在dom中增加节点,只不过是css选择器的一种特殊效果;伪元素会增加节点,flex布局中会影响到其他元素。 为了保证兼容性,css3是允许伪元素使用单个冒号。
2019-12-24 - 🎆我们开源啦 | 基于Skyline开发的组件库🚀
我们开源啦,希望可以给大家的开发之旅带来一些灵感。我后溪的小程序也都会基于这个组件库开发,并且会保持组件库的更新与维护。 我是第一次进行开源,肯定会有错漏,欢迎大家指正,我会以最快的时间响应修改。 Skyline UI 组件库 前言 Skyline 是微信小程序推出的一个类原生的渲染引擎,其使用更精简高效的渲染管线,性能比 WebView 更优异,并且带来诸多增强特性,如 Worklet 动画、手势系统、自定义路由、共享元素等。 使用这个组件库的前提是:通过微信小程序原生+skyline框架开发,所以目前我们不保证兼容webview框架(也就是电脑端与低版本的微信),但后续会进行系统性的兼容。 使用 Skyline UI前,请确保你已经学习过微信官方的 微信小程序开发文档 和 Skyline 渲染引擎文档 。 背景 随着Skyline 渲染引擎 1.1.0 版本发布,我们所运营的小程序也平稳的渡过了阵痛期,团队使用Skyline也越来得心应手,所以接下来,团队的开发重心全面偏向Skyline渲染框架,考虑有大量的UI交互重复,我们决定基于Skyline开发了这个UI组件库。 但团队力量有限,这个新生的组件可能有很多的不尽如人意,所以希望能以开源的方式吸引更多开发者使用Skyline框架,如果这个框架不适合你,也可以借鉴其思路。 Gitee Gitee仓库 在线预览 以下是目前两个使用该框架的小程序 SkylineUI组件库 [图片] NONZERO COFFEE [图片] 开始使用 UI库结构 Skyline UI组件库 依赖于以下四部分,具体使用参考以下的具体说明 utils工具库: 其中包含了UI库自定义的一个工具类SkyUtils,它包含了组件中所含的各种函数,非常重要。 各组件元素:sky-*(组件名) skywxss样式库:其中包含深浅色色彩、文字字体、布局等样式wxss 在小程序中引入 UI库 一、直接下载引入 点击下载组件包 将src下所有文件复制到您项目根目录下的components文件夹中,没有的话请自行新建。 二、npm引入 1.在小程序项目中,可以通过 npm 的方式引入 SkylineUI组件库 。如果你还没有在小程序中使用过 npm ,那先在小程序目录中执行命令: [代码]npm init -y [代码] 2.安装组件库 [代码]npm install jieyue-ui-com [代码] 3.npm 命令执行完后,需要在开发者工具的项目中点菜单栏中的 工具 - 构建 npm 两种引入方式的不同可能导致后续使用时,引用组件的路径不同,请注意区别 1.直接引入components文件夹内,引用地址通常是 ‘./components/‘ 2.npm引入,组件引用地址通常是’./miniprogram_npm/jieyue-ui-com/’ 如何使用 1.在app.js文件中初始化工具类,并且添加两个全局变量 [代码]// app.js App({ onLaunch() { ;(async ()=>{ // 全局注册工具类SkyUtils // 这里默认npm引用,地址为'./components/utils/skyUtils',如果是直接引用组件,地址可能是'./components/utils/skyUtils',后面不再说明 const SkyUtils = await import('./components/utils/skyUtils'); wx.SkyUtils = SkyUtils.default; // 初始化设备与系统数据 wx.SkyUtils.skyInit() // 小程序自动更新方法 wx.SkyUtils.versionUpdate() })() }, globalData: { sky_system:{}, sky_menu:{} }, }) [代码] 2.在app.wxss文件中引入样式文件 [代码]//wxss * _dark.wxss 是适配深色模式的色彩变量 @import '/miniprogram_npm/jieyue-ui-com/skywxss/skycolor.wxss'; @import '/miniprogram_npm/jieyue-ui-com/skywxss/skycolor_dark.wxss'; @import '/miniprogram_npm/jieyue-ui-com/skywxss/skyfontline.wxss'; @import '/miniprogram_npm/jieyue-ui-com/skywxss/skyfont.wxss'; @import '/miniprogram_npm/jieyue-ui-com/skywxss/skyother.wxss'; [代码] 3.page.json中引用组件 [代码]//page.json { "usingComponents": { "sky-text":"/miniprogram_npm/jieyue-ui-com/sky-text/sky-text" } } [代码] 4.页面中使用 [代码] // wxml <sky-text content="文本内容" max-lines="2" fade></sky-text> [代码] 5.其他组件具体使用请参考组件包中的redeme.md 适配深色模式 如果您在开发时,全部使用我们预设好的颜色变量,那么可以自动适配深色模式。 [代码].page{ background-color: var(--bg-l0); } [代码] [代码] <view style="background-color: var(--bg-l0)"></view> <view style="background-color: {{color}}"></view> [代码] [代码] Page({ data: { color: "var(--bg-l0)" } }) [代码]
01-09 - Lottie-前端实现AE动效
项目背景 在海外项目中,为了优化用户体验加入了几处微交互动画,实现方式是设计输出合成的雪碧图,前端通过序列帧实现动画效果: [图片] 序列帧: [图片] 动画效果: [图片] 序列帧: [图片] 帧动画的缺点和局限性比较明显,合成的雪碧图文件大,且在不同屏幕分辨率下可能会失真。经调研发现,Lottie是个简单、高效且性能高的动画方案。 Lottie是可应用于Android, iOS, Web和Windows的库,通过Bodymovin解析AE动画,并导出可在移动端和web端渲染动画的json文件。换言之,设计师用AE把动画效果做出来,再用Bodymovin导出相应地json文件给到前端,前端使用Lottie库就可以实现动画效果。 [图片] Bodymovin插件的安装与使用 关闭AE 下载并安装ZXP installer https://aescripts.com/learn/zxp-installer/ 下载最新版bodymovin插件 https://github.com/airbnb/lottie-web/blob/master/build/extension/bodymovin.zxp 把下载好的bodymovin.zxp拖到ZXP installer [图片] 打开AE,在菜单首选项->常规中勾选☑️允许脚本写入文件和访问网络(否则输出JSON文件时会失败) [图片] 在AE中制作动画,打开菜单窗口->拓展->Bodymovin,勾选要输出的动画,并设置输出文件目录,点击render [图片] 打开输出目录会看到生成的JSON文件,若动画里导入了外部图片,则会在images中存放JSON中引用的图片 前端使用lottie 静态URL https://cdnjs.com/libraries/lottie-web NPM [代码]npm install lottie-web [代码] 调用loadAnimation [代码]lottie.loadAnimation({ container: element, // 容器节点 renderer: 'svg', loop: true, autoplay: true, path: 'data.json' // JSON文件路径 }); [代码] vue-lottie 也可以在vue中使用lottie [代码] import lottie from '../lib/lottie'; import * as favAnmData from '../../raw/fav.json'; export default { props: { options: { type: Object, required: true }, height: Number, width: Number, }, data () { return { style: { width: this.width ? `${this.width}px` : '100%', height: this.height ? `${this.height}px` : '100%', overflow: 'hidden', margin: '0 auto' } } }, mounted () { this.anim = lottie.loadAnimation({ container: this.$refs.lavContainer, renderer: 'svg', loop: this.options.loop !== false, autoplay: this.options.autoplay !== false, animationData: favAnmData, assetsPath: this.options.assetsPath, rendererSettings: this.options.rendererSettings } ); this.$emit('animCreated', this.anim) } } [代码] loadAnimation参数 参数名 描述 container 用于渲染动画的HTML元素,需确保在调用loadAnimation时该元素已存在 renderer 渲染器,可选值为’svg’(默认值)/‘canvas’/‘html’。svg支持的功能最多,但html的性能更好且支持3d图层。各选项值支持的功能列表在此 loop 默认值为true。可传递需要循环的特定次数 autoplay 自动播放 path JSON文件路径 animationData JSON数据,与path互斥 name 传递该参数后,可在之后通过lottie命令引用该动画实例 rendererSettings 可传递给renderer实例的特定设置,具体可看 Lottie动画监听 Lottie提供了用于监听动画执行情况的事件: complete loopComplete enterFrame segmentStart config_ready(初始配置完成) data_ready(所有动画数据加载完成) DOMLoaded(元素已添加到DOM节点) destroy 可使用addEventListener监听事件 [代码]// 动画播放完成触发 anm.addEventListener('complete', anmLoaded); // 当前循环播放完成触发 anm.addEventListener('loopComplete', anmComplete); // 播放一帧动画的时候触发 anm.addEventListener('enterFrame', enterFrame); [代码] 控制动画播放速度和进度 可使用anm.pause和anm.play暂停和播放动画,调用anm.stop则会停止动画播放并回到动画第一帧的画面。 使用anm.setSpeed(speed)可调节动画速度,而anm.goToAndStop(value, isFrame)和anm.goToAndPlay可控制播放特定帧数,也可结合anm.totalFrames控制进度百分比,比如可传anm.totalFrames - 1跳到最后一帧。 [代码]anm.goToAndStop(anm.totalFrames - 1, 1); [代码] 这样的好处是可以把相关联的JSON文件合并,通过anm.goToAndPlay控制动画状态的切换,如下图例中一个JSON文件包含了2个动画状态的数据: [图片] 图片资源 JSON文件里assets设置了对图片的引用: [图片] 若想统一修改静态资源路径或者设置成绝对路径,可在调用loadAnimation时传入assetsPath参数: [代码]lottie.loadAnimation({ container: element, renderer: 'svg', path: 'data.json', assetsPath: 'URL' // 静态资源绝对路径 }); [代码] 功能支持列表 即使用bodymovin成功输出了JSON文件(没有报错),也会出现动效不如预期的情况,比如这是在AE中构建的形象图: [图片] 但在页面中渲染效果是这样的: [图片] 这是因为使用了不支持的Merge Paths功能 [图片] 因此对设计师而言,创建Lottie动画和往常制作AE动画有所不同,此文档记录了Bodymovin支持输出的AE功能列表,动画制作前需跟设计师沟通好,根据动画加载平台来确认可使用的AE功能。 除此之外,尽量遵循官方文档里对设计过程的指导和建议: 动画简单化。创建动画时需时刻记着保持JSON文件的精简,比如尽可能地绑定父子关系,在相似的图层上复制相同的关键帧会增加额外的代码,尽量不使用占用空间最多的路径关键帧动画。诸如自动跟踪描绘、颤动之类的技术会使得JSON文件变得非常大且耗性能。 建立形状图层。将AI、EPS、SVG和PDF等资源转换成形状图层否则无法在Lottie中正常使用,转换好后注意删除该资源以防被导出到JSON文件。 设置尺寸。在AE中可设置合成尺寸为任意大小,但需确保导出时合成尺寸和资源尺寸大小保持一致。 不使用表达式和特效。Lottie暂不支持。 注意遮罩尺寸。若使用alpha遮罩,遮照的大小会对性能产生很大的影响。尽可能地把遮罩尺寸维持到最小。 动画调试。若输出动画破损,通过每次导出特定图层来调试出哪些图层出了问题。然后在github中附上该图层文件提交问题,选择用其他方式重构该图层。 不使用混合模式和亮度蒙版。 不添加图层样式。 全屏动画。设置比想要支持的最宽屏幕更宽的导出尺寸。 设置空白对象。若使用空白对象,需确保勾选可见并设置透明度为0%否则不会被导出到JSON文件。 预览效果 由于以上所说的功能支持问题会导致输出动画效果不确定性,设计师和前端之间有个动画效果联调的过程,为了提高联调效率,设计师可先进行初步的效果预览,再把文件交付给前端。 方法1:输出预览HTML文件 渲染前设置所要渲染的文件 [图片] 勾选☑️Demo选项 [图片] 在输出的文件目录中就可找到可预览的demo.html文件 方法2:LottieFiles分享平台 把生成的JSON文件传到LottieFiles平台,可播放、暂停生成文件的动画效果,可设置图层颜色、动画速度,也可以下载lottie preview客户端在iOS或Android机子上预览。 [图片] LottieFiles平台还提供了很多线上公开的Lottie动画效果,可直接下载JSON文件使用 [图片] 交互hack Lottie的不足之处是没有对应的API操纵动画层,若想做更细化的动画处理,只能直接操作节点来实现。比如当播放完左图动画进入惊讶状态后,若想实现右图随鼠标移动而控制动画层的简单效果: [图片][图片] 开启调试面板可以看到,lottie-web通过使用<g>标签的transform属性来控制动画: [图片] 当元素已添加到DOM节点,找到想要控制的<g>标签,提取其transform属性的矩阵值,并使用rematrix解析矩阵值。 [代码]onIntroDone() { const Gs = this.refs.svg.querySelectorAll('svg > g > g > g'); Gs.forEach((node, i) => { // 过滤需要修改的节点 ... // 获取transform属性值 const styleArr = node.getAttribute('transform').split(','); styleArr[0] = styleArr[0].replace('matrix(', ''); styleArr[5] = styleArr[5].replace(')', ''); const style = `matrix(${styleArr[0]}, ${styleArr[1]}, ${styleArr[2]}, ${styleArr[3]}, ${styleArr[4]}, ${styleArr[5]})`; // 使用Rematrix解析 const transform = Rematrix.parse(style); this.matrices.push({ node, transform, prevTransform: transform }); } } [代码] 监听鼠标移动,设置新的transform属性值。 [代码]onMouseMove = (e) => { this.mouseCoords.x = e.clientX || e.pageX; this.mouseCoords.y = e.clientY || e.pageY; let x = this.mouseCoords.x - (this.props.browser.width / 2); let y = this.mouseCoords.y - (this.props.browser.height / 2); const diffX = (this.mouseCoords.prevX - x); const diffY = (this.mouseCoords.prevY - y); this.mouseCoords.prevX = x; this.mouseCoords.prevY = y; this.matrices.forEach((matrix, i) => { let translate = Rematrix.translate(diffX, diffY); const product = [matrix.prevTransform, translate].reduce(Rematrix.multiply); const css = `matrix(${product[0]}, ${product[1]}, ${product[4]}, ${product[5]}, ${product[12]}, ${product[13]})`; matrix.prevTransform = product; matrix.node.setAttribute('transform', css); }) } [代码] 进一步优化 看到一个方法,在AE中将图层命名为[代码]#id[代码]格式,生成的SVG相应的图层id会被设置为id,命名为[代码].class[代码]格式,相应的图层class会被设置为class [图片] 试了下的确可以,如下图,因此可通过这个方法快速找到需要操作的动画层,进一步简化代码: [图片] 小结 Lottie的缺点在于若在AE动画制作的过程不注意规范,会导致数据文件大、耗内存和性能的问题;Lottie-web的官方文档不够详尽,例如assetsPath参数是在看源码的时候发现的;开放的API不够齐全,无法很灵活地控制动画层。 而优点也很明显,Lottie能帮助提高开发效率,精简代码,易于调试和维护;资源文件小,输出动画效果保真;跨平台——Android, iOS, Web和Windows通用。 总的来说,Lottie的引用可以替代传统的GIF和帧动画,灵活利用好提供的属性和方法可以控制动画的播放,但需注意规范设计和开发的流程,才可以更高效地完成动画的制作与调试。
2019-03-25 - 这个库能轻松解决99%的异步和逻辑加载时机问题(异步篇)
[图片] 你是否纠结过底层业务逻辑(登陆、获取用户信息等)到底是放app.js的onLaunch还是page的onLoad里比较好,或者因为异步问题被迫放在了onload,我们来分析一下优劣 - - - - - - - - - - - - - - - - - - - - - - - - - - - - -我是分割线 - - - - - - - - - - - - - - - - - - - - - - - - - - - - 分析 onLaunch处理 优点:底层业务逻辑集中并且只需写一次,比较好维护 缺点:目前没有一个理想的方案来解决onLaunch和onLoad的异步问题,包括注册回调、重写onLoad、请求拦截等。 onLoad处理 优点:因为不涉及跨页面通知,因此异步逻辑比较好处理 缺点:每个页面都得写一次底层业务逻辑,非常繁琐,而且既然是公用的底层业务逻辑,分散在每个页面的onLoad里,好像也不大对劲。 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -我是分割线 - - - - - - - - - - - - - - - - - - - - - - - - - - - - 抉择 按照高内聚低耦合的原则,那逻辑和数据放onLaunch里肯定的,不应该和普通page逻辑耦合在一起,通用的数据和逻辑应该在入口去处理,执行一次到处使用,就像vue的main.js一样,会注册一些技术层的基础设施(路由、状态管理等插件),那业务层的基础设施不就是token、用户信息、所在位置等逻辑吗? - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -我是分割线 - - - - - - - - - - - - - - - - - - - - - - - - - - - - 想象中的最佳实践 那我们的目标就是如何满足两者的优点,避免两者的缺点,做到真正的“高内聚低耦合” 1.保持底层业务逻辑写在入口app.js,避免耦合page里的逻辑 2.能在任何page里第一时间拿到globalData数据 3.使用方便,做到在业务开发中无感知,不需要写额外的调用、通知等代码 4.无任何副作用,不会影响其他功能,比如重写阻塞onLoad 5.灵活可配,适用以后此类任何业务 - - - - - - - - - - - - - - - - - - - - - - - - - - - - -我是分割线 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 梦想成真先看一段代码 ⬇️ // page.js export default { name: 'Home', onLoadLogin(){ //登录成功(拿到token) && 页面初始化完成 //Tips:适用于某页面发送的请求依赖token的场景 }, onLoadUser(){ //页面初始化完成 && 获取用户信息完成 //Tips:适用于页面初始化时需要用到用户信息去做判断再走页面逻辑的场景 }, onReadyUser(){ //dom渲染完成 && 获取用户信息完成 //Tips:适用于首次进入页面需要在canvas上渲染头像的类似场景 }, onReadyShow(){ //小程序内页面渲染完成 && 页面显示 //Tips:适用于需要获取小程序组件或者dom,并且每次页面显示都会执行的场景 }, } 应该懂什么意思了吧?是不是你理想中的样子,使用起来跟没有似的 ⬆️ 这段示例代码满足了上面的第2、3、4条目标 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -我是分割线 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 再来看一段 ⬇️ // app.js // 配置自定义钩子,所有钩子都可以随意组合搭配使用,执行机制类似于Promise.all(但不是用Promise实现的) CustomHook.install({ 'Login':{ // 自定义钩子名称、必须大写字母开头 name:'Login', watchKey: 'token', onUpdate(token){ //有token则触发此钩子 return !!token; } }, 'User':{ // 自定义钩子名称 name:'User', watchKey: 'userInfo', onUpdate(user){ //获取到userinfo里的userId则触发此钩子 return !!user.userId; } } //依赖globalData中数据 }, globalData) 怎么样,是不是很棒,依赖globalData,名字可配,连触发规则都可配,而且还附加了可随意组合的功能(意外还解决了页面内逻辑执行时机问题,在下篇讲) ⬆️ 这段示例代码满足了上面的第1、5条目标。 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -我是分割线 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 是不是跃跃欲试了,那就赶紧试试,好用回来告诉我! ⬇️(公司内部已接入两年了很稳定) GitHub:https://github.com/1977474741/spa-custom-hooks [图片]
2023-07-07 - 小程序自动销毁后的使用体验优化
2023年12月24日更新 经过测试发现,官方提供的onSaveExitState有一些问题,不太符合官方文档提出的预期,大家暂时慎重使用onSaveExitState功能,用其他方案吧。 ======================================================================== 假设用户在小程序内进行一个答题的活动,或者进行一个测试,这个活动或测试的时间比较长,大概需要10分钟的时间。当用户答题进行到一半的时候,来了一个重要的电话,电话打了十几分钟,回来之后想着继续进行操作,发现小程序是重新打开的状态。之前答题答了5分钟,白费了。这样,用户需要重新进行答题。 问题场景分析 用户离开小程序时间太久(官方说30分钟以上,但测试十几分钟分钟以上)或者手机内存不够用的时候,小程序会被销毁,也就是完全终止运行了。此时用户再想进入小程序进行之前的操作,只能重新操作一遍。 解决方案 以本场景为例,如果用户正在答题,在用户退出小程序的时候,将当前页面的答题进度数据进行一个保存,当用户再重新进入小程序的时候,检查是否有答题进行一半的数据。如果有,自动跳转到答题的页面,并且在onload中恢复退出之前状态的数据,让用户继续进行答题的操作。 微信小程序有一个非常好用的回调函数onSaveExitState。 退出状态onSaveExitState 每当小程序可能被销毁之前,页面回调函数 [代码]onSaveExitState[代码] 会被调用。如果想保留页面中的状态,可以在这个回调函数中“保存”一些数据,下次启动时可以通过 [代码]exitState[代码] 获得这些已保存数据。 代码示例: { "restartStrategy": "homePageAndLatestPage" } Page({ onLoad: function() { var prevExitState = this.exitState // 尝试获得上一次退出前 onSaveExitState 保存的数据 if (prevExitState !== undefined) { // 如果是根据 restartStrategy 配置进行的冷启动,就可以获取到 prevExitState.myDataField === 'myData' } }, onSaveExitState: function() { var exitState = { myDataField: 'myData' } // 需要保存的数据 return { data: exitState, expireTimeStamp: Date.now() + 24 * 60 * 60 * 1000 // 超时时刻 } } }) onSaveExitState 返回值可以包含两项: 字段名 类型 含义 data Any 需要保存的数据(只能是 JSON 兼容的数据) expireTimeStamp Number 超时时刻,在这个时刻后,保存的数据保证一定被丢弃,默认为 (当前时刻 + 1 天) 一个更完整的示例:在开发者工具中预览效果 注意事项如果超过 [代码]expireTimeStamp[代码] ,保存的数据将被丢弃,且冷启动时不遵循 [代码]restartStrategy[代码] 的配置,而是直接从首页冷启动。[代码]expireTimeStamp[代码] 有可能被自动提前,如微信客户端需要清理数据的时候。在小程序存活期间, [代码]onSaveExitState[代码] 可能会被多次调用,此时以最后一次的调用结果作为最终结果。在某些特殊情况下(如微信客户端直接被系统杀死),这个方法将不会被调用,下次冷启动也不遵循 [代码]restartStrategy[代码] 的配置,而是直接从首页冷启动。
2023-12-24 - 用Proxy实现页面和组件公共状态动态更新(类Vuex)
支持vuex中的mapActions,mapMutations辅助函数,也可以在bindStore时添加到页面中,页面卸载时最好调佣 removeStore.remove() 来删除不需要更新的页面对象 页面中使用 [图片] 或者 [图片] 组件中使用 [图片] 也可以这样 [图片] //初始化state值 let state = { } //更新state方法 let mutations = { } //公共方法 let actions = { } let stateProxy; const Store = (options = {}) => { if(options.state) state = options.state if(options.state) mutations = options.mutations if(options.state) actions = options.actions //创建Proxy stateProxy = new Proxy(state, { get(target, property) { return target[property] }, set(target, property, value) { pageArrays.forEach(item => { const newData = {} //判断该页面是否需要更新property if (item.arr.includes(property)) { newData[property] = value } item.self.setData(newData) }) return Reflect.set(target, property, value) } }) return { state, commit: commitFun, dispatch: dispatchFun, } } //以下为主要代码(原理) // Actions commit方法 export const commitFun = (str, params) => { return mutations[str](stateProxy, params) } // Actions dispatch方法 export const dispatchFun = (str, params) => { return actions[str]({ state, commit: commitFun, dispatch: dispatchFun }, params) } //存放页面对象数组 const pageArrays = [] //创建数组索引 let id = 1; //删除卸载的页面和组件 class RemovePageItem { ids = null; constructor(ids) { this.ids = ids } remove() { pageArrays.some((item, index, arr) => { if (item.id === this.ids) { arr.splice(index, 1) return true } }) } } //绑定页面并初始化所需state export const bindStore = (self, useState = [], useMutations = [], useActions = []) => { id++ const newData = {} useState.forEach(item => { newData[item] = (state[item] || state[item] === 0) ? state[item] : '' }) useMutations.forEach(item => { self[item] = (params) => mutations[item](stateProxy, params) }) useActions.forEach(item => { self[item] = (params) => actions[item]({ state, commit: commitFun, dispatch: dispatchFun }, params) }) self.setData(newData) pageArrays.push({ self, id, arr: useState }) return new RemovePageItem(id) } //绑定更新state方法 export const mapMutations = (arr = []) => { const List = {} arr.forEach(item => { List[item] = (params) => mutations[item](stateProxy, params) }) return List } export const mapActions = (arr = []) => { const List = {} arr.forEach(item => { List[item] = params => actions[item]({ state, commit: commitFun, dispatch: dispatchFun }, params) }) return List } const _default = { ...Store() } export default _default
2022-06-14