Navigation
阅读进度0%
No headings found.

React Navigation 实现方案:路由管理与页面跳转

December 19, 2024 (1y ago)

React Native
Navigation
Router

前面我们仅仅是完成了 非常简单的东西,但是对于Router 没有一个明确的实现出来 ,完整的实现出来,说白了,现在的这个项目还是一个 不完整的项目,它连0.0.1 的版本都算不上. 现在我们来完善它,把navigation 完善掉

关于 router navigation 的配置

总体的设计方案和思路

总体的思路比较的简单,我们用原生处理多个View视图(不管是Android还是IOS)并且把他们存储在一个Array中模拟Stuck ,push 就往里面加,pop 就弹出去; 在RN端我们使用react-navigation 处理 主模块。完成侧边栏 + bar-navigation + 一些重要的模块。其他的部分都自己去完成 stuck 操作就好 ,就不要用其他的navigation了只要 一种stuck 就好

// 这个要到mobx 里面去
navigation = {
  statick:{} // 这里保存 本模块 navigation 的实例 或者一些东西
  // stuck 操作
  push:() => {},
  back:() => {},
  // tab 栏
  barNavigation: () => {},
}

IOS 实现细节

RN的集成Navigation

明确我们的目标,我们希望可以实现seller App 一样的效果

那么其重要的细节如下 ,下面是部分参考代码

参考

// 独立页面在此注入路由
const AppStack = {
  default: 'AppStack',
  initialRouteName: 'Guide',
  Screen: [
    {
      // 全局的一些些吗都平铺开
      name: 'Guide',
      option: {headerShown: false},
      components: Guide,
    },
    {
      name: 'PDF',
      option: {headerShown: false},
      components: PDF,
    },
    {
      name: 'WebViewSDK',
      option: {headerShown: false},
      components: WebViewSDK,
    },
    {
      name: 'AfterSalesDetails',
      option: {
        headerShown: false,
        headerTransparent: true,
      },
      components: AfterSalesDetails,
    },
    // 切换底部导航抽屉
    {
      name: 'ScanLayout',
      option: {headerShown: false},
      components: ScanLayout,
    },
    // AppStack -> 抽屉 -> 导航
    {
      name: 'Dre',
      option: {headerShown: false},
      components: DrawerStackNavigator,
    },
  ],
};
 
const AppModules = createNativeStackNavigator();
function AppStackComponent() {
  if (Platform.OS === 'android') {
    StatusBar.setBackgroundColor('#00000000');
  }
  return (
    <AppModules.Navigator {...AppStack}>
      {AppStack.Screen.map((item, index) => {
        const localoptions = Platform.OS === 'android' ? {...item.option, statusBarStyle: 'dark'} : item.option;
        return (
          //@ts-ignore
          <AppModules.Screen
            key={index}
            // @ts-ignore
            options={localoptions}
            listeners={{
              state: (e) => {
                DrawerCode.hide();
              },
            }}
            name={item.name}
            component={RouterComponent(item)}
          />
        );
      })}
    </AppModules.Navigator>
  );
}
 
// 这个RouterCompoent 是一个全局的layout
import {useEffect} from 'react';
import {useNavigation} from '@react-navigation/native';
import store from '@/module/Main/store';
 
const {routerStore} = store;
 
const RouterComponent = (props: any) => {
  // 保存当前状态到RouterStore 
  const navigation = useNavigation();
  useEffect(() => {
    routerStore.setNavigation(navigation);
  }, []);
  return props.components;
};
 
export default RouterComponent;
 
// routerStore
import {makeAutoObservable, observable} from 'mobx';
import {NavigationProp} from '@react-navigation/native';
 
class RouterStore {
  constructor() {
    makeAutoObservable(this, {
      navigation: observable,
    });
  }
 
  navigation: NavigationProp<ReactNavigation.RootParamList> | undefined = undefined;
 
  setNavigation = (newNavigation: NavigationProp<ReactNavigation.RootParamList>) => {
    this.navigation = newNavigation;
  };
}
const routerStore = new RouterStore();
 
export default routerStore;
 
// store
import routerStore from './routerStore';
export default {
  routerStore
}
 
 
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
 
const TabNai = createBottomTabNavigator();
const TabNavigator: React.FC<{}> = (props) => {
  const {t} = useTranslation();
  const {unRed} = useLocalObservable(() => store.messageStore);
 
  const newEgg = t('g-newegg');
  const message = t('g-message');
  const headline = t('g-headline');
  const tabBarLayout = (iconSource: any, isRed?: boolean) => {
    return (
      <View style={styles.icon}>
        {isRed && <View style={[styles.noRed]}></View>}
        <Image style={{width: '100%', height: '100%'}} source={iconSource} />
      </View>
    );
  };
 
  // Tab需要配置项
  const TabStackConfig = {
    name: 'TabStackConfig',
    initialRouteName: 'NavTestHome',
    Screen: [
      {
        name: 'TabNewEgg',
        option: {
          headerShown: false,
          tabBarLabel: newEgg,
          tabBarIcon: (info: any) => {
            const {focused} = info;
            const imgSourcePath = focused
              ? require('../../assets/images/activeICON/default.png')
              : require('../../assets/images/notAcitve/default.png');
            return tabBarLayout(imgSourcePath);
          },
        },
        components: Home,
      },
      {
        name: 'TabMessage',
        option: {
          headerShown: false,
          tabBarLabel: message,
          tabBarIcon: (info: any) => {
            const {focused} = info;
            const imgSourcePath = focused
              ? require('../../assets/images/activeICON/message.png')
              : require('../../assets/images/notAcitve/message.png');
            return tabBarLayout(imgSourcePath, Boolean(unRed));
          },
        },
        components: Community,
      },
      // {
      //   name: 'TabHeadline',
      //   option: {
      //     headerShown: false,
      //     tabBarLabel: headline,
      //     tabBarIcon: (info: any) => {
      //       const {focused} = info;
      //       const imgSourcePath = focused
      //         ? require('../../assets/images/activeICON/nx.png')
      //         : require('../../assets/images/notAcitve/nx.png');
      //       return tabBarLayout(imgSourcePath);
      //     },
      //   },
      //   components: nx,
      // },
    ],
  };
  return (
    <TabNai.Navigator
      screenOptions={() => ({
        tabBarActiveTintColor: globalStyle.colors.orange6,
        tabBarInactiveTintColor: globalStyle.colors.gray7,
      })}
    >
      {TabStackConfig.Screen.map((item, index) => {
        return <TabNai.Screen key={index} options={item.option} name={item.name} component={RouterComponent(item)} />;
      })}
    </TabNai.Navigator>
  );
};
 
export default observer(TabNavigator);
 
// 下面是三个tab
// Tab需要配置项
const CommunityStackConfig = {
  name: 'CommunityStackConfig',  // 这个东西也许不需要🤔 
  initialRouteName: 'NavTestHome',
  Screen: [
    {
      name: 'Community',
      option: {headerShown: false},
      components: Community,
    },
  ],
};
 
const CommunityStackNavigator = createNativeStackNavigator();
const CommunityStack = () => {
  return (
    <CommunityStackNavigator.Navigator {...CommunityStackConfig}>
      {CommunityStackConfig.Screen.map((item, index) => {
        return (
          <CommunityStackNavigator.Screen
            key={index}
            options={item.option}
            name={item.name}
            component={RouterComponent(item)}
          />
        );
      })}
    </CommunityStackNavigator.Navigator>
  );
};
 
export default CommunityStack;
 
// Tab需要配置项
const HomeStackConfig = {
  name: 'NewEggStackConfig',
  Screen: [
    {
      name: 'NewEgg',
      option: {headerShown: false},
      components: Home,
    },
  ],
};
 
const HomeStackNavigator = createNativeStackNavigator();
 
const HomeStack = () => {
  return (
    <HomeStackNavigator.Navigator {...HomeStackConfig}>
      {HomeStackConfig.Screen.map((item, index) => {
        return (
          <HomeStackNavigator.Screen
            key={index}
            options={item.option}
            name={item.name}
            component={RouterComponent(item)}
          />
        );
      })}
    </HomeStackNavigator.Navigator>
  );
};
 
 
// Tab需要配置项 (这个是为了打算做的News模块,目前暂时没有)
const NeweggStackStackNavigatorConfig = {
  name: 'HeadlineStackConfig',
  Screen: [
    {
      name: 'Headline',
      option: {headerShown: false},
      components: NX,
    },
  ],
};
 
const NeweggStackStackNavigator = createNativeStackNavigator();
export const NeweggStackStack = () => {
  return (
    <NeweggStackStackNavigator.Navigator {...NeweggStackStackNavigatorConfig}>
      {NeweggStackStackNavigatorConfig.Screen.map((item, index) => {
        return (
          <NeweggStackStackNavigator.Screen
            key={index}
            options={item.option}
            name={item.name}
            component={RouterComponent(item)}
          />
        );
      })}
    </NeweggStackStackNavigator.Navigator>
  );
};
 
 
import {RouterComponent} from '@/common/components/container';
const DrawerStackConfig = {
  name: 'DrawerStack',  // 思考一下 这个地方是否可以都去掉,直接用外面平铺的东西?
  Screen: [
    {
      name: 'AccountDrawer',
      option: {
        headerShown: false,
        drawerStyle: {width: '86%'},
      } as DrawerNavigationOptions,
      components: TabNavigator,
    },
  ],
};
 
// 抽屉
const StackDrawer = createDrawerNavigator();
const DrawerStackNavigator = (props: any) => {
  return (
    <StackDrawer.Navigator drawerContent={(props) => <SettingDrawer {...props} />} initialRouteName="DrawerStack">
      {DrawerStackConfig.Screen.map((item, index) => {
        return (
          <StackDrawer.Screen
            key={index}
            options={{...item.option, drawerType: 'front'}}
            name={item.name}
            component={RouterComponent(item)}
          />
        );
      })}
    </StackDrawer.Navigator>
  );
};
 
export default DrawerStackNavigator;
 

关于router 如何跳转

import {makeAutoObservable, observable} from 'mobx';
import {NavigationProp} from '@react-navigation/native';
 
class RouterStore {
  constructor() {
    makeAutoObservable(this, {
      navigation: observable,
    });
  }
 
  navigation: NavigationProp<ReactNavigation.RootParamList> | undefined = undefined;
 
  setNavigation = (newNavigation: NavigationProp<ReactNavigation.RootParamList>) => {
    this.navigation = newNavigation;
  };
}
const routerStore = new RouterStore();
 
export default routerStore;
 
// 
const navigateTo = (routeName: string, params?: any) => {
  RouterEvent({
    navigation: routerStore.navigation!,
    action: StackActions.push(routeName, params),
  });
};
 
 
import {NavigationAction, NavigationProp, NavigationState} from '@react-navigation/native';
interface RouterAction {
  navigation: NavigationProp<ReactNavigation.RootParamList>;
  Route?: 'AppStack' | 'DrawerStack' | 'TabStack';
  action: NavigationAction | ((state: NavigationState) => NavigationAction);
}
 
// 父类
const RouterEvent = (prams: RouterAction) => {
  const {navigation, Route = '', action} = prams;
  if (!Route) {
    navigation.dispatch(action);
    return;
  }
  findRoute(navigation, Route).dispatch(action);
};
 
//  递归回溯 找到你需要的Route层级
const findRoute = (
  navigation: RouterAction['navigation'],
  RouteName: 'AppStack' | 'DrawerStack' | 'TabStack',
): RouterAction['navigation'] => {
  const RouteList: Array<RouterAction['navigation']> = [];
  const find = (navigation: RouterAction['navigation']) => {
    if (!navigation?.getParent()?.getState()) {
      RouteList.push(navigation);
      return;
    }
    RouteList.push(navigation);
    find(navigation.getParent());
  };
  find(navigation);
 
  enum MapNames {
    'AppStack' = 'WebViewSDK',
    'DrawerStack' = 'drawer1',
    'TabStack' = '新蛋',
  }
  const fish = RouteList.find((item) => item.getState()?.routeNames.find((it) => it === MapNames[RouteName]));
  return fish || navigation;
};
 
// AppEntryEvent
const entryEvent = () => {};
 
export {RouterEvent, entryEvent};
 

好以上,就是Seller App 的样子,其中的代码都是做来隐私保护,没有全部的源码,只是部分核心

实现

设计

重要说明

  1. index 只放主要的main 模块
  2. 其他的模块都自己去引用 不要,放到index 去引,也不需要模块之前相互引
  3. 公共模块和组件 ,请丢common 包中

navigation 分类

  1. 以stuck 为主,在Main 中分了 navigation bar,和draw, (对于侧边栏 我们需要调整原来的IOS侧 拆包方案,让它能够支持 reanimated)-相关的Issues Reanimated接入问题 · Issue #10 · ljunb/rn-relates

https://github.com/software-mansion/react-native-reanimated/issues/846#issuecomment-943267584,但是目前我们的尝试还是具有局限性的就是,左滑动返回 需要禁用 原生view的返回,要不然当RN-Navigation 去接管的时候回引发 左侧只执行 原生view 的back 而不会执行 RN-Navigation 的back ,如果我们禁用原生的View back ,那么在模块主stuck 的时候无法左滑 back 其余都能正常使用。目前还没有非常好的方式去控制它

  1. 其他模块请全部使用 stuck ,如果有draw需求,请使用draw组件

统一的navigation 处理所有跳转等业务逻辑

为了统一所有的page 跳转行为,我建议封装一个自己用的,但是这个封装要注意!得支持在非 组件中使用,比如全局拦截器中也想控制跳转,但....他是个ts存逻辑,当然可以使用rxjs 的方式发送给根组件,执行跳转也ok,具体看设计,有两种,下面的例子就是使用了 mobx 处理的

 
  if (inMaintain.includes(ResponseCode)) {
    RouterEvent({
      navigation: routerStore.navigation!,
      Route: 'AppStack',
      action: StackActions.replace('Maintain', {
        startTime: timeArr[0] || '2021-10-29T02:04:31.105Z',
        entTime: timeArr[1] || '2021-10-30T07:19:00.000Z',
      }),
    });
    return true;
  }

前置模块

  1. 前置模块安装
# 使用stuck 需要这些模块  https://reactnavigation.org/docs/getting-started 
yarn add react-native-screens react-native-safe-area-context 
# 使用draw 需要这些模块 https://reactnavigation.org/docs/drawer-navigator/
yarn add react-native-gesture-handler react-native-reanimated
# 使用button 需要这些模块 https://reactnavigation.org/docs/material-bottom-tab-navigator (大部分情况下 你需要icon 最好用这个)
yarn add @react-navigation/material-bottom-tabs react-native-paper react-native-vector-icons
  1. 配置 对于  react-native-reanimated 我们要配置 要不然会引发下面的问题

https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/installation/

[Sat Nov 12 2022 16:17:10.780]  ERROR    Error: Failed to initialize react-native-reanimated library, make sure you followed installation steps here: https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/installation/ 
1) Make sure reanimated's babel plugin is installed in your babel.config.js (you should have 'react-native-reanimated/plugin' listed there - also see the above link for details) 
2) Make sure you reset build cache after updating the config, run: yarn start --reset-cache
[Sat Nov 12 2022 16:17:10.782]  ERROR    Invariant Violation: Module AppRegistry is not a registered callable module (calling runApplication)
[Sat Nov 12 2022 16:17:10.783]  ERROR    Invariant Violation: Module AppRegistry is not a registered callable module (calling runApplication)
[Sat Nov 12 2022 16:18:12.743]  ERROR    Invariant Violation: Module AppRegistry is not a registered callable module (calling unmountApplicationComponentAtRootTag)
[Sat Nov 12 2022 16:18:13.786]  ERROR    Invariant Violation: Module AppRegistry is not a registered callable module (calling runApplication)
  1. 正确的处理 需要在bable中加上配置
 
module.exports = {
  presets: ['module:metro-react-native-babel-preset'],
  plugins: [
    [
      'module-resolver',
      {
        extensions: ['.tsx', '.ts', '.js', '.json'],
        alias: {
          [pak.name]: path.join(__dirname, '..', pak.source),
        },
      },
    ],
    ['react-native-reanimated/plugin'],
  ],
};

_如果可能建议使用  yarn start --reset-cache --port=8082_运行

具体细节

/*------------------------------router.ts------*/
/* eslint-disable no-undef */
import DrawerStackNavigator from '../Drawer';
import M1 from '../modules/page/Modules1';
import M2 from '../modules/page/Modules2';
 
export interface InterRouterItem {
  default: string;
  initialRouteName: string;
  Screen: Array<InterScreen>;
}
 
interface InterScreen {
  name: string;
  component: () => React.ReactNode;
  options?: any;
}
 
//  Stuck
const AppStack: InterRouterItem = {
  default: 'AppStack',
  initialRouteName: 'Drawer',
  Screen: [
    {
      name: 'M1',
      component: M1,
      options: {
        headerShown: true, // 默认把头部处理掉,因为它的头部 还是希望交给它自己处理/公用组件
      },
    },
    {
      name: 'M2',
      component: M2,
      options: {
        headerShown: true,
      },
    },
    // 注意 dre 下套 tab 才能达到我们想要的效果
    {
      name: 'Drawer',
      options: {
        headerShown: false,
      },
      component: DrawerStackNavigator,
    },
  ],
};
 
export default AppStack;
 
/*------------------------------App.js------*/
/* eslint-disable @typescript-eslint/no-unused-vars */
import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import 'react-native-gesture-handler';
import 'react-native-reanimated';
 
import AppStack from './routes';
 
const RouterComponent = (props: any) => {
  return props.component;
};
 
// 收口
const AppModules = createNativeStackNavigator();
const Router = () => {
  return (
    <AppModules.Navigator {...AppStack}>
      {AppStack.Screen.map((item) => {
        return (
          <AppModules.Screen
            key={item.name}
            name={item.name}
            options={item.options}
            component={RouterComponent(item)}
          />
        );
      })}
    </AppModules.Navigator>
  );
};
 
export default function App() {
  return (
    <SafeAreaProvider>
      <NavigationContainer>
        <Router />
      </NavigationContainer>
    </SafeAreaProvider>
  );
}
 
 
 
/*------------------------------Drawer,js------*/
/* eslint-disable @typescript-eslint/no-unused-vars */
import {
  createDrawerNavigator,
  DrawerNavigationOptions,
} from '@react-navigation/drawer';
import TabNavigation from './TabNavigation';
import React from 'react';
import { Button, Text, View } from 'react-native';
 
const DrawerStackConfig = {
  name: 'DrawerStack', // 思考一下 这个地方是否可以都去掉,直接用外面平铺的东西?
  Screen: [
    {
      name: 'DrawerStack',
      options: {
        headerShown: false,
        drawerStyle: { width: '86%' },
      } as DrawerNavigationOptions,
      component: TabNavigation,
    },
  ],
};
 
const SettingDrawer = (props: any) => {
  return (
    <View>
      <Text>2</Text>
      <Button title="x" />
    </View>
  );
};
 
const RouterComponent = (props: any) => {
  // 保存当前状态到RouterStore
  // const navigation = useNavigation();
  // useEffect(() => {
  //   routerStore.setNavigation(navigation);
  // }, []);
  return props.component;
};
 
// 抽屉
const StackDrawer = createDrawerNavigator();
const DrawerStackNavigator = () => {
  return (
    <StackDrawer.Navigator
      id="LeftDrawer"
      drawerContent={(props) => <SettingDrawer {...props} />}
      initialRouteName="DrawerStack"
    >
      {DrawerStackConfig.Screen.map((item, index) => {
        return (
          <StackDrawer.Screen
            key={index}
            options={{ ...item.options, drawerType: 'front' }}
            name={item.name}
            component={RouterComponent(item)}
          />
        );
      })}
    </StackDrawer.Navigator>
  );
};
 
export default DrawerStackNavigator;
 
 
/*------------------------------index,js------*/
/* eslint-disable prettier/prettier */
import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Button, Text, View } from 'react-native';
import { CommonActions, useNavigation } from '@react-navigation/native';
import { SafeAreaView } from 'react-native-safe-area-context';
 
const B1 = () => {
  const navigate = useNavigation();
  return (
    <SafeAreaView>
      <View>
        <Text>B1</Text>
        <View>
          <Button
            title="OPEN_D"
            onPress={() => {
              const drawerNavigation = navigate.getParent(
                'LeftDrawer' as any
              ) as any;
              drawerNavigation.openDrawer();
            }}
          />
        </View>
        <View>
          <Button
            title="GO_M1"
            onPress={() => {
              navigate.dispatch(
                CommonActions.navigate({
                  name: 'M1',
                  params: {},
                })
              );
            }}
          />
        </View>
      </View>
    </SafeAreaView>
  );
};
 
const B2 = () => {
  return (
    <View>
      <Text>B2</Text>
    </View>
  );
};
 
const B3 = () => {
  return (
    <View>
      <Text>B3</Text>
    </View>
  );
};
 
const B4 = () => {
  return (
    <View>
      <Text>B4</Text>
    </View>
  );
};
 
const B5 = () => {
  return (
    <View>
      <Text>B5</Text>
    </View>
  );
};
 
const RouterComponent = (props: any) => {
  // 保存当前状态到RouterStore
  // const navigation = useNavigation();
  // useEffect(() => {
  //   routerStore.setNavigation(navigation);
  // }, []);
  return props.component;
};
 
const TabNai = createBottomTabNavigator();
const TabNavigator: React.FC<{}> = () => {
  // Tab需要配置项
  const TabStackConfig = {
    name: 'TabStackConfig',
    initialRouteName: 'B3',
    Screen: [
      {
        name: 'B1',
        option: {
          headerShown: false,
          // tabBarLabel: <></>,
        },
        component: B1,
      },
      {
        name: 'B2',
        option: {
          headerShown: false,
          // tabBarLabel: <></>,
        },
        component: B2,
      },
      {
        name: 'B3',
        option: {
          headerShown: false,
          // tabBarLabel: <></>,
        },
        component: B3,
      },
      {
        name: 'B4',
        option: {
          headerShown: false,
          // tabBarLabel: <></>,
        },
        component: B4,
      },
      {
        name: 'B5',
        option: {
          headerShown: false,
          // tabBarLabel: <Text>2</Text>,
        },
        component: B5,
      },
    ],
  };
 
  return (
    <TabNai.Navigator
    // screenOptions={() => ({
    //   tabBarActiveTintColor: '#F06C00',
    //   tabBarInactiveTintColor: '#858585',
    // })}
    >
      {TabStackConfig.Screen.map((item, index) => {
        return (
          <TabNai.Screen
            key={index}
            options={item.option}
            name={item.name}
            component={RouterComponent(item)}
          />
        );
      })}
    </TabNai.Navigator>
  );
};
 
export default TabNavigator;
 
 

🤔我们能不能去掉多余的全局stuck 全用一个Drawer 其他的全部丢它里面就好啦呢?

....

Android 实现细节

Layout的设计

/* eslint-disable sonarjs/cognitive-complexity */
/*
 * @Author: aoda
 * @Date: 2021-10-20 19:27:50
 * @Description: app头部组件
 */
import React, {memo, ReactNode, useEffect} from 'react';
import {StatusBar, View, Text, Image, ScrollView, TouchableWithoutFeedback, Platform, ViewStyle} from 'react-native';
import {useState} from 'react';
import styles from './style';
import {useNavigation} from '@react-navigation/core';
import RNExitApp from 'react-native-exit-app';
import {SafeAreaView} from 'react-native-safe-area-context';
 
type codeType = string | number | ReactNode;
interface props {
  leftCode?: codeType; // 左边code
  leftEvent?: () => void; // 左边事件
  titleCode: codeType; // 中间code
  rightCode?: codeType; // 右边标题
  rightEvent?: () => void; // 右边事件
  children: ReactNode; //  --------V2-----新增的属性
  scroll?: boolean; // 是否支持滚动
  immersive?: {
    // 是否需要沉浸式的用户体验
    backgroundColor: string; // 背景颜色
  };
  // Header 底部 自定义渲染  一般 需要和 needHeadLind 一起使用
  needBodyStyle?: boolean; // 是否需要body的整个padding距离
  needHeadLind?: boolean; // 是否允许有底部边框
  renderHeaderFooter?: ReactNode;
  wrapBgStyle?: ViewStyle;
  content?: React.ReactNode; //  完完全全的自定义
  notSafe?: false; // 去掉安全区域?
}
 
const PageLayout: React.FC<props> = (props) => {
  const {scroll = true, needHeadLind = true, needBodyStyle = true, renderHeaderFooter} = props;
  const route = useNavigation();
  const [isTopNav, setIsTopNav] = useState(route.canGoBack());
  const goBack = () => {
    if (route.canGoBack()) {
      setIsTopNav(false);
      route.goBack();
    } else {
      setIsTopNav(true);
      RNExitApp.exitApp();
    }
  };
  const {leftCode, leftEvent = goBack, titleCode, rightCode = '', rightEvent, immersive} = props;
 
  // 如果设置了“沉浸式”的效果 则自动运行此代码片段
  useEffect(() => {
    if (immersive && Platform.OS === 'android') {
      StatusBar.setBackgroundColor(immersive?.backgroundColor);
      StatusBar.setTranslucent(true);
    }
    return () => {
      Platform.OS === 'android' && immersive && StatusBar.setTranslucent(false);
    };
  }, []);
 
  return (
    <SafeAreaView
      mode="padding"
      edges={['right', 'top', 'left']}
      style={[styles.safeBgColor, immersive && {backgroundColor: immersive.backgroundColor}]}
    >
      <View style={[styles.layoutWrap]}>
        {props?.content ? (
          props?.content
        ) : (
          <View style={[]}>
            <View
              style={[
                styles.head,
                styles.flexRowSBC,
                !needHeadLind ? styles.headNotBottom : undefined,
                {backgroundColor: immersive?.backgroundColor},
              ]}
            >
              {/* 左快 */}
              {!isTopNav || leftCode ? (
                <View style={[styles.berWrap]}>
                  {typeof leftCode === 'string' ? (
                    <TouchableWithoutFeedback onPress={leftEvent}>
                      <Text style={[styles.rightCode, styles.leftCode]}>{leftCode}</Text>
                    </TouchableWithoutFeedback>
                  ) : (
                    <>{leftCode}</>
                  )}
                </View>
              ) : (
                <TouchableWithoutFeedback onPress={leftEvent}>
                  <View style={[styles.leftIconWrap, styles.flexRowSBC, styles.berWrap]}>
                    <Image
                      source={
                        !immersive
                          ? require('@/common/assets/images/left_direct.png')
                          : require('@/common/assets/images/left_directW.png')
                      }
                      style={styles.leftIcon}
                    ></Image>
                  </View>
                </TouchableWithoutFeedback>
              )}
              {/* 标题 */}
              <View style={{flex: 1, flexDirection: 'row', justifyContent: 'center'}}>
                <Text style={[styles.title, immersive && {color: '#fff'}]}>{titleCode}</Text>
              </View>
              {/* 右快 */}
              <View
                style={[
                  styles.berWrap,
                  {
                    alignItems: 'flex-end',
                  },
                ]}
              >
                {typeof rightCode === 'string' ? (
                  <Text style={styles.rightCode} onPress={rightEvent}>
                    {rightCode}
                  </Text>
                ) : (
                  <>{rightCode}</>
                )}
              </View>
            </View>
            {renderHeaderFooter ? renderHeaderFooter : null}
          </View>
        )}
        {scroll ? (
          <ScrollView
            style={[styles.childrenWrap, !needBodyStyle ? styles.childrenNotWrap : undefined, props.wrapBgStyle]}
          >
            {props.children}
          </ScrollView>
        ) : (
          <>{props?.children}</>
        )}
      </View>
    </SafeAreaView>
  );
};
export default memo(PageLayout);
 
 
import {createNeweggStyles} from '@/common/utils/style';
import {globalStyle} from '@/common/style';
import {FonStyle} from '@/common/utils/style';
const styles = createNeweggStyles({
  safeBgColor: {
    backgroundColor: '#fff',
    flex: 1,
  },
  layoutWrap: {
    flex: 1,
    backgroundColor: '#fff',
  },
  head: {
    height: 48,
    paddingHorizontal: 16,
    borderBottomWidth: 1,
    borderBottomColor: globalStyle.colors.gray3,
  },
  flexRowSBC: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
  },
  headNotBottom: {
    borderBottomWidth: 0,
  },
  leftIconWrap: {
    justifyContent: 'center',
    alignItems: 'center',
  },
  berWrap: {
    minWidth: 75,
    maxWidth: 75,
    overflow: 'hidden',
    paddingVertical: 10,
  },
  leftIcon: {
    width: 10,
    height: 15,
  },
  leftCode: {
    color: globalStyle.colors.gray7,
  },
  title: {
    ...FonStyle({
      fontSize: 19,
    }),
    fontWeight: '600',
    color: '#000',
    fontFamily: 'PingFang SC',
  },
  rightCode: {
    ...FonStyle({
      fontSize: 15,
    }),
    fontWeight: '600',
    color: globalStyle.colors.orange6,
    fontFamily: 'PingFang SC',
  },
  childrenWrap: {
    paddingHorizontal: 16,
  },
  childrenNotWrap: {
    paddingHorizontal: 0,
  },
});
export default styles;
 

关于设计稿Style

750 * 812 = IphoneX

//
import {StyleSheet, Dimensions, Platform} from 'react-native';
import {ImageStyle, TextStyle, ViewStyle} from 'react-native';
import {isTablet} from 'react-native-device-info';
 
export interface CustomNamedRenderStyles {
  [key: string]: ViewStyle & TextStyle & ImageStyle;
}
// 可用于直接转化的属性,
const translateProps = [
  'width',
  'height',
  'lineHeight',
  'minWidth',
  'minHeight',
  'maxWidth',
  'maxHeight',
  'marginTop',
  'marginBottom',
  'marginLeft',
  'marginRight',
  'marginVertical',
  'marginHorizontal',
  'paddingTop',
  'paddingBottom',
  'paddingLeft',
  'paddingRight',
  'paddingVertical',
  'paddingHorizontal',
  'top',
  'bottom',
  'left',
  'right',
  'lineHeight',
  'borderRadius',
  'borderTopWidth',
];
// 可用于字体的转化属性
const translateFontProps = ['fontSize', 'lineHeight'];
 
const translateOnWidth = (value: number) => {
  if (isNaN(value)) {
    return value;
  }
  if (isTablet()) {
    if (value === STANDARD_WIDTH) {
      return DEVICE_WIDTH;
    }
    return value;
  }
  return Math.round((value / STANDARD_WIDTH) * DEVICE_WIDTH);
};
 
// 设计搞尺寸以及,横竖屏切换
const STANDARD_WIDTH = 375;
const STANDARD_HEIGHT = 812;
const FONT_SIZE_SCALER: number = 1;
const DEVICE_WIDTH =
  Dimensions.get('window').width > Dimensions.get('window').height
    ? Dimensions.get('window').height
    : Dimensions.get('window').width;
const DEVICE_HEIGHT =
  Dimensions.get('window').width > Dimensions.get('window').height
    ? Dimensions.get('window').width
    : Dimensions.get('window').height;
 
const GLOBALWIDTH = Dimensions.get('window').width;
const GLOBALHEIGHT = Dimensions.get('window').height;
 
// 横竖屏切换 + 屏幕适配换算
const getTranslateWidthWithDevice = (originWidth: number): number => {
  if (isNaN(originWidth)) {
    return originWidth;
  }
  if (isTablet()) {
    if (originWidth === STANDARD_WIDTH) {
      return DEVICE_WIDTH;
    }
 
    return originWidth;
  }
  return Math.round((originWidth / STANDARD_WIDTH) * DEVICE_WIDTH);
};
 
const getTranslateHeightWithDevice = (originHeight: number): number => {
  if (isNaN(originHeight)) {
    return originHeight;
  }
 
  if (isTablet()) {
    return originHeight;
  }
  return Math.ceil((originHeight / STANDARD_HEIGHT) * DEVICE_HEIGHT);
};
 
// 设置 字体
const FonStyle = (value: Pick<TextStyle, 'fontSize' | 'lineHeight'>) => {
  const {fontSize, lineHeight} = value;
  return {
    ...(fontSize && {fontSize: getTranslateFontSize(fontSize)}),
    ...(lineHeight && {lineHeight: getTranslateLineHeight(lineHeight)}),
  };
};
 
// 字体缩放
const getTranslateFontSize = (originFontsize: number) => {
  return Platform.OS === 'android'
    ? getTranslateWidthWithDevice(originFontsize) * FONT_SIZE_SCALER
    : getTranslateWidthWithDevice(originFontsize);
};
 
// 字体Line缩放
const getTranslateLineHeight = (originLineHeight: number) => {
  return Platform.OS === 'android'
    ? parseInt((getTranslateWidthWithDevice(originLineHeight) * FONT_SIZE_SCALER).toString())
    : getTranslateWidthWithDevice(originLineHeight);
};
 
// 主函数
const createNeweggStyles = (styles: CustomNamedRenderStyles) => {
  const tmpStyles: CustomNamedRenderStyles = {...styles};
  for (const key in styles) {
    if (Array.isArray(styles[key])) {
      const flattenValue = Object.assign({}, styles[key]);
      styles[key] = flattenValue;
    }
    tmpStyles[key] = getTranslatePropertyWithWidth(styles[key]);
  }
  return StyleSheet.create(tmpStyles);
};
 
// 转换副函数
const getTranslatePropertyWithWidth = (style: {[x: string]: any; hasOwnProperty: (arg0: string) => any}) => {
  let tmpStyles: {[x: string]: any} = {...style};
 
  translateProps.forEach((property) => {
    if (style.hasOwnProperty(property)) {
      tmpStyles[property] = getTranslateWidthWithDevice(style[property]);
    }
  });
 
  translateFontProps.forEach((property) => {
    if (style.hasOwnProperty(property)) {
      tmpStyles = {
        ...tmpStyles,
        ...FonStyle(style[property]),
      };
    }
  });
  return tmpStyles;
};
 
export {
  getTranslateWidthWithDevice,
  getTranslateHeightWithDevice,
  FonStyle,
  getTranslateFontSize,
  getTranslateLineHeight,
  createNeweggStyles,
  GLOBALWIDTH,
  GLOBALHEIGHT,
  translateOnWidth,
};
 
const styles = createNeweggStyles({
  afterSaleItem: {
    backgroundColor: '#fff',
    borderRadius: 4,
    marginBottom: 12,
    padding: 8,
  },
  itemCard: {
    flexDirection: 'row',
    padding: 4,
  },
  itemCardWrap: {
    borderBottomColor: globalStyle.colors.gray3,
    marginBottom: 4,
  },
  left: {
    marginRight: 8,
  },
  itemImg: {
    width: 64,
    height: 48,
  },
  right: {
    flex: 1,
  },
  afterSaleItemTitle: {
    ...FonStyle({fontSize: 15}),
    marginBottom: 4,
  },
  sellerPort: {
    ...FonStyle({
      fontSize: 12,
    }),
    color: globalStyle.colors.gray8,
    marginBottom: 4,
  },
  timeWrap: {
    flexDirection: 'row',
    alignItems: 'center',
  },