关于wepy:从-wepy-到-uniapp-变形记

作者:vivo 互联网前端团队-Wan Anwen、Hu Feng、Feng Wei、Xie Tao进入互联网“下半场”,靠“人海战术”的研发模式曾经不再具备竞争力,如何通过技术升级晋升研发效力?前端通过Babel等编译技术倒退实现了工程化体系降级,如何进一步通过编译技术赋能前端开发?或者咱们 wepy 到uniapp 编译的转换实际,能给你带来启发。 一、 背景随着小程序的呈现,借助微信的生态体系和海量用户,使服务以更加便捷形式的触达用户需要。基于此背景,团队很早布局智能导购小程序(为 vivo 各个线下门店导购提供服务的用户经营工具)的开发。 晚期的小程序开发工程体系还不够健全,和当初的前端的工程体系相差较大,体现在对模块化,组件化以及高级JavaScript 语法个性的撑持上。所以团队在做技术选型时,心愿克服原生小程序工程体系上的有余,通过比照最初抉择了腾讯出品的 wepy 作为整体的开发框架。 在我的项目的从0到1阶段,wepy 的确帮忙咱们实现了疾速的业务迭代,满足线下门店导购的需要。但随着工夫的推移,在技术上,社区逐渐积淀出以 uniapp 为代表的 Vue 栈体系和以 Taro 为代表的 React 栈跨端的体系,wepy 目前的社区活跃度比拟低。另外随着业务进入稳固阶段,除大量的 wepy 小程序,H5 我的项目和新的小程序都是基于 Vue 和 uniapp 来构建,团队也是心愿对立技术栈,实现更好的跨端开发能力,升高开发和保护老本,晋升研发效率。 二、思考随着团队决定将智能导购小程序从 wepy 迁徙到 uniapp 的架构体系,咱们就须要思考,如何进行我的项目的安稳的迁徙,同时兼顾效率和品质?通过对以后的我的项目状态和技术背景进行剖析,团队梳理出2个准则3种迁徙思路。 2.1 渐进式迁徙外围出发点,保障我的项目的平稳过渡,给团队更多的工夫,在迭代中逐渐的进行架构迁徙。心愿以此来升高迁徙中的危险和不可控的点。基于此,咱们思考两个计划: 计划一 交融两套架构体系 在目前的我的项目中引入和 uniapp 的我的项目体系,一个我的项目交融了 wepy 和 uniapp 的代码工程化治理,逐渐的将 wepy 的代码改成 uniapp 的代码,待迁徙实现删除 wepy 的目录。这种计划实现起来不是很简单,然而毛病是治理起来比较复杂,两套工程化管理机制,底层的编译机制,各种入口的配置文件等,治理起来比拟麻烦。另外团队每个人都须要消化 wepy 到 uniapp 的畛域常识迁徙,不仅仅是我的项目的迁徙也是常识体系的迁徙。 计划二 设计 wepy-webpack-loader 以 uniapp 为工程体系根底,外围思路是将现有 wepy 代码融入到 uniapp 的体系中来。咱们都晓得 uniapp 的底层依赖于 Vue 的 cli 的技术体系,最底层通过 webpack 实现对 Vue 单组件文件和其余资源文件的 bundle。 ...

October 31, 2022 · 9 min · jiezi

wepy返回上一页修改上一页面data后却没更新

A-B-A过程。 //A页面data = { value:'' }onShow(){ console.log(this.value) } //跳转到B页面methods ={ goB(){ wepy.navigateTo({ url: B); }}//B页面 onLoad(options){ }methods = { goBackA(){ var pages = getCurrentPages(); // 获取页面栈 var prevPage = pages[pages.length - 2]; // 上一个页面 prevPage.setData({ value:2 }) wepy.navigateBack({ delta: 1 }) }}开始觉得不是很简单嘛,原生就有 意想不到的情况发生了,在A页面onShow方法中打印value是空,也取不到,但是页面能渲染。。。懵逼了啊 最后在公司大神提醒下看了试了preload方法官网地址 A页面修改如下 this.$preload('preload',preloadData) wepy.navigateTo({ url: B);然后完美实现,是不是超简单。。具体preload用法参考 https://www.cnblogs.com/l-yabiao/p/9136327.html 文笔一般,给个赞吧

July 4, 2019 · 1 min · jiezi

wepyredux

wepy-reduxredux文件 typetypes里是触发action的函数名称 只是存储函数名字 按照模块去创建type.js //base.jsexport const GETALLHOMEINFO = 'GETALLHOMEINFO'写好了函数名称 在index.js中export出来 export * from './counter'export * from './base'reducers随着应用变得复杂,需要对 reducer 函数 进行拆分,拆分后的每一块独立负责管理 state 的一部分这个时候多个模块的reducer通过combineReducers合并成一个最终的 reducer 函数, import { combineReducers } from 'redux'import base from './base'import counter from './counter'export default combineReducers({ base, counter})模块使用handleActions 来处理reducer,将多个相关的reducers写在一起handleActions有两个参数:第一个是多个reducers,第二个是初始state GETALLHOMEINFO reducer是将异步action返回的值赋值给data //base.jsimport { handleActions } from 'redux-actions'import { GETALLHOMEINFO } from '../types/base'const initialState = { data: {}}export default handleActions({ [GETALLHOMEINFO] (state, action) { return { ...state, data: action.payload } }}, initialState)actionsactions是对数据的处理 在index.js中export出来 ...

July 3, 2019 · 1 min · jiezi

wepy使用Promise简易封装网络请求

network/request.js //封装ajax请求const http = (url,type,parameter) => { return new Promise((resolve,reject) => { wx.request({ url:url, method:type, success:function (res) { resolve(res.data); }, fail:function (err) { reject(err) } }) })}export {http}在index.wpy中使用 import { http } from '../network/request'; http("https://easy-mock.com/mock/5cc66aee7a9a541c744c9c07/example/restful/:id/list","GET").then(function (res) { console.log(res) }).catch(function (err) { });我的微信公众号:天字一等

May 21, 2019 · 1 min · jiezi

微信小程序-wepy学习笔记

wepy文档:https://tencent.github.io/wepy/全局安装wepy: npm i -g wepy-cli初始化项目: wepy init standard myproject切换到项目目录: cd myproject安装依赖: npm installwepy项目目录├── dist // 打包后的文件├── src // 开发文件目录│ ├── components // 组件目录│ ├── images // 图片文件目录│ ├── mixins // 通用函数│ ├── mocks // 模拟数据目录│ ├── pages // 小程序单个页面目录│ ├── store // redux存储数据(页面传值)│ ├── app.wpy // 入口文件(配置页面的跳转)│ ├── index.html // 模版文件├── wepy.config.js // 配置文件重要提醒使用微信开发者工具–>添加项目,项目目录请选择dist目录。微信开发者工具–>项目–>关闭ES6转ES5。 重要:漏掉此项会运行报错。微信开发者工具–>项目–>关闭上传代码时样式自动补全。 重要:某些情况下漏掉此项也会运行报错。微信开发者工具–>项目–>关闭代码压缩上传。 重要:开启后,会导致真机computed, props.sync 等等属性失效。(注:压缩功能可使用WePY提供的build指令代替,详见后文相关介绍以及Demo项目根目录中的wepy.config.js和package.json文件。)本地项目根目录运行npm run dev(wepy build –watch),开启实时编译。(注:如果同时在微信开发者工具–>设置–>编辑器中勾选了文件保存时自动编译小程序,将可以实时预览,非常方便。)开发介绍index.template.html: 为模块文件其中通过wepy.app创建入口文件,wepy.page创建页面文件,wepy.component创建组件文件。app.wpy:入口文件,包含config,globalData,constructor和生命周期。在config中配置路由config:配置pages,window,tabBar。分别为页面路由配置,页面导航栏配置,页面底部栏配置。详细配置在小程序框架配置globalData: 全局参数配置,用于多个页面的公用参数。constructor: 拦截器的配置。生命周期使用其中如果在页面开发中需要用到async/await的话,需要在app.wpy中使用import ‘wepy-async-function’加载模块,不然在编译后页面会报错,导致async/await无法使用。// 设置路由,导航栏,底部栏config = { pages: [ ‘pages/main’, ‘pages/admin-center’ ], window: { backgroundTextStyle: ’light’, navigationBarBackgroundColor: ‘#fff’, navigationBarTitleText: ‘WeChat’, navigationBarTextStyle: ‘black’ }, tabBar = { color: ‘#AEADAD’, selectedColor: ‘#049BFF’, backgroundColor: ‘#fff’, borderStyle: ‘black’, list: [{ pagePath: ‘pages/index’, text: ‘首页’, “iconPath”: “images/ico-home.png”, “selectedIconPath”: “images/ico-home-d.png” }, { pagePath: ‘pages/admin-center’, text: ‘借阅’, “iconPath”: “images/ico-setting.png”, “selectedIconPath”: “images/ico-setting-d.png” }] }}// 全局参数(方便后期各页面中的使用)globalData = { prizeList: [], // 领取的奖品列表}// 设置拦截器,intercept为拦截器函数constructor () { super() intercept(this)}// 页面加载onLaunch(res) { console.log(res)}wpy模块中的属性export default class Index @extends wepy.page{ customData = {} // 自定义数据 customFunction () {} //自定义方法 onLoad () {} // 在Page和Component共用的生命周期函数 onShow () {} // 只在Page中存在的页面生命周期函数 config = {}; // 只在Page实例中存在的配置数据,对应于原生的page.json文件 data = {}; // 页面所需数据均需在这里声明,可用于模板数据绑定 components = {}; // 声明页面中所引用的组件,或声明组件中所引用的子组件 mixins = []; // 声明页面所引用的Mixin实例 computed = {}; // 声明计算属性(详见后文介绍) watch = {}; // 声明数据watcher(详见后文介绍) methods = {}; // 声明页面wxml中标签的事件处理函数。注意,此处只用于声明页面wxml中标签的bind、catch事件,自定义方法需以自定义方法的方式声明 events = {}; // 声明组件之间的事件处理函数}属性说明config页面配置对象,对应于原生的page.json文件,类似于app.wpy中的configcomponents页面组件列表对象,声明页面所引入的组件列表data页面渲染数据对象,存放可用于页面模板绑定的渲染数据methodswxml事件处理函数对象,存放响应wxml中所捕获到的事件的函数,如bindtap、bindchange,不能用于声明自定义方法。eventsWePY组件事件处理函数对象,存放响应组件之间通过$broadcast、$emit、$invoke所传递的事件的函数其它小程序页面生命周期函数,如onLoad、onReady等,以及其它自定义的方法与属性wpy中自定义的函数应当写在与methods平级的位置,不用写在methods中。页面的跳转页面的跳转需要先在app.wpy的config中的pages中设置页面的路由,在页面中通过navigateTo跳转到相应页面。在wepy.page的脚本中可以通过this.$navigate({url:""})实现页面的跳转。而在wepy.component的组件中可以通过this.$parent.$navigate({url:""})或wepy.navigateTo实现。wepy.navigateTo({ url: ‘/pages/info’})wepy中的生命周期wepy中的生命周期的钩子函数有:onLoad,onReady,onShow,onPrefetch等,其中onReady,onShow,onPrefetch只有wepy.page中才有用。wepy.component只支持onLoad,其他都不会触发。onLoad: 页面加载完成时调用,一个页面只会调用一次。(在路由跳转的时候通过navigateTo跳转的话onload会重新执行,通过navigateBack跳转的话onLoad不会重新执行)onShow:页面显示的时候调用。onReady: 页面中的所有资源加载完成时调用。onPrefetch:在页面跳转时触发,用于预加载和预查询数据。onUnload: 在页面卸载时触发(通过redirectTo,switchTab,navigateBack,reLaunch会触发当前页面中的onUnload,但navigateTo不会触发)。生命周期顺序:onPrefetch > onLoad > onShow > onReady。onPrefetch这个生命周期是wepy中扩展的,它的触发需要通过this.$navigate及其他wepy封装的跳转方式才能实现。当设置onPrefetch后,可以在onLoad中设置变量来获取onPrefetch中返回的值。案例:onLoad (params, data) { data.prefetch.then((list) => { this.adminMath = list.chasucccnt.data.succcnt this.recordInfo = list.adminCenter.data.challengeRecList this.heightScore = list.adminCenter.data.hs this.hadMath = list.adminCenter.data.cc this.$apply() })}// chasucccnt,getAdminCenter为请求后台的函数,返回的数据结构是{data:{succcnt:0}。async onPrefetch () { let chasucccnt = await this.chasucccnt() let adminCenter = await this.getAdminCenter() return new Promise((resolve, reject) => { resolve({ chasucccnt: chasucccnt, adminCenter: adminCenter }) })}props实现父子组件之间的传值官方案例:// parent.wpy<child :title = ‘parentTitle’ :syncTitle.sync = ‘parentTitle’ :twoWayTitle = ‘parentTitle’></child>在script中的设置data = { parentTitle: ‘p-title’}// child.wpyprops = { // 静态传值 title: String, // 父向子单向动态传值 syncTitle: { type: String, default: ’null’ }, twoWayTitle: { type: Number, default: ’nothing’, twoWay: true }};onLoad () { console.log(this.title); // p-title console.log(this.syncTitle); // p-title console.log(this.twoWayTitle); // p-title this.title = ‘c-title’; console.log(this.$parent.parentTitle); // p-title. this.twoWayTitle = ’two-way-title’; this.$apply(); console.log(this.$parent.parentTitle); // two-way-title. — twoWay为true时,子组件props中的属性值改变时,会同时改变父组件对应的值 this.$parent.parentTitle = ‘p-title-changed’; this.$parent.$apply(); console.log(this.title); // ‘c-title’; console.log(this.syncTitle); // ‘p-title-changed’ — 有.sync修饰符的props属性值,当在父组件中改变时,会同时改变子组件对应的值。}有上案例可以知道:title为静态传值,即只有第一次有效,后面改变值后子组件中的title不会发生改变,当在属性后添加.sync后,即该属性发生改变会导致子组件中相应的值发生改变,当在子组件中的props中设置twoWay: true后,可以实现父子组件的双向绑定。组件之间的数据通信wepy中的通信主要采用三种方法:$broadcast, $emit, $invoke;$broadcast:父组件触发所有子组件(包含子孙级组件)事件。$emit:子组件触发所有父组件(包含祖先级组件)事件。当在父组件属性中使用.user设置自定义事件后,$emit可以用于触发自定义事件而events中声明的函数将不会再执行。(与vue中的用法不同,vue中需要在父组件中设置子组件的属性,用于在子组件中触发。而wepy中则不需要,只要在events中设置方法就可以在子组件中触发。)$invoke:页面或子组件触发另一个子组件事件。parent.wpy<template> <view> <children @childFun.user = ‘someEvent’></children> </view></template><script>export default class Parent extends wepy.page{ data = { name: ‘parent’ } events = { ‘some-event’: (p1, p2, p3, $event) => { // 输出为’parent receive some-event children’,$event.source指向子组件。 console.log(${this.name} receive ${$event.name} from ${$event.source.name}) } } onLoad () { this.$broadcast(‘getIndex’, 1, 4) } methods = { someEvent (…p) { // 输出[1, 2, 3, _class]。 console.log(p) } }}</script>children.wpy<script>export default class Parent extends wepy.page{ data = { name: ‘children’ } onLoad () { // this.$emit(‘some-event’, 1, 2, 3) // 触发组件中的自定义事件 this.$emit(‘childFun’, 1, 2, 3) } events = { ‘getIndex’: (…p) => { console.log(p) // 输出[1, 4] } }}在父组件中给子组件添加属性@childFun.user = ‘someEvent’后,在子组件中修改触发条件this.$emit(‘childFun’, 1, 2, 3)//$invoke父组件向子组件发送事件:使用import导入子组件后,在使用时可以直接通过this.$invoke(‘子组件,必须要单引号括起来’, ‘子组件方法名称’, param1,param2,param3…….);子组件间发送事件:this.$invoke(‘子组件的相对路径’, ‘子组件方法名称’, param1,param2,param3…….);子组件的相对路径的理解: 当设置’./‘即当前组件,’../‘为父组件,以此类推。它可以指定向哪一个组件分发内容,但只适用于简单的组件树结构,复杂的结构考虑使用redux。在子组件中使用$emit会触发父组件及祖先组件中的相同事件。在父组件中使用$broadcast会触发子组件及子孙组件中的相同事件。其中$emit和$broadcast触发的事件设置在组件中的events中,而$invoke触发的函数与events是平级的,当在组件中已经通过components导入组件的话,$invoke中的第一个参数可以直接设置为components中的值,当设置相对路径时,即根据当前组件在整个组件树中的位置来确定路径,它可以指定向特定的组件传值,但当结构复杂时,需要嵌套的路径会比较复杂,不好维护,考虑用redux实现。Mixins混合默认式混合(模块中的data数据,components,events,自定义事件)当在wepy.mixin中设置了和页面中相同的函数或变量时,以当前页面的函数和变量为主,mixin中的函数和变量会失效。官方案例:// mixin.jsexport default class TestMixin extends wepy.mixin { data = { foo: ‘foo defined by page’, bar: ‘bar defined by testMix’ }; methods: { tap () { console.log(‘mix tap’); } }}….import wepy from ‘wepy’;import TestMixin from ‘./mixins/test’;export default class Index extends wepy.page { data = { foo: ‘foo defined by index’ }; mixins = [TestMixin ]; onShow() { console.log(this.foo); // foo defined by index console.log(this.bar); // bar defined by testMix }}在mixin中申明的函数可以调用引入mixin后的页面的data数据和函数。mixin中的this指向当前页面组件。兼容式混合(生命周期)当在mixin中设置生命周期一类的钩子函数时,会优先执行mixin中的生命周期,再执行页面中的函数。// mixin.jsexport default class TestMixin extends wepy.mixin { onLoad () { console.log(2222) }}….import wepy from ‘wepy’;import TestMixin from ‘./mixins/test’;export default class Index extends wepy.page { data = { foo: ‘foo defined by index’ }; mixins = [TestMixin ]; onLoad() { console.log(11111); }}结果打印为: 222211111wxs的使用,实现过滤器在项目目录下新建wxs文件夹,在该文件夹中新增wxs文件。新增内容如下:// 设置一个过滤器对超过10000的数字进行转化module.exports = { filter: function (num) { if (num < 10000) { return num } else { var reNum = (num / 10000).toFixed(1) return reNum + ‘万’ } }}在页面中通过import导入该过滤器:// template中使用过滤器,mywxs对应下方wxs中设置的key值<view>{{mywxs.filter(mItem.playerCount)}}人</view>…..import mywxs from ‘@/wxs/fixed.wxs’export default class Index extends wepy.page{……wxs = { mywxs: mywxs}…..}导入的wxs文件只能在template中使用,不能在js中使用Promise和async/await的使用(脏数据的检测)// 录音async endVideo (event) { let endTime = event.timeStamp let startTime = this.startTime this.videoInfo = ‘2131’ let video = await new Promise((resolve, reject) => { recorderManager.onStop((res) => { console.log(‘recorder stop’, res) const { tempFilePath } = res resolve(tempFilePath) }) recorderManager.stop() }) if ((endTime - startTime) > 1000) { this.videoInfo = video this.$apply() }}当使用异步函数的时候,我们如果需要重新渲染页面的时候,需要手动调用$apply()方法,才能触发脏数据检查流程的运行。群转发用于实现微信小程序的分享功能,获取每个群的独立ID。在上弹窗中默认有转发按钮,可以通过使用hideShareMenu来隐藏弹窗中的转发按钮.用法在wpy类型文件的onload中设置// 用于显示分享的群列表wepy.showShareMenu({ withShareTicket: true})在onShareAppMessage中设置分享的界面,在分享成功后的回调函数中通过wepy.getShareInfo获取分享群的信息。群的信息是经过加密的,需要通过后台来进行解密操作。onShareAppMessage (res) { if (res.from === ‘button’) { console.log(res.target) } return { title: ‘自定义转发标题’, path: ‘/pages/main’, success: function(res) { let shareId = res.shareTickets[0] // 转发成功 wepy.getShareInfo({ shareTicket: shareId, success: (data) => { var appId = ‘小程序的appID’ var encryptedData = data.encryptedData var iv = data.iv wepy.request({ url: ‘http://localhost:3000/api/decode’, method: ‘post’, data: { appId: appId, encryptedData: encryptedData, iv: iv }, success: (info) => { console.log(‘info:’ + info) }, fail: (info) => { console.log(info) } }) console.log(data) }, fail: (data) => { console.log(data) } }) console.log(res) }, fail: function(res) { // 转发失败 console.log(res) } }}其中onShareAppMessage需要在wepy.page中设置才有效果,在wepy.component中设置无效果。在onShareAppMessage中的path设置的参数可以跟随?ie=值,然后在对应的页面中设置onLoad来获取跟随的值微信小程序和本地接口对接,模拟请求数据后台采用express,代码如下:var express = require(’express’);var app = express();app.listen(3000, function () { console.log(’listen:3000’);})设置前后台的对接接口:var router = express.Router();var bodyParser = require(‘body-parser’);app.use(bodyParser.json());app.use(bodyParser.urlencoded({extended: true}));router.get(’/info’, function (req, res) { console.log(req.query); console.log(req.body);}app.use(’/api’, router) // 前台通过ajax向’/api/info’发送请求加载请求模块(因为前台无法直接向https://api.weixin.qq.com/sns…,该域名无法添加到小程序的服务器域名中):var request = require(‘request’);// 在’/info’配置的接口中使用,获取session_key。request.get({ uri: ‘https://api.weixin.qq.com/sns/jscode2session', json: true, qs: { grant_type: ‘authorization_code’, appid: ‘小程序的appID’, secret: ‘小程序的密钥’, js_code: code }}, (err, response, data) => { if (response.statusCode === 200) { console.log("[openid]", data.openid) console.log("[session_key]", data.session_key) session_key = data.session_key //TODO: 生成一个唯一字符串sessionid作为键,将openid和session_key作为值,存入redis,超时时间设置为2小时 //伪代码: redisStore.set(sessionid, openid + session_key, 7200) res.json({ openid: data.openid, session_key: data.session_key }); } else { console.log("[error]", err) res.json(err) }})解密在官方案例中下载解密需要的文件WXBizDataCrypt.js。从案例中知道需要的参数有4个:1. AppID(微信小程序的id,在微信小程序的后台设置中有),2. session_key(用户登录的时候可以获取到),3. encryptedData(需要解密的字符串,在获取的群信息中加密的字段),4. iv(算法初始向量,在获取的群信息中有返回)。在app.js中引入WXBizDataCrypt.js:// 解密模块var WXBizDataCrypt = require(’./WXBizDataCrypt’)// 解密数据router.post(’/decode’, function (req, res) { var appId = req.body.appId; var sessionKey = session_key; var encryptedData = req.body.encryptedData; var iv = req.body.iv; var pc = new WXBizDataCrypt(appId, sessionKey); var data = pc.decryptData(encryptedData , iv); res.json({data: data});}) ...

April 17, 2019 · 5 min · jiezi

小程序多端框架全面测评:chameleon、Taro、uni-app、mpvue、WePY

摘要: 微信小程序开发技巧。作者:coldsnap原文:小程序多端框架全面测评Fundebug经授权转载,版权归原作者所有。最近前端届多端框架频出,相信很多有代码多端运行需求的开发者都会产生一些疑惑:这些框架都有什么优缺点?到底应该用哪个?作为 Taro 开发团队一员,笔者想在本文尽量站在一个客观公正的角度去评价各个框架的选型和优劣。但宥于利益相关,本文的观点很可能是带有偏向性的,大家可以带着批判的眼光去看待,权当抛砖引玉。那么,当我们在讨论多端框架时,我们在谈论什么:多端笔者以为,现在流行的多端框架可以大致分为三类:1. 全包型这类框架最大的特点就是从底层的渲染引擎、布局引擎,到中层的 DSL,再到上层的框架全部由自己开发,代表框架是 Qt 和 Flutter。这类框架优点非常明显:性能(的上限)高;各平台渲染结果一致。缺点也非常明显:需要完全重新学习 DSL(QML/Dart),以及难以适配中国特色的端:小程序。这类框架是最原始也是最纯正的的多端开发框架,由于底层到上层每个环节都掌握在自己手里,也能最大可能地去保证开发和跨端体验一致。但它们的框架研发成本巨大,渲染引擎、布局引擎、DSL、上层框架每个部分都需要大量人力开发维护。2. Web 技术型这类框架把 Web 技术(JavaScript,CSS)带到移动开发中,自研布局引擎处理 CSS,使用 JavaScript 写业务逻辑,使用流行的前端框架作为 DSL,各端分别使用各自的原生组件渲染。代表框架是 React Native 和 Weex,这样做的优点有:开发迅速复用前端生态易于学习上手,不管前端后端移动端,多多少少都会一点 JS、CSS缺点有:交互复杂时难以写出高性能的代码,这类框架的设计就必然导致 JS 和 Native 之间需要通信,类似于手势操作这样频繁地触发通信就很可能使得 UI 无法在 16ms 内及时绘制。React Native 有一些声明式的组件可以避免这个问题,但声明式的写法很难满足复杂交互的需求。由于没有渲染引擎,使用各端的原生组件渲染,相同代码渲染的一致性没有第一种高。3. JavaScript 编译型这类框架就是我们这篇文章的主角们:Taro、WePY 、uni-app 、 mpvue 、 chameleon,它们的原理也都大同小异:先以 JavaScript 作为基础选定一个 DSL 框架,以这个 DSL 框架为标准在各端分别编译为不同的代码,各端分别有一个运行时框架或兼容组件库保证代码正确运行。这类框架最大优点和创造的最大原因就是小程序,因为第一第二种框架其实除了可以跨系统平台之外,也都能编译运行在浏览器中。(Qt 有 Qt for WebAssembly, Flutter 有 Hummingbird,React Native 有 react-native-web, Weex 原生支持)另外一个优点是在移动端一般会编译到 React Native/Weex,所以它们也都拥有 Web 技术型框架的优点。这看起来很美好,但实际上 React Native/Weex 的缺点编译型框架也无法避免。除此之外,编译型框架的抽象也不是免费的:当 bug 出现时,问题的根源可能出在运行时、编译时、组件库以及三者依赖的库等等各个方面。在 Taro 开源的过程中,我们就遇到过 Babel 的 bug,React Native 的 bug,JavaScript 引擎的 bug,当然也少不了 Taro 本身的 bug。相信其它原理相同的框架也无法避免这一问题。但这并不意味着这类为了小程序而设计的多端框架就都不堪大用。首先现在各巨头超级 App 的小程序百花齐放,框架会为了抹平小程序做了许多工作,这些工作在大部分情况下是不需要开发者关心的。其次是许多业务类型并不需要复杂的逻辑和交互,没那么容易触发到框架底层依赖的 bug。那么当你的业务适合选择编译型框架时,在笔者看来首先要考虑的就是选择 DSL 的起点。因为有多端需求业务通常都希望能快速开发,一个能够快速适应团队开发节奏的 DSL 就至关重要。不管是 React 还是 Vue(或者类 Vue)都有它们的优缺点,大家可以根据团队技术栈和偏好自行选择。如果不管什么 DSL 都能接受,那就可以进入下一个环节:生态以下内容均以各框架现在(2019 年 3 月 11 日)已发布稳定版为标准进行讨论。1. 开发工具就开发工具而言 uni-app 应该是一骑绝尘,它的文档内容最为翔实丰富,还自带了 IDE 图形化开发工具,鼠标点点点就能编译测试发布。其它的框架都是使用 CLI 命令行工具,但值得注意的是 chameleon 有独立的语法检查工具,Taro 则单独写了 ESLint 规则和规则集。在语法支持方面,mpvue、uni-app、Taro 、WePY 均支持 TypeScript,四者也都能通过 typing 实现编辑器自动补全。除了 API 补全之外,得益于 TypeScript 对于 JSX 的良好支持,Taro 也能对组件进行自动补全。CSS 方面,所有框架均支持 SASS、LESS、Stylus,Taro 则多一个 CSS Modules 的支持。所以这一轮比拼的结果应该是:uni-app > Taro > chameleon > WePY、mpvue2. 多端支持度只从支持端的数量来看,Taro 和 uni-app 以六端略微领先(移动端、H5、微信小程序、百度小程序、支付宝小程序、头条小程序),chameleon 少了头条小程序紧随其后。但值得一提的是 chameleon 有一套自研多态协议,编写多端代码的体验会好许多,可以说是一个能戳到多端开发痛点的功能。uni-app 则有一套独立的条件编译语法,这套语法能同时作用于 js、样式和模板文件。Taro 可以在业务逻辑中根据环境变量使用条件编译,也可以直接使用条件编译文件(类似 React Native 的方式)。在移动端方面,uni-app 基于 weex 定制了一套 nvue 方案 弥补 weex API 的不足;Taro则是暂时基于 expo 达到同样的效果;chameleon 在移动端则有一套 SDK 配合多端协议与原生语言通信。H5 方面,chameleon 同样是由多态协议实现支持,uni-app 和 Taro 则是都在 H5 实现了一套兼容的组件库和 API。mpvue 和 WePY 都提供了转换各端小程序的功能,但都没有 h5 和移动端的支持。所以最后一轮对比的结果是:chameleon > Taro、uni-app > mpvue > WePY3. 组件库/工具库/demo作为开源时间最长的框架,WePY 不管从 Demo,组件库数量 ,工具库来看都占有一定优势。uni-app 则有自己的插件市场和 UI 库,如果算上收费的框架和插件比起 WePy 也是完全不遑多让的。Taro 也有官方维护的跨端 UI 库 taro-ui ,另外在状态管理工具上也有非常丰富的选择(Redux、MobX、dva),但 demo 的数量不如前两个。但 Taro 有一个转换微信小程序代码为 Taro 代码的工具,可以弥补这一问题。而 mpvue 没有官方维护的 UI 库,chameleon 第三方的 demo 和工具库也还基本没有。所以这轮的排序是:WePY > uni-app 、taro > mpvue > chameleon4. 接入成本接入成本有两个方面:第一是框架接入原有微信小程序生态。由于目前微信小程序已呈一家独大之势,开源的组件和库(例如 wxparse、echart、zan-ui 等)多是基于原生微信小程序框架语法写成的。目前看来 uni-app 、Taro、mpvue 均有文档或 demo 在框架中直接使用原生小程序组件/库,WePY 由于运行机制的问题,很多情况需要小改一下目标库的源码,chameleon 则是提供了一个按步骤大改目标库源码的迁移方式。第二是原有微信小程序项目部分接入框架重构。在这个方面 Taro 在京东购物小程序上进行了大胆的实践,具体可以查看文章《Taro 在京东购物小程序上的实践》。其它框架则没有提到相关内容。而对于两种接入方式 Taro 都提供了 taro convert 功能,既可以将原有微信小程序项目转换为 Taro 多端代码,也可以将微信小程序生态的组件转换为 Taro 组件。所以这轮的排序是:Taro > mpvue 、 uni-app > WePY > chameleon流行度从 GitHub 的 star 来看,mpvue 、Taro、WePY 的差距非常小。从 NPM 和 CNPM 的 CLI 工具下载量来看,是 Taro(3k/week)> mpvue (2k/w) > WePY (1k/w)。但发布时间也刚好反过来。笔者估计三家的流行程度和案例都差不太多。uni-app 则号称有上万案例,但不像其它框架一样有一些大厂应用案例。另外从开发者的数量来看也是 uni-app 领先,它拥有 20+ 个 QQ 交流群(最大人数 2000)。所以从流行程度来看应该是:uni-app > Taro、WePY、mpvue > chameleon5. 开源建设一个开源作品能走多远是由框架维护团队和第三方开发者共同决定的。虽然开源建设不能具体地量化,但依然是衡量一个框架/库生命力的非常重要的标准。从第三方贡献者数量来看,Taro 在这一方面领先,并且 Taro 的一些核心包/功能(MobX、CSS Modules、alias)也是由第三方开发者贡献的。除此之外,腾讯开源的 omi 框架小程序部分也是基于 Taro 完成的。WePY 在腾讯开源计划的加持下在这一方面也有不错的表现;mpvue 由于停滞开发了很久就比较落后了;可能是产品策略的原因,uni-app 在开源建设上并不热心,甚至有些部分代码都没有开源;chameleon 刚刚开源不久,但它的代码和测试用例都非常规范,以后或许会有不错的表现。那么这一轮的对比结果是:Taro > WePY > mpvue > chameleon > uni-app最后补一个总的生态对比图表:未来从各框架已经公布的规划来看:WePY 已经发布了 v2.0.alpha 版本,虽然没有公开的文档可以查阅到 2.0 版本有什么新功能/特性,但据其作者介绍,WePY 2.0 会放大招,是一个「对得起开发者」的版本。笔者也非常期待 2.0 正式发布后 WePY 的表现。mpvue 已经发布了 2.0 的版本,主要是更新了其它端小程序的支持。但从代码提交, issue 的回复/解决率来看,mpvue 要想在未来有作为首先要打消社区对于 mpvue不管不顾不更新的质疑。uni-app 已经在生态上建设得很好了,应该会在此基础之上继续稳步发展。如果 uni-app 能加强开源开放,再加强与大厂的合作,相信未来还能更上一层楼。chameleon 的规划比较宏大,虽然是最后发的框架,但已经在规划或正在实现的功能有:快应用和端拓展协议通用组件库和垂直类组件库面向研发的图形化开发工具面向非研发的图形化页面搭建工具如果 chameleon 把这些功能都做出来的话,再继续完善生态,争取更多第三方开发者,那么在未来 chameleon 将大有可为。Taro 的未来也一样值得憧憬。Taro 即将要发布的 1.3 版本就会支持以下功能:快应用支持Taro Doctor,自动化检查项目配置和代码合法性更多的 JSX 语法支持,1.3 之后限制生产力的语法只有 只能用 map 创造循环组件 一条H5 打包体积大幅精简同时 Taro 也正在对移动端进行大规模重构;开发图形化开发工具;开发组件/物料平台以及图形化页面搭建工具。结语那说了那么多,到底用哪个呢?如果不介意尝鲜和学习 DSL 的话,完全可以尝试 WePY 2.0 和 chameleon ,一个是酝酿了很久的 2.0 全新升级,一个有专门针对多端开发的多态协议。uni-app 和 Taro 相比起来就更像是「水桶型」框架,从工具、UI 库,开发体验、多端支持等各方面来看都没有明显的短板。而 mpvue 由于开发一度停滞,现在看来各个方面都不如在小程序端基于它的 uni-app 。当然,Talk is cheap。如果对这个话题有更多兴趣的同学可以去 GitHub 另行研究,有空看代码,没空看提交:chameleon: https://github.com/didi/chameleonmpvue: https://github.com/Meituan-Dianping/mpvueTaro: https://github.com/NervJS/tarouni-app: https://github.com/dcloudio/uni-appWePY: https://github.com/Tencent/wepy(按字母顺序排序)关于FundebugFundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java线上应用实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了10亿+错误事件,付费客户有Google、360、金山软件、百姓网等众多品牌企业。欢迎大家免费试用! ...

March 28, 2019 · 2 min · jiezi

微信小程序框架wepy踩坑记录(与vue对比)

wepy框架借鉴了vue的语法风格和功能特性,但是在使用过程中还是发现与vue有很大的不同。现在总结一下自己开发中遇到的问题,共大家参考一下。如果第一次用wepy开发,强烈建议仔细阅读一下这篇文章,一定对你有帮助,会帮你节约很多宝贵的时间。开发过程中也建议大家时不时的回来阅读一次,巩固加强记忆。wepy中的组件组件里面的坑还不是一般的多!首先来说说组件间的数据共享。在vue中你也能做到这一点,只要把 data 写成一个对象就可以了,当然你不想让所有的子组件都共享同一份数据,vue中的解决方案是给 data 写成一个函数就好了,return出来所有的数据,这样组件间的数据就不会共享了。但是wepy中不能。文档中介绍:WePY中的组件都是静态组件,是以组件ID作为唯一标识的,每一个ID都对应一个组件实例,当页面引入两个相同ID的组件时,这两个组件共用同一个实例与数据,当其中一个组件数据变化时,另外一个也会一起变化。所以如果同一个页面引用多个组件,你只能给每个组件定义不同的ID,类似这样import Child from ‘../components/child’; export default class Index extends wepy.page { components = { //为两个相同组件的不同实例分配不同的组件ID,从而避免数据同步变化的问题 child: Child, anotherchild: Child }; }看起来是不是很蠢。但是没办法,你只能这么用。如果页面中只引用两三个同类型组件还好,但是如果我是一个循环,我也不知道我要引用多少组件,该怎么办?接下来再说说组件的循环。wepy官方文档中说明:当需要循环渲染WePY组件时(类似于通过wx:for循环渲染原生的wxml标签),必须使用WePY定义的辅助标签<repeat>。但是不支持在 repeat 的组件中去使用 props, computed, watch 等等特性。什么?props 都不让用??那父组件如何给子组件传参??后来实践发现,如果 props 中的数据在 template 中是能取到的,但是在 method 或者event 中就取不到了,你说神不神奇!所以最后的解决办法是用的 wepy-redux,类似vuex,放在 store 中实现的。视图的渲染之异步数据异步数据的获取后需要手动调用 this.$apply() 方法才能重新渲染视图,这一点也一定要记得。刚开始做的时候是在页面 data 中写的假数据,渲染的好好的。但是数据换成从接口读取后,死活视图出不来。琢磨了半天才想起来需要手动调用 this.$apply()。而 vue 是不需要这么做的。方法定义wepy中页面中的事件需要些在 methods 中,组件之间的处理函数需要写在 events 中,而自己写的自定义方法需要写在与 methods 同级中。不像vue,可以写在 methods 里。在 events 中写的函数,不需要在调用子组件的时候写在子组件中,子组件 $emit 会自动去 events 中寻找同名方法执行。这点也与vue不同。事件传参wepy优化了原生小程序在事件中的传参形式。比如页面中有一个方法,叫 getIndex,目的是取一个循环的 index 属性,在原生中需要额外定义一个 data-index 属性,然后在 getIndex 中通过event.currentTarget.dataset.index 来获取。而wepy中可以直接在事件里传递,但需要加上{{}},写成 getIndex({{index}})这样,这点也与vue不同。数据绑定这个是小程序原生方法中的不好点,wepy不能帮忙背这个锅。数据绑定也是使用 {{}},但是{{}} 里面只能进行简单的运算,具体支持哪些运算可以看官方文档。需求是一个列表,选中的变个样式,正常的思路就是选中的时候触发一个方法,将 index 赋值给 currentActive,在 {{}} 中判断如果 currentActive == index 就应用 active 样式,命名很简单的一个需求。但是写好了就是不好使,找了半天也没发现哪错了,最后看文档,原来是根本就不支持这种写法!!只支持简单的运算,这种不属于简单的范围!!最后的解决办法是弄了一个数组 arr,选中将对应位置置为 true,在 {{}} 判断 arr[index] 是否为 true 解决了这个问题。总结一句话:{{}} 一点也不强大。动态绑定classwepy中需要遵循小程序原生的绑定方式,与vue中也不同。在vue中,动态的和非动态的需要分别写,类似这样:<div class=“class-a” :class="{true ? ‘class-b’: ‘class-c’}"></div>。但是在wepy中,动态和非动态的可以写在一起,类似这样:<view class=“class-a {{true ? ‘class-b’ : ‘class-c’}}">mixin混合wepy中的 mixin 分为两种。对于组件data数据,compontents 组件,events 事件及其他自定义方法采用默认式混合,即如果组件中未定义这些东西,那么 mixin 中的将生效,如果组件中已经定义了,将以组件中定义的为主,mixin 中定义的不会生效。但对于 methods 事件及小程序页面事件,将采用兼容式混合,只要定义了就都会生效。但是先响应组件中定义的,再响应 mixin 中定义的。而vue组件中 methods 里的事件如果与 mixin 中的重名,会采用组件中的事件。而生命周期的钩子函数则是先响应 mixin 中的,在响应组件中的。注:以上问题均是采用wepy1.7.2的版本,祝大家开发愉快,少踩些坑。最后附上官方文档链接,供大家参考:小程序官方文档wepy官方文档 ...

March 11, 2019 · 1 min · jiezi

微信小程序 WePy Utils 工具集整合并封装了常用的小程序 API 和 HTTP Request

安装npm install wepy-utils按需引入import { UTILS, HTTP, TIPS } from ‘wepy-utils’UtilsUTILS.now()获取当前时间戳let now = UTILS.now()console.log(now)UTILS.random()返回任意区间随机数let random = UTILS.random(1, 5)console.log(random)UTILS.param()将对象解析成 url 字符串let obj = {id: 1, name: ‘ming’} // 需解析的对象let strResult = UTILS.param(obj, true) // 第二个参数 true | false 表示是否使用 unDecodeURI 编码,默认 falseconsole.log(strResult) // ?id=1&name=mingUTILS.unparam()将 url 字符串解析成对象let url = ‘?id=1&name=ming’ // 需解析的对象let objResult = UTILS.unparam(url, true) // 第二个参数 true | false 表示是否使用 unDecodeURI 解码,默认 falseconsole.log(objResult) // {id: 1, name: ‘ming’}HTTP RequestGET POST PATCH PUT DELETEHTTP.get()第1种使用方法是URL不带参数。第2种使用方法是在请求URL后带参数,如:?id=1&name=mingHTTP.get(url).then((data) => {}).catch((error) => {})HTTP.get({url: url, params: [JSON Object] }, mask: [Boolean]).then((data) => {}).catch((error) => {})let url = ‘urlpath’HTTP.get({ url: url, params: {id: 1, name: ‘ming’}, mask: true}).then((data) => { console.log(data)}).catch((error) => { console.log(error)})HTTP.post()可自定义 headers,如需 Authorization 等,默认:‘Content-Type’: ‘application/json’HTTP.post({ url: url, params: {id: 1, name: ‘ming’ }, mask: true, loading: false, headers: {‘X-Requested-With’: ‘XMLHttpRequest’}}).then((data) => { console.log(data)}).catch((error) => { console.log(error)})HTTP.patch() HTTP.put() HTTP.delete() 请求方式与 HTTP.post() 写法类似// HTTP PATCHHTTP.patch({url: url, params: [JSON Object], headers: [JSON Object], mask: [Boolean] }).then((data) => {}).catch((error) => {})// HTTP PUTHTTP.put({url: url, params: [JSON Object], headers: [JSON Object], mask: [Boolean] }).then((data) => {}).catch((error) => {})// HTTP DELETEHTTP.delete({url: url, params: [JSON Object], headers: [JSON Object], mask: [Boolean] }).then((data) => {}).catch((error) => {})mask 是否显示透明蒙层,防止触摸穿透,默认:false。loading 在网络请求加过程中是否显示 wx.showLoading 加载动画,默认显示。⚠️ onPullDownRefresh 监听下拉动作与 wx.showLoading 一起使用会出现顶部回弹 BUG。所以在使用 onPullDownRefresh 时可以将 loading 设置为 falseTips界面TIPS.toast()显示消息提示框(可自定义 wx.showToast 的所有参数,除 success、fail、complete)TIPS.toast({title: ‘提示标题’})// 设置 duration > 0 后,隐藏后可支持回调(duration 默认 1500)TIPS.toast({ title: ‘提示标题’}).then(() => { console.log(‘隐藏后回调’)})TIPS.confirm()显示模态弹窗(payload 为 Promise.resolve 是可选项),除 showCancel 改为 cancel 外,其它可选参数与 wx.showModal 相同TIPS.confirm({ title: ‘提示标题’, content: ‘提示内容’, payload: [1,2,3]}).then((arr) => { console.log(‘点击了确定’, arr[2]) // 3}).catch(() => { console.log(‘点击了取消’)})TIPS.setTitle()动态设置当前页面的标题TIPS.setTitle(‘Hello WePy’)TIPS.loading()显示 loading 提示框,可自定义提示内容,默认显示透明蒙层,防止触摸穿透TIPS.loading(‘加载标题’)TIPS.loaded()隐藏 loading 提示框TIPS.loaded()文件TIPS.downloadSaveFile()下载单个文件let url = ‘url’TIPS.downloadSaveFile({ url: url, success: (res) => { console.log(res) }, fail: (err) => { console.log(err) }})TIPS.downloadSaveFiles()下载多个文件let urls = [‘url1’,‘url2’,‘url3’]TIPS.downloadSaveFiles({ urls: urls, progress: true, success: (res) => { // 下载进度(如果设置 progress: false 数据将在全部下载完成后返回) console.log(下载进度:${res.step}%) // 全部加载完成 if (res.step === 100) { console.log(res) res.forEach((value, key) => { console.log(Key:${key} = Value:${value.savedFilePath}) }) } }, fail: (err) => { console.log(err) }})导航TIPS.navigateTo()保留当前页面,跳转到应用内的某个页面let url = ’test’let params = {id:1 ,name: ‘ming’}TIPS.navigateTo(url, params) // test?id=1&name=mingTIPS.redirectTo()关闭当前页面,跳转到应用内的某个页面let url = ’test’let params = {id:1 ,name: ‘ming’}TIPS.redirectTo(url, params) // test?id=1&name=mingTIPS.switchTab()跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面let url = ’test’let params = {id:1 ,name: ‘ming’}TIPS.switchTab(url, params) // test?id=1&name=mingTIPS.reLaunch()关闭所有页面,打开到应用内的某个页面let url = ’test’let params = {id:1 ,name: ‘ming’}TIPS.reLaunch(url, params) // test?id=1&name=mingTIPS.navigateTo() TIPS.redirectTo() TIPS.switchTab() TIPS.reLaunch() 中的 params 参数是一个可选对象 ...

February 20, 2019 · 2 min · jiezi

wepy小程序长列表性能优化实践

背景wepy 1.7.3wepy-redux长列表交互问题wepy框架的列表性性能比较差,主要原因是修改列表中任意字段的时候,会给setData传递完整的列表,详细见这个issue;此时修改长列表任意字段,都会导致页面长时间不响应解决使用字典(Object)与长列表进行组合,因为一般情况下字典的数据量会远远小于列表场景任意弹窗对购物车cart进行修改,产品列表对应的购买数量同步修改// 数据结构// 产品列表(长度3000+)var products = [{id: “79”, name: “精致荤菜”}…]// 购物车字典 // key: productId, value: 购物车数据var cartDict = { 2407: { price: “1.02” num: 2 }}注意由于cartDict数据为用户手动添加,数据量远远小于列表。那么setData时速度也会相应提高此时我们使用组合方式渲染列表的购买量<view class=“num” wx:if="{{ cartDict[product.id] }}">{{cartDict[product.id].num}}</view>通过将每次修改列表转移为每次修改cartDict来达到提升性能的效果;上面那个issue也可以用类似思路制作一个展开产品的字典首次加载白屏问题我们的商品列表一般会比较长(目前最大有3000+个),此时第一次进入页面白屏时间很长(10s+);解决使用h5的优化思路,类似app。只渲染一部分屏幕内的产品,其他绝大部分产品使用骨架展示;使用此方法有一些限制产品高度需要已知,用来计算当前产品是否在屏幕内滚动体验没有不优化的好,小程序其实也是用的这种列表优化思路,因此快速滚动的时候实际效果是白屏(小程序优化) => 骨架(我们的优化) => 出现产品场景我们项目所有产品等高,因此比较好计算当前产品是否应该展示。首先是模板写法<repeat for="{{products}}" item=“dish” index=“index”> <dishItem :dish=“dish” wx:if="{{showTypeDict[type.id]}}"></dishItem> <view wx:else> <image src="{{_skeleton}}"></image> </view></repeat>说明:showTypeDict代表当前需要展示的产品字典,使用字典原因是基于长列表交互问题对产品进行分类,每次只比较分类的坐标然后展示整个分类dishItem是产品对应组件,比较复杂skeleton为骨架监听scroll,根据当前scrollTop和产品分类的坐标来决定showTypeDict的内容,注意截流;使用以上方法优化后3000+产品白屏时间与100+产品持平。滚动时无卡顿,快速滚动时需要等待一会儿产品才能渲染出来;以上

January 24, 2019 · 1 min · jiezi

小程序静态资源无缝转移到腾讯云COS 使用wepy mpvue 等webpack打包的小程序项目

原文链接:https://callmesoul.cn/xiao-cheng-xu-jing-tai-zi-yuan-wu-feng-zhuan-yi-dao-teng-xun-yun-cos-shi-yong-wepy-mpvue-deng-webpackda-bao-de-xiao-cheng-xu-xiang-mu/今天介绍的工具是wecos原生小程序原生的小程序直接根据wecos的文章操作即可,wecos提供了上传本地资源文件到cos、替换小程序的引用本地路径为上传路径等。这里不作详细说明,主要介绍webpack打包的小程序项目。因为原生写起来很不方便。webpack小程序打包项目最好是开发完再来进行这一步,前期专心开发。我前期开发时,引用静态资源用的是相对路径,用的绝对路径应该也可以的。项目开发完后webpack设置打包后的cdn地址, webpack rules选项:{ test: /.(png|jpg|jpeg|gif|svg)$/, use: { loader: ‘file-loader’, options: { name: ‘/[path][name].[ext]’, publicPath: function (file) { if(isProduction){//判断是否生产环境,自己判断咯。 if(file.indexOf(’tabbar’)>=0){//如果有tabbar的,tabbar用一个tabbar的文件夹装起来,因为tabbar图片只支持本地。 return file; } else{ return ‘https://xxx-1234567.cos.ap-guangzhou.myqcloud.com/'+file;//你的腾讯云cos bucket的域名。 } }else{ return file; } } } }, },安装wecosnpm install -g wecos跟目录创建wecos.config.json文件填写wecos.config.json 配置{ “appDir”: “./dist/assets”, “cos”: { “secret_id”: “xxxxx”, “secret_key”: “xxxxx”, “bucket”: “xxx-1234567”,//bucker-appid “region”: “ap-guangzhou”, //创建 bucket 时选择的地域简称 “folder”: “/assets” //资源存放在 bucket 的哪个目录下 }, “uploadFileSuffix”: [".jpg",".png",".gif",".webp",".svg"], “uploadFileBlackList”: [//不上传的图片,填tabbar的目录 “./dist/assets/images/tabbar”, ]}在根目录运行wecos即可。总结为什么不用webpack的 publicPath 而用file-loader的publicPath ?因为webpack的 publicPath只支持字符串,一但改成线上域名,所有静态资源的前缀都会变成cdn域名。而小城的tabbar并不支持网络图片,base64也不支持,只支持本地图片。这时我们就用file-loader的publicPath ,支持函数且返回文件名,可以写条件去过滤掉tabbar的文件。使tabbar文件使用本地的,而其他使用线上cdn域名的文件。为什么要用wecos?当然你也可以不用自己,本地打包后自己手动上传到cos后台。然后删除了本地的图片文件(除了tabbar的图片)。然后开发者工具再上传代码。你会发现这步骤很累赘。使用wecos后,我们打包后只需要跑一下cos就可以自动上传本地的上cdn,且自动删除本地的(tabbar图片除外)。是不是方便多了?赶紧在你项目用上把! ...

December 13, 2018 · 1 min · jiezi

微信小程序渲染html内容

最近又做了一个新的小程序关于物流订单查询欢迎来体验遇到了一个小问题:数据中返回电话号码的字符串识别出来并且高亮和可以绑定事件。比如数据中包含您的派送员黄xx正在派件,电话:137xxxx41460已经在派送。其中就要识别出137xxxx41460并且绑定点击事件可以点击拨打电话号码。对于这个功能搜集了不少资料其中包含了3个解决方案但是各有优缺点因此记录下来wxParse小程序刚上线那会儿,是无法直接渲染HTML内容的,于是就诞生了一个叫做「 wxParse 」的库。它的原理就是把HTML代码解析成树结构的数据,再通过小程序的模板把该数据渲染出来。rich-text后来,小程序增加了「rich-text」组件用于展示富文本内容。然而,这个组件存在一个极大的限制: 组件内屏蔽了所有节点的事件 。也就是说,在该组件内,连「预览图片」这样一个简单的功能都无法实现。web-view再后来,小程序允许通过「web-view」组件嵌套网页,通过网页展示HTML内容是兼容性最好的解决方案了。然而,因为要多加载一个页面,性能是较差的,个人小程序也是没法用webview。 因为我这个只是需求只是需要识别出来电话号码并且不是那种复杂的字符模板。因此参考wxparse 的原理自己写了一个关于电话号码识别的功能。效果如下:技术重点就是1、在数据返回后对于字符串用正则(/(1+)|(d{9,14})|(d{3,4}-d{6,10})/g)识别出电话号码存在一个新的字段并且表示为type:phone ,不是电话号码的字符就放到另外一个字段并且表示为type:text.数据结构如图所示2、新建一个关于字段读取的模板<template name=“wepyhtml”> <block wx:for="{{wxmlData}}" wx:key=""> <template is=“wxml” data="{{item}}"></template> </block></template><template name=“wxml”> <block wx:if="{{item.type == ‘phone’}}" wx:key=""> <text class=“blue” bindtap=“tapTel” data-phone="{{item.acceptStation}}">{{item.acceptStation}}</text> </block> <block wx:else>{{item.acceptStation}}</block></template>其中,在模板里面绑定点击事件方便后期做逻辑处理还可以绑定不同的参数方便获取3、在需要用到的地方引入模板 <import src="/components/html.wxml" /> <template is=“wepyhtml” data="{{wxmlData:item.nodes}}"></template>那就满足现在的功能呢需求了。不用复杂的插件也比直接引入原生组件来得方便。关键就是思路。。特此记录哈。d ↩

December 5, 2018 · 1 min · jiezi

最新个税计算 / 个税计算器 小程序 wepy 开发

根据最新税改后计算个人所得税的计算器。如有其它疑惑,也欢迎提出任何修改意见。可以在主题下留言或者在小程序中点击联系在线客服或者加入qq群:8691139261、扫一扫2、效果图3、小程序官网API:https://developers.weixin.qq….4、wepy框架apihttps://tencent.github.io/wepy/这次开发遇到几个问题因此记录下来。 (1)、在开发微信小程序组件框架时,我遇到了一个问题,微信小程序中的button组件有特定的css,背景可以用“background:none”去掉,但是边框再用“border : none”去掉就不可以了,这也是微信小程序与h5的不同之处。但是在微信小程序中使用:after选择器就可以实现这一功能。/使用 button::after{ border: none; } 来去除边框/(2)、计算需要各个基数可以从各大城市的社保网站扒下来放进数据库,统一保存利用接口返回计算(不同城市有不同基数,如果没有按照全国返回) (3)、获取本地location方便了解知道哪个城市返回其基数做统计。用到 wepy.getSetting 获取其返回值中只会出现小程序已经向用户请求过的权限。判断是否授权并且获取经纬度,利用腾讯地图接入(定位,位置转经纬度)返回其城市。 (4)、数据请求过慢导致页面闪烁,可以在请求前利用 wx.showLoading(); 加载load,在data里面增加参数控制不显示demo内容(load:true)隐藏页面没加载数据的静态节点。数据返回时调用wx.hideLoading(); 隐藏load图案,并且把data里面的参数设置为(load:false).一次性显示加载好的数据和静态节点。

December 5, 2018 · 1 min · jiezi