- 微信小程序教你使用eventbus一步一步构建全局跨页面通信系统
微信小程序提供了页面通信通信解决方案EventChannel,但实际使用中需要在wx.navigateTo跳转页面时使用,且需要在跳转前声明监听的事件,如下图 [图片] 这是一种页面间的通信,但是局限性过于明显,仅可以在跳转间的页面之间建立通信,A跳转B可以建立通信关系,A不跳转G就不可以建立通信关系,在实际开发中如果某个注册页面的信息想做回显,我们可以使用重新请求、放到storage中、glabalData、eventbus全局通信等,但是肯定不能用navigateTo建立eventbus信道进行传值,从交互层面是完全不可接收的。 这时我们就需要一个全局的eventbus来进行通信了,下面讲解一下微信小程序如何搭建全局eventbus进行通信。 注:eventbus传值,如果没有对引用类型进行深拷贝,那么会将引用传过去导致错误。 首先,我们需要开发页面扩展功能,我们知道每一个页面都是一个Page({}),传入的是一个对象,对象中包含双向绑定的数据、生命周期函数、自定义函数,这里我们需要增加页面的生命周期函数。 原理可以参考这篇文章: 小程序页面(Page)扩展 其中我们需要这5个文件: [图片] 其中config.js是小程序全局构造函数App、Page扩展规则配置项,eventBus是eventbus的实现,index是将eventbus扩展的页面上,然后再app.js中引入index文件即可,pageExtends即页面扩展方法,public是初始化eventbus的方法。 使用方法: A页面声明: [图片] B页面触发: [图片] 以下为源码 config.js源码: [代码]/* * @Author: 徐强国 * @Date: 2022-08-15 15:43:32 * @Description: Page公共方法扩展 */ const EventBus = require('./eventBus') let eventBus // 初始化页面的eventbus,事件用法参照dom2事件 export const initEventBus = (pageObj) => { // let eventBus = new EventBus(); if (!eventBus) { eventBus = new EventBus(); } else { } pageObj['$on'] = function () { let argu = Array.prototype.slice.call(arguments); eventBus.on(...argu) } pageObj['$off'] = function () { let argu = Array.prototype.slice.call(arguments); eventBus.off(...argu) } pageObj['$emit'] = function () { let argu = Array.prototype.slice.call(arguments); eventBus.emit(...argu) } // 创建页面声明的自定义事件 let events = pageObj['events']; if (Array.isArray(events)) { events.forEach((event, index) => { if (typeof event === 'string') { eventBus.createEvent(event) } else { console.error(`==请传入String类型的事件名称== index:${index}`, events) } }) } else if (typeof events !== 'undefined') { console.error('==events字段已被占用,用于声明当前页面需要创建的自定义事件,值为字符串数组== events:', events) } } module.exports = { onLoad(options) { this.$initPage() }, $initPage() { if (!this.$on) { initEventBus(this) } }, } [代码] eventBus.js源码 [代码]/** * @authors 徐强国 * @date 2022-8-8 * eventBus,订阅/发布 * */ // 是否是字符串 function isString(str) { return typeof str === 'string' } // 是否是函数 function isFunction(fn) { return typeof fn === 'function' } // 消息中心 class MessageHub { constructor() { this.pubDictionary = {} } // 创建发布者 createEvent(name, isGlobal) { if (!isString(name)) { console.error(`==请传入创建事件的名称 name==`) return false } let _pub = this.pubDictionary[name] if (_pub) { if (!isGlobal) { console.warn(`==${name} 事件已存在==`) } return _pub } else { let pub = new Publish(name, this) this.pubDictionary[name] = pub return pub } } removeEvent(name) { if (!isString(name)) { console.error(`==请传入删除事件的名称 name==`) return false } delete this.pubDictionary[name] } on(name, callback, mark) { if (!isString(name)) { console.error(`==请传入监听事件的名称 name==`) return false } console.log('ononoonon这里的区文体', this.pubDictionary, callback, mark) if (!isFunction(callback)) { console.error(`==请传入监听事件的回调函数 callback==`) return false } let pub = this.pubDictionary[name] if (pub) { let watcher = new Watcher(pub.dep, callback, mark) pub.dep.addSub(watcher) } else { console.error(`==尚未创建 ${name} 事件==`) } } off(name, callback) { if (!isString(name)) { console.error(`==请传入监听事件的名称 name==`) return false } if (!isFunction(callback)) { console.error(`==请传入监听事件的回调函数 callback==`) return false } let pub = this.pubDictionary[name] pub.dep.removeSub(callback) } emit(name, val) { if (!isString(name)) { console.error(`==请传入触发事件的名称 name==`) return false } console.log('这里的区文体emit', this.pubDictionary) let pub = this.pubDictionary[name] if (pub) { pub.refresh(val) } else { console.warn(`==${name} 事件不存在==`) } } clearEvent() { this.pubDictionary = {} } } // 发布者 class Publish { constructor(name, messageHub) { this.name = name this.messageHub = messageHub this.dep = new Dep(this) } refresh(val) { this.dep.notify(val) } } // 订阅者 class Watcher { constructor(dep, run, mark) { this.dep = dep this.run = run this.mark = mark || '' } update() { let val = this.dep.value let run = this.run run(val) } } // 依赖收集 class Dep { constructor(pub) { this.pub = pub this.subs = [] } addSub(sub) { this.subs.push(sub) } removeSub(run) { let sub = this.subs.filter(item => item.run === run)[0] remove(this.subs, sub) } notify(val) { this.value = val let subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } } function remove(arr, el) { for (let i = 0; i < arr.length; i++) { if (arr[i] === el) { arr.splice(i, 1) return true } } return false } module.exports = MessageHub [代码] pageExtends.js源码 [代码]/** * @authors 徐强国 * @date 2022-08-15 * 小程序全局构造函数App、Page扩展 */ const { appLiftTimes, pageLiftTimes } = require('./config'); // 判断是否是App的生命周期及原始方法 function isAppLiftTimes (name, fn) { if (typeof fn === 'function') { return appLiftTimes.indexOf(name) > -1 } return false } // 判断是否是Page的生命周期及原始方法 function isPageLiftTimes(name, fn) { if (typeof fn === 'function') { return pageLiftTimes.indexOf(name) > -1 } return false } // 函数混入 function rewriteFn(context, name, fn) { if (context[name]) { let originFn = context[name]; context[name] = function (e) { let argu = Array.prototype.slice.call(arguments); fn.apply(this, argu); return originFn.apply(this, argu) } } else { context[name] = fn } } // 是否是对象 function isObject(obj) { return obj !== null && typeof obj === 'object' } // 重写App const originApp = App; const appExtendsList = []; App = function (obj) { // app拓展方法 appExtendsList.forEach(item => { rewriteFn(obj, item.key, item.value) }) return originApp(obj) } const appExtends = function (key, value) { if (isAppLiftTimes(key, value)) { appExtendsList.push({ key, value }) } else { console.error('==*App暂不支持非生命周期的扩展*==', key) } } // 重写Page const originPage = Page; const pageExtendsList = []; Page = function (obj) { let illegalKeys = Object.keys(obj).filter(key => /^\$+/.test(key)); if (illegalKeys.length) { // throw new Error(`Page中自定义属性禁止以 \$ 开头, ${illegalKeys.join(', ')}`) console.error(`Page中自定义属性禁止以 \$ 开头, ${illegalKeys.join(', ')}`) } // 页面拓展方法 pageExtendsList.forEach(item => { // 非生命周期属性只能拓展一次 if (isPageLiftTimes(item.key, item.value)) { rewriteFn(obj, item.key, item.value) } else { if (typeof obj[item.key] === 'undefined') { obj[item.key] = item.value; } else { console.error(`Page中已拓展 ${item.key} 属性`, obj[item.key]) } } }) return originPage(obj) } const pageExtends = function (key, value) { // Page拓展属性,非生命周期的属性必须以 $ 开头 if (/^\$+/.test(key) || isPageLiftTimes(key, value)) { if (isPageLiftTimes(key, value) || !pageExtendsList.filter(item => item.key === key).length) { pageExtendsList.push({ key, value }) } else { console.warn(`==*Page中已扩展 ${key} 属性*==`) } } else { console.warn(`==*Page中拓展属性必须以 \$ 开头*==`, `\n key: ${key}`) } } const AppPlus = { appExtends: function (mixinObj, value) { if (typeof mixinObj === 'string') { appExtends(mixinObj, value) } else if (isObject(mixinObj)) { Object.keys(mixinObj).forEach(key => { appExtends(key, mixinObj[key]) }) } else { console.warn('==*请传入 对象 或者 key, value*==') } }, pageExtends: function (mixinObj, value) { if (typeof mixinObj === 'string') { pageExtends(mixinObj, value) } else if (isObject(mixinObj)) { Object.keys(mixinObj).forEach(key => { pageExtends(key, mixinObj[key]) }) } else { console.warn('==*请传入 对象 或者 key, value*==') } } } module.exports = AppPlus [代码] public.js源码 [代码]/* * @Author: 徐强国 * @Date: 2022-08-15 15:43:32 * @Description: Page公共方法扩展 */ const EventBus = require('./eventBus') let eventBus // 初始化页面的eventbus,事件用法参照dom2事件 export const initEventBus = (pageObj) => { // let eventBus = new EventBus(); if (!eventBus) { eventBus = new EventBus(); } else { } pageObj['$on'] = function () { let argu = Array.prototype.slice.call(arguments); eventBus.on(...argu) } pageObj['$off'] = function () { let argu = Array.prototype.slice.call(arguments); eventBus.off(...argu) } pageObj['$emit'] = function () { let argu = Array.prototype.slice.call(arguments); eventBus.emit(...argu) } // 创建页面声明的自定义事件 let events = pageObj['events']; if (Array.isArray(events)) { events.forEach((event, index) => { if (typeof event === 'string') { eventBus.createEvent(event) } else { console.error(`==请传入String类型的事件名称== index:${index}`, events) } }) } else if (typeof events !== 'undefined') { console.error('==events字段已被占用,用于声明当前页面需要创建的自定义事件,值为字符串数组== events:', events) } } module.exports = { onLoad(options) { this.$initPage() }, $initPage() { if (!this.$on) { initEventBus(this) } }, } [代码] index.js源码 [代码]/* * @Author: 徐强国 * @Date: 2022-08-15 15:18:12 * @Description: 小程序提供扩展App、Page扩展入口 * * * AppPlus提供拓展App及Page的接口,校验自定义属性命名 * @param appExtends * @parm pageExtends * * 传入一个对象,此对象的属性及方法将混入App或者Page实例中 * 生命周期函数将与自定义的声明周期混合,且先执行, * 其他属性只能以$开头,且不可覆盖、混入,应避免名称重复 */ const AppPlus = require('./pageExtends') const Public = require('./public') AppPlus.pageExtends(Public) [代码] 代码下载:https://github.com/TobinXu/MiniProgramEventBus/tree/main
2022-08-19 - 小程序富文本解析
wxParse 微信小程序富文本解析 原因 由于原作者仓库 wxParse 不再维护,我们项目中商品信息展示又是以wxParse这个用做富文本解析的; 于是乎,决定采用 递归Component 的方式对其进行重构一番; 原项目使用的 [代码]template[代码] 模板的方式渲染节点,存在以下问题: 节点渲染支持到12层,超出会原样输出 [代码]html[代码] 代码; 每一层级的循环模板都重复了一遍所有的可解析标签,代码十分臃肿。 [代码]li[代码]标签不支持 [代码]ol[代码] 有序列表渲染(统一采用的是 [代码]ul[代码] 无序列表),[代码]a[代码]标签无法实现跳转,也无法获取点击事件回调等等; 节点渲染没有绑定 [代码]key[代码] 值,一是在开发工具看到一堆的 [代码]warning[代码]信息(看着十分难受),二是节点频繁删除添加,无法比较[代码]key值[代码],造成 [代码]dom[代码] 节点频繁操作。 功能标签 目前该项目已经可以支持以下标签的渲染: audio标签(可自行更换组件样式,暂时采用微信公众号文章的[代码]audio[代码]音乐播放器的样式处理) ul标签 ol标签 li标签 a标签 img标签 video标签 br标签 button标签 h1, h2, h3, h4标签 文本节点 其余块级标签 其余行级标签 支持 npm包 引入 [代码]npm install --save wx-minicomponent [代码] 使用 原生组件使用方法 克隆 项目 代码,把 components目录 拷贝到你的小程序根目录下面; 在你的 page页面 对应的 [代码]json[代码] 文件引入 [代码]wxParse[代码] 组件 [代码]{ "usingComponents": { "wxParse": "/components/wxParse/wxParse" } } [代码] 组件调用 [代码]<wxParse nodes="{{ htmlText }}" /> [代码] npm组件使用方法 安装组件 [代码]npm install --save wx-minicomponent [代码] 小程序开发工具的 [代码]工具[代码] 栏找到 [代码]构建npm[代码],点击构建; 在页面的 json 配置文件中添加 [代码]wxParse[代码] 自定义组件的配置 [代码]{ "usingComponents": { "wxParse": "/miniprogram_npm/wx-minicomponent/wxParse" } } [代码] [代码]wxml[代码] 文件中引用 wxParse [代码]<wxParse nodes="{{ htmlText }}" /> [代码] 提示:详细步骤可以参考小程序的npm使用文档 补充组件:代码高亮展示组件使用 在 [代码]page[代码]的 [代码]json[代码] 文件里面引入 [代码]highLight[代码] 组件 原生引入: [代码]{ "usingComponents": { "highLight": "/components/highLight/highLight" } } [代码] npm组件引入: [代码]{ "usingComponents": { "highLight": "/miniprogram_npm/wx-minicomponent/highLight" } } [代码] 组件调用 [代码]<highLight codeText="{{codeText}}" /> [代码] 参数文档 wxParse:富文本解析组件 参数 说明 类型 例子 nodes 富文本字符 String “<div>test</div>” language 语言 String 可选:“html” | “markdown” (“md”) 受信任的节点 节点 例子 audio <audio title=“我是标题” desc=“我是小标题” src=“https://media.lycheer.net/lecture/25840237/5026279_1509614610000.mp3?0.1” /> a <a href=“www.baidu.com”>跳转到百度</a> p div span li ul ol img button h1 h2 h3 h4 … highLight:代码高亮解析组件 参数 说明 类型 例子 codeText 原始高亮代码字符 String “var num = 10;” language 代码语言类型 String 可选值:“javascript/typescript/css/xml/sql/markdown” 提示:如果是html语言,language的值为xml wxAudio:仿微信公众号文章音频播放组件 参数 说明 类型 例子 title 标题 String “test” desc 副标题 String “sub test” src 音频地址 String 示例展示 富文本解析 html文本解析实例 [图片] markdown文本解析实例 [图片] 代码高亮 [图片] 更新历史 2020-5-31 迁移utils目录到wxParse目录下; 富文本增加markdown文本解析支持; 2020-5-21: 富文本组件image标签添加loading过渡态,优化图片加载体验 2020-5-17: 完善组件参数文档,增加wxParse对audio标签标题,副标题的解析功能 2020-5-13: 增加css,html,ts,sql,markdown代码高亮提示支持 2020-5-6: 增加图片预览功能 项目地址 项目地址:https://github.com/csonchen/wxParse
2020-06-01 - 在scroll-view中使用sticky
.container{ width: 600rpx; height: 800rpx; } .a { position: sticky; position: -webkit-sticky; top: 0; width: 100%; height: 200rpx; background-color: black; } .b { width: 100%; height: 3000rpx; background-color: red; } <scroll-view class="container"> <view> <view class="a"></view> <view class="b"></view> </view> </scroll-view> sticky的元素在到达父元素的底部时会失效 scroll-view的高度为800rpx,但是scrollHeight为3200rpx,所以在scroll-view中嵌套一个view就能顺利定位
2019-12-18 - 社区每周 |小程序基础库2.16.0发布、短链接服务更新及上周问题反馈(3.01-3.05)
各位微信开发者: 以下是小程序基础库 2.16.0 发布、关于长链转短链接口停止生成短链公告及上周我们在社区收到的问题反馈与需求的处理进度,希望同大家一同打造小程序生态。 小程序基础库 2.16.0 已发布 小程序基础库 2.16.0 已发布,请大家基于业务情况关注相关变更。如遇问题请及时在该帖下方留言或在小程序交流专区发表标题包含「基础库2.16.0」的帖子反馈。本次更新如下: 新增 API ugc 选择地理位置新增 API wx.getUserProfile接口新增 组件 页面容器组件更新 API wx.login默认可获取unionID更新 API 后台地理位置授权优化更新 API 开放 wx.chooseContact更新 API wx.shareToWeRun 分享数据到微信运动更新 组件 textarea 支持 confirm-hold更新 插件 支持实时日志修复 框架 vConsole 在自定义 tabBar 下被遮挡 修复 API getOpenerEventChannel 返回错误 修复 组件 camera onCameraInitDone 事件偶现丢失问题 关于长链转短链接口停止生成短链的公告 长链转短链服务致力于优化用户体验,在微信中提升扫码速度和成功率,解决开发者原链接(商品、支付二维码等)太长导致微信扫码速度和成功率下降的问题。但随着技术的发展,微信扫码能力已有较大提升,不再需要对原始链接进行转换。 平台将对2021年3月15日之后停止该接口新生成的短链的能力,已生成的短链暂不受影响(预计下半年停止历史生成短链接解析服务),希望开发者尽快调整相关业务逻辑,共创微信绿色、健康的生态环境。 上周问题反馈和处理进度(3.01-3.05) 已修复的问题iphone x与xr 全屏后无画面的问题 查看详情 安卓上 textarea 不显示完成按钮 ,基础库2.16.0,2.15.0不显示的问题 查看详情 修复中的问题 安卓 textarea 中文符号换行问题 查看详情 camera onCameraInitDone 事件偶现丢失问题 查看详情 picker-view 临界时没触发 change 查看详情 virtualHost 的组件节点无法被 selectComponent 和 getRelationNodes 选中 查看详情 ci下载40M+的sourcemap文件时,容易出错 查看详情 lazyCodeLoading 导致sourcemap下载不了 查看详情 map组件在show-compass无效,总是显示指南针 查看详情 开发者工具 input focus 与真机不一致 查看详情 WXML 面板 No matching selector or style 查看详情 微信开发工具调用wx.chooseImage后,不点确定点取消后,不调用onShow生命周期函数 查看详情 工具更新之后调试器面板显示不出来 查看详情 需求反馈需求评估中createInnerAudioContext什么时候支持createScriptProcessor 查看详情 第三方平台代开运维中心接口时,只能查到功能异常的反馈信息? 查看详情 CloudBase CMS支持的 Webhook 触发,仅限与在cms里的操作? 查看详情 微信团队 2021.3.12
2021-03-12 - iconfont硬核,支持多色彩、支持自定义颜色
目前市面上很多教程叫我们如何把iconfont的字体整到小程序中,基本千篇一律,都有一个特点,就是需要用字体文件。 但是用字体文件意味着只能设置一种颜色了(单色)。这是个硬伤~~~ 所以,今天笔者花了一天时间,做了一个支持多色彩、支持自定义颜色的iconfont开源库。你一定会喜欢 [代码]<iconfont name="alipay" /> <iconfont name="alipay" color="{{['red', 'orange']}}" size="300" /> [代码] [图片] 特性: 1、纯组件 2、不依赖字体文件 3、支持px和rpx两种格式 4、原样渲染多色彩图标 4、图标颜色可定制 地址:https://github.com/iconfont-cli/mini-program-iconfont-cli 喜欢的小伙伴记得给个star呦。
2019-09-26