React Native路由系统重构与react-navigation封装实践

Viewed 0

React Native路由系统重构:基于react-navigation的封装实践

在React Native项目中,使用react-navigation作为路由解决方案时,官方示例通常较为基础。随着页面数量增加,App.tsx主文件需要重复添加大量RootStack.Screen组件,且路由对象navigation需层层传递。虽然可以通过useDispatchuseNavigation等hooks获取,但为了提升代码可维护性和便于系统升级,我们封装了routers配置及MRouter管理类。本文将详细介绍路由系统的重构,涵盖路由配置、路由管理和页面使用三部分,文末提供完整代码示例。

路由配置 src/router.js

路由配置主要包括动画枚举、屏幕方向枚举、默认配置及路由对象定义。

动画与屏幕方向枚举

定义路由切换动画类型:

const animationType = {
  default: 'default',
  fade: 'fade',
  fade_from_bottom: 'fade_from_bottom',
  flip: 'flip',
  none: 'none',
  simple_push: 'simple_push',
  slide_from_bottom: 'slide_from_bottom',
  slide_from_right: 'slide_from_right',
  slide_from_left: 'slide_from_left'
};

定义屏幕方向枚举:

const orientationType = {
  default: 'default',
  all: 'all',
  portrait: 'portrait',
  portrait_up: 'portrait_up',
  portrait_down: 'portrait_down',
  landscape: 'landscape',
  landscape_left: 'landscape_left',
  landscape_right: 'landscape_right'
};

默认路由配置

设置默认的导航选项:

const screenConfig: NativeStackNavigationOptions = {
  headerShown: true,
  headerShadowVisible: false,
  headerTitleAlign: 'center',
  headerBackTitle: '',
  headerBackVisible: false,
  headerBackTitleVisible: false,
  headerTintColor: '#3D3F43',
  animation: animationType.slide_from_right,
  orientation: orientationType.portrait,
  headerStyle: {
    backgroundColor: '#fff',
    borderBottomWidth: 0
  },
  headerTitleStyle: {
    fontSize: 18,
    fontWeight: '500',
    color: '#3D3F43'
  }
};

自定义标题与选项生成

创建自定义标题组件:

const getHeaderTitle = (text) => {
  return (
    <View style={{
      height: 28,
      marginBottom: 8,
      justifyContent: 'center',
      alignItems: 'center'
    }}>
      <UIText style={[{
        fontSize: 18,
        fontWeight: '500'
      }, { color: '#3D3F43' }]}>{text}</UIText>
    </View>
  );
};

生成路由选项函数:

const getOptions = (param: NativeStackNavigationOptions) => {
  const options: NativeStackNavigationOptions = {
    ...screenConfig,
    ...param,
    headerLeft: () => <HeaderBackArrow />,
    headerTitle: () => getHeaderTitle(param.title || '')
  };
  return options;
};

路由配置对象

定义路由映射,包含名称、选项和组件:

const routers = {
  Login: { name: 'Login', options: getOptions({ animation: animationType.slide_from_bottom, headerShown: false }), component: Login },
  Info: { name: 'Info', options: getOptions({}), component: Info },
  Tabbar: { name: 'Tabbar', options: getOptions({ animation: animationType.slide_from_right }), component: Tabbar },
  Agreement: { name: 'Agreement', options: getOptions({}), component: Agreement },
  // 其他路由...
};

路由管理 src/framework/MRouter.ts

路由管理类提供全局导航控制方法。

设置路由引用

存储导航器引用:

let _navigator: any;
function setNavigator(navigatorRef: any) {
  _navigator = navigatorRef;
}

核心导航方法

打开新页面,支持参数传递和是否开启新页面标志:

function open(name: string, params?: any, newPage?: boolean) {
  let index = indexOfRouteByName(name);
  let route = getRouteInfoByName(name);
  let item: any = {
    key: route?.key || name,
    name: name,
    path: name,
    params: params || null
  };
  if (newPage || index === -1) item.key = name + generateRandom();
  _navigator.dispatch(CommonActions.navigate(item));
}

重置路由栈到首页:

function home(name: string, params?: object) {
  try {
    let item = {
      name: name,
      path: name,
      params: params,
      key: name + generateRandom()
    };
    _navigator.dispatch(
      CommonActions.reset({
        index: 0,
        routes: [item]
      })
    );
  } catch (e) {
    errorHandler.noRoute(name);
  }
}

替换栈顶路由:

function replace(name: string, params?: any) {
  let routers = _navigator.getRootState().routes || [];
  let index = indexOfRouteByName(name);
  if (name && index > -1) {
    let item = {
      index,
      name: name,
      path: name,
      params: params,
      key: name + generateRandom()
    };
    if (index > -1) routers = routers.slice(0, index);
    routers.push(item);
    _navigator.dispatch(
      CommonActions.reset({
        index: 0,
        routes: routers
      })
    );
  } else {
    routers.pop();
    let item = {
      name: name,
      params: params,
      path: name,
      key: name + generateRandom()
    };
    routers.push(item);
    _navigator.dispatch(
      CommonActions.reset({
        index: 0,
        routes: routers
      })
    );
  }
}

路由返回,支持指定回退目标:

function back(name?: string, params?: any) {
  let index = -1;
  let route = null;
  if (name) {
    index = indexOfRouteByName(name);
    route = getRouteInfoByName(name);
  } else {
    route = _navigator.getRootState().routes[_navigator.getRootState().routes.length - 1];
  }
  if (index > -1) {
    open(route.name, params);
  } else {
    close();
  }
  params && _navigator.dispatch(
    CommonActions.setParams({
      params: params,
      key: route && route.key,
      source: route && route.key
    })
  );
}

关闭当前路由:

function close() {
  if (_navigator.canGoBack()) {
    _navigator.dispatch(CommonActions.goBack());
  }
}

辅助函数

查找路由在栈中的位置:

function indexOfRouteByName(name: string) {
  let routers = _navigator.getRootState().routes || [];
  let index = -1;
  let routeIndex = routers.length;
  while (routeIndex > 0) {
    routeIndex--;
    let route = routers[routeIndex];
    if (route.name === name) {
      index = routeIndex;
      break;
    }
  }
  return index;
}

根据名称获取路由信息:

function getRouteInfoByName(name: string) {
  let routers = _navigator.getRootState().routes || [];
  let routeIndex = routers.length;
  while (routeIndex) {
    routeIndex--;
    let route = routers[routeIndex];
    if (route.name === name) {
      return route;
    }
  }
  return routers[routers.length];
}

App.tsx配置

在应用入口文件中集成路由系统。

导航容器设置

使用NavigationContainer包裹导航器:

function App() {
  const navigationRef = useNavigationContainerRef();
  return (
    <SafeAreaProvider>
      <NavigationContainer ref={navigationRef}>
        <NavigatorFn navigationRef={navigationRef} />
      </NavigationContainer>
    </SafeAreaProvider>
  );
}

MRouter初始化与路由动态生成

在组件中初始化MRouter并动态生成路由屏幕:

const NavigatorFn: React.FC<{ navigationRef: any }> = ({ navigationRef }) => {
  useEffect(() => {
    MRouter.setNavigator(navigationRef?.current);
  }, []);
  return (
    <RootStack.Navigator
      screenOptions={{
        headerShown: false,
        orientation: 'portrait'
      }}>
      {routers &&
        Object.keys(routers).map((key, index) => {
          return (
            <RootStack.Screen
              key={index}
              name={routers[key].name}
              options={routers[key].options}
              getComponent={() => routers[key].component}
            />
          );
        })}
    </RootStack.Navigator>
  );
};

状态驱动路由跳转

根据应用状态(如登录状态)自动跳转:

useEffect(() => {
  AsyncStorage.getItem('token')
    .then(token => {
      dispatch(setToken({ token }));
      if (!token) {
        MRouter.home(routers.Login.name);
      }
    })
    .catch(err => {
      MRouter.home(routers.Login.name);
    });
}, [token]);

MRouter使用说明

MRouter可在任何页面直接调用,无需传递navigation对象,并兼容原navigation方法。

常用操作示例

打开页面:

MRouter.open('Login');
MRouter.open(routers.Login.name, { name: 'abc', key: 1 });
MRouter.open(routers.Login.name, { name: 'abc', key: 1 }, true); // 强制开启新页面

参数获取:

  • 在类组件中通过this.props.params获取。
  • 在函数组件中使用useRoute() hook获取。

重置路由栈:

MRouter.home(routers.Login.name);
MRouter.home(routers.Login.name, { key: 2, name: 'bcd' });

替换路由:

MRouter.replace(routers.Login.name);
MRouter.replace(routers.Login.name, { key: 2, name: 'bcd' });

返回操作:

MRouter.back(); // 返回上一页
MRouter.back(routers.Login.name); // 返回到指定页面
MRouter.back(routers.Login.name, { key: 2, name: 'bcd' });

代码示例摘要

关键代码文件概览:

  • MRouter.ts:封装完整的路由管理方法,包括打开、重置、替换、回退等。
  • router.tsx:定义路由配置,包括枚举、选项和路由映射。
  • App.tsx:应用入口,集成导航容器和状态管理。

总结

通过封装react-navigation,我们构建了一个可维护、易扩展的路由系统。MRouter类提供了统一的API,简化了页面跳转和状态管理,同时保持与原生导航方法的兼容性。这种重构提升了代码组织性,适用于中大型React Native项目。

0 Answers