前端及开发文章及欣赏

基于Webpack4.X,小程序工程化落地实践

seo达人

小程序开发现状:

  1. 开发工具不好使用(无法热更新,编译缓慢);
  2. 无法使用css预处理语言(Sass、Less),有些IDE的插件可以监听编译,但不同编辑器需要额外安装;
  3. 无法使用工程化(图片自动压缩,文件监听编译等);
  4. 编码繁琐(创建一个页面,需要新建4个文件(.wxml、.js、.json、.wxss),每次新建都需要新建4次或者复制文件比较浪费时间);
  5. 团队多人协作,代码风格、使用的编辑器不一致;

技术选型:

在进行小程序项目启动,进行技术选型的时候,对市场上多个小程序框架进行了考虑:

  • uni-app、mpVue、wepy、taro、 kbone

团队成员mpvue、wepy、uni-app都有实际的项目经验,且根据Github上的star数还有issue,最后决定回到到使用原生开发。

原因:

虽然框架有些很成熟,有工程化和跨端的解决方案,也有实际的上线项目,但考虑到后续一些支撑性的问题(维护,文档,坑等),在github上看了issue,有些已经没在维护了。

想着让项目持续迭代,不受第三方框架限制,保持稳健,最后决定使用原生,跟着官方的迭代升级,自己维护,引入前端工程化的思想,提高繁琐的流程以及开发效率。

引入工程化

  1. 基于Webpack4.x,自定义Webpack配置

    • scss编译为wxss:定义全局变量,使用公共的样式文件,提高css开发效率和可维护性;

    • 自动压缩图片资源 : 小程序对包大小有限制,压缩图片大小可以减少空间,加快页面加载;普通的图片压缩需要将图片上传到在线图片压缩网站,压缩完再保存下来,效率比较低。现在执行命令就可以自动压缩图片。

  2. 代码规范

    • eslint: 能在js运行前就识别一些基础的语法错误,减少不必要的小问题,提高调试效率;

    • husky、line-staged、prettier: 统一团队代码规范: 当执行代码提交到git仓库时,会将已改动文件的代码格式化统一规范的代码风格;

  1. 命令行创建页面和组件模板

    • 小程序每次新建页面或者组件,需要依赖4个文件(.wxml,.js,.wxss,.json)。只需要执行npm run create命令,会提示选择创建页面还是组件,选择完成输入页面或者组件的名字,会自动生成4个模板文件(.wxml,.js,json,.scss)到对应的目录

  1. 引入jest单元测试

    • 生成测试覆盖率

项目结构

app -> 小程序程序的入口,使用微信开发者工具制定app目录cli -> 生pagescomponents的模板脚手架img ->

 图片资源原文件.eslintignore.eslintrc.js.gitignore(忽略wxss的提交,多人和做改动,容易有冲突,将scss文件传到服务器就好了).prettierrc.js(代码格式化风格配置)babel.config.jsjest.config.js(单元测试配置文件)webpack.compress.js(指定入口图片资源文件,将图片压缩编译到小程序的资源目录)webpack.config.js -> (工程化入口文件,指定入口scss文件,监听文件变化,自动将scss编译为wxss)

项目使用的包文件

  • webpack、babel、eslint: 转换、规范js
  • chalk: console.log打印彩色颜色
  • scss、css-loader: 编译scss
  • figlet: 控制台显示字体样式
  • husky,line-staged,prettier: 代码格式化相关
  • jest、miniprogram-simulate: 单元测试

项目运行

. 安装依赖    npm install 或 yarn install. 编译scss   

 npm run dev. 压缩图片    npm run img. 单元测试    npm run test(生成测试报告)    npm run test:watch(监听测试文件改动—开发环境下使用)

示例

编译scss

执行 npm run dev

压缩图片

执行 npm run img

将图片压缩到app/assets/img目录下,一张7k的图片变成5k,肉眼看不出有什么差别。

新建页面

执行 npm run create

终端会提示选择页面还是组件,选择页面,按Enter键,输入页面的名称,会自动将4个文件创建到app/pages/xxx下。

新建组件

执行 npm run create

终端会提示选择页面还是组件,选择组件,按Enter键,输入组件的名称,会自动将4个文件创建到app/components/xxx下。

单元测试

执行 npm run test 生成测试报告执行 npm run test:watch 监听测试文件,方便开发使用

其他思考

工程化的初衷就是为了减少重复性的操作,提高编码的效率和乐趣。

JavaScript是弱类型语言,好处是灵活,坏处是太灵活(多人协作,维护别人写的代码就是很痛苦了)。

项目最主要的是稳健,可高度自定义拓展,不拘束于版本和地上那方,特别多人协作的团队,工程化能给团队带来更多的收益,后续也会考虑将TypeScript等其他好的方案引入项目。

蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务

ES6 模块知识点总结

前端达人

模块化 export 和 import

import 导入模块、export 导出模块
可以直接在任何变量或者函数前面加上一个 export 关键字,就可以将它导出。
在一个文件中:

export const sqrt = Math.sqrt; export function square(x) { return x * x; } export function diag(x, y) { return sqrt(square(x) + square(y)); }  
    然后在另一个文件中这样引用:
import { square, diag } from 'lib'; console.log(square(11)); // 121 console.log(diag(4, 3));  

总结

//mod.js // 第一种模块导出的书写方式(一个个的导出) // 导出普通值 export let a = 12; export let b = 5; // 导出json export let json = { a, b }; // 导出函数 export let show = function(){ return 'welcome'; }; // 导出类 export class Person{ constructor(){ this.name = 'jam'; } showName(){ return this.name; } } //index.js //导出模块如果用default了,引入的时候直接用,若没有用default,引入的时候可以用{}的形式 // 导入模块的方式 import { a, b, json, show, Person } from './mod.js'; console.log(a); // 12 console.log(b); // 5 console.log(json.a); // 12 console.log(json.b); // 5 console.log(show()); // welcome console.log(new Person().showName()); // jam //mod1.js // 第二种模块导出的书写方式 let a = 12; let b = 5; let c = 10; export { a, b, c as cc // as是别名,使用的时候只能用别名,特别注意下 }; //index1.js // 导入模块的方式 import { a, b, cc // cc是导出的,as别名 } from './mod1.js'; console.log(a); // 12 console.log(b); // 5 console.log(cc); // 10 //mod2.js // 第三种模块导出的书写方式 ---> default // default方式的优点,import无需知道变量名,就可以直接使用,如下 // 每个模块只允许一个默认出口 var name = 'jam'; var age = '28'; export default { name, age, default(){ console.log('welcome to es6 module of default...'); }, getName(){ return 'bb'; }, getAge(){ return 2; } }; //index2.js // 导入模块的方式 import mainAttr from './mod2.js'; var str = ' '; // 直接调用 console.log(`我的英文名是:${mainAttr.name}我的年龄是${mainAttr.age}`); mainAttr.default(); // welcome to es6 module of default... console.log(mainAttr.getName()); // bb console.log(mainAttr.getAge()); // 2 //mod3.js var name = 'jam'; var age = '28'; export function getName(){ return name; }; export function getAge(){ return age; }; //index3.js // 导入模块的方式 import * as fn from './mod3.js'; // 直接调用 console.log(fn.getName()); // 


uni-app页面之间的传参,让下一个页面接收上一个页面的值

seo达人

为了实现页面之间的通讯,或者数据交换,我们要实现一个页面到另一个页面的传参,可以通过点击跳转的时候进行页面之间的传值。


<template>

   <view>

       <navigator url="../a/a?id=1" hover-class="none">

           <view>跳转到A页面</view>

       </navigator>

       <navigator url="../b/b?id=2" hover-class="none">

           <view>跳转到B页面</view>

       </navigator>

       <navigator url="../c/c?id=3" hover-class="none">

           <view>跳转到C页面</view>

       </navigator>

   </view>

</template>


<script>

export default {

   data() {

       return {


       }

   },

   methods: {


   },

   onLoad: function (option) {

       //获得上一个页面传过来的id

       var pageid = option.id;

       console.log(pageid);

   }

}

</script>

Author:TANKING

Web:http://www.likeyun.cn/

Date:2020-8-13

WeChat:face6009

蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务

如何实现小程序用户增长,开发者给了几点关键建议

seo达人

目前除了微信小程序,还有支付宝小程序,百度小程序,字节跳动小程序、京东小程序等,各大流量平台都希望借助小程序的服务连接能力,为企业和用户提供更好的服务。企业在小程序赛道上的战略布局,可以借助平台的流量,获得更多的用户和变现。

有些人会认为小程序用户增长是运营的事情,与产品和开发无关,实则不然。对于全栈工程师来说,他们不仅能做好小程序,还能玩溜小程序,真正全方位、无死角实现用户增长最大化。

小晓云近期采访了几位开发者,他们的小程序有些已经突破百万用户,在自己的领域中获得持续性的发展。开发者围绕用户增长中的拉新、留存、转化等维度,为我们提供了关于产品设计和开发的建议,希望以下的经验对你们都有帮助。

分享 1:小程序视觉和交互是重点,可以借鉴同行让产品更符合大众需求

小程序的特点是「即搜即用,用完即走」,更适合那些轻量级的工具型应用。用户来得快去得也快,则更需要简洁、轻便的设计定位。UI 设计师和前端开发者平常可以通过知晓商店等小程序商店,多参考同类型优秀小程序的设计与交付,以此优化自己的小程序。虽然小程序视觉和交互并不会带来用户裂变式增长,但这个是留住用户最基本的要求了。

分享 2:微信小程序订阅消息是一个用户回流的神器

今年微信小程序从模板消息升级为订阅消息,这是一个帮助小程序实现用户回流重要能力了。对于开发者来说,重点思考:如何合理的向用户直观传达订阅消息、在何处弹出订阅消息。开发者在接入订阅消息能力时,应该选择适合业务场景的模板,并进行文案的引导,同时可以尝试,发起一次订阅时同时订阅多个模版,让用户一次性获得更多消息,提高订阅率。新用户粘性较低,可以借助订阅消息发送奖励通知等,召回用户。

分享 3:结合业务和用户特点,策划符合平台调性的活动

我们原先是做体育领域的资讯和商城 App,微信小程序发布后,庞大的微信流量给了我们一个拓展用户的新机会,于是在 2017 年与知晓云结缘,并相伴 3 年了。用户增长其实不是简单的一两句话可以总结,所以我先分享其中一点。体育领域的资讯,能吸引和留住用户的,主要靠资讯的时效性、赛事活动、体育明星的加持,因此我们曾组织体育明星的投票活动,粉丝乐于参与,同时也提高了认同感和优越感。投票功能设置的门槛很低,但我们增加了很多排行榜的文案引导,加强粉丝的紧迫感,同时也增加了很多分享文案引导,让粉丝主动分享。这个是针对体育领域上很成功的增长案例,当然也可以应用于其它追星平台等。

分享 4:做好数据分析,让数据驱动用户增长

精细化运营的核心就是数据驱动,明确关键指标,并且通过数据分析的方式进行评估,然后不断优化。数据埋点的缺点是开发成本高,所以我们是基于无埋点,一次性集成 SDK,采集页面访问、点击行为、用户特征等全量数据。

分享 5:研究小程序平台的用户喜好,提供他们想要的服务

我是做 QQ 小程序的, QQ 小程序的用户对于社交、恋爱等偏娱乐的场景更感兴趣,针对性提供头像制作、起网名、小游戏等服务有比较大的市场。所以可以结合平台用户的特点和喜好,开发相关服务的小程序,更有利于小程序的发展和变现。

以上内容均来自知晓云开发者的经验分享,如果你对哪一个内容感兴趣,可以在文末或者小晓云微信上留言,对于大家感兴趣的内容,我们将再次邀请开发者进行更全面更完整的分享。

知晓云成立三年以来,通过提供不断更新的开发工具,帮助开发者提高开发效率,轻松完成优秀的作品。但我们服务不止于快、省,还要在增长与变现上赋能开发者/企业。

知晓推送

在线可视化操作加上业务系统轻松集成,可以一键推送全平台的订阅消息推送服务,轻松触达亿万用户。

实时数据库

支持各类高实时性的业务场景,以简便的开发方式、更高的时效性实现在云端和客户端来的数据实时同步。通过实时数据库实现的即时聊天室、投票、直播间送礼和弹幕、小游戏等互动功能,提升用户留存和转化。

运营后台

一键生成可视化的运营管理后台,User Dash API 和开箱即用的前端组件库,开发者可以快速编写一套独立的运营后台,并支持一键部署至知晓云服务器,是开发者的利器,也是运营者的福音。

蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务

模板商终结者——微信小商店

seo达人

微信小商店已经正式上线,对企业、个体和个人三种开店类型全量开放。微信小商店可以帮助商家免开发、零成本、一键生成卖货小程序。微信小商店团队将负责商品发布、订单管理、交易结算、物流售后、直播带货等技术和服务流程。

微信小商店个人开店非常简单,3秒搞定,毫不夸张,堪称模板商终结者。个人开店仅需身份认证即可,绑定银行卡可以提现,1个微信号仅支持开通1个个人主体的小商店。

企业、个体工商户需要上传营业执照、经营者信息、结算银行账户信息等基础信息,1个微信号可以开通3个“企业和个体”主体的小商店。

当前微信小商店现阶段支持售卖的商品类目超过1500个,主要包括:宠物生活、家用电器、手机、通讯、数码、电脑、办公、服饰内衣等,后续可售品类会增多。

如何开店

  1. 只需搜索小程序 小商店助手
  2. 进入后只需填入店名等极少量信息,选择个人店铺的话不需要上传资质
  3. 点击确认就能极速拥有自己的小程序店铺啦!
  4. 麻麻再也不用担心我被模板商折磨啦!

小程序助手

在 小商店助手 里面还能查看店铺数据在售商品新增商品待付款商品订单管理客服管理店铺设置 等,功能非常强大!

小程序助手

小程序助手

小程序助手

小程序助手

上架新商品也是非常简单快捷,直接上传商品图片,加上标题和一些描述信息就可以。

而且不论是开店审核还是商品上架审核,都非常迅速,作者尝试了几次都在一分钟左右就审核完了!

需要注意的一点是微信会收 0.6% 的交易金额提成哦,毕竟此路他开此树他栽树嘛~

小程序助手

小程序助手蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务

微信小程序发送订阅消息(之前是模板消息)

seo达人

之前的模板消息已经废弃,现在改为订阅消息,订阅消息发布前,需要用户确认后才能接收订阅消息。


image


小程序端

index.wxml


<button bindtap="send">发送订阅消息</button>

index.js


const app = getApp()

Page({ data: {

 }, send:function(){

   wx.requestSubscribeMessage({ tmplIds: ['WZiCliW1zVtHXqX7dGnFNmFvxhW-wd9S_W4WfrwNvss'],


success:(res)=> { // 在登录的时候,获取到的openid进行缓存,现在直接把openid提取出来即可 wx.getStorage({ key: 'openid',


         success (res) { console.log(res.data)

           wx.request({ url: 'https://www.xxx.com/send.php?openid='+res.data, data: {},


header: { 'content-type': 'application/json' },


             success (res) { // 推送 if(res.data.errcode == '43101'){ console.log("拒绝订阅消息")

               }else if(res.data.errcode == '0'){ console.log("发送订阅消息")

               }else{ console.log("未知错误")

               }

             }

           })

         },

         fail (res) { console.log("没有openid,无法发送")

         }

       })

     }

   })

 }

})

后端

<?php //设置 header  header("Content-type:application/json"); //接收参数 $openid = $_GET["openid"];


//初始化 CURL $ch = curl_init(); // 获取access_token // include ''; require_once("access_token.php");


//目标服务器地址  curl_setopt($ch, CURLOPT_URL,


'https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token='.$access_token);


//设置要POST的数据 curl_setopt($ch, CURLOPT_POST, true);


$data = '{

 "touser": "'.$openid.'",

 "template_id": "模板ID",

 "page": "pages/index/index",// 要跳转的页面

 "lang":"zh_CN",

 "data": {

     "thing4": {

         "value": "欢迎使用专插本最前线小程序"

     },

     "thing5": {

         "value": "小程序由公众号:广东专插本最前线开发"

     }

 }

}';

curl_setopt($ch, CURLOPT_POSTFIELDS, $data);

curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST"); // 对认证证书来源的检查 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); // 从证书中检查SSL加密算法是否存在 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); //获取的信息以文件流的形式返回,而不是直接输出 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); //发起请求 $result = curl_exec($ch); echo $result; //关闭请求 curl_close($ch); ?>

access_token.php


<?php // 声明页面header header("Content-type:charset=utf-8"); // APPID、APPSECRET $appid = "你的小程序APPID";

$appsecret = "你的小程序APPSECRET"; // 获取access_token和jsapi_ticket function getToken(){

   $file = file_get_contents("access_token.json",true);//读取access_token.json里面的数据 $result = json_decode($file,true); //判断access_token是否在有效期内,如果在有效期则获取缓存的access_token //如果过期了则请求接口生成新的access_token并且缓存access_token.json if (time() > $result['expires']){

       $data = array();

       $data['access_token'] = getNewToken();

       $data['expires'] = time()+7000;

       $jsonStr =  json_encode($data);

       $fp = fopen("access_token.json", "w");

       fwrite($fp, $jsonStr);

       fclose($fp); return $data['access_token'];

   }else{ return $result['access_token'];

   }

} //获取新的access_token function getNewToken($appid,$appsecret){ global $appid; global $appsecret;

   $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=".$appid."&secret=".$appsecret."";

   $access_token_Arr =  file_get_contents($url);

   $token_jsonarr = json_decode($access_token_Arr, true); return $token_jsonarr["access_token"];

}


$access_token = getToken(); ?>

逻辑

1、通过button控件出发send函数

2、send函数调用wx.requestSubscribeMessageAPI,微信允许接收订阅消息

3、 wx.request向send.php后端请求

4、后端获取access_token后,调用订阅消息接口POST一段json数据即可发送订阅消息


官方文档

1、https://developers.weixin.qq.com/miniprogram/dev/api/open-api/subscribe-message/wx.requestSubscribeMessage.html


2、https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.addTemplate.html


Author:TANKING

Date:2020-08-24

Web:http://www.likeyun.cn/

WeChat:face6009

蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务

Flutter 实战:增删查改功能示例代码

seo达人

七月,我们上线重磅基础能力——实时数据库,并开了实战直播课,让大家可以更好的理解并使用该服务。你的聊天室、站内信、投票、小游戏等需要高实时的功能正在想你招手,赶紧使用实时数据库服务又快又简单的开发它们吧。


点击此处回顾教学视频,看看知晓云大前端组长如何在十分钟内搞定一个视频弹幕微信小程序。


Ps:目前实时数据库限时免费,就算以后收费,费用也是低到忽略不计。速速用上,不要错过这么硬核的能力。


八月,我们迎来知晓云三周年,推出各式各样的福利活动。开发者在这个全年最优惠的时间里,升级、续费,甚至购买三年期包年套餐,与知晓云锁定下一个三年。非常感谢大家的支持,我们会继续努力,不断输出更强大的能力。


九月初,Flutter SDK 已进入测试阶段,很快就可以跟大家见面了。

Flutter SDK 的使用比较简明易懂,例如对数据表的增删查改,在指定数据表后,对数据项进行对应操作即可,例如新增(create)、查找(get)、修改(update)和删除(delete)。


以下是对 Flutter 增删查改功能进行展示:


TableObject product = new TableObject('product'); // 获取名为 product 的数据表


// 新增数据

TableRecord record = product.create(); // 创建一条空白记录


// 为属性字段赋值

record.set('name', '知晓云 flutter sdk'); // 对 name 字段进行赋值

record.set('version', '1.0'); // 对 version 字段进行赋值


// 将数据保存到服务器

record = await record.save(); // 保存


// 从服务器获取一条数据

TableRecord record = await product.get(record.id);


// 更新数据

record.set('version', '1.1');

await record.update();


// 删除数据

await product.delete(recordId: record.id);

目前知晓云 Flutter SDK 支持的功能如下:


数据表

用户

内容库

文件

云函数调用

获取服务器时间

本地存储

Flutter SDK 正式上线后,我们还会输出实战教学视频,敬请期待!


另外,我们提前开启内测申请通道,点击此处或微信扫一扫扫描下方卡片二维码即可申请,获得内测资格的开发者,不仅可优先体验新功能,同时还可以与知晓云工程师近距离交流,你使用后的建议也可以得到更快的反馈与实现。


知晓云 Flutter SDK


2020 年已过去三分之二,好消息是,即将到来的中秋&国庆小长假以及知晓云近期的更新内容,除了即将上线的 Flutter SDK ,还有以下更新。


1. 支持 QQ 小程序订阅消息,消息能力又前进一步。

与微信订阅消息不同在于,QQ 小程序订阅消息不仅支持分为「一次性订阅」,还支持「长期订阅」,如果用户之前已经同意授权长期订阅,则不会再出现弹窗询问。>>> 查看开发文档


2. iOS 和 Android SDK 支持微博登录。


查看 iOS 开发文档

查看 Android 开发文档

如果你有其他需求,可以通过文末

蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务

uni-app提交表单到后端,接收表单数据

seo达人

要想接收表单数据,首先要在表单进行数据的绑定,我们可以使用v-model="keyword"进行绑定。


然后在js获取这个绑定的值。


index.vue


<template>

<view>

   <view class="search-con">

           <view class="form-con">

           <form class="search-form">

               <input type="text" v-model="keyword" @tap="showsearchbtn" focus="true"/>

               <button form-type="submit" hover-class='none' @tap="keyword">提交</button>  

           </form>

           </view>

   </view>

</view>

<template>

js


<script>

export default {

   data() {

       return {


           }

       },

       methods: {

           keyword(e){

               // 获取表单值

               let kw = this.keyword;

               console.log(kw);

           }

       }

   }

</script>

Author:TANKING

Web:http://www.likeyun.cn

Date:2020-8-13

WeChat:face6009

蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务

滴滴出行小程序I18n最佳实践

seo达人

背景

I18n = Internationalization,国际化,因为单词由首末字符i/n和中间18个字母组成,简称i18n。对程序来说,就是要在不修改内部代码的情况下,能根据不同语言及地区显示相应的界面,以支持不同语言的人顺利使用程序。

业务背景

互联网行业进入下半场,精细化运营是关键。多语言支持能让产品更好地服务境内的其他语言用户,也为产品出海打下基础,随着 WeChat/Alipay 的全球化,你的小程序是否做好准备了呢?

4月初,滴滴出行小程序团队接到支持英文版的需求,预计上线时间为6月上旬。当前滴滴出行小程序集成的众多业务线和各种公共库,展示给用户的有前端硬编码的静态文本和服务端下发的文案,都要同步接入多语言。考虑到小程序当前的体量,光文本收集、语料翻译、npm package 支持,联调,测试,沟通成本等等,并且前端开发只投入1.5人力的情况下,时间是蛮紧迫的,但是我们抗住了压力,最终英文版滴滴出行小程序如期上线,截止目前运行稳定,用户反馈良好,得到了超出预期的收益。

当然这一切得益于各团队同学的工作,和各团队的通力配合,更得益于部门技术团队 Mpx框架优雅的多语言能力支持。划重点来咯,所谓工欲善其事必先利其器,如果你的公司业务需要开发小程序,也需要接入多语言,那么请搬好小板凳,我们来看一下小程序框架 Mpx 是如何优雅支持多语言能力。相信看完这篇,可以帮助你认识 Mpx(https://github.com/didi/mpx) ,加深对框架的理解,最终利用 Mpx 框架迭代小程序,年终奖多出那部分可以打赏一下作者,买杯咖啡哈(偷笑.jpg)

以下是滴滴出行小程序的中英文版本对比:

滴滴出行微信小程序i18n

也欢迎大家在微信/支付宝里搜索滴滴出行小程序,实际使用感受下。PS:切换语言的方法是,打开小程序,点击左上角用户头像,进入侧边栏设置页面,点击切换中英文即可体验。

技术背景

在上述业务背景下,Mpx 框架——滴滴自研的专注提升小程序开发体验的增强型小程序框架,内建 i18n 能力便提上日程。

与 WEB 不同,小程序(本文以微信小程序为例)运行环境采用双线程架构设计,渲染层的界面使用 WebView 进行渲染,逻辑层采用 JSCore 线程运行 JS脚本。逻辑层数据改变,通过 setData 将数据转发到 Native(微信客户端),Native 再将数据转发到渲染层,以此更新页面。由于线程间通信成本较高,实际项目开发时需要控制频次和数量。另外小程序的渲染层不支持运行 JS ,一些如事件处理等操作无法在渲染层实现,因此微信官方提供了一套脚本语言 WXS ,结合 WXML ,可以构建出页面的结构(不了解 WXS ?戳这里)。

基于小程序的双线程架构设计,实现 i18n 存在一些技术上的难点与挑战,由于 Mpx 框架早期构建起来的强大基础,最终得以优雅支持多语言能力,实现了和vue-i18n 基本一致的使用体验。

使用

在使用上,Mpx 支持 i18n 能力提供的 API 与 vue-i18n 大体对齐,用法上也基本一致。

模板中使用 i18n

编译阶段通过用户配置的 i18n 字典,结合框架内建的翻译函数通过 wxs-i18n-loader 合成为可执行的 WXS 翻译函数,并自动注入到有翻译函数调用的模板中,具体调用方式如下图。

// mpx文件 <template> <view> <view>{{ $t('message.hello', { msg: 'hello' })}}</view> 

<!-- formattedDatetime计算属性,可基于locale变更响应刷新 --> <view>{{formattedDatetime}}</view> </view> </template>

JS 中使用 i18n

通过框架提供的 wxs2js 能力,将 WXS 翻译函数转换为 JS 模块注入到 JS 运行时,使运行时环境中也能够调用翻译函数。

// mpx文件 <script> import mpx, { createComponent } from '@mpxjs/core' createComponent({ 

ready () { // js中使用 console.log(this.$t('message.hello', { msg: 'hello' }))

// 局部locale变更,生效范围为当前组件内 this.$i18n.locale = 'en-US' setTimeout(() =>

{ // 全局locale变更,生效范围为项目全局 mpx.i18n.locale = 'zh-CN' }, 10000)

}, computed: { formattedDatetime () { return this.$d(new Date(), 'long') } } }) </script>

定义 i18n 字典

项目构建时传入 i18n 配置对象,主要包括语言字典和默认语言类型。

new MpxWebpackPlugin({ i18n: { locale: 'en-US',

// messages既可以通过对象字面量传入,也可以通过messagesPath指定一个js模块路径,

在该模块中定义配置并导出,dateTimeFormats/dateTimeFormatsPath和numberFormats/numberFormatsPath同理

messages: { 'en-US': { message: { hello: '{msg} world' }

}, 'zh-CN': { message: { hello: '{msg} 世界' } } }, // messagesPath: path.resolve(__dirname, '../src/i18n.js') } })

如果是通过 Mpx 提供的 cli 工具生成的项目,这部分配置会在 mpx.conf.js 文件中,不光可以直接内联写在该文件中,也可以指定语言包的路径。

以上,Mpx 的 i18n 方案接入成本低,使用优雅,体验优秀。直观感受可参考下面 mpx i18n demo :https://github.com/didi/mpx/t...

方案

Mpx框架的 i18n 支持几乎完全实现了 vue-i18n 的全部能力,下面我们来详细说明 Mpx 框架 i18n 能力的具体实现。

方案探索

基于小程序运行环境的双线程架构,我们尝试了不同方案,具体探索过程如下:

方案一:基于 Mpx 框架已提供的数据增强能力 computed 计算属性,来支持 i18n 。该方案与 uniapp 的实现思路相似(后文会进行对比分析),存在一定不足,包括线程通信带来的性能开销和for循环场景下的处理较复杂等,最终放弃。
方案二:基于 WXS + JS 支持 i18n 适配。通过视图层注入 WXS,将 WXS 语法转换为 JS 后注入到逻辑层,这样视图层和逻辑层均可实现 i18n 适配,并且在一定程度上有效减少两个线程间的通信耗时,提高性能。

从性能和合理性上考虑,我们最终采用了方案二进行 Mpx 的 i18n 方案实现。

mpx-i18n内部流程示意图

Mpx i18n 架构设计图

由于各大小程序平台上,WXS 语法和使用均存在较大差异,因此该方案实现过程中也存在一些技术上的难点,这些难点基于 Mpx 框架的早期构建起来的跨平台能力也一一得以攻克,具体如下。

实现难点

WXS 在模板中运行的跨平台处理

WXS 是运行在视图层中的 JS,可以减少与逻辑层通信耗时,提高性能。因此 Mpx 框架在迭代初期便已支持在模板和 JS 运行环境使用 WXS 语言,并且针对小程序跨平台 WXS 语法进行抹平。
在模板中,Mpx 自定义一个 webpack chunk template,以微信 WXS 作为 DSL,利用 babylon 将注入的 WXS 转化成 ast,然后遍历 ast 节点,抹平各大平台对 WXS 语法的处理差异,输出各平台可以识别的类 WXS 文件。目前主要支持微信(WXS)、支付宝(sjs)、百度(filter)、QQ(qs)、头条(sjs)等小程序平台。

WXS 在逻辑层运行的跨平台处理

WXS 与 JavaScript 是不同的语言,有自己的语法,并不和 JavaScript 一致。并且 WXS 的运行环境和其他 JavaScript 代码是隔离的,WXS 中不能调用其他 JavaScript 文件中定义的函数,也不能调用小程序提供的API。
因此在逻辑层,Mpx 将注入的 WXS 语法转化为 JS,通过 webpack 注入到当前模块。例如 WXS 全局方法 getRegExp/getDate 在 JS 中是无法调用的,Mpx将它们分别转化成 JS 模块,再通过 webpack addVariable 将模块注入到 bundle.js 中。
同理,Mpx 会将编译时注入的 i18n wxs 翻译函数和 i18n 配置对象挂载到全局 global 对象上,利用 mixin 混入到页面组件,并监听 i18n 配置对象,这样JS和模板中即可直接调用 i18n 翻译函数,实现数据响应。

以上便是 Mpx 框架在小程序中支持 i18n 能力的技术细节,由于 WXS 是可以在视图层执行的类 JS 语法的一门语言,这样就减少了小程序逻辑层和视图层的通信耗时,提升性能。但是由于实现依赖类 WXS 能力,以及 WXS 执行环境的限制,目前模板上可直接使用的翻译函数包括 $t/$tc/$te ,如果需要格式化数字或日期可以使用对应的翻译函数在 JS 中 Mpx 提供的计算属性中实现。

输出 web 时使用 i18n

Mpx同时还支持转换产出H5,而 Mpx 提供的 i18n 能力在使用上与 vue-i18n 基本一致,输出 web 时框架会自动引入 vue-i18n,并使用当前的 Mpx i18n 配置信息对其进行初始化,用户无需进行任何更改,即可输出和小程序表现完全一致的 i18n web 项目。

对比

上面分析了 Mpx 框架的 i18n 方案的技术细节,我们来看下和其他方案的对比,主要是和 uniapp - 基于 Vue 编写小程序的方案,和微信官方的方案,两者提供的 i18n 支持与Mpx的对比有何优劣。

uniapp的方案

uniapp 提供了对 i18n 能力的支持,是直接引入vue-i18n。但小程序中无法在模板上调用 JS 方法,本质上是利用计算属性 Computed 转换好语言,然后利用模板插值在小程序模板中使用。

模板中:
<view>{{ message.hello }}</view>

JS里需要写:

 computed: {  
    message () { return { hello: this.$t('message.hello') }
    }
  }

因此该方案存在一个性能问题,最终的渲染层所看到的文本还是通过 setData 跨线程通信完成,这样就会导致线程间通信增多,性能开销较大。

并且,早期这种形式使用成本较高,后来 uniapp 也针对其做过优化,实现了可以在模板上写 $t() 的能力,使用上方便了不少。

这个 t() 的实现是在编译时候识别到t 就自动替换,帮你替换成一个 uniapp 的 computed 数据,因此数据部分还是和之前一样要维护两份。尤其是模板上的for循环,即使 for 里只有一个数据要被转换,整个列表都要被替换成一个计算属性,在线程间通信时进一步加大了性能开销。

微信官方的方案

微信小程序本身也提供了一个 i18n 的方案,仓库地址是:wechat-miniprogram/miniprogram-i18n 。

这个方案从 i18n 本身的实现来讲和Mpx框架的设计是类似的,也是基于 WXS 实现(英雄所见略同啊)。但因为周边配套上没有完整的体系,整体使用体验上就也略逊于基于Mpx框架来开发支持 i18n 的国际化小程序了。

主要的点就是,官方提供的方案,要基于 gulp 工具进行一次额外构建,同时在JS中使用时候还要额外引入一个 behavior 去让JS中也可以使用翻译能力。

而Mpx框架通过一次统一的Webpack构建产出完整的内容,用户无需担心语言包更新后忘记重新构建,在JS中使用的时候不光更方便,而且语言信息还是个响应式的,任何组件都可以很方便地监听语言值的变化去做一些其他的事情。

最后,Mpx的 i18n 方案对比微信官方的方案还有个巨大的优点,结合Mpx的跨平台能力,能实现均以这个方案,一套代码产出支持微信/支付宝/百度/QQ/头条多个平台的支持 i18n 的小程序。

总结

Mpx 框架专注小程序开发,期望为开发者提供最舒适的开发体验,有众多优秀的功能特性,帮助开发者提效。本文介绍的是其内置的 i18n 能力,通过对比分析得出相比其他框架方案在使用成本和性能等方面有明显的优势,欢迎各位有相关需求的同学进行体验尝试。

未来 Mpx 还会持续迭代优化,提供更多更好的能力帮助小程序开发者提效。在使用过程中遇到任何问题,欢迎大家在 Git 上提 issue,团队成员会及时响应。同时也鼓励大家一起为开源社区做贡献,参与到 Mpx 共建中来,为小程序技术发展添砖加瓦。

Git地址 [https://github.com/didi/mpx]
Mpx文档 [https://mpxjs.cn/]

欢迎技术交流与反馈,顺便star一下鼓励开源项目贡献者,我们将持续发力贡献社区。

附:以往Mpx文章链接
滴滴开源小程序框架Mpx - https://mpxjs.cn/articles/1.0.html
滴滴小程序框架Mpx发布2.0,支持小程序跨平台开发,可直接转换已有微信小程序 - https://mpxjs.cn/articles/2.0.html
小程序开发者,为什么你应该尝试下MPX - https://mpxjs.cn/articles/mpx1.html
Mpx 小程序框架技术揭秘 - https://mpxjs.cn/articles/mpx2.html

滴滴出行小程序体积优化实践 - https://mpxjs.cn/articles/size-control.html

蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务

JavaScript专题之深浅拷贝(系列五)[转载]

前端达人

JavaScript专题之深浅拷贝

了解拷贝背后的过程,避免不必要的错误,Js专题系列之深浅拷贝,我们一起加油~

目录

一、拷贝示例

当我们在操作数据之前,可能会遇到这样的情况:

  1. 会经常改动一组数据,但可能会用到原始数据
  2. 我需要两组一样的数据,但我不希望改动一个另一个随之改动
  3. 我需要对数据操作前后进行对比

当我们遇到类似需要场景时,首先想到的就是拷贝它,殊不知拷贝也大有学问哦~

下面简单的例子,你是否觉得熟悉?

1.1 基本类型拷贝示例
var str = 'How are you'; var newStr = str; newStr = 10 console.log(str); // How are you console.log(newStr); // 10 

大家都能想到,字符串是基本类型,它的值保存在栈中,在对它进行拷贝时,其实是为新变量开辟了新的空间。 strnewStr就好比两个一模一样的房间,布局一致却毫无关联。

1.2 引用类型拷贝示例
var data = [1, 2, 3, 4, 5]; var newData = data; newData[0] = 100; console.log(data[0]); // 100 console.log(newData[0]); // 100 

类似的代码段,但这次我们使用数组这个引用类型举例,你会发现修改赋值后的数据,原始数据也跟着改变了,这显然不满足我们的需要。本篇文章就来聊一聊引用数据拷贝的学问

如果大家对Js的数据类型存在着疑问,不妨看看《JavaScript中的基本数据类型》

在这里插入图片描述

二、浅拷贝

拷贝的划分都是针对引用类型来讨论的,浅拷贝——顾名思义,浅拷贝就是“浅层拷贝”,实际上只做了表面功夫:

var arr = [1, 2, 3, 4]; var newArr = arr; console.log(arr, newArr); // [1,2,3,4] [1,2,3,4] newArr[0] = 100; console.log(arr, newArr) // [100,2,3,4] [100,2,3,4] 

不发生事情(操作)还好,一旦对新数组进行了操作,两个变量中所保存的数据都会发生改变。

发生这类情况的原因也是因为引用类型的基本特性:

  • 存储在变量处的值是一个指针(point),指向存储对象的内存地址。赋值给新变量相当于配了一把新钥匙,房间并没有换。

数组中的slice和concat都会返回一个新数组,我们一起来试一下:

var arr = [1,2,3,4]; var res = arr.slice(); // 或者 res = arr.concat() res[0] = 100; console.log(arr); // [1,2,3,4] 

这个问题这么快就解决了?虽然对这一层数据进行了这样的的处理后,确实解决了问题,但!

var arr = [ { age: 23 }, [1,2,3,4] ]; var newArr = arr.concat(); arr[0].age = 18; arr[1][0] = 100; console.log(arr) // [ {age: 18}, [100,2,3,4] ] console.log(newArr) // [ {age: 18}, [100,2,3,4] ] 

果然事情没有那么简单,这也是因为数据类型的不同。

S 不允许我们直接操作内存中的地址,也就是说不能操作对象的内存空间,所以,我们对对象的操作都只是在操作它的引用而已。

既然浅拷贝达不到我们的要求,本着效率的原则,我们找找有没有帮助我们实现深拷贝的方法。

在这里插入图片描述

三、深拷贝的方法?

数据的方法失败了,还有没有其他办法?我们需要实现真正意义上的拷贝出独立的数据。

3.1 JSON

这里我们利用JSON的两个方法,JSON.stringify()JSON.parse()来实现最简洁的深拷贝

var arr = ['str', 1, true, [1, 2], {age: 23}] var newArr = JSON.parse( JSON.stringify(arr) ); newArr[3][0] = 100; console.log(arr); // ['str', 1, true, [1, 2], {age: 23}] console.log(newArr); // ['str', 1, true, [100, 2], {age: 23}] 

这个方法应该是实现深拷贝最简洁的方法,但是,它仍然存在问题,我们先来看看刚才都做了些什么:

  1. 定义一个包含都过类型的数组arr
  2. JSON.stringify(arr), 将一个 JavaScript 对象或值转换为 JSON 字符串
  3. JSON.parse(xxx), 方法用来解析JSON字符串,构造由字符串描述的值或对象

理解:

我们可以理解为,将原始数据转换为新字符串,再通过新字符串还原为一个新对象,这中改变数据类型的方式,间接的绕过了拷贝对象引用的过程,也就谈不上影响原始数据。

限制:

这种方式成立的根本就是保证数据在“中转”时的完整性,而JSON.stringify()将值转换为相应的JSON格式时也有缺陷:

  • undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)。
  • 函数、undefined 被单独转换时,会返回 undefined,
    • 如JSON.stringify(function(){})
    • JSON.stringify(undefined)
  • 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。
  • NaN 和 Infinity 格式的数值及 null 都会被当做 null。
  • 其他类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性。

所以当我们拷贝函数、undefined等stringify转换有问题的数据时,就会出错,我们在实际开发中也要结合实际情况使用。

举一反三:

既然是通过改变数据类型来绕过拷贝引用这一过程,那么单纯的数组深拷贝是不是可以通过现有的几个API来实现呢?

var arr = [1,2,3]; var newArr = arr.toString().split(',').map(item => Number(item)) newArr[0] = 100; console.log(arr); // [1,2,3] console.log(newArr); // [100,2,3] 

注意,此时仅能对包含纯数字的数组进行深拷贝,因为:

  1. toString无法正确的处理对象和函数
  2. Number无法处理 false、undefined等数据类型

但我愿称它为纯数字数组深拷贝

在这里插入图片描述

3.2 Object.assign()

有的人会认为Object.assign(),可以做到深拷贝,我们来看一下

var obj = {a: 1, b: { c: 2 } } var newObj = Object.assign({}, obj) newObj.a = 100; newObj.b.c = 200; console.log(obj); // {a: 1, b: { c: 200 } } console.log(newObj) // {a: 100, b: { c: 200 } } 

神奇,第一层属性没有改变,但第二层却同步改变了,这是为什么呢?

因为 Object.assign()拷贝的是(可枚举)属性值。

假如源值是一个对象的引用,它仅仅会复制其引用值。MDN传送门

四、自己实现深浅拷贝

既然现有的方法无法实现深拷贝,不妨我们自己来实现一个吧~

4.1 浅拷贝

我们只需要将所有属性即其嵌套属性原封不动的复制给新变量一份即可,抛开现有的方法,我们应该怎么做呢?

var shallowCopy = function(obj) { if (typeof obj !== 'object') return; // 根据obj的类型判断是新建一个数组还是对象 var newObj = obj instanceof Array ? [] : {}; // 遍历obj,并且判断是obj的属性才拷贝 for (var key in obj) { if (obj.hasOwnProperty(key)) { newObj[key] = obj[key]; } } return newObj; } 

我们只需要将所有属性的引用拷贝一份即可~

4.2 深拷贝

相信大家在实现深拷贝的时候都会想到递归,同样是判断属性值,但如果当前类型为object则证明需要继续递归,直到最后

var deepCopy = function(obj) { if (typeof obj !== 'object') return; var newObj = obj instanceof Array ? [] : {}; for (var key in obj) { if (obj.hasOwnProperty(key)) { newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key]; } } return newObj; } 

我们用白话来解释一下deepCopy都做了什么

const obj = [1, { a: 1, b: { name: '余光'} } ]; const resObj = deepCopy(obj); 
  • 读取 obj,创建 第一个newObj
    • 判断类型为 []
    • key为 0 (for in 以任意顺序遍历,我们假定按正常循序遍历)
    • 判断不是引用类型,直接复制
    • key为 1
    • 判断是引用类型
    • 进入递归,重新走了一遍刚才的流程,只不过读取的是obj[1]

另外请注意递归的方式虽然可以深拷贝,但是在性能上肯定不如浅拷贝,大家还是需要结合实际情况来选择。




作者: 

转自:https://blog.csdn.net/jbj6568839z/article/details/107964274?utm_medium=distribute.pc_category.none-task-blog-hot-4.nonecase&depth_1-utm_source=distribute.pc_category.none-task-blog-hot-4.nonecase&request_id=

日历

链接

blogger

蓝蓝 http://www.lanlanwork.com

存档