开发RN时,我是如何剥离taro框架的

1,550 阅读4分钟

序言

项目选用技术栈时,由于前期规划需要发布多个平台问题,所以采用了taro跨端框架来开发APP,后期方便跨平台迁移,但在项目上线后一段时间后采用taro框架开发RN的问题就慢慢暴露出来,一个是升级taro版本需要连同react-native,这风险太大,二是taro-router耦合封装了react-navigation这类组件,当我使用其他组件也用到时,版本之间会冲突导致有崩溃的情况,所以对项目的改造迫在眉睫。

计划

在沟通理解了基本不会发布多平台的可能性后,我们便开始考虑剥离taro框架,避免后续开发过程中技术栈和打包工具深度耦合,导致后期维护困难,而为了尽可能减少框架剥离给项目带来的不稳定性,实施分成了三个步骤:

  1. 运行时剥离:将代码中使用到的taro方法统一迁移到项目中,进而剥离代码和框架间的依赖;
  2. 编译时剥离:移除taro预编译代码,彻底将技术栈和打包工具解耦;
  3. 编译器剥离:彻底移除taro框架,采用metro方法启动react-native,移除taro-cli等能力

一、运行时剥离

所谓运行时剥离,就是将原本taro框架提供的方法,组件统一迁移到项目再进行改造,从而剥离运行时和框架的耦合,但这里注意,我们剥离的只是运行时的引用,对于taro预编译的代码,无法迁移的,我们先将其保留原有的引用,将放到下一个步骤去实现剥离。

我们在 src 下建立一个 tarojs 文件夹,并在文件夹下,实现项目中使用到的各类组件,例如

src
  ├── tarojs       
    ├── components
      ├── View
      ├── Image
      ├── Text
    ├── taro
      ├── getSystemInfoSync
      ├── showToast
      ├── showLoading

而对应的 import 也进行批量修改

注:这里也能利用 alias,将 @tarojs 指向到 src/tarojs

import { View } from '@tarojs/components';

=>

import { View } from '@/tarojs/components';

修改过程中也并非完全顺利的,一开始以为 ViewText 这里组件只需要使用 react-native 自身的组件就行,后面看了源码发现 View 针对类型 TextNode 之类的节点又额外用 Text 进行包裹,否则 react-native 编译不过,所以最后大部分实现都是迁移源码再稍加修改,确保源代码的稳定。

二、编译时剥离

类似taro,umi这种技术栈+编译器一体的框架,都会存在编译时预处理,例如taro框架的routermodel这类公共方法 由于为了适配小程序语法,都会在项目编译过程中加入编译代码来实现效果,所以第二步我们将技术栈彻底和taro进行剥离,将项目中使用的生命周期和路由等耦合代码彻底剥离。

1. 比如 taro 中的 showToast,本质上是使用 react-native-root-siblings 去实现的,而使用 react-native-root-siblings 实现

就需要向 App层 注入 RootSiblingParent 来实现,而这里就需要我们改造 App.jsx 来满足原逻辑

const App = (props) => {
  const { children } = props;

  //...其他代码

  return children;
}

=>

const App = (props) => {
  const { children } = props;

  //...其他代码

  return <RootSiblingParent>
    {children}
  </RootSiblingParent>;
}
2. 除此之外,最为复杂的便是剥离路由和生命周期这类深度绑定的代码,由于taro是源于小程序的,所以其各类写法是基于小程序的路由配置和生命周期去实现的,因此这里需要对其逐步修改;
  • 移除 children,直接使用 @react-navigation 方法去实现路由功能。
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';

const Stack = createNativeStackNavigator();
const App = () => {

  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="pagesHomeIndex" component={HomeScreen} />
        <Stack.Screen name="pagesLoginIndex" component={LoginScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  )
}
  • 通过 createRef 获取路由实例,进而实现 getCurrentPages 这类能力。
const navigationRef = createRef();

const App = () => {

  return (
    <NavigationContainer ref={navigationRef}>
    </NavigationContainer>
  )
}

// 其他地方
const pages = Taro.getCurrentPages();
const route = pages[pages.length - 1].route;
consonle.log(route); // pages/login/index

=> 

const route = navigationRef.current?.getCurrentRoute();
consonle.log(route.name); // pagesLoginIndex 

这里由于项目总我们使用的地方不多,所以直接是改代码的,如果项目用的地方太多,可以跟第一点那样,通过在同一方法转化去处理。

  • 使用 react-nativereact-navigation 这类的方法去实现生命周期,例如 useDidShow
import { useFocusEffect } from '@react-navigation/native';
import { useCallback, useEffect } from 'react';
import { AppState } from 'react-native';

export function useDidShow(callback) {
  const onShow = useCallback(() => callback(), []);

  useFocusEffect(onShow);

  // 切换到前台时调用
  useEffect(() => {
    const subscription = AppState.addEventListener('change', (nextAppState) => {
      if (nextAppState === 'active') {
        onShow(); /
      }
    });

    // 在组件卸载时移除监听
    return () => {
      subscription.remove();
    };
  }, []);
}

三、编译器剥离

到了这个步骤,我们基本可以将 tarojs 的相关的代码依赖移除,大致只剩下 命令行编译启动 这三个东西了

1. 这里最简单的就是命令行了,这里通过查代码和打断点,替换掉原有的 taro-cli 命令行。

image.png

注:这里我用了fastlane进行打包,所以在Fastfile实现了build命令

image.png

2. 打包处理注意的是样式处理,由于我们项目中使用的是 less,而 react-native 是不支持的,所以这里为了项目稳定,就依然沿用 taro-css-to-react-native 插件,实现 lessStyleSheet 的转化

image.png

3. 最后移除 metro.config.js 中的 @tarojs/rn-supporter,并实现样式转化等能力

image.png

最后

这就是最后跑起来的效果了~

image.png