- Painter 一款轻量级的小程序海报生成组件
生成海报相信大家有的人都做过,但是canvas绘图的坑太多。大家可以试试这个组件。然后附上楼下大哥做的可视化拖拽生成painter代码的工具:链接地址https://developers.weixin.qq.com/community/develop/article/doc/000e222d9bcc305c5739c718d56813
2019-09-27 - xquery, 小程序开发工具包
xquery基于原生小程序,是一套类似于jquery的小程序开发工具库,方便引用且无破坏小程序原生模式。 支持组件元素选取 方便的结构嵌套 弱模板化,方便维护 事件方法重新封装,支持query传递参数 支持钩子方法 支持LRU缓存 支持MARKDOWN富文本 支持HTML富文本 [图片] 无侵入的Pager 使用Pager方法替换小程序Page,其他用法一致。无侵入性语法 [代码]const Pager = require('components/aotoo/core/index') Pager({ data: { itemElement: {...} }, onReady(){ ... } }) [代码] 完整的目录结构 [图片] 实例抓取 类似于在web开发中可以使用[代码]getElementsById[代码]抓取html dom元素对象,下列针对item组件对象实现 wxml模板 [代码]<ui-item item="{{itemElement}}" /> [代码] js [代码]const Pager = require('components/aotoo/core/index') Pager({ data: { itemElement: { $$id: 'item-id', title: '这是一个item组件' } }, onReady(){ let $item = this.getElementsById('item-id') $item.addClass('active') } }) [代码] onReay类似于web中的body.onLoad,所有dom元素都已经准备妥当,使用[代码]getElementsById[代码]去抓取实例 事件封装 query传递参数更贴近web前端开发(事件封装是基于Pager及xquery定义的组件才有效,不会影响原生开发) wxml模板 [代码]<ui-item item="{{itemElement}}" /> [代码] js [代码]Pager({ data: { itemElement: { title: '按钮', tap: 'onTap?username=张三' // 允许query参数 // tap => bind:tap的别名 // aim => catch:tap的别名 // longpress => bind:longpress的别名 // longtap => bind:longtap的别名 // touchstart => bind:touchstart 别名 // touchmove => bind:touchmove的别名 // touchend => bind:touchend的别名 // touchcancel => bind:touchcancel的别名 // catchlongpress => catch:longpress的别名 // ...其他catch方法以此类推 } }, onTap(e, param, inst){ // e => 原生event事件对象 // param => {username: '张三'} // inst => <ui-item />组件实例对象,支持update, addClass, removeClass等方法 inst.update({ title: param.username+'的按钮' }, function(){ let $data = inst.getData() console.log($data.title) // 张三的按钮 }) } }) [代码] 数据缓存及数据过期 [代码]const Pager = require('components/aotoo/core/index') const lib = Pager.lib const dataEntity = lib.hooks('DATA-ENTITY', true) // true表示缓存数据到storage onReay(){ // 将用户信息缓存一天 dataEntity.setItem('names', {name: '', sex: ''}, 24*60*60*1000) setTimeout(()=>{ let namesData = dataEntity.getItem('names') console.log(namesData) // {name: '', sex: ''} },3000) } [代码] LRU缓存设置 小程序本地缓存10M,内存128M,因此数据缓存到本地受到很多限制,比如图片,需要使用LRU缓存机制来对图片/文件进行管理 [代码]const Pager = require('components/aotoo/core/index') const lib = Pager.lib const imgEntity = lib.hooks('IMG-ENTITY', { localstorage: true, // 开启本地缓存 expire: 24 * 60 * 60 * 1000, // 过期时间为1天,所有缓存的数据 max: 50 // 最大缓存数为50条,即当前hooks实例只会存储最常用的50条数据 }) onReay(){ // 将用户信息缓存一天 // img-src => 图片的具体地址或者一个唯一地址 // 挂载一个方法,当该钩子存储数据时 imgEntity.setItem('image-src', {img: 'cloude://....'}) saveImgToLocal('cloude://...') setTimeout(()=>{ let imgsData = imgEntity.getItem('image-src') console.log(imgsData) // {img} || undefined if (!imgsData) { imgEntity.setItem('image-src', {img: 'cloude://....'}) saveImgToLocal('cloude://...') } },3000) } [代码] markdown支持 有两种方式支持markdown文本 组件方式 嵌入方式 嵌入方式比较简单,下面示例markdown文本以嵌入方式实现 [代码]const Pager = require('components/aotoo/core/index') Pager({ data: { itemElement: { "@md": ` ## 马克党标题 基于xquery的基类开发的组件可以直接内嵌使用马克党文档 ` } }, onReady(){ ... } }) [代码] html支持 前端从后端拿去富文本数据,直接转化成小程序模板结构输出,下面示例html文本以嵌入方式实现 [代码]const Pager = require('components/aotoo/core/index') Pager({ data: { itemElement: { "@html": ` <div class="container"> <img src="http://..." /> <span>...</span> <br /> <section> ... ... </section> </div> ` } }, onReady(){ ... } }) [代码] github地址:https://github.com/webkixi/aotoo-xquery 小程序demo演示,下列小程序基于xquery的个人开发,公司的就不放了 xquery [图片] saui [图片] 嘟嘟倒计时 [图片]
2019-12-19 - 让小程序页面和自定义组件支持 computed 和 watch 数据监听器
习惯于 VUE 或其他一些框架的同学们可能会经常使用它们的 [代码]computed[代码] 和 [代码]watch[代码] 。 小程序框架本身并没有提供这个功能,但我们基于现有的特性,做了一个 npm 模块来提供 [代码]computed[代码] 和 [代码]watch[代码] 功能。 先来个 GitHub 链接:https://github.com/wechat-miniprogram/computed 如何使用? 安装 npm 模块 [代码]npm install --save miniprogram-computed [代码] 示例代码 [代码]const computedBehavior = require('miniprogram-computed') Component({ behaviors: [computedBehavior], data: { a: 1, b: 1, }, computed: { sum(data) { return data.a + data.b }, }, }) [代码] [代码]const computedBehavior = require('miniprogram-computed') Component({ behaviors: [computedBehavior], data: { a: 1, b: 1, sum: 2, }, watch: { 'a, b': function(a, b) { this.setData({ sum: a + b }) }, }, }) [代码] 怎么在页面中使用? 其实上面的示例不仅在自定义组件中可以使用,在页面中也是可以的——因为小程序的页面也可用 [代码]Component[代码] 构造器来创建! 如果你已经有一个这样的页面: [代码]Page({ data: { a: 1, b: 1, }, onLoad: function() { /* ... */ }, myMethod1: function() { /* ... */ }, myMethod2: function() { /* ... */ }, }) [代码] 可以先把它改成: [代码]Component({ data: { a: 1, b: 1, }, methods: { onLoad: function() { /* ... */ }, myMethod1: function() { /* ... */ }, myMethod2: function() { /* ... */ }, }, }) [代码] 然后就可以用了: [代码]const computedBehavior = require('miniprogram-computed') Component({ behaviors: [computedBehavior], data: { a: 1, b: 1, }, computed: { sum(data) { return data.a + data.b }, }, methods: { onLoad: function() { /* ... */ }, myMethod1: function() { /* ... */ }, myMethod2: function() { /* ... */ }, }, }) [代码] 应该使用 [代码]computed[代码] 还是 [代码]watch[代码] ? 看起来 [代码]computed[代码] 和 [代码]watch[代码] 具有类似的功能,应该使用哪个呢? 一个简单的原则: [代码]computed[代码] 只有 [代码]data[代码] 可以访问,不能访问组件的 [代码]methods[代码] (但可以访问组件外的通用函数)。如果满足这个需要,使用 [代码]computed[代码] ,否则使用 [代码]watch[代码] 。 想知道原理? [代码]computed[代码] 和 [代码]watch[代码] 主要基于两个自定义组件特性: 数据监听器 和 自定义组件扩展 。其中,数据监听器 [代码]observers[代码] 可以用来监听数据被 [代码]setData[代码] 操作。 对于 [代码]computed[代码] ,每次执行 [代码]computed[代码] 函数时,记录下有哪些 data 中的字段被依赖。如果下一次 [代码]setData[代码] 后这些字段被改变了,就重新执行这个 [代码]computed[代码] 函数。 对于 [代码]watch[代码] ,它和 [代码]observers[代码] 的区别不大。区别在于,如果一个 data 中的字段被设置但未被改变,普通的 [代码]observers[代码] 会触发,但 [代码]watch[代码] 不会。 如果遇到问题或者有好的建议,可以在 GitHub 提 issue 。
2019-07-24 - 原生小程序全局状态管理方案
原生小程序状态管理: mpsm 具备vue+react开发体验 整合了【vue的属性监听watch、计算属性computed】和【 dvajs 的全局状态管理模式】,解决跨页通信问题, 提出【组件圈子】概念,摆脱各种父子、兄弟、姐妹、街坊邻居、远房表兄弟等组件间的通信浆糊困扰。 数据流 不管是页面间,还是组件间,嵌套组件内部,都可以通过简单的dispach来管理全局状态或圈子状态(局部)。 [图片] [图片] 使用介绍 完全兼容原生代码,已有的业务逻辑代码,即便不适配也可使用此库,不影响已有业务逻辑。 原生: [代码]Page()[代码] 变为 [代码]page()()[代码] 原生: [代码]Component()[代码] 变为 [代码]component()()[代码] 初始化 [代码]//app.js import {page} from './mpsm/index' import models from './models/index' page.init(models, {}, {}) [代码] 第二、三参数分别为页面和组件的公共options,会在每个页面或组件生效,对于同名的对象和函数,会进行合并处理。 页面或组件注册 小:即只需将Page、Component首字母小写。 尾巴:即尾部多调用一次: [代码]page({ //component // ... })() [代码] [代码]// 数据更新有两种方式 // 第一种,直接修改this.data, 需要更新时调用this.update() this.data.a = 1 this.update() // 第二种 this.$setData this. $setData({ list: list }) //第一种更新方式,根据需要自己确定数据更新的时机,调用this.update() //第二种更新方式,会进行批处理,待函数执行结束后统一批量更新,异步操作立即更新,类似react中的setState [代码] 方法 方法 说明 备注 page 用于注册页面 page()() component 用于注册组件 component()() dispatch 状态分发 参数object, {type, action, lazy}, 默认lazy: true, 懒更新,表示只更新当前展示页面的状态,其它页面待onShow触发后再更新 getComponentOps 获取公共组件配置选项 不包含函数,只是简单的JSON格式数据 getOps 获取公共页面配置选项 不包含函数,只是简单的JSON格式数据 subscribe 订阅单一数据源 参数: ‘userInfo/setup’ unsubscribe 取消订阅单一数据源 参数: ‘userInfo/setup’ 单一数据源的订阅取消 方法: unsubscribe, subscribe 取消订阅不使用dva的[代码]app.unmodel()[代码],同时,subscription 必须返回 unlisten 方法,用于取消数据订阅。 [代码]subscribe('userInfo/setup'); unsubscribe('userInfo/setup'); [代码] 页面注册、组件注册示例 [代码]import {dispatch, page, component} from '../../mpsm/index' page({ // 或者 component watch: { // 属性监听 isLogin(newState, oldState) { } }, computed: { // 计算属性 countComputed(data) { return data.count * 2 } }, data: { count: 2 }, onLoad() {}, login() { dispatch({ type: 'userInfo/save', payload: { isLogin: true } }) }, changeGroupState() { this.dispatch({ type: 'group/index-a-1', payload: { nameA: 'name' } }) }, })(({userInfo}) => {//订阅全局状态 return { isLogin: userInfo.isLogin } }, (groups) => {//订阅圈子状态,page为全局圈子,component为组件所在圈子 return { nameA: groups.nameA && groups.nameA.name || '--' } }) [代码] tips: dispatch用于分发全局状态,风格与dva保持一致, 不过多了一个lazy字段, 默认true(懒更新,只更新当前页面,其他页面onShow时再更新),false的话,就是全量更新; Page和Component实例内置this.dispatch方法,用于分发局部状态。 3、组件可监听page的生命周期函数,无需做版本兼容,只需将想要监听的函数名与page内一直即可,即 [代码]// 原生的pageLifetimes可监听的周期太少,且版本要求高,监听了onShow,就不需要监听show了,避免执行两次 component({ pageLifetimes: { onShow: function () { }, onHide: function () { }, onPageScroll: function () { }, }, })() [代码] 目前可监听的生命周期 [代码]['onShow', 'onHide', 'onResize', 'onPageScroll', 'onTabItemTap', 'onPullDownRefresh', 'onReachBottom'][代码] 有了这个功能,就可以编写很多自控组件了,比如吸顶效果的导航栏等。 models 全局状态 参考 dvajs [代码]export default { namespace: '', state: { }, subscriptions: { }, effects: { }, reducers: { } } [代码] model 方法 详情参阅 dva select、put、history tips: put只用于简单的model内部的action分发,未封装put.resolve等方法,可使用async/await实现同等效果; 依据小程序特性,history回调中两个参数分别为当前页面路由信息和上一页路由信息,为一个对象,格式如。 [代码] export default { subscriptions: { setup({dispatch, history, select}) { const callback = (current, prev) => { dispatch({ type: 'save', payload: { prev, // {route: 'pages/index/index',options: {id: 123}} current } }) } history.listen(callback) return () => history.unlisten(callback) } } } [代码] 组件圈子 传统的组件数据流 对于嵌套组件间的数据通信,往往存在所谓的父子组件关系,内层组件想要向外层组件或其它分支上的组件传递数据,往往通过外层的组件通过监听函数来接收并派发,层层传递,这种 这种层层转接的模式,繁琐且需要专门的函数去维护,不利于组件的拓展和移植。 [图片] 小程序的官方组件数据流 而对于小程序的原生组件系统来说,behaviors, relations, definitionFilter, triggerEvent, getRelationNodes等编写组件需要使用的属性或方法, 真的是辣眼睛!小程序官方组件系统基本丧失了作为组件的意义, 虽然它还在努力地更新升级, 时不时文档中出现[代码]"不推荐使用这个字段,而是使用另一个字段代替,它更加强大且性能更好"[代码]的字眼, 大家不要信!!! [图片] [图片] 什么叫组件?相对独立,具有明确约定的接口,依赖语境,可自由组合,可拓展,可独立部署,亦可复合使用!!!你说你除了相对独立,你说你还有啥,你说你还有个啥!!! [图片] 组件圈子数据流 对于一个组件来说,在最初编写时,不应该去关心自己会被挂载到哪,自己只需要为圈子提供数据, 甚至是数据键名也不用关心,放到圈子里,谁爱用谁用,无需回调函数来协助传递数据, 这才是作为组件可移植可复用的意义,在一个圈子里,不存在父子兄弟的概念,也没有哪个组件生来是给人当儿子的,组件间的往来只有最纯粹的数据通信。 如下图中的组件C与组件F之间的数据通信,一个提供数据,另一个直接去使用就行了,不管层级多少,直接通信,没有多余的步骤。 [图片] [代码] <!--组件a in page--> <a group-name="index-a-1" group-keys="{{ {name: 'nameA1'} }" group-data="{{ {a: 1} }}"></a> [代码] 只需给组件赋值一个[代码]group-name[代码]属性,便给组件分配了一个圈子, 组件内[代码]this.dipatch({})[代码]分发地[代码]payload[代码]便是该组件给圈子贡献的数据, 也是该组件对外约定的数据值。 [代码]group-keys[代码]是用于避免字段名称冲突的,示例中可将a贡献地数据键值更改为[代码]nameA1[代码], 而值不变,所以圈子中地组件很纯粹,只向所在圈子提供必要的数据值, 至于你将组件放置何处,数据给谁用,名称怎么修改,都与我无关。 [代码]group-data[代码]是组件所依赖的外部值,是一个内置属性,可在组件的[代码]watch[代码]配置中进行监听。 页面、组件实例对象 注意: 对配置中的函数,以及setData进行了简单封装,在页面注册或组件注册options的函数中, setData的数据并不会马上更新,而是合并收集,待当前函数执行完毕后, 先模拟计算出computed,与收集的结果合并,再diff,最后setData一次。 与react更新机制类似,对于函数中出现的异步操作,不会进行数据收集,而是直接模拟计算值,diff,接着setData 页面 对圈子状态的管理拥有最高权限 属性 属性 说明 备注 $groups 获取当前页面内的所有圈子状态值 object, 只读 [代码] this.$groups['index-a-1'] [代码] 方法 dispatch 用于强制更新某个圈子中的状态 [代码] this.dispatch({ type: 'data/index-a-1', payload: { nameA1: 'name' } }) [代码] 参数 说明 备注 type 格式: 更新类型/圈子名称,更新类型:data 或 group,data表示更新圈子数据的同时,也将payload中的数据更新至this.data, 即this.setData(payload) string payload 需要更新的状态值 object 组件 只更新所在圈子状态 属性 属性 说明 备注 $groups 获取所在页面内的所有圈子状态值 object, 只读 $group 获取所在圈子状态值 object, 只读 [代码] this.$group['nameA1'] [代码] 方法 dispatch 用于更新所在圈子中的状态 [代码] this.dispatch({ type: 'data', // group payload: { nameA1: 'name' } }) [代码] 参数 说明 备注 type 格式: 更新类型,更新类型:data 或 group,data表示更新圈子数据的同时,也将payload中的数据更新至this.data, 即this.setData(payload) string payload 需要更新的状态值 object tips 更新类型 之所以会有data更新类型,是因为组件提供的数据名称,有可能会被改写,所以组件不要去监听自己的数据,那是给外人用的。 有时候组件向圈子贡献了数据,但自身并不需要这些数据更新到data,所以会有group更新类型 状态更新机制 [图片] 定制diff 腾讯官方的westore库,为小程序定制了一个diff,本想用在自己库里, 但使用中发现diff结果不太对,他会将删除的属性值重置为null,我并不需要为null, 当去遍历对象的属性值,就会多出一个为null的属性,莫名其妙,删了就是删了,不必再保留, 并且其内部实现,在进行diff前先进行一次同步下key键,这个完全没必要,浪费性能。 westore的diff应该是用在全局状态新旧状态上,其结果违反setData的数据拼接规则, 也不符合原生数据更新的习惯, 所以我得自己写一个diff,先列出基本情况: 1、不改变原生setData的使用习惯; 2、明确diff使用的场景,针对具体场景定制:属性更新时需要diff,setData时需要diff; 3、diff返回结果的格式,setData会用到,需要满足 [代码]a.b[0].a[代码]这样的格式,另外如果属性变化,还需要根据键值调用watch; 4、参与对比的两个参数都有一个共同特点,就是都是拿局部变化的数据,去对比全量旧数据,属性的话,两者的键值对更是对等的,所以无需同步键值; 定制的diff: [代码] diff({ a: 1, b: 2, c: "str", d: { e: [2, { a: 4 }, 5] }, f: true, h: [1], g: { a: [1, 2], j: 111 } }, { a: [], b: "aa", c: 3, d: { e: [3, { a: 3 }] }, f: false, h: [1, 2], g: { a: [1, 1, 1], i: "delete" }, k: 'del' }) [代码] diff结果 [代码] const diffResult = { result: { a: 1, b: 2, c: "str", 'd.e[0]': 2, 'd.e[1].a': 4, 'd.e[2]': 5, f: true, g: { a: [1,2], j: 111 }, h: [1] }, rootKeys: { a: true, b: true, c: true, d: true, g: true, h: true, } } [代码] 记录rootKeys是因为属性监听不需要 'a.b’这样的格式,在diff中记录下来,便少了一次遍历筛选拆分。
2019-12-16 - 【接口相关】聊一聊数据接口的登录态校验以及JWT
最近和群里网友(就是评论区厚颜无耻要冠名的那个子不语啦)聊天,发现他在数据接口中校验登录状态用的还是session,在我及时劝说和科普之后,他最终决定改用JWT。那么接下来我们就聊一聊数据接口应该怎么管理登录状态以及什么是JWT 混合开发的时候是怎么做的 前后端混合开发的时候,用户登录状态的管理一般都是通过session来实现的,原理很简单:用户登录后,服务端将登录用户信息存储到服务器上的特定位置,并生成对应的session id存储到浏览器的cookie中。需要校验的时候先读取cookie中的session id,找到服务器中对应的存储内容,完成校验。 很显然,这个机制是建立在cookie基础上的,cookie又依赖于浏览器,而且有域名限制。是不适合app、小程序、以及前后端时数据接口采用其他域名等情况的。 app、小程序、前后端分离的时候要怎么做 app、小程序、前后端分离时的数据接口一般采用token来做登录信息校验。原理是用户登录后,服务端生成对应用户的一个token(一般都是一段无意义的唯一字符串)后返回,app、小程序、前端(以下统称为前端)拿到token后保存,在需要校验用户登录的接口请求中加入token(可以是get、post参数或者http header的形式),服务端拿到token后校验真实性、有效性等信息后完成登录校验。一般为了防止盗用,还会设置一套签名校验的过程。 其实token和session的原理是差不多的,都是服务端将对应用户的一个key(session的时候是session id,token的时候就是token)交给前端,前端通过token请求服务端,服务端再去反查用户,获取用户登录状态。 现在一般微信、微博等接口都是采用的这种方式。但是这种方式也有弊端,主要是: 服务端必须保存token,以及有效期,校验的时候必须要有数据读取的过程; 校验签名的时候一般需要一个secret做为加密签名的附加字符,前端必须也要同时保存这个secret,这样显然不适合代码会暴露的网页前端。 这时候,就轮到我们这次的主角JWT出场了。 什么是JWT JWT是JSON Web Token的简称,有官网详细介绍,大家可以看一看,这里简单说一下。 JWT其实就是一种特殊的token,原理和使用方法自然和token一样。 JWT是由三部分组成的字符串,结构是:头部+主体内容(官方称之为Payload)+签名,三部分用“.”连接。头部和主体内容都是json格式的字符串再经过base64编码,为了方便放在get请求中,还需要把类似“=”、“/”等特殊字符替换掉。 头部内容是固定的,原始json就是下面这样 [代码]{ "alg": "HS256", "typ": "JWT" } [代码] 主要是说明了最后签名部分的加密算法。 重点是中间的主体内容,原始json一般是类似下面这样的 [代码]{ "user": "John Doe", "exp": "2020-01-01 12:24:30" } [代码] 主体内容一个是当前登录的用户,可以是用户id,也可以是用户名等可以检索定位到用户的信息;还有一个就是过期时间。还可以加入一些其他不私密的信息。 服务端拿到JWT之后可以在不读取数据的情况下,仅通过解码这部分信息就可以完成获取登录用户以及判断是否过期等初期工作。 最后的签名一般是把头部、主体内容再加上secret拼接成字符串再加密,这一步在用户登录生成JWT的时候就完成了。服务端拿到JWT之后只需要把前两部分加上secret再计算一次签名加以比对就可以完成校验签名,前端不需要同时保存secret。 JWT官网提供了各种服务端语言的生成代码,这里我提供一个我自己用PHP写的相对简化的方法,供大家参考 [代码]private function _jwt($payload){ $header['alg']='HS256'; $header['typ']='JWT'; $jwt_header=$this->_base64url($header); $jwt_payload=$this->_base64url($payload); $jwt_sign=hash_hmac('sha256', $jwt_header.'.'.$jwt_payload, $this->secret); $jwt['token']=$jwt_header.'.'.$jwt_payload.'.'.$jwt_sign; $jwt['sign']=$jwt_sign; return $jwt; } private function _base64url($a){ $c=base64_encode(json_encode($a)); $c=str_replace('=', '', $c); $c=str_replace('+', '-', $c); $c=str_replace('/', '_', $c); return $c; } [代码] 我这个方法里需要把主题内容以数组形式的参数传入,最终返回了生成的JWT和签名,方便接收时校验签名。 最后再说一下缺点 JWT在实际使用中也是存在问题的,目前想到以下几点: 安全性:签名包含在token中,一旦token整体被盗用,将没有办法区分,所以有网友称之为“裸奔”; 过期时间放在token中而不是服务端保存处理,一旦token生成并签发出去,将无法灵活的控制有效期; 最后一个是用户体验,其实可以算是token方式的通病。这个问题我也和群友讨论过,大家在访问社区的时候应该会遇到过,还在访问过程中突然变成未登录。我觉得这主要时因为服务端在token过期后就即时判断为用户登录失败,不管你在网页上处于什么状态。这个问题在session方式中是不存在的,前面说过session依赖于cookie,而存储session id的cookie是会保存在整个浏览器进程,就是说只要浏览器不关闭,用户就可以一直保持登录状态。
2019-12-03 - 小程序开发指南之性能优化
作者:HerryLo 原文永久链接: https://github.com/AttemptWeb/Record... 将从代码层面和项目层面,聊一聊微信小程序的性能优化问题,希望有所帮助。也是自己最近的一个总结。(前置知识:微信小程序的视图层是WebView支持,逻辑层是JSCore支持,逻辑层通过setData与视图层发生交互。每一个页面都是一个WebView页面)详见:小程序的运行环境 下面的内容不论是使用Taro框架开发,还是微信小程序原生开发,都是适用的。 代码层面 拆分组件 图片压缩 减少不必要数据 避免频繁setData 使用webView组件开发 项目层面 拆分小程序 分包预加载 尽量升级版本库 #代码层面通过代码细节提升性能,而且在这方面,空间是非常大的。对于比较早期的小程序项目,由于代码细节方面没有过多的考虑,也导致了开发出的小程序非常的消耗性能。下面将提到的一些点,不论是正在开发的项目,还是在维护的项目,都会有一定的帮助。 #👇· 拆分组件对于小程序的项目,由于野蛮式开发,不会太多的考虑组件拆分。当然对于关注组件开发的公司,肯定会在早期就做好一定的规划。做好组件的拆分,可以保证组件的复用,大量的减少重复的代码。(组件开发的思想,目前基本适用所有的前端开发项目)。如何有效拆分组件方法,详见[译] 你是如何拆分组件的?。 #👇· 图片压缩在微信小程序项目中,不会在本地存放图片,基本都是网络图片,而网络图片的下载,需要消耗一定的时间和内存。较小图片的大小,可以提升小程序的渲染时间,减少内存的占用。通过CDN静态资源服务器获取图片资源,添加图片压缩规则,可以极大的提升性能。 #👇· 减少不必要数据减少data中的亢余数据。不必要数据,传入setData会造成不必要的性能消耗。建议:不要直接将接口数据全部保存在data中,只有页面需要渲染的 数据,才通过setData进行挂载,非必要的数据可以直接挂载在当前页面page的this上。 [代码]let data = {[代码][代码] [代码][代码]list: [item, item, item, item, item],[代码][代码] [代码][代码]title: [代码][代码]"xxxx"[代码][代码],[代码][代码] [代码][代码]path: [代码][代码]'xx/xx/xx'[代码][代码]}[代码] [代码] [代码] [代码]// ❌不好的处理[代码] [代码] [代码] [代码]this.setData({[代码] [代码] [代码][代码]data: data[代码] [代码]})[代码] [代码] [代码] [代码]// 👌好的处理 只挂载必要参数[代码] [代码] [代码] [代码]this.setData({[代码] [代码] [代码][代码]dataList: data.list[代码] [代码]})[代码] [代码]this[代码][代码].title = data.title[代码] [代码]this.path = data.pat[代码] 👇· 避免频繁setData [代码]// ❌不好的处理[代码] [代码]this.setData({[代码] [代码] [代码][代码]count: 1[代码] [代码]})[代码] [代码]this[代码][代码].setData({[代码] [代码] [代码][代码]count: 2[代码] [代码]})[代码] [代码]this[代码][代码].setData({[代码] [代码] [代码][代码]count: 3[代码] [代码]})[代码] 三次[代码]setData[代码]会被全部触发嘛?是的,会被全部调用。 在微信小程序中,并不像react,把多次操作合并。每次setData的过程,就是一次JSCore逻辑层 和 webView视图层的交互。过多的交互,会降低小程序的用户体验。例如 滑动监听滚动,操作setData,非常的消耗性能,可以考虑使用节流函数,降低调用频率。 官方给出的说法是:setData接口的调用涉及逻辑层与渲染层间的线程通信,通信过于频繁可能导致处理队列阻塞,界面渲染不及时而导致卡顿,应避免无用的频繁调用。来修改一下上面的代码。 [代码]// 👌好的处理[代码][代码]let data = { count: 1 }[代码][代码]data.count = 2;[代码][代码]data.count = 3;[代码] [代码] [代码] [代码]this[代码][代码].setData(data)[代码] # 👇· webView组件开发 灵活性非常强的页面,同时需要多端使用,建议使用微信小程序提供的webView组件,套用h5页面进行页面开发,避免重复开发。当微信小程序包过大之后,也建议使用h5页面替换开发。详见webView 组件 [代码]<web-view [代码][代码]src=[代码][代码]"{{link}}"[代码][代码]bindmessage=[代码][代码]"bindMes"[代码][代码]bindload=[代码][代码]"bindLoad"[代码][代码]/>[代码]# 项目层面 通过微信小程序项目配置来提升加载性能,下面的方案可以有效的解决问题,不过也要试情况而定。 #👇· 拆分小程序在包过大的情况下下,可以进行小程序包的拆分,详见:分包加载。对小程序进行分包,可以优化小程序首次启动的下载时间,以及在多团队共同开发时可以更好的解耦协作。如果业务庞大,也可以单独抽离一个新的小程序。 #👇· 分包预加载采用分包配置之后,分包加载也是需要时间的。对于一些比较常用,或者比较重要的页面,可以采取分包预加载的手段,来完成分包的提前下载,减小分包载入的时间。详见 分包预下载 #👇· 尽量升级版本库注意基础版本库的问题,在合适的前提下升级版本库,尽量将版本库升级为最新版本。因为版本库升级为最新,可以有效避免很多问题。 目前随着各大公司业务的不断迭代,小程序的体积不断真大,小程序渐渐的不在小。请让小程序尽量的小起来,发挥它小的作用。希望以上内容可以帮到你。 更多内容: '小程序个人开发指南 ps: 微信公众号:Yopai,有兴趣的可以关注,每周不定期更新,分享可以增加世界的快乐 [图片]
2019-12-05 - 【圣诞节特供】雪花飘落组件
圣诞节到啦! 在写页面的时候想到以前会出现的雪花飘落 用超简单的方法实现了 [代码] [代码] [代码] 代码片段: https://developers.weixin.qq.com/s/2AUMkEmC7cdi[代码] 动画的原理是CSS中的 [代码] [代码] [代码]view { [代码][代码] [代码][代码]transition: [代码][代码]all[代码] [代码]5[代码][代码]s ease-in;[代码] [代码]}[代码] [代码] [代码] 雪花数量以及出现实际的实现的方法是 在data里面放一个数组,用于存雪花的x轴偏移量。 用setTimeOut的方法递归实现 1~2 秒雪花的增量 [代码] let snowInterval = e => {[代码][代码] [代码][代码]this[代码][代码].data.snowArray.push(Math.random() * 750);[代码][代码] [代码][代码]this[代码][代码].setData({ snowOffset: [代码][代码]this[代码][代码].data.snowArray.length - 2, snowArray: [代码][代码]this[代码][代码].data.snowArray });[代码][代码] [代码][代码]setTimeout(snowInterval, Math.random() * 1000 + 300);[代码][代码] [代码][代码]};[代码][代码] [代码][代码]snowInterval();[代码]再以雪花数组的长度以及当前雪花的键名 定义雪花的不同周期(出现→ 飘落 →沉底 → 消失) [代码] [代码] [代码]<[代码][代码]view[代码] [代码] wx:for[代码][代码]=[代码][代码]"{{snowArray}}"[代码] [代码] wx:if="{{index + 30 > snowOffset}}" [代码] [代码] wx:key="index" style="left: {{item}}rpx; {{index < [代码][代码]snowOffset[代码] [代码]? 'top: 100%' : ''}}" [代码] [代码] class[代码][代码]=[代码][代码]"dot"[代码] [代码]></[代码][代码]view[代码][代码]>[代码] [代码] [代码] 肥肠简单的实现方法,想想以前用网页去实现花费好多时间( 不过那个时候的版本会随着鼠标飘动 )
2019-12-16 - [打怪升级]小程序自定义头部导航栏“完美”解决方案
[图片] 为什么要做这个? 主要是在项目中,智酷君发现的一些问题 一些页面是通过扫码和订阅消息访问后,没有直接可以点击去首页的,需要添加一个home链接 需要添加自定义搜索功能 需要自定义一些功能按钮 [图片] 其实,第一个问题,在最近的微信版本更新中已经优化了,通过 小程序模板消息 过来的,系统会自动加上home按钮,但对于其他的访问方式则没有支持~ 一个不大不小的问题:两边ICON不对齐问题 [图片] 智酷君之前尝试了各种解决方法,发现有一个问题,就是现在手机屏幕太多种多样,有 传统头部、宽/窄刘海屏、水滴屏等等,无法八门,很多解决方案都无法解决特殊头部,系统**“胶囊按钮”** 和 自定义按钮在Android屏幕可能有 几像素不对齐 的问题(强迫症的噩梦)。 下面分享下一个相对比较完善的解决方案: [图片] 小程序代码段DEMO Link: https://developers.weixin.qq.com/s/cuUaCimT72cH ID: cuUaCimT72cH 智酷君做了一个demo代码段,方便大家直接用IDE工具查看源码~ [图片] 页面配置 1、页面JSON配置 [代码]{ "usingComponents": { "NavComponent": "/components/nav/common" //以插件的方式引入 }, "navigationStyle": "custom" //自定义头部需要设置 } [代码] 如果需要自定义头部,需要设置navigationStyle为 “custom” 2、页面代码 [代码]<!-- home 类型的菜单 --> <NavComponent v-title="自定义头部" bind:commonNavAttr="commonNavAttr"></NavComponent> <!-- 搜索菜单 --> <NavComponent is-search="true" bind:commonNavAttr="commonNavAttr"></NavComponent> [代码] 可以在自定义导航标签上添加属性配置来设置功能,具体按照实际需要来 3、目录结构 [代码]│ ├─components │ └─nav │ common.js │ common.json │ common.wxml │ common.wxss │ ├─images │ back.png │ home.png │ └─index index.js index.json index.wxml index.wxss search.js search.json search.wxml search.wxss [代码] 仅供参考 插件对应的JS部分 components/nav/common.js部分 [代码]const app = getApp(); Component({ properties: { vTitle: { type: String, value: "" }, isSearch:{ type: Boolean, value: false } }, data: { haveBack: true, // 是否有返回按钮,true 有 false 没有 若从分享页进入则没有返回按钮 statusBarHeight: 0, // 状态栏高度 navbarHeight: 0, // 顶部导航栏高度 navbarBtn: { // 胶囊位置信息 height: 0, width: 0, top: 0, bottom: 0, right: 0 }, cusnavH: 0, //title高度 }, // 微信7.0.0支持wx.getMenuButtonBoundingClientRect()获得胶囊按钮高度 attached: function () { if (!app.globalData.systeminfo) { app.globalData.systeminfo = wx.getSystemInfoSync(); } if (!app.globalData.headerBtnPosi) app.globalData.headerBtnPosi = wx.getMenuButtonBoundingClientRect(); console.log(app.globalData) let statusBarHeight = app.globalData.systeminfo.statusBarHeight // 状态栏高度 let headerPosi = app.globalData.headerBtnPosi // 胶囊位置信息 console.log(statusBarHeight) console.log(headerPosi) let btnPosi = { // 胶囊实际位置,坐标信息不是左上角原点 height: headerPosi.height, width: headerPosi.width, top: headerPosi.top - statusBarHeight, // 胶囊top - 状态栏高度 bottom: headerPosi.bottom - headerPosi.height - statusBarHeight, // 胶囊bottom - 胶囊height - 状态栏height (胶囊实际bottom 为距离导航栏底部的长度) right: app.globalData.systeminfo.windowWidth - headerPosi.right // 这里不能获取 屏幕宽度,PC端打开小程序会有BUG,要获取窗口高度 - 胶囊right } let haveBack; if (getCurrentPages().length != 1) { // 当只有一个页面时,并且是从分享页进入 haveBack = false; } else { haveBack = true; } var cusnavH = btnPosi.height + btnPosi.top + btnPosi.bottom // 导航高度 console.log( app.globalData.systeminfo.windowWidth, headerPosi.width) this.setData({ haveBack: haveBack, // 获取是否是通过分享进入的小程序 statusBarHeight: statusBarHeight, navbarHeight: headerPosi.bottom + btnPosi.bottom, // 胶囊bottom + 胶囊实际bottom navbarBtn: btnPosi, cusnavH: cusnavH }); //将实际nav高度传给父类页面 this.triggerEvent('commonNavAttr',{ height: headerPosi.bottom + btnPosi.bottom }); }, methods: { _goBack: function () { wx.navigateBack({ delta: 1 }); }, bindKeyInput:function(e){ console.log(e.detail.value); } } }) [代码] 解决不同屏幕头部不对齐问题的终极办法是 wx.getMenuButtonBoundingClientRect() 这个方法从微信7.0.0开始支持,通过这个方法我们可以获取到右边系统胶囊的top、height、right等属性,这样无论是水滴屏、刘海屏、异形屏,都能完美对齐右边系统默认的胶囊bar,完美治愈强迫症~ APP.js 部分 [代码]//app.js App({ /** * 加载页面 * @param {*} options */ onShow: function (options) { }, onLaunch: async function () { let self = this; //设置默认分享 this.globalData.shareData = { title: "智酷方程式" } // this.getSysInfo(); }, globalData: { //默认分享文案 shareData: {}, qrCodeScene: false, //二维码扫码进入传参 systeminfo: false, //系统信息 headerBtnPosi: false, //头部菜单高度 } }); [代码] 将获取的参数存储在一个全局变量globalData中,可以减少反复调用的性能消耗。 插件HTML部分 [代码]<view class="custom_nav" style="height:{{navbarHeight}}px;"> <view class="custom_nav_box" style="height:{{navbarHeight}}px;"> <view class="custom_nav_bar" style="top:{{statusBarHeight}}px; height:{{cusnavH}}px;"> <!-- 搜索部分--> <block wx:if="{{isSearch}}"> <input class="navSearch" style="height:{{navbarBtn.height-2}}px;line-height:{{navbarBtn.height-4}}px; top:{{navbarBtn.top+1}}px; left:{{navbarBtn.right}}px; border-radius:{{navbarBtn.height/2}}px;" maxlength="10" bindinput="bindKeyInput" placeholder="输入文字搜索" /> </block> <!-- HOME 部分--> <block wx:else> <view class="custom_nav_icon {{!haveBack||'borderLine'}}" style="height:{{navbarBtn.height}}px;line-height:{{navbarBtn.height-2}}px; top:{{navbarBtn.top}}px; left:{{navbarBtn.right}}px; border-radius:{{navbarBtn.height/2}}px;"> <view wx:if="{{haveBack}}" class="icon-back" bindtap='_goBack'> <image src='/images/back.png' mode='aspectFill' class='back-pre'></image> </view> <view wx:if="{{haveBack}}" class='navbar-v-line'></view> <view class="icon-home"> <navigator class="home_a" url="/pages/home/index" open-type="switchTab"> <image src='/images/home.png' mode='aspectFill' class='back-home'></image> </navigator> </view> </view> <view class="nav_title" style="height:{{cusnavH}}px; line-height:{{cusnavH}}px;"> {{vTitle}} </view> </block> </view> </view> </view> [代码] 主要是对几种状态的判断和定位的计算。 插件CSS部分 [代码]/* components/nav/test.wxss */ .custom_nav { width: 100%; background: #3a7dd7; position: relative; z-index: 99999; } .custom_nav_box { position: fixed; width: 100%; background: #3a7dd7; z-index: 99999; border-bottom: 1rpx solid rgba(255, 255, 255, 0.3); } .custom_nav_bar { position: relative; z-index: 9; } .custom_nav_box .nav_title { font-size: 28rpx; color: #fff; text-align: center; position: absolute; max-width: 360rpx; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; top: 0; left: 0; right: 0; bottom: 0; margin: auto; z-index: 1; } .custom_nav_box .custom_nav_icon { position:absolute; z-index: 2; display: inline-block; border-radius: 50%; vertical-align: top; font-size:0; box-sizing: border-box; } .custom_nav_box .custom_nav_icon.borderLine { border: 1rpx solid rgba(255, 255, 255, 0.3); background: rgba(0, 0, 0, 0.1); } .navbar-v-line { width: 1px; margin-top: 14rpx; height: 32rpx; background-color: rgba(255, 255, 255, 0.3); display: inline-block; vertical-align: top; } .icon-back { display: inline-block; width: 74rpx; padding-left: 20rpx; vertical-align: top; /* margin-top: 12rpx; vertical-align: top; */ height: 100%; } .icon-home { /* margin-top: 8rpx; vertical-align: top; */ display: inline-block; width: 80rpx; text-align: center; vertical-align: top; height: 100%; } .icon-home .home_a { height: 100%; display: inline-block; vertical-align: top; width: 35rpx; } .custom_nav_box .back-pre, .custom_nav_box .back-home { width: 35rpx; height: 35rpx; vertical-align: middle; } .navSearch { width: 200px; background: #fff; font-size: 14px; position: absolute; padding: 0 20rpx; z-index: 9; } [代码] 总结: 通过微信API: getMenuButtonBoundingClientRect(),结果各类手机屏幕的适配问题 将算好的参数存储在全局变量中,一次计算全局使用,爽YY~ 往期回顾: [填坑手册]小程序PC版来了,如何做PC端的兼容?! [填坑手册]小程序Canvas生成海报(一) [拆弹时刻]小程序Canvas生成海报(二)
2021-09-13 - 小程序顶部自定义导航组件实现原理及坑分享
为什么使用自定义导航 对比默认导航栏,我们会更需要: 统一Android、IOS手机对于页面title的展示样式及位置 更丰富的导航栏定制效果,如添加home图标等 左上角返回事件的监听处理 统一实现双击返回顶部功能 自定义导航组件实现思路 自定义导航组件实现的核心是需要计算导航栏的真实高度 这里以官方文档->扩展能力中的Navigation组件为例分析实现思路。当使用"navigationStyle": "custom"时,默认导航被移除,页面的开始位置变成了屏幕顶部,这时我们需要实现的导航栏是在状态栏下面。 导航栏的真实高度=状态栏高度+导航栏内容。 [图片] 使用wx.getSystemInfo获取到statusBarHeight便是导航栏的高度,但是导航栏内容高度呢? 有人可能觉得导航栏内容高度顾名思义就是导航栏内容高度啊,内容撑起还用管嘛!要,必须要! 因为右上角胶囊按钮是原生加载的,我们的导航栏内容需要正好贴在胶囊的下方且垂直居中。 导航栏内容高度=(胶囊按钮的顶部距离 - 状态高度)*2 + 胶囊高度 [图片] 如何计算胶囊的数据呢?幸运的是我们有 wx.getMenuButtonBoundingClientRect() 获取胶囊按钮的布局位置信息,那么动态计算导航栏的内容高度就很方便啦。 好了,以上就是动态计算的核心思路,我们再来看官方Navigation组件高度是怎么实现的 [代码]page{--height:44px;--right:190rpx;} .weui-navigation-bar .android{--height:48px;--right:222rpx} .weui-navigation-bar__inner{ position:fixed;top:0;left:0;z-index:5001;display:flex;align-items:center; height:var(--height);padding-right:var(--right);width:calc(100% - var(--right)) } [代码] 导航栏内容的高度是通过- -height这个css变量提前声明好的,安卓机型会重新覆盖为新的css变量值,目前没发现有适配问题。 官方就是官方啊,具体尺寸都知道,那就不用一番计算周折啦,直接拿来主义即可。 导航的布局位置已经搞定啦,剩下就是写具体的内容,不同业务实现需求不同这里就不一一赘述了。 完善官方顶部导航组件 本着拿来主义,直接使用官方Navigation组件,但在实际业务开发中还是遇到不少需要自定义的需求,就比如: loadding样式没实现 标题内容超出没有出现省略号 和原生顶部的样式不兼容,导致单个页面引入时跳转有明显差异出现 没有双击返回顶部功能开关功能 引入页面需要获取导航栏的高度,来控制其他元素距离顶部的位置, 不能根据页面栈数据动态显示隐藏back按钮, 针对以上需求,我们对官方的组件进行二次完善开发,满足常规的自定义需求绰绰有余,直接引入开箱即用。 源码使用示例 https://github.com/YuniorZen/minicode-debug/tree/master/minicode02 [图片] 使用说明 [代码]/*自定义头部导航组件,基于官方组件Navigation开发。*/ <navigation-bar title="会员中心" bindgetBarInfo="getBarInfo"></navigation-bar> [代码] 组件属性列表 属性 类型 描述 bindgetBarInfo function 组件实例载入页面时触发此事件,首参为event对象,event.detail携带当前导航栏信息,如导航栏高度 event.detail.topBarHeight bindback function 点击back按钮触发此事件响应函数 backImage string back按钮的图标地址 homeImage string home按钮的图标地址 ext-class string 添加在组件内部结构的class,可用于修改组件内部的样式 title string 导航标题,如果不提供为空 background string 导航背景色,默认#ffffff color string 导航字体颜色 dbclickBackTop boolean 是否开启双击返回顶部功能,默认true border boolean 是否显示顶部导航下边框分割线,默认false loading boolean 是否显示标题左侧的loading,默认false show boolean 显示隐藏导航,隐藏的时候navigation的高度占位还在,默认true left boolean 左侧区域是否使用slot内容,默认false center boolean 中间区域是否使用slot内容,默认false Slot name 描述 left 左侧slot,在back按钮位置显示,当left属性为true的时候有效 center 标题slot,在标题位置显示,当center属性为true的时候有效 自定义顶部导航目前存在的坑 弹窗的背景蒙层无法覆盖原生胶囊按钮 页面下拉刷新的圆点会被自定义导航遮盖 如果要自定义顶部导航,以上问题避免不了,只能忍着接受。 目前还没遇到完美的解决方案,针对下拉刷新圆点被遮挡的问题微信官方还在需求开发中,如果你有好的想法欢迎留言反馈,一起学习交流。
2019-10-31 - ✨5G时代来了,电商小程序商品视频播放解决方案,利用腾讯视频插件,好处多多,不占自己服务器空间和带宽
今天给大家介绍一下电商小程序商品加视频的解决方案 样例小程序: 各种好处: 空间节约:5G时代来临,视频播放为基本要求,还在为视频的存储空间发愁么,用腾讯视频插件,视频直接上传到腾讯服务器,无任何服务器空间消耗.带宽节约:空间不是问题的话,如何保证视频的播放流畅度?用腾讯视频插件播放,不占用自己服务器带宽,还省了CDN的钱,至于速度,你觉得腾讯视频的服务器会卡么?技术成本:视频的增删改查,代码都不用写,视频文件的地址直接用VID不到20个字符代替,开发方便,维护简单资质问题:用原生video标签,视频多了,上线审核的时候会要求文娱资质,有官方正面回答用腾讯视频播放,无需资质[图片] 视频审核:视频上传腾讯服务器的时候,腾讯自己就会审核好视频合法性 好处多多,我们来看下实现方法 一 添加插件: [图片] 进入小程序后台:设置->第三方设置->添加插件 输入APPID: wxa75efa648b60994b 腾讯视频插件官网地址:https://developers.weixin.qq.com/community/servicemarket/detail/00066e5ce0ce503bd9d837c1456415 二 小程序代码: 在app.json中加入代码,引用插件,版本号如果不是最新,开发工具上会有提示最新版版本号 [代码]"plugins": {[代码] [代码] "tencentvideo": {[代码] [代码] "version": "1.3.7",[代码] [代码] "provider": "wxa75efa648b60994b"[代码] [代码] }[代码] [代码]}[代码] 在需要播放视频的小程序页面的json中加入代码: [代码]{[代码] [代码] "navigationBarTitleText": "商品详情",[代码] [代码] "usingComponents": {[代码] [代码] "txv-video": "plugin://tencentvideo/video"[代码] [代码] }[代码] [代码]}[代码] 需要播放视频页面的wxml中加入代码: [代码]<!-- 商品轮播图开始 -->[代码] [代码]<swiper autoplay="{{autoplay}}"[代码] [代码]indicator-dots='{{indicator}}'[代码] [代码]indicator-active-color='#f54000'[代码] [代码]class='swiper'>[代码] [代码]<swiper-item wx:if="{{good.video}}">[代码] [代码] <txv-video vid="{{good.video}}"[代码] [代码]playerid="txv1"[代码] [代码]width="750rpx"[代码] [代码]height="720rpx"[代码] [代码]bindplay="videoplay"[代码] [代码]bindpause='videopause'[代码] [代码]bindpause='videoended'[代码] [代码]isHiddenStop="true"></txv-video>[代码] [代码]</swiper-item>[代码] [代码]<block wx:for="{{good.img}}"[代码] [代码]wx:key=''>[代码] [代码] <swiper-item>[代码] [代码] <image src="{{item}}"[代码] [代码]class='swiperimg'[代码] [代码]bindtap='previewImage'[代码] [代码]data-current='{{item}}'/>[代码] [代码] </swiper-item>[代码] [代码]</block>[代码] [代码]</swiper>[代码] [代码]<!-- 商品轮播图结束 -->[代码] 我这里是放到轮播图的第一张,做了判断 如果该商品无视频则不显示 讨论几个问题,视频播放的时候轮播图自动滚动了,页面下滑,视频继续播放,影响用户体验 所以增加三个方法和一个设置 视频播放时,设置轮播图为不自动轮播,消除轮播图位置点 binplay:视频播放时触发,设置轮播图为不自动播放,不显示位置点 [代码]//视频播放方法[代码] [代码]videoplay:function(){[代码] [代码] this.setData({[代码] [代码] autoplay:false,[代码] [代码] indicator: false,[代码] [代码] })[代码] [代码]},[代码] bindpause:视频暂停时触发,设置轮播图为自动播放,显示位置点 [代码]//视频暂停方法[代码] [代码] videopause:function(){[代码] [代码] this.setData({[代码] [代码] autoplay: true,[代码] [代码] indicator: true,[代码] [代码] })[代码] [代码] },[代码] bindended:视频播放结束时触发,设置轮播图为自动播放,显示位置点 [代码]//视频播放结束方法[代码] [代码]videoended:function(){[代码] [代码] this.setData({[代码] [代码] autoplay: true,[代码] [代码] indicator: true,[代码] [代码] })[代码] [代码]},[代码] 设置页面滑动,使视频不在可见范围时自动停止播放视频,isHiddenStop:ture 样例如图: [图片] 三 后台代码: 前端用VID播放了,可是后台客户怎么在后台网页上预览他上传的视频呢?------引用腾讯视频H5播放器插件 <div id="txvideo"></div> <input id="vid" name="video" class="inputl" placeholder="请输入腾讯视频vid" value='{$vid}'/> [代码]<script type="text/javascript"[代码] [代码]src="//vm.gtimg.cn/tencentvideo/txp/js/iframe/api.js"></script>[代码] [代码]<script>[代码] [代码] // 点播[代码] [代码] var[代码] [代码]vid = document.getElementById("vid").value//获取输入框元素[代码] [代码] var[代码] [代码]player = new[代码] [代码]Txp.Player({[代码] [代码] containerId: 'txvideo',[代码] [代码] vid: vid[代码] [代码] });[代码] [代码]</script>[代码] 效果如图: [图片] 四 获取腾讯视频vid方法: 进入腾讯视频.找到要播放的视频 ,鼠标放到分享那里,点击复制通用代码 [图片] 复制出来的代码如下: <iframe frameborder="0" src="https://v.qq.com/txp/iframe/player.html?vid=y3033v5o6ru" allowFullScreen="true"></iframe> 其中的vid=y3033v5o6ru 就是该视频的vid码 五 通过腾讯视频地址,盗播获取真实播放地址对比 用腾讯视频插件播放视频,是有前置广告的,可以花钱去广告,具体费用看该网站:https://v.qq.com/open 网上也有通过腾讯的视频地址盗播获取该视频真实播放地址的方法,将地址直接写到video标签就行.而且播放时没有广告,怎么说这种方法试过,毕竟上面的Q&A截图里人家说了,这种方法弊端,毕竟违规.仁者见仁吧 我没用这个方法主要是这个方法是有时效的,腾讯视频的地址是动态生成的,等token有效期一过,你的盗播地址就要跟着换,视频少还好,我这个是给电商加的视频播放,那么多商品,得换死... 六 槽点 1.视频广告,哎,不多说了 2.本来我得视频占比是750rpx:750rpx 结果这样得话下面得播放控制条显示不全,所以成了750x720 3.放到轮播图里广告结束时,跳过广告那个按钮会显示到其他轮播图上 七 建议 这个插件是真的好,算是官方得良心产品了,建议是开放视频上传接口,你说说这么好用得东西,唯一让客户不买单的就是我上传个视频还要到腾讯视频里上传.毕竟你有上传接口,共享出来,资质得话可以和小程序得APPID绑定啊 用token验证,毕竟能上线小程序得都能通过实名认证么 ,如果开放了上传接口,基本以后小程序根本不需要为播放视频开发任何东西了,你可以广告收费啊,毕竟现在也是收费去广告,大不了多收点,这点钱比起我们开发代码,买服务器空间,增加带宽,增加CDN得钱,根本不算什么
2021-04-15 - 小程序代码审核周期,如何加快小程序审核速度
我们开发小程序的时候,在提交代码审核时有时会等很久,有时会审核的很快,那么小程序的代码审核到底要多久呢?我们该如何加快自己小程序的被审核速度。下面程序猫给大家分享一下。 首先小程序的代码审核目前快的话几分钟就完成了,慢的话2-3天也有可能,如果超过3天,那说明你的小程序有歧义,审核员无法直接判断,尤其要注意审核期间不要撤销,一旦撤销重提,那就要重新排队审核。很多朋友小程序1天没有审就急得要死,这个完全没有必要,因为官方给出的审核时间是1-7天,如果没有超过7天,你再怎么发帖官方都是不会加急的。所以还是和客户说清楚吧,让客户耐心等待。如果是接触小程序很早的同学应该知道,过去审核都是5-7天,现在的审核速度真的算很快了。 另外有些类目是需要二次审核的,比如社交类目,视频类目。由于二次审核是政府主管机构审核,政府很忙的,所以你懂得,审核时间一般都要超过7天。还好微信考虑到实际情况,提出了超过7天审核可以预上线的功能。这里要注意下这个7天是从开始二审开始计算,而不是你提交审核开始计算。 目前能加速程序审核的只有通过小程序评测功能了 我简单介绍一下微信小程序的分项评定主要由运营指标,性能指标,用户指标这三项指标构成的,我们主要看一下这三项指标怎么提高。 运营指标: 运营指标的判定结果和其他两项略有不同的是,这个判定结果只有两种:达标和不达标。运营指标达标比较简单,三个月内没有违规即为达标,但是如果违规一次那么同样意味这至少三个月不达标所以这一点还是要慎重。 性能指标: 性能指标主要和小程序的开发水平有关,主要分为优秀,良好,一般,待优化,急待优化五种。性能指标一般需要达到的是优秀,关于如何达到优秀这个标准我们则可以根据评测的结果进行针对性的优化。 用户指标: 用户指标主要和小程序的用户挂钩,换句话也就是和运营有关,同样分为优秀,良好,一般,待优化,急待优化五种。关于如何提高用户指标这一项,我们可以主要针对两点,用户成长和用户的粘性下功夫即可。
2019-08-26 - 小程序审核注意事项与部分技巧
[转载] 创建一个小程序最尴尬的场景是:好不容易开发完了,却在代码提交给官方审核时被卡住了。小程序的审核风格跟苹果的App Store很像,审核非常严格,而且时间有时快有时慢,特别是一样会有一些让人哭笑不得的拒绝理由。为了让大家摆脱这些坑,今天,程序猫就整理了10种常见的被拒理由,大家仔细阅读,避免在小程序提交审核时掉进坑里。 1、类目不完善或者是类目选择不当这是目前最普遍、也是最容易出现的坑。小程序服务类目所对应的页面中的核心内容必须与该类目一致,并且跳转不要超过2次。例如,如果你做的是,小程序里却搞起了电商。千万不能搞一些挂羊头卖狗肉的小程序,这是肯定要禁止的。建议大家提交审核前一定要仔细看区分,可以了解下竞品使用的是什么类目。 2、功能不完善/功能不完整出现这个问题,一般是审核人员不会用,误以为你的小程序没开发完整,如果你确定没问题的话可以多提交一次试试。反正程序猫遇到很多非常奇怪的现象,当一个小程序未通过审核时,程序猫没做任何改动,再次提交竟发现神奇的通过了。当然,大家不要学程序猫,如果你有的是时间,倒是可以试试。如果急着要用,那就把小程序的功能完善好再提交审核。 3、产品的某个功能有bug 如果审核人员在体验小程序时,找到了Bug,那也是不能通过审核的。审核人员还能帮忙找BUG……也真是贴心。但是能不能说清楚一点:到底BUG在哪里?! 4、小程序简介没有介绍小程序功能很多人刚开始开发的时候,随便填写了一下简介,开发结束后忘记补充完整就直接提交了。将简介补充完整,重新上传就可以了。这种低级失误还是少犯才好,来回折腾很浪费时间的。 5、含有声音视频类目,请补充对应类目音频和视频文件不好管理,容易出现风险,所以微信官方审核特别严格。一定要慎之又慎! 6、图片被压缩体验不好这是饱汉不知饿汉饥,被压缩就是体验不好吗?那不压缩用户访问速度慢,体验岂不是更不好?再说服务器资源很贵的哟! 7、不得展示和推荐第三方小程序。不能做小程序导航、小程序排行榜之类的产品或功能,真是太不合理了。其实有很多做小程序的客户都在问:我的小程序在哪展示才能获得更多流量?哪怕微信官方做一个小程序商店也好啊! 8、不能含有诱导分享的内容这一条可谓教训惨痛!此前,仅 5 小时就产生了1700万PV(访问量)的「匿名聊聊」小程序,因为涉嫌诱导分享直接被封号;随后重新上线的「走心聊聊」不到一个星期,也被封号;被称为小程序版阅后即焚的「闪照」也因诱导分享被暂停服务。 9、不能包含赌博、竞猜和抽奖等内容很好奇,无码科技发布的小程序「抽奖助手」是怎么通过审核的? 10、慎用「附近的小程序」功能「附近的小程序」是小程序最主要的流量入口之一,特别是对于线下连锁门店而言,简直是曝光神器。不过,近期「附近的小程序」审核比较严格,一个经营资质只能添加一个地点,一个地点只能展现一个小程序,并且,一个小程序最多能开启10个附近展示的地址,每个地址都需要主体、资质证件号、地址等。如果用非常规手段实现了小程序的多地分身,也有可能会被微信永久性封禁「附近的小程序」功能。比如有些商家在「附近的小程序」中设置跨区多定位,如明明是南宁的门店,却定位到广州,而且还不是旗下分店,被用户举报后,将有可能会被微信官方永久性封禁「附近的小程序」功能。
2019-08-23 - 极致的scroll-view的下拉刷新扩展组件
不敢说是最好的,但是感觉也应该是性能和体验比较极致的下拉刷新扩展了,老规矩,代码片段放最后了~ 2020.2.22 修复了小程序基础库v2.10.2带来的不能滚动的问题,最新代码片段见scroll-view-extends 原理 其实原理很简单,和普通H5以及市面上有的下拉刷新没有特别大的区别,都是基于[代码]touch[代码]手势检测事件来实现下拉刷新的。[代码]touchstart[代码]的时候记录当前触摸点,[代码]touchmove[代码]的时候开始计算移动方向和移动距离, [代码]touchend[代码]的时候计算是否要进行下拉刷新操作。如图所示: [图片] 实现方法 调研了一些实现方法,目前大部分都是通过js计算,然后setData来改变元素的[代码]transform[代码]值实现下拉刷新。考虑到性能问题,此处使用了[代码]wxs[代码]的响应式能力来实现整个计算逻辑,不用通过逻辑层和视图层通信,直接在视图层进行渲染。具体文档请参考wxs响应事件。 这里在[代码]list[代码]组件(由[代码]scroll-view[代码]组成)下抽出了一个[代码]scroll.wxs[代码]作为响应事件的事件处理函数集合,源码基本上就在[代码]scroll.wxs[代码]和[代码]list[代码]组件。 [代码]scroll.wxs[代码]定义了如下变量和函数: [代码]var moveStartPosition = 0 //开始位置 var moveDistance = 0 //移动距离 var moveRefreshDistance = 60 //达到刷新的阈值 var moveMaxDistance = 100 //最大可滑动距离 var isRefreshMaxDown = false //是否达到了最大距离, 用来判断是否要震动提示 var loading = false //是否正在loading ... ... module.exports = { touchStart: touchStart, //手指开始触摸事件 touchMove: touchMove, //手指移动事件 touchEnd: touchEnd, //手指离开屏幕事件 loadingTypeChange: loadingTypeChange, //请求状态变化监听,监听刷新请求开始和请求完成 triggerRefresh: triggerRefresh //主动触发刷新操作,比如点击页面上一个按钮,重新刷新list,这就需要用到这个方法 } [代码] [代码]touchStart[代码]和[代码]touchMove[代码]就不用说了,代码注释都很明白,普通的监听移动和处理逻辑。 [代码]touchEnd[代码]主要是判断移动距离是否达到了阈值,然后根据结果,调用监听实例的[代码]callMethod[代码]方法触发[代码]refreshStart[代码]或者[代码]refreshCancel[代码]方法,这两个方法都是写到[代码]list[代码]组件里面的,用来触发刷新方法或者取消刷新。 [代码]loadingTypeChange[代码]方法主要是监听刷新是否完成,以此来触发动画效果。 [代码]triggerRefresh[代码]通过监听主动触发的变量来处理。如果需要主动触发刷新,则调用[代码]list[代码]组件内部的[代码]forceRefresh[代码]方法,具体使用示例在[代码]index/index/js[代码]的[代码]onLoad[代码]函数有: [代码]this.selectComponent('.list').forceRefresh()[代码] [代码]scroll.wxs[代码]里面还有一个未导出的方法,叫[代码]drawTransitionY[代码],这个方法主要是因为[代码]ios12[代码]对于[代码]transition[代码]动画效果支持的不好,所以自己写了个Y轴方向的动画([代码]linear[代码]线性的),大佬们可以自己往上添加各种[代码]ease-in-out[代码]效果。 里面具体的实现可以查看代码注释哦~ 使用 好了,前面讲了实现的原理和方法,那么在代码里面,应该怎么直接使用呢?如下代码所示: [代码]<!-- 使用示例 --> <list class="list" refresh-loading="{{refreshLoading}}" loading="{{loading}}" bindrefresh="initList" bindloadmore="loadmore"> <!-- your code --> </list> [代码] [代码]refresh-loading[代码]属性用来通过外部loading态来控制刷新动画的开始结束,因为每当变化[代码]refresh-loading[代码]的值时,会将变化同步到组件内的[代码]showRefresh[代码]属性,[代码]wxs[代码]通过监听[代码]showRefresh[代码]来处理动画逻辑。 [代码]loading[代码]属性是上拉加载更多的时候触发的loading态展示,跟刷新无关 [代码]bindrefresh[代码]是刷新触发时绑定的函数,下拉刷新动画成功开始后触发这个函数 [代码]bindloadmore[代码]透传[代码]scroll-view[代码]的加载更多方法 当然,源码里面也包含了一个[代码]list-item[代码]组件,这个跟本文没太大关系,是用来做瀑布流长列表内容太多时的内存不足问题解决方案的,具体请看解决小程序渲染复杂长列表,内存不足问题 干货 最后,上代码片段, 小程序代码片段 github地址
2020-02-22 - 小程序页面通信、数据刷新、事件总线 、event bus 终极解决方案之 iny-bus
#### 背景介绍 在各种小程序中,我们经常会遇到 这种情况 有一个 列表,点击列表中的一项进入详情,详情有个按钮,删除了这一项,这个时候当用户返回到列表页时, 发现列表中的这一项依然存在,这种情况,就是一个 `bug`,也就是数据不同步问题,这个时候测试小姐姐 肯定会找你,让你解决,这个时候,你也许会很快速的解决,但过一会儿,测试小姐姐又来找你说,我打开了 四五个页面更改了用户状态,但我一层一层返回到首页,发现有好几个页面数据没有刷新,也是一个 bug, 这个时候你就犯愁了,怎么解决,常规方法有下面几种 #### 解决方法 1. 将所有请求放到 生命周期 `onShow` 中,只要我们页面重新显示,就会重新请求,数据也会刷新 2. 通过用 `getCurrentPages` 获取页面栈,然后找到对应的 页面实例,调用实例方法,去刷新数据 3. 通过设置一个全局变量,例如 App.globalData.xxx,通过改变这个变量的值,然后在对应 onShow 中检查,如果值已改变,刷新数据 4. 在打开详情页时,使用 redirectTo 而不是 navigateTo,这样在打开新的页面时,会销毁当前页面, 返回时就不会回到这个里面,自然也不会有数据不同步问题 #### 存在的问题 1. 假如我们将 所有 请求放到 onShow 生命周期中,自然能解决所有数据刷新问题,但是 onShow 这个生命周期,有两个问题 第一个问题,它其实是在 onLoad 后面执行的,也就是说,假如请求耗时相同,从它发起请求到页面渲染, 会比 onLoad 慢 第二个问题,那就是页面隐藏、调用微信分享、锁频等等都会触发执行,请求放置于 `onShow` 中就会造成 大量不需要的请求,造成服务器压力,多余的资源浪费、也会造成用户体验不好的问题 2. 通过 `getCurrentPages` 获取页面栈,然后找到对应的 页面实例,调用实例方法,去刷新数据,这也 不失为一个办法,但是就如微信官方文档所说 > 不要尝试修改页面栈,会导致路由以及页面状态错误。 > 不要在 App.onLaunch 的时候调用 `getCurrentPages()`,此时 page 还没有生成。 同时、当需要通信的页面有两个、三个、多个呢,这里去使用 `getCurrentPages` 就会比较困难、繁琐 3. 通过设置全局变量的方法,当需要使用的地方比较少时,可以接受,当使用的地方多的时候,维护起来 就会很困难,代码过于臃肿,也会有很多问题 4. 使用 redirectTo 而不是 navigateTo,从用来体验来说,很糟糕,并且只存在一个页面,对于 tab 页面,它也无能为力,不推荐使用 #### 最佳实践 在 Vue 中, 可以通过 new Vue() 来实现一个 event bus作为事件总线,来达到事件通知的功能,在各大 框架中,也有自身的事件机制实现,那么我们完全可以通过同样的方法,实现一个事件中心,来管理我们的事件, 同时,解决我们的问题。iny-bus 就是这样一个及其轻量的事件库,使用 typescript 编写,100% 测试覆 盖率,能运行 js 的环境,就能使用 传送门 [源码](https://github.com/landluck/iny-bus) [NPM](https://www.npmjs.com/package/iny-bus) [文档](https://landluck.github.io/iny-bus/docs/) #### 简单使用 iny-bus 使用及其简单,在需要的页面 onLoad 中添加事件监听, 在需要触发事件的地方派发事件,使监 听该事件的每个页面执行处理函数,达到通信和刷新数据的目的,在小程序中的使用可以参考以下代码 [代码] // 小程序[代码] [代码] import bus from [代码][代码]'iny-bus'[代码] [代码] // 添加事件监听[代码] [代码] // 在 onLoad 中注册, 避免在 onShow 中使用[代码] [代码] onLoad () {[代码] [代码] this[代码][代码].eventId = bus.on([代码][代码]'事件名'[代码][代码], (a, b, c, d) => {[代码] [代码] // 支持多参数[代码] [代码] console.log(a, b, c, d)[代码] [代码] this[代码][代码].setData({ a [代码]}) [代码] // 调用页面请求函数,刷新数据[代码] [代码] this[代码][代码].refreshPageData()[代码] [代码] })[代码] [代码] // 添加只需要执行一次的 事件监听[代码] [代码] this[代码][代码].eventIdOnce = bus.once([代码][代码]'事件名'[代码][代码], () => {[代码] [代码] // do some thing[代码] [代码] })[代码] [代码] }[代码] [代码] // 移除事件监听,该函数有两个参数,第二个事件id不传,会移除整个事件监听,传入ID,会移除该[代码] [代码] // 页面的事件监听,避免多余资源浪费, 在添加事件监[代码][代码]/// 听后,页面卸载(onUnload)时建议移除[代码] [代码] onUnload () {[代码] [代码] bus.remove([代码][代码]'事件名'[代码][代码], [代码][代码]this[代码][代码].eventId)[代码] [代码] }[代码] [代码] // 派发事件,触发事件监听处更新视图[代码] [代码] // 支持多参传递[代码] [代码] onClick () {[代码] [代码] bus.emit([代码][代码]'事件名'[代码][代码], a, b, c)[代码] [代码] }[代码] 更详细的使用和例子可以参考 [Github iny-bus 小程序代码](https://github.com/landluck/iny-bus/tree/master/examples) #### iny-bus 具体实现 * 基本打包工具,这里使用非常优秀的开源库 [typescript-library-starter](https://github.com/alexjoverm/typescript-library-starter),具体细节不展开 * 测试工具 使用 facebook 的 [jest](https://github.com/facebook/jest) * build ci 使用 [travis-ci](https://www.travis-ci.org/) * 测试覆盖率上传使用 [codecov](https://codecov.io/) * 具体的其他细节大家可以看源码中的 [package.json](https://github.com/landluck/iny-bus/blob/master/package.json),这里就一一展开讲了 iny-bus 的核心代码,其实就这么多,总的来说,非常少,但是能解决我们在小程序中遇到的大量 通信 和 数据刷新问题,是采用 各大平台小程序 原生开发时,页面通信的不二之选,同时,100% 的测试覆盖率,确保了 iny-bus 在使用中的稳定性和安全性,当然,每个库都是从简单走向复杂,功能慢慢完善,如果 大家在使用或者源码中发现了bug或者可以优化的点,欢迎大家提 pr 或者直接联系我 最后,如果 iny-bus 给你提供了帮助或者让你有任何收获,请给 作者 点个赞,感谢大家 [点赞](https://github.com/landluck/iny-bus)
2019-08-04 - 购物车数量加减问题
-- 问题 -- 我现在点击一个商品的加减,其他商品的加减一起加一起减,我如何点击单个商品是,其他商品加减不懂,只有对应的商品的加减动。 -- html -- <view class="container"> <view wx:for="{{userIndexProList}}" wx:key="this" wx:for-item="item"> <view style='margin-top:35rpx;' class='item_container' bindtap='itemSelected' data-index='{{index}}'> <image class='select_icon' src="{{item.isSelected?'/images/wmn_select_have.png':'/images/wmn_select_none.png'}}" style='width:40rpx;margin-right:30rpx;'></image> </view> <view class="carts-text" style='width:calc(100% - 35rpx);'> <text class="carts-title">{{item.product.title}}</text> <text class="carts-title">{{item.product.items}}</text> <view class="carts-subtitle"> <text class="carts-price">¥{{item.product.price}}</text> <text bindtap="bindMinus" data-index="{{index}}">-</text> <text>{{num}}</text> <text bindtap="bindPlus" data-index="{{index}}">+</text> </view> </view> </view> </view> -- js -- data: { //默认是1 num: 1, }, //数量加 bindPlus: function() { var num = this.data.num; num++; this.setData({ num: num }); }, //数量减 bindMinus: function() { var num = this.data.num; if (num > 1) { num--; } this.setData({ num: num }); }
2019-04-15 - 小程序里使用es7的async await语法
我们做小程序开发时,有时候想让自己代码变得整洁,异步操作时避免回调地狱.我们会使用es6的promise. es7的async,await . promise在小程序和云开发的云函数里都可以使用. async和await只能在云开发的云函数里使用.我们在小程序的代码里直接使用,就会报如下错误. [图片] 这个报错就是告诉我们不能在小程序里直接使用es7的async和await语法.但是这么好的语法我们用起来确实显得代码整洁,逼格高. 那接下来我就教大家如何在小程序代码里使用es7的async和await语法. 一,下载facebook出的runtime.js类库 [图片] 其实这个问题,一些大厂已经给出了解决方案.如上图,我们只需要把facebook出的这个runtime.js类库下载下来,然后放到我们的小程序项目里. 下载链接:https://github.com/facebook/regenerator/blob/master/packages/regenerator-runtime/runtime.js github有时候下载比较慢,我也提前把这个类库下载好放我网盘里了. [图片] 下载链接:https://pan.baidu.com/s/19n5wmjIKK3PAPbcXBzWmQA 提取码:xxll 如果链接失效,可以在底部 留言,或者私信石头哥获取. 二,下载后,把runtime.js放到我们项目里 我这里把runtime.js放到我的utils目录下,如果你没有utils目录,可以新建. [图片] 三,代码里引入runtime.js类库 这里建议大家用 require语法引入. [图片] 这里需要注意的是.上图我们引入runtime.js时的变量名regeneratorRuntime必须和我这里一模一样.要不然就会引入不成功. 引入完后,在编译代码,可以看到控制台不再报我们一开始的错误 [图片] 四,简单使用async和await 首先要知道我们async和await是结合使用的. [图片] 上图是我简单写的一个定时器来模拟异步等待.只要我们这里成功的引入runtime.js类库,后面想使用async和await就方便很多了. 今天就讲到这里.想学习更多小程序相关的知识,请持续关注.下期见
2019-12-10 - 小程序奇技淫巧之 -- globalDataBehavior管理全局状态
Behaviors 自定义组件中,提供了[代码]behaviors[代码]的使用和定义。 从官方文档我们能看到: [代码]behaviors[代码]是用于组件间代码共享的特性,类似于一些编程语言中的“mixins”或“traits”。 每个[代码]behavior[代码]可以包含一组属性、数据、生命周期函数和方法,组件引用它时,它的属性、数据和方法会被合并到组件中,生命周期函数也会在对应时机被调用。每个组件可以引用多个[代码]behavior[代码]。 简单来说,我们能通过[代码]behaviors[代码]来重构[代码]Component[代码]的能力。Behavior的用处很多,前面也有介绍 computed 计算属性、watch 观察属性的实现,都是使用的 Behavior。 全局状态管理 我们希望全局共享一些数据状态,如果只是通过一个文件的方式进行维护,那么我们无法在状态更新的时候及时地同步到页面。我们需要额外调用 setData 才能更新页面中的 data 数据,才能告诉渲染层这块的数据渲染需要变更,而很多的 Store 状态管理库也是通过这样的方式实现的(事件通知 + setData + 全局状态)。 在小程序 Behavior 能力的支持下,我们可以通过一个全局的 globalData Behavior 注入到每个需要用到的 Component 中,这样就可以在需要的页面中直接引入该 Behavior,就能获取到了。不啰嗦,Behavior的实现如下: [代码]// globalDataStore 用来全局记录 globalData,为了跨页面同步 globalData 用 export let globalDataStore = {}; // 获取本地的 gloabalData 缓存 try { const gloabalData = wx.getStorageSync("gloabalData"); // 有缓存的时候加上 if (gloabalData) { globalDataStore = { ...gloabalData }; } } catch (error) { console.error("gloabalData getStorageSync error", "e =", error); } // globalCount 用来全局记录 setGlobalData 的调用次数,为了在 B 页面回到 A 页面的时候, // 检查页面 __setGlobalDataCount 和 globalCount 是否一致来判断在 B 页面是否有 setGlobalData, // 以此来同步 globalData let globalCount = 0; export default Behavior({ data: { globalData: Object.assign({}, globalDataStore) }, lifetimes: { attached() { // 页面 onLoad 的时候同步一下 globalCount this.__setGlobalDataCount = globalCount; // 同步 globalDataStore 的内容 this.setData({ globalData: Object.assign( {}, this.data.globalData || {}, globalDataStore ) }); } }, pageLifetimes: { show() { // 为了在 B 页面回到 A 页面的时候,检查页面 __setGlobalDataCount 和 globalCount 是否一致来判断在 B 页面是否有 setGlobalData if (this.__setGlobalDataCount != globalCount) { // 同步 globalData this.__setGlobalDataCount = globalCount; this.setGlobalData(Object.assign({}, globalDataStore)); } } }, methods: { // setGlobalData 实现,主要内容为将 globalDataStore 的内容设置进页面的 data 的 globalData 属性中。 setGlobalData(obj: any) { globalCount = globalCount + 1; this.__setGlobalDataCount = this.__setGlobalDataCount + 1; obj = obj || {}; let outObj = Object.keys(obj).reduce((sum, key) => { let _key = "globalData." + key; sum[_key] = obj[key]; return sum; }, {}); this.setData(outObj, () => { globalDataStore = this.data.globalData; }); }, // setGlobalDataAndStorage 实现,先调用 setGlobalData,然后存到 storage 里 setGlobalDataAndStorage(obj: any) { this.setGlobalData(obj); try { let gloabalData = wx.getStorageSync("gloabalData"); // 有缓存的时候加上 if (gloabalData) { gloabalData = { ...gloabalData, ...obj }; } else { gloabalData = { ...obj }; } wx.setStorageSync("gloabalData", gloabalData); } catch (e) { console.error("gloabalData setStorageSync error", "e =", e); } } } }); [代码] 显然,该 Behavior 主要提供了几个能力: 会在小程序 data 添加 globalData 的属性,在 WXML 文件中可以直接通过[代码]{{globalData.xxxx}}[代码]获取到 提供[代码]setGlobalData()[代码]方法,用于更新全局状态 提供[代码]setGlobalDataAndStorage()[代码]方法,用于更新全局状态,同时写入缓存(会在下次启动应用的时候自动获取缓存数据) 这样,我们在初始化 Component 的时候直接引入就可以使用: [代码]Component({ // 在behaviors中引入globalDataBehavior behaviors: [globalDataBehavior], // 其他选项 methods: { test() { // 使用this.setGlobalData可以更新全局的数据状态 this.setGlobalData({ test: "hello world" }); // 使用this.setGlobalDataAndStorage可以更新全局的数据状态,并写入缓存 // 下次globalDataBehavior会默认从缓存中获取 this.setGlobalDataAndStorage({ test: "hello world" }); } } }); [代码] 在引入了 globalDataBehavior 之后,我们的 WXML 就可以直接使用了: [代码]<view>{{ globalData.test }}</view> [代码] 页面如何使用 Behavior [代码]Component[代码]是[代码]Page[代码]的超集,因此可以使用[代码]Component[代码]构造器构造页面。 看看官方文档:事实上,小程序的页面也可以视为自定义组件。因而,页面也可以使用[代码]Component[代码]构造器构造,拥有与普通组件一样的定义段与实例方法。但此时要求对应[代码]json[代码]文件中包含[代码]usingComponents[代码]定义段。 更详细的使用方法,在 computed 计算属性、watch 观察属性两篇文章中也有描述,大家可以自行参考。 或者直接查看最终的项目代码:wxapp-typescript-demo。 参考 Component构造器 behaviors 结束语 Behavior 其实是很强大的一个能力,我们能用它来对自己的小程序做很多的能力拓展,缺啥补啥,还可以“混入”给每个 Component 每个方法打入日志,就不用每个组件自己手动打印代码拉。
2019-12-10 - 小程序页面(Page)扩展,为所有页面添加公共的生命周期、事件处理等函数
背景 在小程序的原生开发中,页面中经常会用到一些公共方法,例如在页面onLoad中验证权限、所有页面都需要onShareAppMessage设置分享等 假设我们在编码时每个页面都写一遍,显然不是一个高级程序员会干的事情,太Low了。如果我们定义一个公共文件,导出这些公共方法,每个页面都引入,然后再生命周期或者事件处理函数中调用,虽然看起来很方便,但不够优雅,达不到我们最终的目的(偷懒)。 下面给大家介绍一种相对比较优雅的实现方式,扩展Page来实现以上的操作。 Page(页面) 需要传入的是一个 [代码]object[代码] 类型的参数,那么我们重载一个 [代码]Page[代码] 函数,将这个 [代码]object[代码] 参数拦截改掉就可以了,下面直接上代码。 实现 1、在根目录新建一个 [代码]page-extend.js[代码] 文件,公共的逻辑都写在这里面 [代码]/** * * Page扩展函数 * * @param {*} Page 原生Page */ const pageExtend = Page => { return object => { // 导出原生Page传入的object参数中的生命周期函数 // 由于命名冲突,所以将onLoad生命周期函数命名成了onLoaded const { onLoaded } = object // 公共的onLoad生命周期函数 object.onLoad = function (options) { // 在onLoad中执行的代码 ... // 执行onLoaded生命周期函数 if (typeof onLoaded === 'function') { onLoaded.call(this, options) } } // 公共的onShareAppMessage事件处理函数 object.onShareAppMessage = () => { return { title: '分享标题', imageUrl: '分享封面' } } return Page(object) } } // 获取原生Page const originalPage = Page // 定义一个新的Page,将原生Page传入Page扩展函数 Page = pageExtend(originalPage) [代码] 2、在 [代码]app.js[代码] 中引入 [代码]page-extend.js[代码] 文件 [代码]require('./page-extend') App({ // 其他代码 ... }) [代码] 代码片段 https://developers.weixin.qq.com/s/Cyx8iGmV7Ldp 本文内容及评论未经允许,禁止任何形式的转载与复制(代码可在程序中使用)
2019-12-24 - 小程序顶部导航栏,可滑动,可动态选中放大
最近在研究小程序顶部导航栏时,学到了一个不错的导航栏,今天就来分享给大家。 老规矩,先看效果图 [图片] 可以看到我们实现了如下功能 1,顶部导航栏 2,可以左右滑动的导航栏 3,选中条目放大 原理其实很简单,我这里把我研究后的源码发给大家吧。 wxml文件如下 [代码]<!-- 导航栏 --> <scroll-view scroll-x class="navbar" scroll-with-animation scroll-left="{{scrollLeft}}rpx"> <view class="nav-item" wx:for="{{tabs}}" wx:key="id" bindtap="tabSelect" data-id="{{index}}"> <view class="nav-text {{index==tabCur?'tab-on':''}}">{{item.name}}</view> </view> </scroll-view> [代码] wxss文件如下 [代码]/* 导航栏布局相关 */ .navbar { width: 100%; height: 90rpx; /* 文本不换行 */ white-space: nowrap; display: flex; box-sizing: border-box; border-bottom: 1rpx solid #eee; background: #fff; align-items: center; /* 固定在顶部 */ position: fixed; left: 0rpx; top: 0rpx; } .nav-item { padding-left: 25rpx; padding-right: 25rpx; height: 100%; display: inline-block; /* 普通文字大小 */ font-size: 28rpx; } .nav-text { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; letter-spacing: 4rpx; box-sizing: border-box; } .tab-on { color: #fbbd08; /* 选中放大 */ font-size: 38rpx !important; font-weight: 600; border-bottom: 4rpx solid #fbbd08 !important; } [代码] js文件如下 [代码]// pages/test2/test2.js Page({ data: { tabCur: 0, //默认选中 tabs: [{ name: '等待支付', id: 0 }, { name: '待发货', id: 1 }, { name: '待收货', id: 2 }, { name: '待签字', id: 3 }, { name: '待评价', id: 4 }, { name: '五星好评', id: 5 }, { name: '差评订单', id: 6 }, { name: '编程小石头', id: 8 }, { name: '小石头', id: 9 } ] }, //选择条目 tabSelect(e) { this.setData({ tabCur: e.currentTarget.dataset.id, scrollLeft: (e.currentTarget.dataset.id - 2) * 200 }) } }) [代码] 代码里注释很明白了,大家自己跟着多敲几遍就可以了。后面会更新更多小程序相关的知识,请持续关注。
2019-11-22 - 获取小程序任何页面链接的方法
小程序不像网站,任何页面都可以复制出来链接。要访问某个页面,直接点击链接就可以了。其实小程序也是可以复制出链接。 不废话,马上上干货~! 1、首先进入小程序后台,把要获取链接的微信添加到项目成员。 [图片] [图片] 2、进入生成小程序码工具,添加获取链接的微信号。 不知道怎么进入生成小程序码工具,请看来一间上一篇文章:一个独特的小程序码生成方法。 [图片] 点“获取更多页面路径”打开窗口,然后输入上面添加的微信号点击“开启”按钮。如上图“开启入口成功”字样就会显示出来。这时代表这个微信号能复制出当前小程序的任意可显示页面的链接。 3、进入当前小程序,就可以获取到当前显示页面的链接。 [图片] 获取到小程序链接有什么用?请看看来一间上一篇文章。 最后再送出一个小彩蛋:其实小程序有个大原则:所见即所得! 就是你进入的页面,转发出去的页面也是当前你打开的页面。
2019-11-19