Taro原理分析、迁移指南及开发注意事项

MangoGoing
English
如果你使用 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代码实现了 BaseComponentcreateComponentBaseComponent的作用主要是重写了react里面的render、setState等核心代码,createComponent 主要作用是调用 Component() 构建页面,处理以下等对接工作完成适配:

  • 将组件的 state 对应为小程序组件配置对象的 data
  • 将组件的生命周期对应为小程序组件的生命周期
  • 将组件的事件处理函数对应为小程序的事件处理函数
  • ...

简单来说就是先将代码编译成各个平台结构化语言的代码,然后通过适配器模式等等方法适配到各个平台能够让之运行起来,整个Taro2的架构编译时做的工作占主要部分,运行时工作量较小。

总结

  • 重编译时,轻运行时:这从两边代码行数的对比就可见一斑。
  • 编译后代码与 React 无关:Taro 只是在开发时遵循了 React 的语法。
  • 直接使用 Babel 进行编译:这也导致在工程化和插件方面的羸弱。

Taro3

Taro3可以大致理解为解释型架构,这个工作就主要是在运行时"对代码进行解释",怎么理解呢?升级为Taro后你可以发现package.json文件里面多了个(当然不止这一个)@taro/runtime的依赖,打开包所在目录:

惊奇的发现了我们在web中才会有的bomdom相关的关键字,原来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中可能遇到的报错

  1. Error: module "app.js" is not defined;module "common.js" is not defined (分包后公共组件打包问题)
  2. webpack Parser.pp$4.raise 报错 (webpack环境编辑使用的DOMAIN='',应该使用DOMAIN='""')
  3. Can't resolve './style/index.scss' (taro ui2.+和taro3.+版本不兼容)
  4. TypeError: Cannot read property 'prototype' of undefined (注意第三方库的依赖引用导致不兼容问题)
  5. MobX,Store Is Not Available, Make Sure It Is Provided By Some Provider (入口文件app.js使用<Provider {...store}></Provider>)注入全局store
  6. TypeError: Cannot read property 'getBehaviorPageData' of undefined (https://github.com/NervJS/tar...)(Taro.getCurrentPages获取到的page实例上没有$component)
  7. taro路径别名需要在webpack也配置一下
  8. 样式错乱问题,taro3中如不使用css module,则默认全局样式。

开发Taro2注意事项

凭记忆写下几点,具体问题可能与版本号也有关系,仅供参考

  1. 有状态组件尽量使用类组件开发,在页面中尤其注意这点,函数式组件中hooks和一些api功能bug较多,甚至某些钩子出现不执行的bug
  2. 函数式组件中无法通过Taro.createSelectorQuery获取元素信息,因为taro2需要传递scope,而在函数组件中无法获取,及时使用useScope
  3. useEffectuseEffectLayout执行时机与普通的react项目有区别
  4. useDidShow在抖音端不执行
  5. Taro.hideLoading 微信真机上会同时关闭 Taro.showToast
  6. 组件中不能导出常量,如果需要常量,需要另开一个文件单独导出使用
  7. jsx中渲染函数前缀必须是render开头,如renderItem = () => xxx
  8. Jsx中使用switch语法不能用default分支,会提示你逻辑多余
  9. jsx中试用if..else..语法渲染节点,有时候并不会如你所意,尝试定义一个变量node,在分支中赋值给node后,最终return这个node节点解决问题
  10. 如果使用了mobx,抖音端在jsx条件渲染的时候需要用toJS包一下,如: toJS(list).length > 0 && <View>xxx</View>
  11. Swiper组件的nextMarginpreviousMargin属性在h5端无效
  12. h5有时候容器高度会比小程序呈现的高,需设置box-sizing: border-box;
  13. 不使用 ViewText 标签选择器,H5内不生效。解决方案:使用className

开发Taro3注意事项

凭记忆写下几点,具体问题可能与版本号也有关系,仅供参考

  1. 更新组件会把兄弟节点也重新渲染了,问题描述

    解决方案:给兄弟节点或自身增加层级

  2. 使用Taro.createAnimationanimationData的初始值不能为null,否则过度动画的效果会失效,直接呈现最终的样式,解决这一问题可以使用{}
  3. 版本3.4.0之前Taro.createAnimation在H5端失效
  4. 由于层级过深导致的渲染问题可以尝试使用CustomWrapper组件包裹,它的作用是创建一个原生自定义组件。对后代节点的 setData 将由此自定义组件进行调用,达到局部更新的效果,从而提升更新性能。
  5. video组件有时在第一次渲染的时候获取不到时长,无法自动播放问题。
  6. 伪元素和伪类在小程序端失效
  7. 全局样式问题,taro3中如果不是css module,则默认样式文件作用所有组件
  8. Image在h5的实现是有一层包装层,这点在taro2中同样存在
  9. ScrollView中使用position: sticky无效,这点在taro2中同样存在
  10. hidden异常,通过 state 数据控制显隐,不会生效。解决方案:通过三目运算解决

阅读 914

开源项目:详见个人详情

538 声望
1.1k 粉丝
0 条评论

开源项目:详见个人详情

538 声望
1.1k 粉丝
文章目录
宣传栏