结合趣店FED近半年的实践经验,我们开发了首款Taro三端统一应用:taro-(高仿网易严选微信小程序),探索Taro多端开发的正确方法。
趣店FED早在去年10月就全面使用Taro框架开发小程序(当时的版本为1.1.0-beta.4),至今已上线2个微信小程序和2个支付宝小程序。
之所以选择Taro,是为了解决微信小程序原生开发的痛点。 另一方面,团队也希望能够跨多个平台进行统一开发。 太郎无疑是当时最受支持的一位。 此外,它也与团队整体技术栈保持一致,可以显着降低团队学习成本。
可以说,Taro 在小程序端和 H5 端的支持水平都不错,网上有很多例子可以查看。 但就支持而言,RN中披露的项目没有一个适合RN:
这种情况是可以理解的。 毕竟多个终端很难统一。 需要准确把握各个终端的差异,做出合理的选择。 虽然 Taro 是以多终端为目标来设计的,但重点还是在小程序端,并没有正确的选择。 杜端设定了一定的发展限制,所以无从下手也是很正常的。 作者曾在2018 iWeb峰会-微信站分享《多终端统一开发实践》。 他提到了使用Taro开发RN终端的陷阱和总体思路,并付诸实践。
结合趣店FED近半年的实践经验,我们开发了首款Taro三端统一应用:taro-(高仿网易严选微信小程序)来探讨本文的重点:开发多端的正确方法-Taro 的终端应用程序。
在线预览
H5、RN端可在线预览(直接调用网易严选接口,如需体验登录和购物车功能,请使用网易邮箱账号登录):
在线预览:
以下是操作截图:
风格管理
样式管理是多终端开发的首要挑战,因为对通用Web样式的支持差异很大。 上述不适配RN的多端项目大多一直停留在样式上,使用了大量RN不支持的样式。 这种情况下要兼容RN就相当于重写页面,肯定是无能为力的。 这也是本文所强调的,需要把握正确的多终端开发姿势。
从风格上看,H5最灵活,小程序次之,RN最弱。 统一多端风格意味着对准短板,即用RN的约束来管理风格,同时考虑到小程序的局限性。 核心可以概括为三点:
使用 Flex 布局
在进一步阐述之前,有必要了解RN端影响样式方案的几个主要差异:
使用Flex布局不仅仅是因为RN的View标签有一个默认的样式:flex; flex-:,但更重要的是,Flex可以解决鬼空白问题:
// View 标签高度不会是 100px,图片下方会有几像素空白,称为幽灵空白
const imgStyle = { height: '100px' }
<View>
<Image src={...} style={imgStyle}
View>
常规的解决方案是在View标签上设置font-size/line-:0,或者:-等,但这些在RN中是不支持的。 在 View 标签上设置:flex 是唯一可靠的解决方案。
况且Flex有强大的布局能力,为什么不使用呢? 只需注意RN中View标签默认的主轴方向是。 如果不改变另一端与RN一致,则需要在所有使用flex的地方显式声明主轴方向。
基于BEM的写作风格
RN 实际上只支持一种声明样式的方式,即声明属性:
const viewStyle = { height: '100%' }
<View style={viewStyle}
这也导致Taro基本上只支持RN端的选择器编写(最终编译成对象字面量),而BEM()在这里扮演了合适的角色:
例如,对于每行 2 个元素的列表,每行的最后一个元素具有特定的样式。 使用伪元素选择器:nth-(even) 很容易实现。 不过,需要在RN中自己计算一下:
{list.map((item, index) => (
<View className={classNames('block__element',
index % 2 === 1 && 'block__element--even'
)} />
)}
编写风格基于BEM,不依赖其他选择器。 虽然代码会有些繁琐,但也能保证多终端可行,不会出现支持问题。
使用属性覆盖组件样式
小程序和RN在页面和组件之间传递样式时存在问题:
// 目前 Taro RN 端还未实现往组件传递 className 对应样式
<CompA compClass='my-style' />
// CompA,样式不生效
<View className={this.props.compClass} />
虽然上面的场景小程序可以通过组件外部样式来实现,但是官网文档强调“在同一个节点上使用普通样式类和外部样式类时,两个类的优先级是未定义的,所以最好避免这种情况” “情况”;可以使用全局样式,但这种样式将很难维护。
那么,传递和重写组件样式就成为唯一的选择。 需要注意的是,样式文件会被编译处理以兼容多终端,但方法需要在运行时兼容:
style={postcss({ background: '#fff' })} />
// 简单演示,如 RN 不支持 background,需改成 background-color
function postcss(style) {
const { background, ...restStyle } = style
const newStyle = {}
if (background) {
newStyle.backgroundColor = background
}
return { ...newStyle, ...restStyle }
}
从这个角度来说,- 可能是多端开发最好的样式方案网易严选小程序开发,但是Taro还不支持,微信小程序官方文档也提到“尽量避免将静态样式写入其中,以免影响渲染速度”, all 将所有样式都写在中间不太靠谱,但只覆盖少数样式可能不会有太大影响。
风格兼容
即使掌握了上述风格管理思路,多端风格差异的问题依然存在。 例如-:这种风格会在RN端报错。 太郎提供了一个解决方案:
.text {
/*postcss-pxtransform rn eject enable*/
white-space: nowrap;
/*postcss-pxtransform rn eject disable*/
}
不过项目中不止一处会出现这个问题。 全部写成这样,不太美观。 可以用Sass稍微封装一下:
@mixin eject($attr, $value) {
/*postcss-pxtransform rn eject enable*/
#{$attr}: $value;
/*postcss-pxtransform rn eject disable*/
}
.text {
@includes eject(white-soace, nowrap);
}
Sass无法解决差异,但是对于一些两端不兼容的样式,通过Sass统一处理是比较合理的方式。 代码比较美观,也容易维护。
终端能力差异
相比款式,终端能力的差距还不错。 终端之间的差异是客观存在的,更何况iOS和上的RN已经存在很大的差异。
为了应对终端能力的差异,要么需要改变实现思路。 例如,如果RN终端尚不支持Taro.(get/set),则使用 / + Taro.(get/set) 代替,否则必须使用环境判断方法。
Taro 提供了 .env。 用于环境判断。 大多数小的差异可以通过以下方式解决:
function foo() {
if (process.env.TARO_ENV === 'weapp') {
// 微信小程序逻辑
}
if (process.env.TARO_ENV === 'h5') {
// H5 逻辑
}
if (process.env.TARO_ENV === 'rn') {
// RN 逻辑
}
}
这时候也考验开发者的封装能力。 一般建议对这些不同逻辑的判断进行统一,比如封装在src/中,向外界提供一致的接口,业务页面中尽量不要混合过多的判断。
对于简单的环境判断无法解决的问题,只能使用原生开发。 例如,Taro 不支持 RN 端组件,因此需要使用原生 RN 自行实现:
// Taro 页面,根据环境引入 RN 原生页面
import { WebView } from '@tarojs/components'
const WebViewRN = process.env.TARO_ENV === 'rn' ? require('./rn').default : null
export default class extends Component {
render() {
return process.env.TARO_ENV === 'rn' ?
<WebViewRN src={this.url} /> :
<WebView src={this.url} />
}
}
// 原生 RN 页面,从 react-native 引入 WebView
import Taro, { Component } from '@tarojs/taro'
import { WebView } from 'react-native'
export default class WebViewRN extends Component {
render() {
const source = { uri: this.props.src }
return <WebView source={source} />
}
}
.env。 是在编译时而不是运行时处理的,这意味着如果不编译RN,上述用编写的RN页面将不会被打包,确保编译到其他终端时不会引入不支持的内容。
可以引入原生页面,多端问题有基本的实现保障。
Taro RN 方面的陷阱
太郎RN这边还存在很多小问题。 在该项目的开发过程中还解决了一些Bug:
另外,还有几个问题由于时间关系还没有通过PR解决,所以暂时绕过,但其中有两个值得一提。
不支持 RN 的 View 标签,但这是一个很常见的需求。 原生的解决方案是设置一层组件,比如:
onPress={this.handlePress}>
{...}
引入Taro来响应用户操作:
{...PanResponder.carete({ ...})}
style={wrapperStyle}
>
style={innerStyle} />
问题是多了一层View嵌套,样式被拆分出来单独应用。 但是样式拆分存在问题,导致绑定后元素的样式乱了。 这在开发过程中仍然是一个很大的陷阱。
自适应宽度和高度
问题还好,可以通过改变样式来规避,但是自适应宽高的陷阱就比较尴尬了。
小程序和H5可以使用rpx/em来实现自适应,而RN的自适应方案则比较麻烦。 一般需要获取宽度和高度,然后进行转换。 Taro.() 可以解决这个问题,但是在编译 RN 端样式文件时没有考虑到这一点,即:会被编译成:50 而不是:Taro.(100),无法适应不同的屏幕尺寸。
因此,Taro RN 目前想要实现自适应并不容易。 要么使用+Taro.()来实现非百分比宽度和高度,要么你必须自己编写一个脚本来处理编译后的样式文件。
这两个问题在2204年和2205年都曾提出过,如有需要,可以关注决议的进展。
其他
要实现多端统一,可以讨论的细节太多了。 上述实现思路虽然简单,但也隐含着针对各个终端差异性的挣扎和权衡。 本文仅列出最基本的要点。 用于讲解Taro多终端开发的核心思想。
这个项目的代码并没有太过封装,很容易阅读,而且也实现了足够的样式细节,避免了坑。 具体涉及到的坑和注意事项都在代码中用//TODO(Taro暂不支持)、//NOTE(开发技巧、注意事项)注释了,更多内容等待大家去实践和体验。
总结
正如前言中提到的,虽然 Taro 在设计时就考虑了多终端,但其重点还是在小程序端,而目前对 RN 端的支持还不是特别理想。 不过,在充分了解多终端之间的差异,掌握正确的多终端开发姿势后(尤其是风格管理方面,避免项目成型后需要在兼容性方面进行大刀阔斧的改动),你绝对可以一展身手在简单的项目上。
两周开发一个小程序很常见,但是两周就完成了小程序端(微信、支付宝、百度等),并且H5、H5端都已经完成。 后续更新只需要进行一项更改。 地方,产量和维护效率真是惊人。 这大概就是“一次,跑”的魅力(虽然在前端领域很容易发展成“一次,”)
相信随着小程序热度的不断上升,将会出现更多优秀的开源框架和解决方案。 我们并不倾向于重新发明轮子,而是专注于如何在现有解决方案的基础上更好地开发多终端应用。
项目开源地址: