背景
众所周知如今市面上端的形态多种多样,手机Web、ReactNative、微信小程序, 支付宝小程序, 快应用等,每一端都是巨大的流量入口,当业务要求同时在不同的端都要求有所表现的时候,针对不同的端去编写多套代码的成本显然非常高,这时候只编写一套代码就能够适配到多端的能力就显得极为需要。目前比较流行的框架有wepy, mpvue ,taro
WEPY tencent.github.io/wepy/document腾讯团队开源的一款类vue语法规范的小程序框架,借鉴了Vue的语法风格和功能特性,支持了Vue的诸多特征,比如父子组件、组件之间的通信、computed计算属性、wathcer监听器、props传值、slot槽分发,Mixin混入等。WePY发布的第一个版本是2016年12月份,也就是小程序刚刚推出的时候,到目前为止,WePY已经发布了52个版本, 最新版本为1.7.2;MpVue mpvue.com/mpvue/#-html美团团队开源的一款使用 Vue.js 开发微信小程序的前端框架。使用此框架,开发者将得到完整的 Vue.js 开发体验,同时为 H5 和小程序提供了代码复用的能力。mpvue在发布后的几天间获得2.7k的star,上升速度飞起,截至目前为止已经有13.7k的star;Taro taro.aotu.io/京东凹凸实验室开源的一款使用 React.js 开发微信小程序的前端框架。它采用与 React 一致的组件化思想,组件生命周期与 React 保持一致,同时支持使用 JSX 语法,让代码具有更丰富的表现力,使用 Taro 进行开发可以获得和 React 一致的开发体验,同时因为使用了react的原因所以除了能编译h5, 小程序外还可以编译为ReactNative;对比如下:
本文主要讲一下京东凹凸实验室的Taro.个人感觉是这三种框架中最好用的。
Taro 是什么?
Taro 是由京东 - 凹凸实验室打造的一套遵循 React 语法规范的多端统一开发框架。
Taro 是一套遵循 React语法规范的 多端开发 解决方案。
使用 Taro,我们可以只书写一套代码,再通过 Taro 的编译工具,将源代码分别编译出可以在不同端(微信/百度/支付宝/字节跳动小程序、QQ轻应用、 H5、React-Native React语法规范,它采用与 React 一致的组件化思想,组件生命周期与 React 保持一致,同时支持使用 JSX 语法,让代码具有更丰富的表现力,使用 Taro 进行开发可以获得和 React 一致的开发体验。
import Taro, { Component } from '@tarojs/taro'
import { View, Button } from '@tarojs/components'
export default class Index extends Component {
constructor () {
super(...arguments)
this.state = {
title: '首页',
list: [1, 2, 3]
}
}
componentWillMount () {}
componentDidMount () {}
componentWillUpdate (nextProps, nextState) {}
componentDidUpdate (prevProps, prevState) {}
shouldComponentUpdate (nextProps, nextState) {
return true
}
add = (e) => {
// dosth
}
render () {
return (
{this.state.title}
{this.state.list.map(item => {
return (
{item}
)
})}
)
}
}
相关连接
Github:
https://github.com/NervJS/taro
官网
https://taro.aotu.io/
为什么要使用Taro?
1.一次编写,多端运行
既然是一个多端解决方案,Taro 最重要的能力当然是写一套代码输出多端皆可运行的代码。目前 Taro 已经支持一套代码同时生成 H5 和微信小程序,App(React Native)端也即将支持,同时诸如快应用等端也将于近期得到支持。
同时 Taro 也已经投入到了生产环境使用,目前已经支撑了一个 3 万行代码小程序 TOPLIFE 的开发,以及部分京东购物小程序和一起有局小程序,未来也将会支撑更多的京东核心业务小程序。
2.现代前端开发流程
和微信自带的小程序框架不一样,Taro 积极拥抱社区现有的现代开发流程,Taro 立足于微信小程序开发,众所周知小程序的开发体验并不是非常友好,比如小程序中无法使用 npm 来进行第三方库的管理,无法使用一些比较新的 ES 规范等等,针对小程序端的开发弊端,Taro 具有以下的优秀特性
import Taro from '@tarojs/taro'
Taro.setStorage({ key: 'key', data: 'value' })
.then(res => console.log(res))
3.和 React 完全一致的 API 和组件化系统
在 Taro 中,你不用像小程序一样区分什么是 App 组件,什么是 Page 组件,什么是 Component 组件,Taro 全都是 Component 组件,并且和 React 的生命周期完全一致。可以说,一旦你掌握了 React,那就几乎掌握了 Taro。而学习 React 的资源也几乎是汗牛充栋,完全不用担心学不会。
Taro 和 React 一样,同样使用声明式的 JSX 语法。相比起字符串的模板语法,JSX 在处理精细复杂需求的时候会更得心应手。
class LoginStatus extends Component {
render () {
const isLoggedIn = this.props.isLoggedIn
// 这里最好初始化声明为 `null`,初始化又不赋值的话
// 小程序可能会报警为变量为 undefined
let status = null
if (isLoggedIn) {
status = 已登录
} else {
status = 未登录
}
return (
{status}
)
}
}
// app.js
import LoginStatus from './LoginStatus'
// 这样会渲染 `已登录`
class App extends Component {
render () {
return (
)
}
}
Taro的安装
1.先全局安装@tarojs/cli
$ npm install -g @tarojs/cli
$ yarn global add @tarojs/cli
2.之后我们初始化一个名为myApp的项目:
$ taro init myApp
3.然后输入你的配置:
之后等待所有依赖安装完毕。
taro项目目录如下:
├── config 配置目录
| ├── dev.js 开发时配置
| ├── index.js 默认配置
| └── prod.js 打包时配置
├── src 源码目录
| ├── components 公共组件目录
| ├── pages 页面文件目录
| | ├── index index 页面目录
| | | ├── banner 页面 index 私有组件
| | | ├── index.js index 页面逻辑
| | | └── index.css index 页面样式
| ├── utils 公共方法库
| ├── app.css 项目总通用样式
| └── app.js 项目入口文件
└── package.json
Taro的基本原理
初衷
用React写微信小程序。微信小程序原生方式开发起来太费劲。遂想用React开发微信小程序。
延伸
在React业务代码转微信小程序代码这个最初的需求实现之后,发现依靠同样的转换思路可以适配多端,即从1对1延伸到1对n:
P.S.其中Nerv是一种类React框架,API与React类似
P.S.Taro组件库之所以以微信小程序为标准,也是初衷使然(都做完了不能浪费啊)
一套代转生成多端的思路
想要一份代码通吃n端,无非2种思路:
1.直接从1端向n - 1端转换
2.加一层抽象,从这层抽象转换到n端
Taro也采用了第二种思路,这层抽象就是Taro业务代码:
核心实现
以微信小程序为例,它由4部分组成:
1.配置(JSON)
2.模板(WXML)
3.样式(WXSS)
4.逻辑(JS)
配置与样式没什么好说的,难点在于模板的转换和逻辑的转换
P.S.ReactNative样式转换另说,也是一个难题,因为RN在选择器、属性名/值及默认值,甚至CSS特性支持程度都存在较大差异
编译转换
要把一份代码A转换成另一份代码B,需要做3件事情:
1.解析代码A生成抽象描述(AST)
2.根据一些映射规则操作AST,生成新的AST
3.根据新的AST生成代码B
模板的转换
以小程序为例,把 JSX 语法转换成可以在小程序运行的字符串模板。
输入JSX:
render () {
return (
{this.props.counter.num}
Hello, World
)
}
经@tarojs/transformer-wx转换,输出微信小程序模板:
{{counter.num}}
Hello, World
逻辑的转换
类似于组件库需要做多端适配,各端能力差异也同样需要适配:
组件库以及端能力都是依靠不同的端做不同实现来抹平差异
运行时框架负责适配各端能力,以支持跑在上面的Taro业务代码,主要有3个作用:
适配组件化方案、配置选项等基础API
适配平台能力相关的API(如网络请求、支付、拍照等)
提供一些应用级的特性,如事件总线(
Taro.Events
、Taro.eventCenter
)、运行环境相关的API(Taro.getEnv()
、Taro.ENV_TYPE
)、UI适配方案(Taro.initPxTransform()
)等
实现上,@tarojs/taro
是API适配的统一入口,编译时分平台替换:
- @tarojs/taro:只是一层空壳,提供API签名
平台适配相关的package有6个:
@tarojs/taro-alipay:适配支付宝小程序
@tarojs/taro-h5:适配Web
@tarojs/taro-rn:适配ReactNative
@tarojs/taro-swan:适配百度小程序
@tarojs/taro-tt:适配头条小程序
@tarojs/taro-qapp:适配快应用
这些API都可以直接使用,不用关心当前平台是否支持,因为运行时框架的适配工作的一部分就是抹平平台能力API差异,例如:
H5 端就无法调用扫码、蓝牙等端能力,各个小程序对页面函数的事件支持的程度也不一样。例如;
页面事件函数各端支持程度如下
方法 | 作用 | 微信小程序 | 百度小程序 | 字节跳动小程序 | 支付宝小程序 | H5 | RN |
---|---|---|---|---|---|---|---|
onPullDownRefresh | 页面相关事件处理函数--监听用户下拉动作 | ✔️ | ✔️ | ✔️ | ✔️ | ✘ | ✘ |
onReachBottom | 页面上拉触底事件的处理函数 | ✔️ | ✔️ | ✔️ | ✔️ | ✘ | ✘ |
onShareAppMessage | 用户点击右上角转发 | ✔️ | ✔️ | ✔️ | ✔️ | ✘ | ✘ |
onPageScroll | 页面滚动触发事件的处理函数 | ✔️ | ✔️ | ✔️ | ✔️ | ✘ | ✘ |
onTabItemTap | 当前是 tab 页时,点击 tab 时触发 | ✔️ | ✔️ | ✔️ | ✔️ | ✘ | ✘ |
onResize | 页面尺寸改变时触发,详见 响应显示区域变化 | ✔️ | ✘ | ✘ | ✘ | ✘ | ✘ |
componentWillPreload | 预加载 | ✔️ | ✘ | ✘ | ✘ | ✘ | ✘ |
onTitleClick | 点击标题触发 | ✘ | ✘ | ✘ | ✔️ | ✘ | ✘ |
onOptionMenuClick | 点击导航栏额外图标触发 | ✘ | ✘ | ✘ | ✔️(基础库 1.3.0) | ✘ | ✘ |
onPopMenuClick | ✘ | ✘ | ✘ | ✔️(基础库 1.3.0) | ✘ | ✘ | |
onPullIntercept | 下拉截断时触发 | ✘ | ✘ | ✘ | ✔️(基础库 1.11.0) | ✘ | ✘ |
以上成员方法在 Taro 的页面中同样可以使用,书写同名方法即可,不过需要注意的,目前暂时只有小程序端支持(支持程度如上)这些方法,编译到 H5/RN 端后这些方法均会失效。
采用微信小程序标准,所以这些 API 在 H5 端运行的时候将什么也不做。
同时在业务层区分目标环境,保证这些平台相关的代码仅在预期的目标环境下执行:
编译时:
process.env.TARO_ENV
运行时:
Taro.getEnv()
例如:
// 分平台调用API或者分平台处理兼容性问题
if (process.env.TARO_ENV === 'weapp') {
Taro.textToAudio()
}
// 分平台使用不同组件
{process.env.TARO_ENV === 'weapp' && }
{process.env.TARO_ENV === 'h5' && }
P.S.编译时静态的环境区分足够应对大多数场景了,运行时的环境区分仅备不时之需
项目生命周期之间的匹配
首先一张图看一下小程序的生命周期
1.小程序初始化完成后,页面首次加载触发onLoad,只会触发一次。
2.当小程序进入到后台,先执行页面onHide方法再执行应用onHide方法。
3.当小程序从后台进入到前台,先执行应用onShow方法再执行页面onShow方法。应用生命周期和页面生命周期不是分开的,两者一起进行,相互交叉使用,会用到相同的方法,比如onShow和onHide。
然后看一下react生命周期
一个例子说明小程序和taro生命周期的映射关系为:
生命周期方法 | 作用 | 说明 |
---|---|---|
componentWillMount | 程序被载入 | 对应微信小程序onLaunch |
componentDidMount | 程序被载入 | 对应微信小程序onLaunch,在componentWillMount之后执行 |
componentDidShow | 程序展示出来 | 对应微信小程序onShow |
componentDidHide | 程序被隐藏 | 对应微信小程序onHide |
componentDidCatchError | 错误监听函数 | 对应微信小程序 onError |
componentDidNotFound | 页面不存在 | 对应微信小程序 onPageNotFound |
不过当然也包含componentWillUnmout和componentWillReceiveProps等react原始生命周期函数,用来编写自定义组件和h5页面。
在小程序中 ,页面还有一些专属的方法成员,如下:
onPullDownRefresh: 页面相关事件处理函数–监听用户下拉动作
onReachBottom: 页面上拉触底事件的处理函数
onShareAppMessage: 用户点击右上角转发
onPageScroll: 页面滚动触发事件的处理函数
onTabItemTap: 当前是 tab 页时,点击 tab 时触发
componentWillPreload: 预加载,只在微信小程序中可用
设计稿及尺寸单位
在 Taro 中尺寸单位建议使用 px、 百分比 %,Taro 默认会对所有单位进行转换。在 Taro 中书写尺寸按照 1:1 的关系来进行书写,即从设计稿上量的长度 100px,那么尺寸书写就是 100px,当转成微信小程序的时候,尺寸将默认转换为 100rpx,当转成 H5 时将默认转换为以 rem 为单位的值。
如果你希望部分 px 单位不被转换成 rpx 或者 rem ,最简单的做法就是在 px 单位中增加一个大写字母,例如 Px 或者 PX 这样,则会被转换插件忽略。
结合过往的开发经验,Taro 默认以 750px 作为换算尺寸标准,如果设计稿不是以 750px 为标准,则需要在项目配置 config/index.js 中进行设置,例如设计稿尺寸是 640px,则需要修改项目配置 config/index.js 中的 designWidth 配置为 640:
const config = {
projectName: 'myProject',
date: '2018-4-18',
designWidth: 640,
....
}
目前 Taro 支持 750、 640 、 828 三种尺寸设计稿,他们的换算规则如下:
const DEVICE_RATIO = {
'640': 2.34 / 2,
'750': 1,
'828': 1.81 / 2
}
建议使用 Taro 时,设计稿以 iPhone 6 750px
作为设计尺寸标准。
API
在编译时,Taro 会帮你对样式做尺寸转换操作,但是如果是在 JS 中书写了行内样式,那么编译时就无法做替换了,针对这种情况,Taro 提供了 API Taro.pxTransform
来做运行时的尺寸转换。
Taro.pxTransform(10) // 小程序:rpx,H5:rem
配置
默认配置会对所有的 px
单位进行转换,有大写字母的 Px
或 PX
则会被忽略。
参数默认值如下:
{
onePxTransform: true,
unitPrecision: 5,
propList: ['*'],
selectorBlackList: [],
replace: true,
mediaQuery: false,
minPixelValue: 0
}
Type: Object | Null
onePxTransform
(Boolean)
设置 1px 是否需要被转换
unitPrecision
(Number)
REM 单位允许的小数位。
propList
(Array)
允许转换的属性。
replace
(Boolean)
直接替换而不是追加一条进行覆盖。
mediaQuery
(Boolean)
允许媒体查询里的 px 单位转换
minPixelValue
(Number)
设置一个可被转换的最小 px 值
selectorBlackList
(Number)
黑名单里的选择器将会被忽略。
配置规则对应到 config/index.js
,例如:
{
h5: {
publicPath: '/',
staticDirectory: 'static',
module: {
postcss: {
autoprefixer: {
enable: true
},
pxtransform: {
enable: true,
config: {
selectorBlackList: ['body']
}
}
}
}
},
weapp: {
// ...
module: {
postcss: {
pxtransform: {
enable: true,
config: {
selectorBlackList: ['body']
}
}
}
}
}
}
忽略
属性
当前忽略单个属性的最简单的方法,就是 px 单位使用大写字母。
/* `px` is converted to `rem` */
.convert {
font-size: 16px; // converted to 1rem
}
/* `Px` or `PX` is ignored by `postcss-pxtorem` but still accepted by browsers */
.ignore {
border: 1Px solid; // ignored
border-width: 2PX; // ignored
}
文件
对于头部包含注释 /*postcss-pxtransform disable*/
的文件,插件不予处理。