React Navigation 5 - 在导航到另一个选项卡之前从另一个堆栈重置堆栈(类似于 popToTop())

Vic*_*ina 5 javascript reactjs react-native react-navigation

假设 Tab Navigator 中有两个堆栈屏幕:

  1. 选项卡 A -> 相机
  2. 选项卡 B -> 个人资料

在配置文件屏幕中,在其堆栈中推送了其他相同类型(“配置文件”)(具有不同参数)的屏幕。现在,如果您在“相机”屏幕中并执行以下操作:

    navigation.navigate("Profile", { screen: "Profile", params });
Run Code Online (Sandbox Code Playgroud)

您将导航到“配置文件”屏幕,这些参数将被发送到堆栈中的最后一个屏幕。如果我想导航到传递参数的堆栈的根,我该怎么办?

我试过:

   // In the profile screen
   useEffect(() => {
       if (navigation.canGoBack())
            navigation.popToTop(); // Go back to the root of the stack

       showParams(params);
   }, [params])
Run Code Online (Sandbox Code Playgroud)

但是有了这个,“showParams”操作不在根目录中执行,我也没有从“相机”屏幕直接导航到堆栈的根目录。

我想我必须在导航之前在“相机”屏幕中执行以下操作:

  navigation.dispatch(
        CommonActions.reset({
          // some stuff
        })
  );

  navigation.navigate("Profile", { screen: "Profile", params });
Run Code Online (Sandbox Code Playgroud)

但是我找不到任何方法来实现我的目标。

有任何想法吗?谢谢你。

更新 - 我的导航系统

堆栈(这里我定义了多个堆栈:“HomeStacks”、“SearchStacks”、“ProfileStacks”...)

const Stack = createStackNavigator();

export function ProfileStacks() { <------ Over this stack I do .push()
  return (
    <Stack.Navigator
      initialRouteName="Profile"
    >
      <Stack.Screen name="Profile" children={Profile} />
      <Stack.Screen name="EditProfile" children={EditProfile} />
    </Stack.Navigator>
  );
}

...
Run Code Online (Sandbox Code Playgroud)

底部标签导航器

<Tab.Navigator>
  <Tab.Screen
    name="Camera"
    component={CameraPlaceholder}
    listeners={({ navigation }) => ({
      tabPress: (event) => {
        event.preventDefault();
        navigation.navigate("CameraModal");
      },
    })}
  />

  <Tab.Screen
    name="Profile"
    component={ProfileStacks}
  />
</Tab.Navigator>
Run Code Online (Sandbox Code Playgroud)

ROOT STACK NAVIGATOR(应用程序的主导航器)

在这个堆栈中,我实现了身份验证流程,并且还声明了一些额外的堆栈(仅用于外观目的)。

export default function RootNavigator(props) {
  /* 
    This navigator is implemented using the
    'Protected Routes' pattern
  */
  const { isUserLoggedIn } = props;

  const RootStack = createStackNavigator();

  return (
    <RootStack.Navigator>
      {isUserLoggedIn ? (
        <>
          <RootStack.Screen
            name="BottomTabNavigator"
            component={BottomTabNavigator}
          />

          <RootStack.Screen
            name="CameraModal"
            component={Camera}
          />
        </>
      ) : (
        <>
          <RootStack.Screen name="SignIn" component={SignIn} />

          <RootStack.Screen
            name="SignUp"
            component={SignUp}
          />

          <RootStack.Screen
            name="ForgotPassword"
            component={ForgotPassword}
          />
        </>
      )}
    </RootStack.Navigator>
  );
Run Code Online (Sandbox Code Playgroud)

我见过的相关问题

如何使用 React Navigation 5.x 在不同的选项卡中重置堆栈

https://github.com/react-navigation/react-navigation/issues/6639

https://github.com/react-navigation/react-navigation/issues/8988

这是我的个人资料选项卡的导航数据

  Object {
        "key": "Profile-Ty4St1skrxoven-jkZUsx",
        "name": "Profile",
        "params": undefined,
        "state": Object {
          "index": 1,
          "key": "stack-8nWDnwDJZRK8iDuJok7Hj",
          "routeNames": Array [
            "Profile",
            "EditProfile",
          ],
          "routes": Array [
            Object {
              "key": "Profile-m0GQkvNk5RjAhGABvOy9n",
              "name": "Profile",
              "params": undefined,
            },
            Object {
              "key": "Profile-tZAEmSU0eEo1Nt7XC09t1",
              "name": "Profile",
              "params": Object {
                "otherUserData": Object {
                  "username": "jeffbezos",
                },
                "post": null,
              },
            },
          ],
          "stale": false,
          "type": "stack",
        },
      },
    ],
    "stale": false,
    "type": "tab",
  },
Run Code Online (Sandbox Code Playgroud)

我只需要从我的应用程序的另一个选项卡的“配置文件”选项卡中的堆栈“配置文件”中弹出第二条路由,然后导航到此屏幕。

Vic*_*ina 7

更新(重构代码)

import { useNavigation, CommonActions } from "@react-navigation/native";

export default function useResetProfileStackNavigator() {
  const navigation = useNavigation();

  return () => {
    const bottomTabNavigator = navigation
      .getState()
      ?.routes?.find(({ name }) => name === "BottomTabNavigator");

    const profileTab = bottomTabNavigator?.state?.routes?.find(
      ({ name }) => name === "ProfileStacks"
    );

    const { key: target, routes } = profileTab?.state ?? {};

    if (!target || routes?.length <= 1) return;

    routes.length = 1; // popToTop()

    navigation.dispatch({
      ...CommonActions.reset({ routes }),
      target,
    });
  };
}
Run Code Online (Sandbox Code Playgroud)

以下是如何使用它:

export default function useSendPostToProfile() {
  const navigation = useNavigation();

  const isSending = useRef(false);

  const resetProfileStackNavigator = useResetProfileStackNavigator();

  return (post) => {
    if (isSending.current) return;

    isSending.current = true;

    // Make sure there is only one route open in the profile stack
    resetProfileStackNavigator();

    navigation.navigate("BottomTabNavigator", {
      screen: "ProfileStacks",
      params: {
        screen: "Profile",
        params: {
          post,
        },
      },
    });
  };
}
Run Code Online (Sandbox Code Playgroud)

以前的解决方案

经过几个小时的研究这个问题,我找到了解决方案。它不是最好的,但它适用于我的用例,并且肯定也适用于其他人的用例。

我试图实现的目标是重置堆栈导航器中“配置文件”屏幕的路由,该屏幕又位于我当前堆栈屏幕所在的选项卡导航器的另一个选项卡中。这听起来有点令人困惑,但它基本上与上传照片时在 Instagram 上发生的情况类似。

如果在 Instagram 中您从主屏幕导航到其他用户个人资料,然后将照片上传到您的帐户,您将看到如何从“发布您的照片”屏幕转到“主页”选项卡中的堆栈导航器的根目录,饲料。

在我的用例中,我正在做类似的事情,我可以从我自己的个人资料导航到其他用户的个人资料,并且照片上传到此屏幕中,并带有进度条。

从一开始我就打算使用navigation.popToTop(),但我一直无法获得我想要的结果,因为正如我之前在问题中评论的那样,参数(包含帖子)丢失了。所以我别无选择,只能从我的“发布照片”屏幕模拟这种行为。

我遵循的步骤如下:

  1. 由于我的“发布照片”屏幕通过选项卡导航器与我的“个人资料”屏幕共享导航(这是显而易见的,因为如果不是这样我就无法执行 navigation.navigate()),所以我遵循了导航从这里到配置文件选项卡的堆栈导航器的路径,然后我尝试获取它的密钥和路由。
  1. 如果我找到了当前的键和路径,这意味着堆栈导航器已安装(在我的情况下,该选项卡对我的所有页面进行了延迟初始化,这就是我说“尝试采取”的原因)。因此,有必要应用步骤 3 和 4。
  1. 模拟navigation.popToTop()将路由的大小减少到1(注意堆栈导航器的根是“routes”数组的第一个位置中的项目)
  1. 使用导航 API 通过配置文件的堆栈导航器分派重置操作。
  1. 最后一步,导航到堆栈屏幕,通常将照片作为参数传递。

这是代码:

  const resetProfileStackNavigator = () => {
      const currentNavigationState = navigation.dangerouslyGetState();

      // Find the bottom navigator
      for (let i = 0; i < currentNavigationState?.routes?.length; i++) {
        if (currentNavigationState.routes[i].name === "BottomTabNavigator") {
          // Get its state
          const bottomNavigationState = currentNavigationState.routes[i].state;

          // Find the profile tab
          for (let j = 0; j < bottomNavigationState?.routes?.length; j++) {
            if (bottomNavigationState.routes[j].name === "Profile") {
              // Get its state
              const profileTabState = bottomNavigationState.routes[j].state;

              // Get the key of the profile tab's stack navigator
              var targetKey = profileTabState?.key;
              var targetCurrentRoutes = profileTabState?.routes;

              break;
            }
          }
          break;
        }
      }

      // Reset the profile tab's stack navigator if it exists and has more than one stacked screen
      if (targetKey && targetCurrentRoutes?.length > 1) {
        // Set a new size for its current routes array, which is faster than Array.splice to mutate
        targetCurrentRoutes.length = 1; // This simulates the navigation.popToTop()

        navigation.dispatch({
          ...CommonActions.reset({
            routes: targetCurrentRoutes, // It is necessary to copy the existing root route, with the same key, to avoid the component unmounting
          }),
          target: targetKey,
        });
      }
 }


  /*
    Maybe, the stack navigator of the profile tab exists and has changed from its initial state...
    In this situation, we will have to find the key of this stack navigator, which is also 
    nested in the same tab navigator in which this stack screen is.
  */
  resetProfileStackNavigator();

  // Finally, navigate to the profile stack screen and pass the post as param
  navigation.navigate("Profile", {
    screen: "Profile",
    params: {
      post,
    },
  });
Run Code Online (Sandbox Code Playgroud)

Pd:我知道有一些适用的重构,但我更喜欢以这种方式显示代码,以便我上面讨论的步骤清晰可见。

如果读过这篇文章的人设法使用 ES6 将这段代码概括为通用函数,请留下它作为答案,因为它对我和其他用户都非常有用。