上下文更新时重新安装组件
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:
的逻辑
- 应用呈现,同时创建一个新的
BottomTabNavigator
组件
- 此 BottomTabNavigator 在根堆栈屏幕中呈现
- 传递给
Provider
的上下文存储在具有 useState
的 App
组件中的本地状态中
- 在
Flashcards
中,当您单击按钮时,它会调用 handlePress
,后者会调用上下文的 setWholeWordList
函数,更新上下文
- 在这种情况下,上下文是
App
组件中的本地状态,因此它会更新该状态值并触发 App
中的 re-render
- 在 re-rendering 期间,它会在内存 中创建一个全新的
BottomTabNavigator
组件 并渲染该组件
- 由于 技术上
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
时它不会在内存中重新创建
我一直在尝试使用 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:
- 应用呈现,同时创建一个新的
BottomTabNavigator
组件 - 此 BottomTabNavigator 在根堆栈屏幕中呈现
- 传递给
Provider
的上下文存储在具有useState
的 - 在
Flashcards
中,当您单击按钮时,它会调用handlePress
,后者会调用上下文的setWholeWordList
函数,更新上下文 - 在这种情况下,上下文是
App
组件中的本地状态,因此它会更新该状态值并触发App
中的 re-render
- 在 re-rendering 期间,它会在内存 中创建一个全新的
BottomTabNavigator
组件 并渲染该组件 - 由于 技术上
BottomTabNavigator
是一个不同的组件,React 认为它是一个完全独立的组件,所以“旧”组件被卸载,这个“新”组件被卸载安装
App
组件中的本地状态中
解决此问题的方法是确保 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