- 【高校开发者】简单分享下
简单分享下 写这篇文章主要是想跟刚迈入编程这条水泥路的小程序新手开发人员一起交流交流的,因为咱也只是个开发小程序的菜鸟人员,技术帖咱是写不来了,就只能跟大家唠唠嗑了。 1. 开篇 接触微信小程序也有一年多了,在这一年多的时间里也写过五、六个小程序,最初接触小程序是因为我们想通过小程序,来推广运营公众号和扩展公众号功能。 之后的开发就完全是因为好玩和兴趣了吧! 好玩??? 怎么玩? 大一大二咱大家刚接触算法编程,或许对算法编程是这样的: [图片] [图片] [图片] [图片] 而且经常会听到学这到底干什么用的呀! 好烦,这周数据结构又学了几个算法,二叉树?图?那行吧,咱就用这些个数据结构写个小程序吧! 又或者我们玩了一些好玩的小程序,那行吧,咱也试着用我们学过的算法知识在小程序上也写一个吧? 即使是些老掉牙的游戏,贪吃蛇、扫雷、连连看消消乐、成语接龙等等这些小游戏,实现后你还能在这些个游戏基础上发挥你的脑洞,进行创作改编。或许还能让你编个爆款小程序出来也说不定,是吧。 这些小游戏,小程序内部或多或少都用到了传说中的算法。 之所以选择用小程序来实现,还有一个最主要的原因就是小程序能够很轻易、很低调的就让我们秀一波。 小程序在真机上运行测试很容易,一个二维码就搞定,而且开发的小程序,不仅仅只是让我们本专业同学能够测测玩玩,还能够让更多非本专业的,好比你对象呀同学呀爸爸妈妈呀爷爷奶奶呀哥哥姐姐弟弟妹妹姑姑婶婶阿姨婆婆呀等等等等(只要他们有微信就行)都能够一起体验我们写的这些个小游戏呀。(o(∩_∩)o ) 所以在小程序开发过程中不仅学到了知识,还让我们见到了知识,玩到了知识,还能从中时不时的得到些个赞赏鼓励。你说这样子学习,你学不好才怪! 小程序还可以是一个表白神器,花点心思写一个专属 Ta 的独一无二的小程序,只给你的 Ta 开放个权限,再送给 Ta 比你花多少钱买个什么礼物都珍贵,你说我说的在理不!所以,你说小程序开发好不好,爱情学习双丰收呀。 2. 案例 好吧,上面废话有点多,下面先放几个自己做的小程序聊聊,素材什么的都是自己花心思画的(丑不丑无所谓,自己看着舒服就行),而且写一款完完全全凭自己感觉设计出来的小程序会让自己更有成就感。 2.1 反义词消消乐 反义词消消乐这个就是去年参加微信小程序应用开发赛做的一个小程序,为了增加游戏趣味性也增加了多人对战的功能。 反义词的逻辑匹配算法其实很简单,后台字词数据表,表中每条数据存储正、反两词,因此拥有共同的id值(int类型),前端获取到数据后只需将每条数据进行简单分割处理,可以让正意思词的标记值等于+id,反意思词的标记值等于-id,这样我们就能通过两标记值相加为0,来判断字词是否匹配成功。 游戏内的所有逻辑都交给小程序来判断运转,后端只是用来实现数据的存取,中转和传输。多人中前后端是使用 WebSocket 通道进行实时双向通信的,确保游戏内所有消息能够快速同步。逻辑如下: [图片] 简单解释下上图,当用户点击匹配后,发送信号与后台建立websocket连接,连接成功后传回连接的websocket的id值即sid,前端打包用户数据,sid,用户状态,用户转态就是此时是否已经退出游戏,发送给后端,后端保存信息,放入用户匹配池中,当准备就绪后,传回前端,前端发送匹配请求,后端再次开始为用户分配房间号,之后判断实时监听判断分配的房间是否满足两人,满足即可开始游戏逻辑。当游戏开始后,前端与后端的处理逻辑为如下所示: [图片] 上图是表示游戏匹配过程中的逻辑,其中打包小程序端发送的游戏信息其实就是用户点击反义词数据的状态信息,有助于后台判断分数加减等情况。服务器端的定向广播游戏信息,就是将处理好的分数等情况广播发送给定向房间内的用户。 反义词小程序整体流程大致是这么实现,而分配玩家的逻辑可以是按随机,按先到先服务,又或者按指定条件(如胜率分段等)将玩家分配到一起。 [图片] [图片] 2.2 困住小星星 困住小星星这个小程序,其玩法是模仿日本游戏设计师TaroIto2007年制作的“黑猫”(ChatNoir)游戏,而我在将其成功围住之后放了一些彩蛋,具体内容嘛就不说了 (✿◡‿◡),你们可以自己更改主题后随意发挥。 玩法就是游戏初始化墙的个数大概是9-13个之间。玩家需自己想办法点一个圈,目的就是要将其围住,不让她从边界跑掉即可。 小星星的寻路机制算法用到了最小路径和最大通路的算法,在每次寻路前先计算出各个非墙点的最小路径步数,以及最大通路步数,当最小路径步数相同时,就用最大通路步数来进行比较,最终找到最优路径,所以要想困住其实还是有点难的。 因为游戏中的地图是9*9,因此在最短路径算法中,其中点是(4,4),要计算到各边界的最短路径问题,可以从外往里开始计算。比如最外围,因为是边界,所以其路径长度为0,之后每往里一层,其路径长度就加1,直到到达主角位置。在不存在墙的情况下如下示: [图片] 最小路径: 其实要想计算出这个图的路径,我们不难发现,计算某个点的路径时(例如上图标红处),我们只需遍历找出每个点周围(即左、左上、右上、右、右下、左下)路径最小的值并加1,即是最终的最短路径长度。 如何实现?首先我们规定图中81个点初始path值可设为 -100,障碍物的path值可设为 100,边界path值可设为 0,主角位置可设为 999。之后再分别从四个大方向,即左上、右上、右下、左下这四个方向进行反复计算。 如何计算,以左上为例,左上计算即表示从左到右,从上到下,对各个点进行遍历。在遍历各点的同时,判断该点是否为障碍物,是则直接返回path的值为100,若该点为边界,则直接返回path的值为0。 如果都不是,则找出该点周围的6个点,并分别遍历周围的这6个点,判断这6个点的路径值,找出周围6个点路径的最小值min,min初值设为100。 如果筛选出来周围的点中存在没有计算过的点,即存在小于0的值,那需要等以后来计算他们的值,因此先不参与比较判断。而周围点的path值满足大于0的条件时,与min值比较,选出其中的最小值并赋给min,遍历完6个点后。当min值满足小于100的条件时,那么说明该点周围的最小值已经计算出来,直接将该点周围最小值路径值加1即为该点path值。 上面说的有点绕,直接上代码: [代码]calPath: function(location) { //计算路径 var row = location.row var col = location.col if (this.data.map_location[row][col] == 1) { //墙直接返回100 location.path = 100 return location.path } if (row == 0 || col == 0 || row == 8 || col == 8) { //边界直接返回0 location.path = 0 return location.path } var sixDir = this.calSixDir(location) //寻找周围的六个点 //遍历周围6个点,找出最小值 var min = 100 for (var i = 0; i < 6; i++) { if (sixDir[i].path >= 0) { // 存在点没有值,那需要等下次再来计算其值 var tmp = sixDir[i].path if (min > tmp) { min = tmp } } } if (min < 100) { location.path = min + 1 } else { location.path += 1 } return location.path }, [代码] 以此类推,从右上、右下、左下这三个方向进行计算,也都是相同的逻辑操作。 最大通路: 最大通路其实与最短路径的计算差不多,只是最大通路我们计算的是该点周围能走的个数,即寻找出该点周围6个点后,统计一下该点周围非墙的个数即可。因此计算时只需两个for循环遍历一遍图中所有点并分别计算一下即可。 [图片] [图片] 2.3 fly 拖鞋、摩斯密码 这个名字嘛fly 拖鞋,就是我随便取的。当时画素材的时候,就是随笔画了画涂了涂,看着有点像拖鞋就叫了 fly 拖鞋,这个游戏其实是用小游戏写的,游戏内容其实就跟fly bird一样。 开发这个游戏的过程中其实没用到什么算法,也没什么好说的。但用到了些物理知识,就是高中咱都学过的上抛与自由落体的公式,说这个就是想你知道写游戏的时候其实很经常用到物理知识。 这个摩斯密码,就是因为当时跟那谁谁谁聊起过摩斯密码,感觉那滴滴哒哒的也很神奇,而且又凑巧那天看到了个摩斯密码表,于是就照着那个表写了这个翻译器。所以用小程序写个小工具自己用还是很方便的。我觉得吧小程序是最适合自己写工具的,方便,而且干什么都行,就看你怎么用了。 [图片] [图片] 上面说那么多其实就想让学弟学妹们知道小程序可以这么玩。把我们所学的知识用可视化的形式体现出来,这可能比单纯的学习课本上的知识更有趣。 3.聊聊 小程序开发起来不会太难,在这个过程中其实也没有遇到太多大问题,遇到的bug无非就两种,要么语法错误,要么逻辑错误。 其实当遇到一个问题的时候,最好的解决办法就是官方文档了。这个真不是废话,很多时候遇到的bug其实就是我们对api的不熟悉,错用,用错而已,再不是,那就是咱自己代码逻辑错误了吧。 3.1 开发 开发个小程序的过程无非就这五个过程:问题—>分析—>设计—> 编码—>优化。 本人开发的心路历程首先就是自己要明白到底要开发个什么东西出来,之后便是去官方文档中简单查询下这些api,看看我们设计实现的这些个功能到底可不可行能不能实现。实现不了的,就换个思路看看有什么可替代的方案。 之后就是得想清楚,具体的实现步骤,实现逻辑,并分配好前后端的功能逻辑,之后再简单搭建小程序的整体框架,架构。我写的小程序其实大部分的实现逻辑都是在前端实现,后端我只是单纯的用来做些数据存取、中转等功能。 当然现在小程序的云开发,大大方便了我们,直接可以将数据放到云开发数据库上,用官方的原话讲就是一个既可在小程序前端操作,也能在云函数中读写的 JSON 数据库,而且这也大大简化了后台服务器的环境构建,运营维护等。可以减少很大的工作量。 最后再是动手编码实现我们的想法,将小程序的基础功能逻辑基本实现后,再慢慢的对其进行优化,完善。 其实我最喜欢就是完善和优化的这个过程,它能够让我很惬意的去设计、体验,将我们自己的小程序从粗糙的demo到精致的成品这个过程,是一件很有成就感的事。作出一个美美哒,精致的小程序,你就会连吃饭学习睡觉都感觉很舒畅。 3.2 收尾 最后总结就是,在开发过程中,可以先分析好功能逻辑,再开发个拥有主要功能的demo出来(先不管页面什么的设计有多难看),之后我们在这个demo的基础上一版版的优化修改,一步步迭代,最终成型。是的,小程序的开发就是这么容易,这么简单,这么好学,这么有趣。 还有就是要开发设计一个小程序其实不需要太多太复杂的功能,开发一个小程序的真谛就是简洁! 不仅仅是界面简洁,操作简洁,其实最主要的就是功能简洁,这或许也就是小程序设计的初衷吧。 最后还有就是遇到问题咱们首先确定一下是不是语法错误,再看看是不是逻辑错误,再不是,可以去文档上看看找找原因,文档还没有就去开发者社区里搜搜,开发者社区还没有的话可以先在这里面提个问题,最后咱们再去百度google,查查看,如果还没有解决方案的话。 那没办法了,只能开大招了—— 小黄鸭调试法! [图片] 在很久很久以前,有一个传说,传说中有一位程序大师,他随身携带了一只小黄鸭,每当在调试代码的时候会在桌上放上这只小黄鸭,然后详细地向鸭子解释每行代码…没了,这个传说就这么短,这就是传说中的小黄鸭调试发。 为什么呢?维基百科是这么解释的,小黄鸭调试法,又称橡皮鸭调试法,或者黄鸭除虫法,是在软件工程中使用的代码调试的一种方法,方法就是在程序调试,测试,除错过程中,操作人向小黄鸭耐心的解释每一行程序的作用,以此来激发灵感与发现矛盾 说白了就是回归带自己代码上,然后自言自语一翻。 你还别说,这个真管用,说出来更容易帮自己捋清楚思路,这个方法真的是屡试不爽,一直试一直爽! 要是你有幸能够把问题解决好了,最好可以记录一下踩过的坑那是最好的了,如果你再善良一点那就来开发者社区中分享一波吧。不管问题多简单,你要知道比你懂的少的人还是有很多的,他们那些新新手可能更需要的是咱们这些菜鸟新手的帮助吧。 最后再说一句话: [图片]
2019-05-27 - 微信登录能力介绍
为了便于用户便捷使用App、网站、移动端网页、小程序的服务,微信提供不同的技术方案,便于开发者在不同终端平台的服务中接入微信登录。 通过这个教程,开发者可以了解平台提供的针对各终端平台的微信登录能力,并可以根据实际使用场景合理选择接入方式。 以下为几类型微信登录的功能说明: 类型授权域/接口 用户侧使用流程 接入流程 App 接入微信SDK,并调用snsapi_userinfo (1)在App内选择使用微信登录 (2)拉起微信客户端,打开用户授权页,完成登录授权(1)注册微信开放平台(open.weixin.qq.com)帐号,并完成开发者资质认证 (2)申请【App移动应用】并审核通过后可以使用,查看开发文档网站应用 snsapi_login (1)用户使用微信“扫一扫”,在PC端扫码 (2)客户端打开授权页,完成登录授权 (1)注册微信开放平台(open.weixin.qq.com)帐号,并完成开发者资质认证 (2)申请【网站应用】并审核通过后可以使用,查看开发文档 微信客户端内H5使用公众号的登录能力: snsapi_base snsapi_userinfosnsapi_base:静默授权 snsapi_userinfo: (1)用户在H5内点击登录,唤起授权弹窗 (2)用户侧完成登录授权(1)注册微信公众号,选择“服务号”类型,并完成微信认证 (2)在公众号管理后台设置回调域名 (3)接入微信登录能力,查看开发文档 小程序wx.login wx.getUserInfowx.login:静默授权,开发者可获取openid wx.getUserInfo: (1)用户在小程序内点击组件,唤起登录窗口(2)用户侧完成登录授权 (1)注册小程序 (2)接入微信登录功能,查看开发文档,查看登录流程设计指引 开发者在不同使用场景下接入微信登录,应该注册符合要求的帐号并使用对应的登录能力。 【常见问题】 Q1: 在调用微信登录时,出现了“此帐号并没有这些scope的权限,错误码:10005”,是什么原因? A:对于场景与帐号属性、能力项不对应时(如在移动端网页中使用网站应用的AppID调用登录能力),将会出现以下的错误提示:此帐号并没有这些scope的权限,错误码:10005 [图片] Q2:我的服务同时有App、官网、公众号、小程序,那我怎么打通用户数据? A:对于多平台的服务,若开发者希望能识别用户身份,例如:希望用户在小程序内也能查看到在App内购买的商品订单,则可以通过平台提供的UnionID机制来实现用户身份识别。
2019-03-06 - 从源码看微信小程序启动过程
一、写作背景 接触小程序一年多,真实体验就是小程序开发门槛相对而言确实比较低。不过小程序的开发方式,一直是开发者吐槽的,如习惯了 Vue,React 开发的开发者经常会吐槽小程序一个 Page 必须由多个文件组成,组件化支持不完善或者说不能非常愉快的开发组件。在以前小项目中没太大感觉,从加入有赞,参与有赞微商城小程序的开发,是真切的体会到对于大型小程序项目开发的复杂性。 有赞从微信小程序内测就开始开发小程序,在不支持自定义组件的时代,只能通过 import 的形式拆分模块或实现组件。在业务复杂的页面,可能会 import 非常多的模块,而相应的 wxss 也需要 import 样式,除了操作繁琐,有时候也难免遗漏。 作为开发者,我们当然希望可以让工作更简单,更愉快,也希望改善我们的开发方式。所以希望能够更了解微信小程序框架,减少不必要的试错,于是有了一次对小程序框架的 debug 之旅。(基础库 1.9.93) 通过三周空余时间的 debug,也算对小程序框架有了一些浅显的认识,达到了最初的目的;对小程序启动,实例,运行等有了真切的体会。这篇文章记录了小程序框架的基本代码结构,启动流程,以及程序实例化过程。 本文的目的是希望把我看到的分享给对小程序感兴趣或者正在开发小程序的读者,主要解答“框架对传入的对象等到底做了什么”。 二、从启动流程一窥小程序框架细节 在开发者工具中使用 help() 方法,可以查看一些指令和方法。使用其中的 openVendor 方法可以打开微信开发者工具在小程序框架所在目录。其中以包括以基础库命名的目录和其他帮助文件,如其中有两个工具 wcc,wcsc。wcc 可把 wxml 转换为对应的 JS 函数 —— $gwx(path, global),wcsc 可将 wxss 转换为 css。而基础库目录包括 WAService.js 和 WAWebview.js 文件。小程序框架在开发者工具中以 WAService.js 命名(WAWebview.js 不知其作用,听说在真机环境使用该文件)。 在开发中工具命令行使用 document.head 可以查看到小程序的启动流程大致如下: [图片] 以小节的方式分别介绍这些流程,小程序是如何处理的(小节编号与图中编号相同)。 1、初始化全局变量 下图是小程序启动是初始化的一些全局的变量: [图片] 那些使用“__”开头,未在文档中提及可使用变量是不建议使用的,wxAppCode 在开发者工具中分为两类值,json 类型和 wxml 类型。以 .json 结尾的,其 key 值为开发者代码中对应的 json 文件的内容,.wxml 结尾的,其 key 值为通过调用 $gwx(’./pages/example/index.wxml’) 将得到一个可执行函数,通过调用这个函数可得到一个标识节点关系的 JSON 树。 [图片] 2、加载框架(WAService.js) 使用工具对 WAService.js 进行格式化后进行 debug。可以发现小程序框架大致由: WeixinJSBridge、 NativeBuffer、 wxConsole、 WeixinWorker、 JavaScript兼容(这部分为猜测)、 Reporter、 wx、 exparser、 virtualDOM、 appServiceEngine 几部分组成。 其中除了 wx 和 WeixinJSBridge 这两个基础 API 集合, exparser, virtualDOM, appServiceEngine 这三部分作为框架的核心, appServiceEngine 提供了框架最基本的接口如 App,Page,Component; exparser 提供了框架底层的能力,如实例化组件,数据变化监听,view 层与逻辑层的交互等;而 virtualDOM 则起着链接 appServiceEngine 和 exparser 的作用,如对开发者传入 Page 方法的对象进行格式化再传入 exparser 的对应方法处理。 框架对外暴露了以下API:Behavior,App,Page,Component,getApp,getCurrentPages,definePlugin,requirePlugin,wx。 3、业务代码的加载 在小程序中,开发者的 JavaScript 代码会被打包为 [代码]define('xxx.js', function(require, module, exports, window, document, frames, self, location, navigator, localStorage, history, Caches, screen, alert, confirm, prompt, fetch, XMLHttpRequest, WebSocket, webkit, WeixinJSCore, Reporter, print, WeixinJSBridge) { 'use strict'; // your code }) [代码] 这里的 define 是在框架中定义的方法,在框架中提供了两个方法:require 和 define 用来定义和使用业务代码。其方式有些像 AMD 规范接口,通过 define 定义一个模块,使用 require 来应用一个模块。但是也有很大区别,首先 define 限制了模块可使用的其他模块,如 window,document;其次 require 在使用模块时只会传入 require 和 module,也就是说参数中的其他模块在定义的模块中都是 undefined,这也是不能在开发者工具中获取一些浏览器环境对象的原因。 在小程序中,JavaScript 代码的加载方式和在浏览器中也有些不同,其加载顺序是首先加载项目中其他 js 文件(非注册程序和注册页面的 js 文件),其次是注册程序的 app.js,然后是自定义组件 js 文件,最后才是注册页面的 js 代码。而且小程序对于在 app.js 以及注册页面的 js 代码都会加载完成后立即使用 require 方法执行模块中的程序。其他的代码则需要在程序中使用 require 方法才会被执行。 下面详细介绍了 app.js,自定义组件,页面 js 代码的处理流程。 4、加载 app.js 与注册程序 在 app.js 加载完成后,小程序会使用 require(‘app.js’) 注册程序,即对 App 方法进行调用,App 方法是对 appServiceEngine.App 方法的引用。 下图是框架对于 App 方法调用时的处理流程: [图片] App 方法根据传入的对象实例化一个 app 实例,其生命周期函数 onLaunch 和 onShow 因为使用不同的方式获取 options的参数。在有些需要根据场景值来实现需求的,或许使用 onShow 中的场景值更合适。 在实际开发过程中发现,在微信顶部唤起小程序和在小程序列表唤起的 options 也是不一样的。在该案例中通过点击分享的小程序进入后,关闭小程序,再通过不同方式进入小程序,通过顶部唤起的还是 options 的 path 属性还是分享出来的 path,但是通过列表中打开直接回到了首页,这里 App 中的 onShow 就会获取到不同的 options。 5、加载自定义组件代码以及注册自定义组件 自定义组件在 app.js 之后被加载,小程序会在这个过程中加载完所有的自定义组件(分包中自定义组件没有有测试过),并且是加载完成后自动注册,只有注册完成后才会加载下一个自定义组件的代码。 下图是框架对于 Component 方法处理流程: [图片] 图中介绍了框架如何对传入 Component 方法的对象的处理,其后面还有很多深入的对于组件实例化的步骤没有在图中表示出来,具体可以在文章最后的附件中查看。 自定义组件在小程序中越来越完善,其拥有的能力也比 Page 更强大,而后面会提到在使用自定义组件的 Page 中,Page 实例也会使用和自定义组件一样的实例化方式,也就是说,他拥有和自定义组件一样的能力。 6、加载页面代码和注册页面 加载页面代码的处理流程和加载自定义组件一样,都是加载完成后先注册页面,然后才会加载下一个页面。 下图是注册一个页面时框架对于 Page 方法的处理流程: [图片] Page 方法会根据是否使用自定义组件做不同的处理。使用自定义组件的 page 对象会被处理为和自定义组件的结构,并在页面实例化时使用不同的处理流程进行实例化。当然对于开发而言没任何不同。 从图中可以发现 Page 传入的(生命周期)代码并不会在这里被执行,可以通过下面小节了解 Page 实例化的详细过程。 7、等待页面 Ready 和 Page 实例化 还记得上面介绍的启动流程中最后一步等待页面 Ready?严格来讲是等待浏览器 Ready,小程序虽然有部分原生的组件,不过本质上还是一个 web 程序。 在小程序中切换页面或打开页面时会触发 onAppRoute 事件,小程序框架通过 wx.onAppRoute 注册页面切换的处理程序,在所有程序就绪后,以 entryPagePath 作为入口使用 appLaunch 的方式进入页面。 下图是处理导航的程序流程: [图片] 从图中可以看出页面的实例化是在进入页面时进行,下图是具体的实例化过程: [图片] 下图是最终可得到 Page 实例: [图片] 可以发现其中多了 onRouteEnd API,实际该接口不会被调用。其中以 component 标记的表示只有在使用了自定义组件时才会有的方法和属性。在前面第 5 小节提到了对于使用自定义组件的页面会按照自定义组件方式解析,这些属性和方法与自定义组件表现一致。 8、关于 setData 小程序框架是一个以数据驱动的框架,当然不能少了对他如何实现数据绑定的探索,下图是 Page 实例的 setData 执行流程: [图片] 其中 component:setData 表示使用自定义组件的 Page 实例的 setData 方法。 三、写在最后 这是一次不完全的小程序框架探索,是在微信开发工具中 debug 的结果。虽然对于实际开发没有什么太大的帮助,但是对框架如何对开发的 js 代码进行处理有了一个很明确的认识,在使用一些 js 特性时可以有明确的感知。如果你还疑惑“小程序框架对传入的对象等到底做了什么”那一定是我表达能力太差,说声对不起。 通过这一次 debug ,也给我引入了新的问题,还希望能够有更多的讨论: · 自定义组件太多启动时会耗时处理自定义组件 · 文件太多会耗时读文件 · 合理的设计分包很重要 当然最后对于框架中已有的能力,还是非常希望微信可以开放更多稳定的接口,并在文档中告知开发者,让开发变得简单一些。
2019-03-05 - 小打卡 | 如何基于微信原生构建应用级小程序底层架构(上)
[图片] 大家好,我是小打卡的前端负责人金轩正,今天分享的主题是如何基于微信原生构建应用级小程序底层架构,这个命题看上去好像有些大,不过不要紧,这次分享我把它拆一下,大致从 小程序原生开发面临的问题 小打卡整体架构演进 开发中摸索与实践 这三个方面来看这个讲一下 [图片] 小程序原生开发面临的问题[图片] ok,首先第一个方面原生开发遇到的问题 小程序从17年诞生2年来一直处于互联网风口,不过对于开发者而言的整个开发体验不是特别友好,在17-18年之间我和很多开发小程序的小伙伴们聊过,大多数的反馈可能分为下面大致几类,当然还有更多: 没有父类,无法使用继承挂载全局方法,扩展生命周期没有父类,无法使用继承挂载全局方法,扩展生命周期 不支持跨页面/多页面通讯 setData的性能瓶颈 代码包大小限制 1/2/4/8 M,没有npm包 代码发布流程繁琐 其根本原因是将刚刚诞生的小程序与已经非常成熟的React,vue,angular作对比,而没有将小程序作为一个新的生态来看待,当然这个是一种看待事物的进步,并不是倒退,我在这里说这句话的意思是有更多的问题需要我们开发者主动去解决问题,推动整个生态的前进与发展 [图片] 其实这里可能有些朋友会问,已经有很多优秀的框架已经解决了这些问题,那么为什么还要使用原生开发? 确实在这段时间内出现了很多优秀的解决方案,我们不用并不是因为情怀哈(当然还是有那么一丢丢) 更多的是下面几点: 历史包袱,改造成本过高 小打卡在小程序刚出现的时候就进入开发了,当时框架还不成熟,而且对创业公司来说时间和迭代效率高于一切,在人手不足,业务模式尚未形成,还处于探索阶段的情况下花费大量时间去做对产品影响较小, 甚至delay迭代速度事情不是很赚 减少与第三方沟通成本 高速迭代的情况下,将时间尽可能的覆盖于业务上,避免在整个开发-上线闭环上增加节点 避免开发黑盒,控制风险 虽然整个社区是非常活跃的,fixed一个问题同样是需要花费一定时间,但是很多时候需求是不会等你bug fixed 如非必要,勿增实体 即“简单有效原理”,这句话还是我去年刚来公司的时候和阿赖聊他所说过的 放在项目开发上我的理解是在架构层面要做的尽可能的薄,避免过度设计 这样才有足够的扩展性,灵活性,容错性 这些框架虽好,但是对我们当前业务来说可能过于复杂,比如跨端在之前的阶段还没有这方面需求,而像组件化小程序已经支持,自动化构建我们自己也是可以搭建的并不复杂 相信微信小程序团队 是真正的想把这件事情做好,而且做的是一个生态,不论是小程序对于反馈响应速度,和迭代速度非常给力,还是对开发者社区运营,比如是社区活跃与审核速度挂钩,社区周刊,优质个人和优质企业 对齐web标准,并且更加开放 [图片] 小打卡整体架构演进其实小打卡整个架构并非一蹴而就的,就像前面所说的如非必要,勿增实体,而是大量的实际开发中遇到的共同问题解决方案的集合题 [图片] 常规架构这个是微信小程序给出的快速开发模版的一个开发模式: server模块提供数据,App作为全局对象直连所有的业务模块,工具函数提供api处理业务模块的需求 优点: 整个模型非常简单,上手快,学习成本 低结构清晰,在业务不复杂的情况下可以快速开发 不瞒大家其实小打卡在最初的半年内基本都是这套模式。 当然是在业务不复杂的情况下,复杂情况下会出现哪些问题呢? App作为全局对象在有大量业务模块连接的情况下,代码很容易膨胀,在多人开发的时候问题非常明显,无论是fixed bug还是正常的业务开发都会造成麻烦 页面之间独立,缺少公共模块,唯一的工具函数又要尽可能保持单一职责来提供服务(小打卡当时就是因为这个问题导致很多工具函数内部存储直接修改外部状态,导致大量强耦函数合无法拆分) 业务层直连server层,未拆分数据层的情况下,基本不存在复用性 上面所述的问题,从我接手这个项目到真正的调整持续了挺长一段时间,主要是缺乏一个契机来进行优化 优化的转折点 [图片] 然后突然有一天产品同学跑过来说: 我们要有自己的核心数据仓库,我们要看实时数据 ok,涉及到数据采集的问题了,我这边从浅到深大概列了几项: 最基础的多个页面pv,uv如何监控,不可能每个页面都要手动收集 为了统计页面和事件的分享和回流的数据,需要在分享事件携带大量的参数 微信的wx.previewImage, wx.chooseImage 等api对于用户session的收集造成很大麻烦 我们先解决第一个问题,如何收集页面pv,uv 容易陷入的误区 [图片] 在解决问题之前,我们先说一下开发小程序容易进入的误区 App 和 Page 等函数工厂是微信原生提供,不可修改 小程序项目结构是基于App, Page, 工具函数三个模块构建的 小程序的全局存储只有globalData和本地缓存 其实产生这些误区最根本的原因是小程序没有提供在复杂业务逻辑下的开发范式,比如vue,react有自己的通用开发模版 如果保持这些观念来进行开发的话,很容易将路子走窄,并且难以解决一些实际上的问题, 其实不论小程序和传统web有多少不同, 本质上还是在js环境下开发 小打卡架构图解 [图片] 为了更好的方便理解后面的具体实现,我提前放了一张目前小打卡的架构图 首先很熟悉的server这一边垫了一个数据层,主要将数据层和业务层解耦,提高复用性,并且提供一些通用功能,比如返回格式化数据问题,参数校验,日志监控... 在App对象和业务层同样增加了一个全局模块,提供独立于业务和工具类,只提供api之间双向通讯的渠道 工具模块的话其实就是对业务层的增强,比如常见的请求模块,上传模块,路由拦截等等 业务模块的话基本除了增加Component和中间层外没有太大变化 这个图上可能有两块可能大家觉得比较怪异,一个是global里面的函数重载,还有一个是业务模块的中间层是什么? 函数重载其实就是修改微信提供的App, Page, Component函数,使其更符合我们的业务场景, 业务模块的中间层就是依赖于函数重载的扩展 其实小打卡的整套架构都是基于这两个模块,这两个模块赋予了更多的可能性,然而实现却十分的简单 点击查看:小打卡 | 如何基于微信原生构建应用级小程序底层架构(下)
2019-04-22 - Painter 一款轻量级的小程序海报生成组件
生成海报相信大家有的人都做过,但是canvas绘图的坑太多。大家可以试试这个组件。然后附上楼下大哥做的可视化拖拽生成painter代码的工具:链接地址https://developers.weixin.qq.com/community/develop/article/doc/000e222d9bcc305c5739c718d56813
2019-09-27 - 【微信小程序】性能优化
为什么要做性能优化? 一切性能优化都是为了体验优化 1. 使用小程序时,是否会经常遇到如下问题? 打开是一直白屏 打开是loading态,转好几圈 我的页面点了怎么跳转这么慢? 我的列表怎么越滑越卡? 2. 我们优化的方向有哪些? 启动加载性能 渲染性能 3. 启动加载性能 1. 首次加载 你是否见过小程序首次加载时是这样的图? [图片] 这张图中的三种状态对应的都是什么呢? 小程序启动时,微信会为小程序展示一个固定的启动界面,界面内包含小程序的图标、名称和加载提示图标。此时,微信会在背后完成几项工作:[代码]下载小程序代码包[代码]、[代码]加载小程序代码包[代码]、[代码]初始化小程序首页[代码]。下载到的小程序代码包不是小程序的源代码,而是编译、压缩、打包之后的代码包。 2. 加载顺序 小程序加载的顺序是如何? 微信会在小程序启动前为小程序准备好通用的运行环境。这个运行环境包括几个供小程序使用的线程,并在其中完成小程序基础库的初始化,预先执行通用逻辑,尽可能做好小程序的启动准备。这样可以显著减少小程序的启动时间。 [图片] 通过2,我们知道了,问题1中第一张图是[代码]资源准备[代码](代码包下载);第二张图是[代码]业务代码的注入以及落地页首次渲染[代码];第三张图是[代码]落地页数据请求时的loading态[代码](部分小程序存在) 3. 控制包大小 提升体验最直接的方法是控制小程序包的大小,这是最显而易见的 勾选开发者工具中“上传代码时,压缩代码”选项; 及时清理无用的代码和资源文件(包括无用的日志代码) 减少资源包中的图片等资源的数量和大小(理论上除了小icon,其他图片资源从网络下载),图片资源压缩率有限 从开发者的角度看,控制代码包大小有助于减少小程序的启动时间。对低于1MB的代码包,其下载时间可以控制在929ms(iOS)、1500ms(Android)内。 4. 采用分包加载机制 根据业务场景,将用户访问率高的页面放在主包里,将访问率低的页面放入子包里,按需加载; [图片] 使用分包时需要注意代码和资源文件目录的划分。启动时需要访问的页面及其依赖的资源文件应放在主包中。 5 采用分包预加载技术 在4的基础上,当用户点击到子包的目录时,还是有一个代码包下载的过程,这会感觉到明显的卡顿,所以子包也不建议拆的太大,当然我们可以采用子包预加载技术,并不需要等到用户点击到子包页面后在下载子包,而是可以根据后期数据,做子包预加载,将用户在当先页可能点击的子包页面先加载,当用户点击后直接跳转; [图片] 这种基于配置的子包预加载技术,是可以根据用户网络类型来判断的,当用户处于网络条件好时才预加载;是灵活可控的 6. 采用独立分包技术 目前很多小程序[代码]主包+子包[代码](2M+6M)的方式,但是在做很多运营活动时,我们会发现活动(红包)是在子包里,但是运营、产品投放的落地页链接是子包链接,这是的用户在直达落地时,必须先下载主包内容(一般比较大),在下载子包内容(相对主包,较小),这使得在用户停留时间比较短的小程序场景中,用户体验不是很好,而且浪费了很大部分流量; [图片] 可以采用独立分包技术,区别于子包,和主包之间是无关的,在功能比较独立的子包里,使用户只需下载分包资源; 7. 首屏加载的优化建议 7.1 提前请求 异步请求可以在页面onLoad就加载,不需要等页面ready后在异步请求数据;当然,如果能在前置页面点击跳转时预请求当前页的核心异步请求,效果会更好; 7.2 利用缓存 利用storage API, 对变动频率比较低的异步数据进行缓存,二次启动时,先利用缓存数据进行初始化渲染,然后后台进行异步数据的更新,这不仅优化了性能,在无网环境下,用户也能很顺畅的使用到关键服务; 7.3 避免白屏 可以在前置页面将一些有用的字段带到当前页,进行首次渲染(列表页的某些数据–> 详情页),没有数据的模块可以进行骨架屏的占位,使用户不会等待的很焦虑,甚至走了; 7.4 及时反馈 及时的对需要用户等待的交互操作进行反馈,避免用户以为小程序卡了,无响应 渲染性能优化 1. 小程序渲染原理 双线程下的界面渲染,小程序的逻辑层和渲染层是分开的两个线程。在渲染层,宿主环境会把WXML转化成对应的JS对象,在逻辑层发生数据变更的时候,我们需要通过宿主环境提供的setData方法把数据从逻辑层传递到渲染层,再经过对比前后差异,把差异应用在原来的Dom树上,渲染出正确的UI界面。 [图片] 分析这个流程不难得知:页面初始化的时间大致由页面初始数据通信时间和初始渲染时间两部分构成。其中,数据通信的时间指数据从逻辑层开始组织数据到视图层完全接收完毕的时间,数据量小于64KB时总时长可以控制在30ms内。传输时间与数据量大体上呈现正相关关系,传输过大的数据将使这一时间显著增加。因而减少传输数据量是降低数据传输时间的有效方式。 [图片] 2. 避免使用不当setData 在数据传输时,逻辑层会执行一次[代码]JSON.stringify[代码]来去除掉[代码]setData[代码]数据中不可传输的部分,之后将数据发送给视图层。同时,逻辑层还会将[代码]setData[代码]所设置的数据字段与[代码]data[代码]合并,使开发者可以用[代码]this.data[代码]读取到变更后的数据。因此,为了提升数据更新的性能,开发者在执行[代码]setData[代码]调用时,最好遵循以下原则: 2.1 不要过于频繁调用setData,应考虑将多次setData合并成一次setData调用; [图片] 2.2 数据通信的性能与数据量正相关,因而如果有一些数据字段不在界面中展示且数据结构比较复杂或包含长字符串,则不应使用[代码]setData[代码]来设置这些数据; [图片] 2.3 与界面渲染无关的数据最好不要设置在data中,可以考虑设置在page对象的其他字段下 [图片] 提升数据更新性能方式的代码示例 [代码]Page({ onShow: function() { // 不要频繁调用setData this.setData({ a: 1 }) this.setData({ b: 2 }) // 绝大多数时候可优化为 this.setData({ a: 1, b: 2 }) // 不要设置不在界面渲染时使用的数据,并将界面无关的数据放在data外 this.setData({ myData: { a: '这个字符串在WXML中用到了', b: '这个字符串未在WXML中用到,而且它很长…………………………' } }) // 可以优化为 this.setData({ 'myData.a': '这个字符串在WXML中用到了' }) this._myData = { b: '这个字符串未在WXML中用到,而且它很长…………………………' } } }) [代码] 利用setData进行列表局部刷新 在一个列表中,有[代码]n[代码]条数据,采用上拉加载更多的方式,假如这个时候想对其中某一个数据进行点赞操作,还能及时看到点赞的效果 解决方法 1、可以采用setData全局刷新,点赞完成之后,重新获取数据,再次进行全局重新渲染,这样做的优点是:方便,快捷!缺点是:用户体验极其不好,当用户刷量100多条数据后,重新渲染量大会出现空白期(没有渲染过来) 2、说到重点了,就是利用[代码]setData[代码]局部刷新 [代码]> a.将点赞的`id`传过去,知道点的是那一条数据, 将点赞的`id`传过去,知道点的是那一条数据 [代码] [代码]<view wx:if="{{!item.status}}" class="btn" data-id="{{index}}" bindtap="couponTap">立即领取</view> [代码] [代码]> b.重新获取数据,查找相对应id的那条数据的下标(`index`是不会改变的) > c.用setData进行局部刷新 [代码] [代码]this.setData({ list[index] = newList[index] }) [代码] 其实这个小操作对刚刚接触到微信小程序的人来说应该是不容易发现的,不理解setData还有这样的写法。 2.4 切勿在后台页面进行setData 在一些页面会进行一些操作,而到页面跳转后,代码逻辑还在执行,此时多个[代码]webview[代码]是共享一个js进程;后台的[代码]setData[代码]操作会抢占前台页面的渲染资源; [图片] [图片] 3. 用户事件使用不当 视图层将事件反馈给逻辑层时,同样需要一个通信过程,通信的方向是从视图层到逻辑层。因为这个通信过程是异步的,会产生一定的延迟,延迟时间同样与传输的数据量正相关,数据量小于64KB时在30ms内。降低延迟时间的方法主要有两个。 1.去掉不必要的事件绑定(WXML中的[代码]bind[代码]和[代码]catch[代码]),从而减少通信的数据量和次数; 2.事件绑定时需要传输[代码]target[代码]和[代码]currentTarget[代码]的[代码]dataset[代码],因而不要在节点的[代码]data[代码]前缀属性中放置过大的数据。 [图片] 4. 视图层渲染原理 4.1首次渲染 初始渲染发生在页面刚刚创建时。初始渲染时,将初始数据套用在对应的WXML片段上生成节点树。节点树也就是在开发者工具WXML面板中看到的页面树结构,它包含页面内所有组件节点的名称、属性值和事件回调函数等信息。最后根据节点树包含的各个节点,在界面上依次创建出各个组件。 [图片] 在这整个流程中,时间开销大体上与节点树中节点的总量成正比例关系。因而减少WXML中节点的数量可以有效降低初始渲染和重渲染的时间开销,提升渲染性能。 简化WXML代码的例子 [代码]<view data-my-data="{{myData}}"> <!-- 这个 view 和下一行的 view 可以合并 --> <view class="my-class" data-my-data="{{myData}}" bindtap="onTap"> <text> <!-- 这个 text 通常是没必要的 --> {{myText}} </text> </view> </view> <!-- 可以简化为 --> <view class="my-class" data-my-data="{{myData}}" bindtap="onTap"> {{myText}} </view> [代码] 4.2 重渲染 初始渲染完毕后,视图层可以多次应用[代码]setData[代码]的数据。每次应用[代码]setData[代码]数据时,都会执行重渲染来更新界面。初始渲染中得到的data和当前节点树会保留下来用于重渲染。每次重渲染时,将[代码]data[代码]和[代码]setData[代码]数据套用在WXML片段上,得到一个新节点树。然后将新节点树与当前节点树进行比较,这样可以得到哪些节点的哪些属性需要更新、哪些节点需要添加或移除。最后,将[代码]setData[代码]数据合并到[代码]data[代码]中,并用新节点树替换旧节点树,用于下一次重渲染。 [图片] 在进行当前节点树与新节点树的比较时,会着重比较[代码]setData[代码]数据影响到的节点属性。因而,去掉不必要设置的数据、减少[代码]setData[代码]的数据量也有助于提升这一个步骤的性能。 5. 使用自定义组件 自定义组件的更新只在组件内部进行,不受页面其他不能分内容的影响;比如一些运营活动的定时模块可以单独抽出来,做成一个定时组件,定时组件的更新并不会影响页面上其他元素的更新;各个组件也将具有各自独立的逻辑空间。每个组件都分别拥有自己的独立的数据、setData调用。 [图片] 6. 避免不当的使用onPageScroll 每一次事件监听都是一次视图到逻辑的通信过程,所以只在必要的时候监听pageSrcoll [图片] 总结 小程序启动加载性能 控制代码包的大小 分包加载 首屏体验(预请求,利用缓存,避免白屏,及时反馈 小程序渲染性能 避免不当的使用setData 合理利用事件通信 避免不当的使用onPageScroll 优化视图节点 使用自定义组件
2019-03-07