- json2canvas:使用JSON生成小程序海报
作者:诗人的咸鱼 原文:小程序生成分享海报,一个json就够了。同时支持web Fundebug经授权转载,版权归原作者所有。 需求 在项目里写过几个canvas生成分享海报页面后,觉得这是个重复且冗余的工作.于是就想有没有能通过类似json直接生成海报的库. 然后就在github找到到两个项目: wxa-plugin-canvas,不太喜欢配置文件的写法.就没多去了解 mp_canvas_drawer,使用方式就比较符合直觉,不过可惜功能有点少. 然后就想着能不能自己再造个轮子.于是就有了这个项目 json2canvas,你可以简单的理解为是mp_canvas_drawer的增强版吧. json2canvas canvas绘制海报,写个json就够了. 项目的canvas绘制是基于cax实现的.所以天然的带来一个好处,json2canvas同时支持小程序和web 功能 支持缩放. 如果设计稿是750,而画布只有375时.你不需要任何换算,只需要将scale设置为0.5即可. 支持文本(长文本自动换行,感谢 coolzjy@v2ex 提供的正则 https://regexr.com/4f12l ,优化了换行的计算方式(不会粗暴的折断单词)) 支持图片(圆角) 支持圆型,矩形,矩形圆角 支持分组(cax里很好用的一个功能) 同时支持小程序和web 示例 web-demo 界面左边的json,可以进行编辑,直接看效果哟~ 小程序-demo [代码]git clone https://github.com/willnewii/json2canvas.git 微信开发者工具导入项目 example/weapp/ [代码] 小程序安装 [代码]npm i json2canvas 微信开发者工具->工具->构建npm [代码] 在需要使用的界面引入Component [代码]{ "usingComponents": { "json2canvas":"/miniprogram_npm/json2canvas/index" } } [代码] 效果图 想要生成一个这样的海报,需要怎么做?(红框是图片元素,蓝框是文字元素,其余的是一张背景图。) [图片] 一个json就搞定.具体支持的元素和参数,请查看项目readme [代码]{ "width": 750, "height": 1334, "scale": 0.5, "children": [ { "type": "image", "url": "http://res.mayday5.me/wxapp/wxavatar/tmp/bg_concerts_1.jpg", "width": 750, "height": 1334 }, { "type": "image", "url": "http://res.mayday5.me/wxapp/wxavatar/tmp/wxapp_code.jpg", "width": 100, "x": 48, "y": 44, "isCircular": true, }, { "type": "circle", "r": 50, "lineWidth": 5, "strokeStyle": "#CCCCCC", "x": 48, "y": 44, }, { "type": "text", "text": "歌词本", "font": "30px Arial", "color": "#FFFFFF", "x": 168, "y": 75, "shadow": { "color": "#000", "offsetX": 2, "offsetY": 2, "blur": 2 } }, { "type": "image", "url": "http://res.mayday5.me/wxapp/wxavatar/tmp/medal_concerts_1.png", "width": 300, "x": "center", "y": 361 }, { "type": "text", "text": "一生活一场 五月天", "font": "38px Arial", "color": "#FFFFFF", "x": "center", "y": 838, "shadow": { "color": "#000", "offsetX": 2, "offsetY": 2, "blur": 2 } }, { "type": "text", "text": "北京6场,郑州2场,登船,上班,听到你想听的歌了吗?", "font": "24px Arial", "color": "#FFFFFF", "x": "center", "y": 888, "shadow": { "color": "#000", "offsetX": 2, "offsetY": 2, "blur": 2 } }, { "type": "rect", "width": 750, "height": 193, "fillStyle": "#FFFFFF", "x": 0, "y": "bottom" }, { "type": "image", "url": "http://res.mayday5.me/wxapp/wxavatar/tmp/wxapp_code.jpg", "width": 117, "height": 117, "x": 47, "y": 1180 }, { "type": "text", "text": "长按识别小程序二维码", "font": "26px Arial", "color": "#858687", "x": 192, "y": 1202 }, { "type": "text", "text": "加入五月天 永远不会太迟", "font": "18px Arial", "color": "#A4A5A6", "x": 192, "y": 1249 }] } [代码] 问题反馈 有什么问题可以直接提issue
2019-06-29 - BeautyWe.js 一套专注于微信小程序的开发范式
摘要: 小程序框架… 作者:JerryC 原文:BeautyWe.js 一套专注于微信小程序的开发范式 Fundebug经授权转载,版权归原作者所有。 [图片] 官网:beautywejs.com Repo: beautywe 一个简单的介绍 BeautyWe.js 是什么? 它是一套专注于微信小程序的企业级开发范式,它的愿景是: 让企业级的微信小程序项目中的代码,更加简单、漂亮。 为什么要这样命名呢? Write beautiful code for wechat mini program by the beautiful we! 「We」 既是我们的 We,也是微信的 We,Both beautiful! 那么它有什么卖点呢? 专注于微信小程序环境,写原汁原味的微信小程序代码。 由于只专注于微信小程序,它的源码也很简单。 插件化的编程方式,让复杂逻辑更容易封装。 再加上一些配套设施: 一些官方插件。 一套开箱即用,包含了工程化、项目规范以及微信小程序环境独特问题解决方案的框架。 一个CLI工具,帮你快速创建应用,页面,组件等。 它由以下几部分组成: 一个插件化的核心 - BeautyWe Core 对 App、Page 进行抽象和包装,保持传统微信小程序开发姿势,同时开放部分原生能力,让其具有「可插件化」的能力。 一些官方插件 — BeautyWe Plugins 得益于 Core 的「可插件化」特性,封装复杂逻辑,实现可插拔。官方对于常见的需求提供了一些插件:如增强存储、发布/订阅、状态机、Logger、缓存策略等。 一套开箱即用的项目框架 - BeautyWe Framework 描述了一种项目的组织形式,开箱即用,集成了 [代码]BeautyWe Core[代码] ,并且提供了如:全局窗口、开发规范、多环境开发、全局配置、NPM 等解决方案。 一个CLI工具 - BeautyWe Cli 提供快速创建应用、页面、插件,以及项目构建功能的命令行工具。并且还支持自定义的创建模板。 一个简单的例子 下载 [图片] 用 BeautyWe 包装你的应用 [图片] 之后,你就能使用 BeautyWe Plugin 提供的能力了。 [图片] 开放原生App/Page,支持插件化 [代码]new BtApp({...})[代码] 的执行结果是对原生的应用进行包装,其中包含了「插件化」的处理,然后返回一个新的实例,这个实例适配原生的 [代码]App()[代码] 方法。 下面来讲讲「插件化」到底做了什么事情。 首先,插件化开放了原生 App 的四种能力: Data 域 把插件的 Data 域合并到原生 App 的 Data 域中,这一块很容易理解。 原生钩子函数 使原生钩子函数(如 [代码]onShow[代码], [代码]onLoad[代码])可插件化。让原生App与多个插件可以同时监听同一个钩子函数。如何工作的,下面会细说。 事件钩子函数 使事件钩子函数(与 view 层交互的钩子函数),尽管在实现上有一些差异,但是实现原理跟「原生钩子函数」一样的。 自定义方法 让插件能够给使用者提供 API。为了保证插件提供的 API 足够的优雅,支持当调用插件 API 的时候(如 event 插件 [代码]this.event.on(...)[代码]),API 方法内部仍然能通过 [代码]this[代码] 获取到原生实例。 钩子函数的插件化 原生钩子函数,事件钩子函数我们统一称为「钩子函数」。 对于每一个钩子函数,内部是维护一个以 Series Promise 方式执行的执行队列。 以 [代码]onShow[代码] 为例,将会以这样的形式执行: native.onShow → pluginA.onShow → pluginB.onShow → … 下面深入一下插件化的原理: [图片] 工作原理是这样的: 经过 [代码]new BtApp(...)[代码] 包装,所有的钩子函数,都会有一个独立的执行队列, 首先会把原生的各个钩子函数 [代码]push[代码] 到对应的队列中。然后每 [代码]use[代码] 插件的时候,都会分解插件的钩子函数,往对应的队列 [代码]push[代码]。 当 [代码]Native App[代码](原生)触发某个钩子的时候,[代码]BtApp[代码] 会以 Promise Series 的形式按循序执行对应队列里面的函数。 特殊的,[代码]onLaunch[代码] 和 [代码]onLoad[代码] 的执行队列中,会在队列顶部插入一个初始化的任务([代码]initialize[代码]),它会以同步的方式按循序执行 [代码]Initialize Queue[代码] 里面的函数。这正是插件生命周期函数中的 [代码]plugin.initialize[代码]。 这种设计能提供以下功能: 可插件化。 只需要往对应钩子函数的事件队列中插入任务。 支持异步。 由于是以 Promise Series 方式运行的,其中一个任务返回一个 Promise,下一个任务会等待这个任务完成再开始。如果发生错误,会流转到原生的 [代码]onError()[代码] 中。 解决了微信小程序 [代码]app.js[代码] 中 [代码]getApp() === undefinded[代码]问题。 造成这个问题,本质是因为 [代码]App()[代码] 的时候,原生实例未创建。但是由于 Promise 在 event loop 中是一个微任务,被注册在下一次循环。所以 Promise 执行的时候 [代码]App()[代码] 早已经完成了。 一些官方插件 BeautyWe 官方提供了一系列的插件: 增强存储: Storage 数据列表:List Page 缓存策略:Cache 日志:Logger 事件发布/订阅:Event 状态机:Status 它们的使用很简单,哪里需要插哪里。 由于篇幅的原因,下面挑几个比较有趣的来讲讲,更多的可以看看官方文档:BeautyWe 增强存储 Storage 该功能由 @beautywe/plugin-storage 提供。 由于微信小程序原生的数据存储生命周期跟小程序本身一致,即除用户主动删除或超过一定时间被自动清理,否则数据都一直可用。 所以该插件在 [代码]wx.getStorage/setStorage[代码] 的基础上,提供了两种扩展能力: 过期控制 版本隔离 一些简单的例子 安装 [代码]import { BtApp } from '@beautywe/core'; import storage from '@beautywe/plugin-storage'; const app = new BtApp(); app.use(storage()); [代码] 过期控制 [代码]// 7天后过期 app.storage.set('name', 'jc', { expire: 7 }); [代码] 版本隔离 [代码]app.use({ appVersion: '0.0.1' }); app.set('name', 'jc'); // 返回 jc app.get('name'); // 当版本更新后 app.use({ appVersion: '0.0.2' }); // 返回 undefined; app.get('name'); [代码] 更多的查看 @beautywe/plugin-storage 官方文档 数据列表 List Page 对于十分常见的数据列表分页的业务场景,[代码]@beautywe/plugin-listpage[代码] 提供了一套打包方案: 满足常用「数据列表分页」的业务场景 支持分页 支持多个数据列表 自动捕捉下拉重载:[代码]onPullDownRefresh[代码] 自动捕捉上拉加载:[代码]onReachBottom[代码] 自带请求锁,防止帕金森氏手抖用户 简单优雅的 API 一个简单的例子: [代码]import BeautyWe from '@beautywe/core'; import listpage from '@beautywe/plugin-listpage'; const page = new BeautyWe.BtPage(); // 使用 listpage 插件 page.use(listpage({ lists: [{ name: 'goods', // 数据名 pageSize: 20, // 每页多少条数据,默认 10 // 每一页的数据源,没次加载页面时,会调用函数,然后取返回的数据。 fetchPageData({ pageNo, pageSize }) { // 获取数据 return API.getGoodsList({ pageNo, pageSize }) // 有时候,需要对服务器的数据进行处理,dataCooker 是你定义的函数。 .then((rawData) => dataCooker(rawData)); }, }], enabledPullDownRefresh: true, // 开启下拉重载, 默认 false enabledReachBottom: true, // 开启上拉加载, 默认 false })); // goods 数据会被加载到,goods 为上面定义的 name // this.data.listPage.goods = { // data: [...], // 视图层,通过该字段来获取具体的数据 // hasMore: true, // 视图层,通过该字段来识别是否有下一页 // currentPage: 1, // 视图层,通过该字段来识别当前第几页 // totalPage: undefined, // } [代码] 只需要告诉 [代码]listpage[代码] 如何获取数据,它会自动处理「下拉重载」、「上拉翻页」的操作,然后把数据更新到 [代码]this.data.listPage.goods[代码] 下。 View 层只需要描述数据怎么展示: [代码]<view class="good" wx:for="listPage.goods.data"> ... </view> <view class="no-more" wx:if="listPage.goods.hasMore === false"> 没有更多了 </view> [代码] [代码]listpage[代码] 还支持多数据列表等其他更多配置,详情看:@beautywe/plugin-listpage 缓存策略 Cache [代码]@beautywe/plugin-cache[代码] 提供了一个微信小程序端缓存策略,其底层由 super-cache 提供支持。 特性 提供一套「服务端接口耗时慢,但加载性能要求高」场景的解决方案 满足最基本的缓存需求,读取(get)和保存(set) 支持针对缓存进行逻辑代理 灵活可配置的数据存储方式 How it work 一般的请求数据的形式是,页面加载的时候,从服务端获取数据,然后等待数据返回之后,进行页面渲染: [图片] 但这种模式,会受到服务端接口耗时,网络环境等因素影响到加载性能。 对于加载性能要求高的页面(如首页),一般的 Web 开发我们有很多解决方案(如服务端渲染,服务端缓存,SSR 等)。 但是也有一些环境不能使用这种技术(如微信小程序)。 Super Cache 提供了一个中间数据缓存的解决方案: [图片] 思路: 当你需要获取一个数据的时候,如果有缓存,先把旧的数据给你。 然后再从服务端获取新的数据,刷新缓存。 如果一开始没有缓存,则请求服务端数据,再把数据返回。 下一次请求缓存,从第一步开始。 这种解决方案,舍弃了一点数据的实时性(非第一次请求,只能获取上一次最新数据),大大提高了前端的加载性能。 适合的场景: 数据实时性要求不高。 服务端接口耗时长。 使用 [代码]import { BtApp } from '@beautywe/core'; import cache from '@beautywe/plugin-cache'; const app = new BtApp(); app.use(cache({ adapters: [{ key: 'name', data() { return API.fetch('xxx/name'); } }] })); [代码] 假设 [代码]API.fetch('xxx/name')[代码] 是请求服务器接口,返回数据:[代码]data_from_server[代码] 那么: [代码]app.cache.get('name').then((value) => { // value: 'data_from_server' }); [代码] 更多的配置,详情看:@beautywe/plugin-cache 日志 Logger 由 [代码]@beautywe/logger-plugin[代码] 提供的一个轻量的日志处理方案,它支持: 可控的 log level 自定义前缀 日志统一处理 使用 [代码]import { BtApp } from '@beautywe/core'; import logger from '@beautywe/plugin-logger'; const page = new BtApp(); page.use(logger({ // options })); [代码] API [代码]page.logger.info('this is info'); page.logger.warn('this is warn'); page.logger.error('this is error'); page.logger.debug('this is debug'); // 输出 // [info] this is info // [warn] this is warn // [error] this is error // [debug] this is debug [代码] Level control 可通过配置来控制哪些 level 该打印: [代码]page.use(logger({ level: 'warn', })); [代码] 那么 [代码]warn[代码] 以上的 log ([代码]info[代码], [代码]debug[代码])就不会被打印,这种满足于开发和生成环境对 log 的不同需求。 level 等级如下: [代码]Logger.LEVEL = { error: 1, warn: 2, info: 3, debug: 4, }; [代码] 更多的配置,详情看:@beautywe/plugin-logger BeautyWe Framework [代码]@beautywe/core[代码] 和 [代码]@beautywe/plugin-...[代码] 给小程序提供了: 开放原生,支持插件化 —— by core 各种插件 —— by plugins 但是,还有很多的开发中实际还会遇到的痛点,是上面两个解决不到的。 如项目的组织、规范、工程化、配置、多环境等等 这些就是,「BeautyWe Framework」要解决的范畴。 它作为一套开箱即用的项目框架,提供了这些功能: 集成 BeautyWe Core NPM 支持 全局窗口 全局 Page,Component 全局配置文件 多环境开发 Example Pages 正常项目需要的标配:ES2015+,sass,uglify,watch 等 以及我们认为良好的项目规范(eslint,commit log,目录结构等) 也是由于篇幅原因,挑几个有趣的来讲讲,更多的可以看看官方文档:BeautyWe 快速创建 首先安装 [代码]@beautywe/cli[代码] [代码]$ npm i @beautywe/cli -g [代码] 创建应用 [代码]$ beautywe new app > appName: my-app > version: 0.0.1 > appid: 123456 > 这样可以么: > { > "appName": "my-app", > "version": "0.0.1", > "appid": "123456" > } [代码] 回答几个问题之后,项目就生成了: [代码]my-app ├── gulpfile.js ├── package.json └── src ├── app.js ├── app.json ├── app.scss ├── assets ├── components ├── config ├── examples ├── libs ├── npm ├── pages └── project.config.json [代码] 创建页面、组件、插件 页面 主包页面:[代码]beautywe new page <path|name>[代码] 分包页面:[代码]beautywe new page --subpkg <subPackageName> <path|name>[代码] 组件 [代码]beautywe new component <name>[代码] 插件 [代码]beautywe new plugin <name>[代码] 自定义模板 在 [代码]./.templates[代码] 目录中,存放着快速创建命令的创建模板: [代码]$ tree .templates .templates ├── component │ ├── index.js │ ├── index.json │ ├── index.scss │ └── index.wxml ├── page │ ├── index.js │ ├── index.json │ ├── index.scss │ └── index.wxml └── plugin └── index.js [代码] 可以修改里面的模板,来满足项目级别的自定义模板创建。 全局窗口 我们都知道微信小程序是「单窗口」的交互平台,一个页面对应一个窗口。 而在业务开发中,往往会有诸如这种述求: 自定义的 toast 样式 页面底部 copyright 全局的 loading 样式 全局的悬浮控件 … 稍微不优雅的实现可以是分别做成独立的组件,然后每一个页面都引入进来。 这种做法,我们会有很多的重复代码,并且每次新建页面,都要引入一遍,后期维护也会很繁琐。 而「全局窗口」的概念是:希望所有页面之上有一块地方,全局性的逻辑和交互,可以往里面搁。 global-view 组件 这是一个自定义组件,源码在 [代码]/src/components/global-view[代码] 每个页面的 wxml 只需要在顶层包一层: [代码]<global-view id="global-view"> ... </global-view> [代码] 需要全局实现的交互、样式、组件,只需要维护这个组件就足够了。 全局配置文件 在 [代码]src/config/[代码] 目录中,可以存放各种全局的配置文件,并且支持以 Node.js 的方式运行。(得益于 Node.js Power 特性)。 如 [代码]src/config/logger.js[代码]: [代码]const env = process.env.RUN_ENV || 'dev'; const logger = Object.assign({ prefix: 'BeautyWe', level: 'debug', }, { // 开发环境的配置 dev: { level: 'debug', }, // 测试环境的配置 test: { level: 'info', }, // 线上环境的配置 prod: { level: 'warn', }, }[env] || {}); module.exports.logger = logger; [代码] 然后我们可以这样读取到 config 内容: [代码]import { logger } from '/config/index'; // logger.level 会根据环境不同而不同。 [代码] Beautywe Framework 默认会把 config 集成到 [代码]getApp()[代码] 的示例中: [代码]getApp().config; [代码] 多环境开发 BeautyWe Framework 支持多环境开发,其中预设了三套策略: dev test prod 我们可以通过命令来运行这三个构建策略: [代码]beautywe run dev beautywe run test beautywe run prod [代码] 三套环境的差异 Beautywe Framework 源码默认在两方面使用了多环境: 构建任务([代码]gulpfile.js/env/...[代码]) 全局配置([代码]src/config/...[代码]) 构建任务的差异 构建任务 说明 dev test prod clean 清除dist文件 √ √ √ copy 复制资源文件 √ √ √ scripts 编译JS文件 √ √ √ sass 编译scss文件 √ √ √ npm 编译npm文件 √ √ √ nodejs-power 编译Node.js文件 √ √ √ watch 监听文件修改 √ scripts-min 压缩JS文件 √ sass-min 压缩scss文件 √ npm-min 压缩npm文件 √ image-min 压缩图片文件 √ clean-example 清除示例页面 √ Node.js Power Beautywe Framework 的代码有两种运行环境: Node.js 运行环境,如构建任务等。 微信小程序运行环境,如打包到 [代码]dist[代码] 文件夹的代码。 运行过程 Node.js Power 本质是一种静态编译的实现。 把某个文件在 Node.js 环境运行的结果,输出到微信小程序运行环境中,以此来满足特定的需求。 Node.js Power 会把项目中 [代码]src[代码] 目录下类似 [代码]xxx.nodepower.js[代码] 命名的文件,以 Node.js 来运行, 然后把运行的结果,以「字面量对象」的形式写到 [代码]dist[代码] 目录下对应的同名文件 [代码]xxx.nodepower.js[代码] 文件去。 以 [代码]src/config/index.nodepower.js[代码] 为例: [代码]const fs = require('fs'); const path = require('path'); const files = fs.readdirSync(path.join(__dirname)); const result = {}; files .filter(name => name !== 'index.js') .forEach((name) => { Object.assign(result, require(path.join(__dirname, `./${name}`))); }); module.exports = result; [代码] 该文件,经过 Node.js Power 构建之后: [代码]dist/config/index.nodepower.js[代码]: [代码]module.exports = { "appInfo": { "version": "0.0.1", "env": "test", "appid": "wx85fc0d03fb0b224d", "name": "beautywe-framework-test-app" }, "logger": { "prefix": "BeautyWe", "level": "info" } }; [代码] 这就满足了,随意往 [代码]src/config/[代码] 目录中扩展配置文件,都能被自动打包。 Node.js Power 已经被集成到多环境开发的 dev, test, prod 中去。 当然,你可以手动运行这个构建任务: [代码]$ gulp nodejs-power [代码] NPM BeautyWe Framework 实现支持 npm 的原理很简单,总结一句话: 使用 webpack 打包 [代码]src/npm/index.js[代码] ,以 commonjs 格式输出到 [代码]dist/npm/index.js[代码] [图片] 这样做的好处: 实现简单。 让 npm 包能集中管理,每次引入依赖,都好好的想一下,避免泛滥(尤其在多人开发中)。 使用 [代码]ll dist/npm/index.js[代码] 命令能快速看到项目中的 npm 包使占了多少容量。 新增 npm 依赖 在 [代码]src/npm/index.js[代码] 文件中,进行 export: [代码]export { default as beautywe } from '@beautywe/core'; [代码] 然后在其他文件 import: [代码]import { beautywe } from './npm/index'; [代码] 更多 总的来说,BeautyWe 是一套微信小程序的开发范式。 [代码]core[代码] 和 [代码]plugins[代码] 扩展原生,提供复杂逻辑的封装和插拔式使用。 而 [代码]framework[代码] 则负责提供一整套针对于微信小程序的企业级项目解决方案,开箱即用。 其中还有更多的内容,欢迎浏览官网:beautywejs.com 关于Fundebug Fundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java线上应用实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了10亿+错误事件,付费客户有阳光保险、核桃编程、荔枝FM、掌门1对1、微脉、青团社等众多品牌企业。欢迎大家免费试用!
2019-07-09 - 在小程序中实现 Mixins 方案
作者:jrainlau 原文:在小程序中实现 Mixins 方案 Fundebug经授权转载,版权归原作者所有。 在原生开发小程序的过程中,发现有多个页面都使用了几乎完全一样的逻辑。由于小程序官方并没有提供 Mixins 这种代码复用机制,所以只能采用非常不优雅的复制粘贴的方式去“复用”代码。随着功能越来越复杂,靠复制粘贴来维护代码显然不科学,于是便寻思着如何在小程序里面实现 Mixins。 什么是 Mixins Mixins 直译过来是“混入”的意思,顾名思义就是把可复用的代码混入当前的代码里面。熟悉 VueJS 的同学应该清楚,它提供了更强大了代码复用能力,解耦了重复的模块,让系统维护更加方便优雅。 先看看在 VueJS 中是怎么使用 Mixins 的。 [代码]// define a mixin object var myMixin = { created: function () { this.hello() }, methods: { hello: function () { console.log('hello from mixin!') } } } // define a component that uses this mixin var Component = Vue.extend({ mixins: [myMixin] }) var component = new Component() // => "hello from mixin!" [代码] 在上述的代码中,首先定义了一个名为 [代码]myMixin[代码] 的对象,里面定义了一些生命周期函数和方法。接着在一个新建的组件里面直接通过 [代码]mixins: [myMixin][代码] 的方式注入,此时新建的组件便获得了来自 [代码]myMixin[代码] 的方法了。 明白了什么是 Mixins 以后,便可开始着手在小程序里面实现了。 Mixins 的机制 Mixins 也有一些小小的细节需要注意的,就是关于生命周期事件的执行顺序。在上一节的例子中,我们在 [代码]myMixin[代码] 里定义了一个 [代码]created()[代码] 方法,这是 VueJS 里面的一个生命周期事件。如果我们在新建组件 [代码]Component[代码] 里面也定义一个 [代码]created()[代码] 方法,那么执行结果会是如何呢? [代码]var Component = Vue.extend({ mixins: [myMixin], created: function () { console.log('hello from Component!') } }) var component = new Component() // => // Hello from mixin! // Hello from Component! [代码] 可以看运行结果是先输出了来自 Mixin 的 log,再输出来自组件的 log。 除了生命周期函数以外,再看看对象属性的混入结果: [代码]// define a mixin object const myMixin = { data () { return { mixinData: 'data from mixin' } } } // define a component that uses this mixin var Component = Vue.extend({ mixins: [myMixin], data () { return { componentData: 'data from component' } }, mounted () { console.log(this.$data) } }) var component = new Component() [代码] [图片] 在 VueJS 中,会把来自 Mixins 和组件的对象属性当中的内容(如 [代码]data[代码], [代码]methods[代码]等)混合,以确保两边的数据都同时存在。 经过上述的验证,我们可以得到 VueJS 中关于 Mixins 运行机制的结论: 生命周期属性,会优先执行来自 Mixins 当中的,后执行来自组件当中的。 对象类型属性,来自 Mixins 和来自组件中的会共存。 但是在小程序中,这套机制会和 VueJS 的有一点区别。在小程序中,自定义的方法是直接定义在 Page 的属性当中的,既不属于生命周期类型属性,也不属于对象类型属性。为了不引入奇怪的问题,我们为小程序的 Mixins 运行机制多加一条: 小程序中的自定义方法,优先级为 Page > Mixins,即 Page 中的自定义方法会覆盖 Mixins 当中的。 代码实现 在小程序中,每个页面都由 [代码]Page(options)[代码] 函数定义,而 Mixins 则作用于这个函数当中的 [代码]options[代码] 对象。因此我们实现 Mixins 的思路就有了——劫持并改写 [代码]Page[代码] 函数,最后再重新把它释放出来。 新建一个 [代码]mixins.js[代码] 文件: [代码]// 保存原生的 Page 函数 const originPage = Page Page = (options) => { const mixins = options.mixins // mixins 必须为数组 if (Array.isArray(mixins)) { delete options.mixins // mixins 注入并执行相应逻辑 options = merge(mixins, options) } // 释放原生 Page 函数 originPage(options) } [代码] 原理很简单,关键的地方在于 [代码]merge()[代码] 函数。[代码]merge[代码] 函数即为小程序 Mixins 运行机制的具体实现,完全按照上一节总结的三条结论来进行。 [代码]// 定义小程序内置的属性/方法 const originProperties = ['data', 'properties', 'options'] const originMethods = ['onLoad', 'onReady', 'onShow', 'onHide', 'onUnload', 'onPullDownRefresh', 'onReachBottom', 'onShareAppMessage', 'onPageScroll', 'onTabItemTap'] function merge (mixins, options) { mixins.forEach((mixin) => { if (Object.prototype.toString.call(mixin) !== '[object Object]') { throw new Error('mixin 类型必须为对象!') } // 遍历 mixin 里面的所有属性 for (let [key, value] of Object.entries(mixin)) { if (originProperties.includes(key)) { // 内置对象属性混入 options[key] = { ...value, ...options[key] } } else if (originMethods.includes(key)) { // 内置方法属性混入,优先执行混入的部分 const originFunc = options[key] options[key] = function (...args) { value.call(this, ...args) return originFunc && originFunc.call(this, ...args) } } else { // 自定义方法混入 options = { ...mixin, ...options } } } }) return options } [代码] Mixins 使用 在小程序的 [代码]app.js[代码] 里引入 [代码]mixins.js[代码] [代码]require('./mixins.js') [代码] 撰写一个 [代码]myMixin.js[代码] [代码]module.exports = { data: { someData: 'myMixin' }, onShow () { console.log('Log from mixin!') } } [代码] 在 [代码]page/index/index.js[代码] 中使用 [代码]Page({ mixins: [require('../../myMixin.js')] }) [代码] [图片] 大功告成!此时小程序已经具备 Mixins 的能力,对于代码解耦与复用来说将会更加方便。
2019-06-28 - 小程序web-view转发url中参数丢失
小程序web-view转发url中参数丢失 我在web-view中打开了一个点击a a就会显示a的页面,但是分享过去的时候能获取到a的完整地址,可是在分享的哪里点开就只能获取地址,不能获取到后面的参数 我想在分享哪里打开也是【http://192.168.1.41/pro/buitef/?fri_beautician_id=1】这个地址,请我这个是哪里的问题 [图片] [图片] [图片]
2018-08-02