如果你使用 Taro 开发感觉 Bug 少,那说明你的 React 代码写得很规范。 -- Taro团队
趁前段时间做基于taro2的微店铺以及taro2升taro3的项目经验,简单介绍Taro2跟Taro3的各自的优缺点以及实际使用场景下的语法区别,并分享Taro3升级中和使用中的踩坑点。
推荐阅读
Taro1/2
Taro3之前的整体架构可以看成两部分:编译时和运行时。这里解释一下两者的用途:
编译时:
通过对⽤户的 React 代码进⾏编译来转化代码语法,如jsx
转小程序xml
等,甚至转换成各个平台(抖音小程序、微信小程序、H5等等)都可以运⾏的代码。编译时工作流程主要是通过babel
将 Taro 代码解析成抽象语法树,然后操作语法树生成目标平台的代码,也就是parse -> replace -> generate
这样一个工作流程。
以build:weapp
编译微信小程序端为例:
render() {
return (
<View>
{
dataList.map((data, index) => (<Text key={index}>{data.title}</Text>))
}
</View>
)
}
经过babel转换后:
<view wx:for="{{dataList}}" wx:for-item="data" wx:for-index="index">
<text>{data.title}</text>
</view>
我们都知道 JSX 是一个 JavaScript 的语法扩展,它的写法千变万化,十分灵活。这里我们是采用 穷举 的方式对 JSX 可能的写法进行了一一适配,这一部分工作量很大,实际上 Taro 有大量的 Commit 都是为了更完善的支持 JSX 的各种写法。
这是摘自官网对taro2编译时的一句描述,由于使用穷举的适配方式,势必会造成jsx的各种各样的奇怪的bug产生和各种开发时的限制。
运行时
可以知道的是,我们开发taro项目时,引用的是taro库下面的api和组件,调用类似微信原生的api,如: wx.getSettings
,在taro里面需要从 @taro/taro
引用,然后调用 Taro.getSettings
,组件则是通过@taro/components
引用。这是因为Taro制定了一套运行时的标准组件库和api,通过对原生api进行拓展和配合编译时已经抹平了状态、事件绑定、页面配置和生命周期等的差异,完成了框架的适配工作。具体点描述:
编译后的taro代码实现了 BaseComponent
和 createComponent
,BaseComponent
的作用主要是重写了react里面的render、setState等核心代码,createComponent
主要作用是调用 Component()
构建页面,处理以下等对接工作完成适配:
- 将组件的 state 对应为小程序组件配置对象的 data
- 将组件的生命周期对应为小程序组件的生命周期
- 将组件的事件处理函数对应为小程序的事件处理函数
- ...
简单来说就是先将代码编译成各个平台结构化语言的代码,然后通过适配器模式等等方法适配到各个平台能够让之运行起来,整个Taro2的架构编译时做的工作占主要部分,运行时工作量较小。
总结
- 重编译时,轻运行时:这从两边代码行数的对比就可见一斑。
- 编译后代码与 React 无关:Taro 只是在开发时遵循了 React 的语法。
- 直接使用 Babel 进行编译:这也导致在工程化和插件方面的羸弱。
Taro3
Taro3可以大致理解为解释型架构,这个工作就主要是在运行时"对代码进行解释",怎么理解呢?升级为Taro后你可以发现package.json
文件里面多了个(当然不止这一个)@taro/runtime
的依赖,打开包所在目录:
惊奇的发现了我们在web中才会有的bom
跟dom
相关的关键字,原来Taro3自己实现了一套类似浏览器的BOM/DOM那一套API,通过webpack的plugin注入到小程序的逻辑层,打包编译后,你最终的代码都是基于BOM/DOM这几个API来实现你的具体功能,比如:不管什么平台,都有自己一套元素dom的规则,都有各自平台的类似bom的全局api规则,Taro3做的就是整合这些厂家的规则封装为类似BOM/DOM的思想去用,也就是说,我不管你开发时用的什么框架,我只要保证你运行时能帮你适配到各个平台即可。
这样做的最直观的好处就是前面说到的,不再受限制与框架本身了,理论上来说,Taro不仅可以支持Vue和React,也能用jquery、Angular等等的库进行跨端开发。站在React的使用者角度:通过taro2和taro3两个项目开发经验来说还有一点最直观的感受,taro3中写JSX
更加舒服了!其实就更加友好的支持JSX
这一点,应该是顺理成章的,因为taro的架构其实就是无限接近于React的开发体验,适配的工作是通过运行时的BOM/DOM去完成的,而不是像之前版本一样,通过穷举 的方式对 JSX 的写法进行适配。
有个关键点:既然Taro3自己实现了BOM/DOM这一套api,而react的中的渲染器,如react-dom
中调用的是浏览器的BOM/DOM的api,那taro肯定会有自己一套渲染器来链接react的协调器(reconciler,diff算法所在阶段)和taro-runtime
的 BOM/DOM api。源码路径:@tarojs/react
,description里面的描述如下:"like react-dom, but for mini apps."
如何选择
从上述Taro 3架构来说,改变了Taro1/2的重编译时的局面从而转向重运行时,其实重编译时的局限性不仅是对开发者的开发规范有更大的限制,也不利于维护taro框架的源码,因为一旦各厂商增删改了她们小程序的某个规范或者api时,要去维护升级taro的代码成本是比较高的。
但这不意味着taro3一定是最适合的版本,同等条件下,编译时做的工作越多,也就意味着运行时做的工作越少,性能会更好。随时时代的进度,硬件的条件越来越好,牺牲硬件的前提下能够带来更好的开发体验也许是比较明智的选择,但是在逻辑比较复杂或者需要更多运行内存的小程序,还是要侧重于重编译时的Taro2!
迁移指南
升级Taro Next前,请仔细阅读官方迁移指南
转换外部样式 withExternalStyle
Taro1/2 中需指定addGlobalClass
才能使用外部的 className,但是 Taro3 提升了所有的样式文件到 common.css 中,所以需要对外部样式进行转换。
转换 hooks withHooks
Taro1/2 中所有的 hooks 和一些特殊的功能组件如 Component, memo 等从 Taro 中导出。但 Taro3 中只维护了 Taro 独有的 hooks 和功能组件,所以将这些从 Taro2 中拆分出来。
转换类组件生命周期
React 中有一些废弃的生命周期需要加上 UNSAFE 的前缀标识。
升级 mobx4 到 mobx6
如项目使用了mobx,请注意此条规则。
装饰器目前方案不稳定,在 mobx6 中更是减少了装饰器的使用。而且 Taro3 中使用 mobx-react 而不是由 Taro 维护的 mobx-taro。
转换页面配置
页面配置文件单独拆分出来。
转换路由引用
访问路由和路由参数使用 Taro3 的新的 api。
转换作用域引用 withScope
Taro1/2 使用编译时的框架,Taro3 使用的是运行时的框架,所以 scope 直接废弃了。在转换中尝试进行兼容处理。
转换 hidden 属性
Taro1/2 中废弃了自定义的一个属性 hidden。
将全局样式的 css 修改为 module.less
Taro3 中为了减少组件样式文件的痛点,将所有的单独作用域的样式文件全部都提升到了全局。直接转换后如不使用 css module,则会产生大量的样式混乱干涉。所以为了保持样式的一致性,需要把样式进行转换,将 css 转换为 css module。
package.json
所依赖的 taro 极其相关的依赖手动更改(截止 2022-01-25,Taro 最新版 3.4.0)
{
"@tarojs/cli": "3.3.17",
"@tarojs/components": "3.3.17",
"@tarojs/react": "3.3.17",
"@tarojs/runtime": "3.3.17",
"@tarojs/taro": "3.3.17",
}
删除掉 taro1/2 中的 config/index.js 中的关于 babel 的配置,新增文件babel.config.js
module.exports = {
presets: [
[
'taro',
{
framework: 'react',
ts: true,
},
],
],
};
配置 config/index.js 中,指定使用的框架为 react
{
framework: 'react',
}
修改项目入口文件 app.ts
// 修改render函数,默认render this.props.children
render() {
return this.props.children
}
// 修改导出,直接将App导出
export default App;
// Taro.render(<App />, document.getElementById('app'));
原生组件的使用
“Taro3 的组件是没有配置文件的,因此 usingComponents 必须配置在“页面”的配置文件中”。但是 Taro1/2 的组件允许声明配置文件,所以在 Taro3 中需要把这些原生组件的配置声明提升到具体引用具体的页面。可参考 #withUsingComponentForHaicaoyun
组件标签废弃
在 Taro1/2 中,对应的组件将会编译成同名的组件。例如 HcyButton 组件编译成 hcy-button,所以如果项目中处理样式文件时对对应的样式做了定义,那么在 Taro3 中需要手动进行修改。
page.$component
的使用
在 Taro1/2 中允许通过$component 的方式访问具体组件的 Taro 实例,进而可以访问类组件的任意方法和属性,甚至修改其状态。例如下面示例,访问上个页面的实例,并设置其状态
const pages = getCurrentPages();
const prevPage = pages[pages.length - 2];
const prevPageInstance = prevPage.$component;
prevPageInstance.setState({
formItems: [],
});
这种在 Taro3 中并没有简单的替换方案,只能对所有使用这部分的地方进行一种合适的重构
taro2升taro3中可能遇到的报错
- Error: module "app.js" is not defined;module "common.js" is not defined (分包后公共组件打包问题)
- webpack Parser.pp$4.raise 报错 (webpack环境编辑使用的DOMAIN='',应该使用DOMAIN='""')
- Can't resolve './style/index.scss' (taro ui2.+和taro3.+版本不兼容)
- TypeError: Cannot read property 'prototype' of undefined (注意第三方库的依赖引用导致不兼容问题)
- MobX,Store Is Not Available, Make Sure It Is Provided By Some Provider (入口文件app.js使用<Provider {...store}></Provider>)注入全局store
- TypeError: Cannot read property 'getBehaviorPageData' of undefined (https://github.com/NervJS/tar...)(Taro.getCurrentPages获取到的page实例上没有$component)
- taro路径别名需要在webpack也配置一下
- 样式错乱问题,taro3中如不使用css module,则默认全局样式。
开发Taro2注意事项
凭记忆写下几点,具体问题可能与版本号也有关系,仅供参考
- 有状态组件尽量使用类组件开发,在页面中尤其注意这点,函数式组件中hooks和一些api功能bug较多,甚至某些钩子出现不执行的bug
- 函数式组件中无法通过
Taro.createSelectorQuery
获取元素信息,因为taro2需要传递scope,而在函数组件中无法获取,及时使用useScope
useEffect
和useEffectLayout
执行时机与普通的react项目有区别useDidShow
在抖音端不执行Taro.hideLoading
微信真机上会同时关闭Taro.showToast
- 组件中不能导出常量,如果需要常量,需要另开一个文件单独导出使用
- jsx中渲染函数前缀必须是render开头,如
renderItem = () => xxx
- Jsx中使用switch语法不能用default分支,会提示你逻辑多余
- jsx中试用if..else..语法渲染节点,有时候并不会如你所意,尝试定义一个变量node,在分支中赋值给node后,最终return这个node节点解决问题
- 如果使用了mobx,抖音端在jsx条件渲染的时候需要用
toJS
包一下,如:toJS(list).length > 0 && <View>xxx</View>
Swiper
组件的nextMargin
和previousMargin
属性在h5端无效- h5有时候容器高度会比小程序呈现的高,需设置
box-sizing: border-box;
- 不使用
View
、Text
标签选择器,H5内不生效。解决方案:使用className
开发Taro3注意事项
凭记忆写下几点,具体问题可能与版本号也有关系,仅供参考
更新组件会把兄弟节点也重新渲染了,问题描述
解决方案:给兄弟节点或自身增加层级
- 使用
Taro.createAnimation
,animationData
的初始值不能为null
,否则过度动画的效果会失效,直接呈现最终的样式,解决这一问题可以使用{}
- 版本3.4.0之前
Taro.createAnimation
在H5端失效 - 由于层级过深导致的渲染问题可以尝试使用
CustomWrapper
组件包裹,它的作用是创建一个原生自定义组件。对后代节点的setData
将由此自定义组件进行调用,达到局部更新的效果,从而提升更新性能。 - video组件有时在第一次渲染的时候获取不到时长,无法自动播放问题。
- 伪元素和伪类在小程序端失效
- 全局样式问题,taro3中如果不是css module,则默认样式文件作用所有组件
Image
在h5的实现是有一层包装层,这点在taro2中同样存在ScrollView
中使用position: sticky
无效,这点在taro2中同样存在- hidden异常,通过
state
数据控制显隐,不会生效。解决方案:通过三目运算解决
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。