上下文更新时重新安装组件

Component remounting on context update

我一直在尝试使用 React 上下文更新我的应用程序上多个屏幕使用的全局状态,这似乎是我找到的建议

然而,每次在屏幕上更新上下文时,它最终都会卸载并再次安装。我该如何防止这种情况?

Link to sandbox。如果单击该按钮,您将看到来自我的 useEffect 的新控制台日志。

代码如下:

import React, {
  useContext,
  useState,
  useEffect,
  createContext,
  Dispatch,
  SetStateAction
} from "react";
import { Button } from "react-native";
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
import { NavigationContainer } from "@react-navigation/native";
import { createStackNavigator } from "@react-navigation/stack";

export type FlashcardWithScore = {
  frontSide: string;
  backSide: string;
  score: number;
};

interface WordListContextInterface {
  wholeWordList: FlashcardWithScore[];
  setWholeWordList: Dispatch<SetStateAction<FlashcardWithScore[]>>;
}

const wordListContext = createContext<WordListContextInterface | null>(null);

const Flashcards = () => {
  const appContext = useContext(wordListContext);
  if (!appContext) return null;
  const { wholeWordList, setWholeWordList } = appContext;

  useEffect(() => {
    console.log("component has been mounted");
  }, []);

  const handlePress = () => {
    setWholeWordList([
      ...wholeWordList,
      { frontSide: "foo", backSide: "bar", score: 0 }
    ]);
  };

  return (
    <>
      <Button title="press me to see issue!" onPress={handlePress} />
    </>
  );
};

export default function App() {
  const [wholeWordList, setWholeWordList] = useState<FlashcardWithScore[]>([
    { frontSide: "more", backSide: "edits", score: 0 }
  ]);

  const RootStack = createStackNavigator();
  const BottomTab = createBottomTabNavigator();

  const BottomTabNavigator = () => {
    return (
      <BottomTab.Navigator initialRouteName="Learn">
        <BottomTab.Screen
          name="Learn"
          component={Flashcards}
          options={{
            title: "Flashcard Learn"
          }}
        />
      </BottomTab.Navigator>
    );
  };
  return (
    <wordListContext.Provider value={{ wholeWordList, setWholeWordList }}>
      <NavigationContainer>
        <RootStack.Navigator>
          <RootStack.Screen name="Root" component={BottomTabNavigator} />
        </RootStack.Navigator>
      </NavigationContainer>
    </wordListContext.Provider>
  );
}

这是因为 BottomTabNavigator 的定义在 App 组件的 re-renders 之间没有持久化。遵循从 App 呈现到按钮单击到 re-render:

的逻辑
  1. 应用呈现,同时创建一个新的 BottomTabNavigator 组件
  2. 此 BottomTabNavigator 在根堆栈屏幕中呈现
  3. 传递给 Provider 的上下文存储在具有 useState
  4. App 组件中的本地状态中
  5. Flashcards 中,当您单击按钮时,它会调用 handlePress,后者会调用上下文的 setWholeWordList 函数,更新上下文
  6. 在这种情况下,上下文是 App 组件中的本地状态,因此它会更新该状态值并触发 App
  7. 中的 re-render
  8. 在 re-rendering 期间,它会在内存 中创建一个全新的 BottomTabNavigator 组件 并渲染该组件
  9. 由于 技术上 BottomTabNavigator 是一个不同的组件,React 认为它是一个完全独立的组件,所以“旧”组件被卸载,这个“新”组件被卸载安装

解决此问题的方法是确保 BottomTabNavigator 不会在渲染之间进行不必要的更改。一种方法是,就像 Flashcards 一样,将它从 App 中移出到它自己单独的组件定义中。另一种方法是用 useCallback 来记忆它,即

  const BottomTabNavigator = useCallback(() => {
    return (
      <BottomTab.Navigator initialRouteName="Learn">
        <BottomTab.Screen
          name="Learn"
          component={Flashcards}
          options={{
            title: "Flashcard Learn"
          }}
        />
      </BottomTab.Navigator>
    );
  }, []);

这意味着当 App re-renders

时它不会在内存中重新创建