State 不会在 react-native 中使用 AsyncStorage 更新其值
State does not update its value using AsyncStorage in react-native
我正在尝试将值保存到异步存储,然后根据异步存储的值结果导航到正确的页面。我可以将数据存储在 AsyncStorage 中,但我的状态不会更新,我必须重新加载应用程序才能更新状态。这是我的代码:
我这里有一个 Welcome/Obnoarding 屏幕。我希望此屏幕只显示给新的应用程序用户。因此,当用户按下继续按钮时,我想将一个值保存到异步存储中,以便他们下次登录时不必再次看到登录页面。这是我的入职页面:
const WelcomeScreen: FC<IWelcomeScreen> = ({ navigation }) => {
const { width, height } = Dimensions.get("window");
const btnText = "Contiunue";
const title = "Book";
const subTitle = "Fab";
let [fontsLoaded] = useFonts({
PinyonScript_400Regular,
});
const continueBtn = async () => {
try {
await AsyncStorage.setItem('@viewedOnboarding', 'true');
} catch (error) {
console.log('Error @setItem: ', error);
};
};
if (!fontsLoaded) {
return <Text>...Loading</Text>;
} else {
return (
<View style={containerStyle(height, width).container}>
<ImageBackground
resizeMode={"cover"}
style={styles.image}
source={require("../assets/model.jpg")}
>
<LinearGradient
colors={["#00000000", "#000000"]}
style={styles.gradient}
>
<View style={styles.container}>
<View style={styles.logoTextContainer}>
<Text style={styles.logoText}>{title}</Text>
<Text style={styles.logoText}>{subTitle}</Text>
</View>
<ContinueBtn label={btnText} callback={continueBtn} />
</View>
</LinearGradient>
</ImageBackground>
</View>
);
}
};
在我的 AppNavigator 中,我想决定用户应该看到哪个导航。但是当我按下继续页面时,我的应用程序不会导航到我的 TabsNavigator。它保留在我的入职页面上,但如果我刷新该应用程序,该应用程序将导航到我的选项卡导航器。这是我确定用户应该在哪里的代码,具体取决于他们是新用户还是“旧”用户:
const WelcomeScreen: FC<IWelcomeScreen> = ({ navigation }) => {
const { width, height } = Dimensions.get("window");
const btnText = "Contiunue";
const title = "Book";
const subTitle = "Fab";
let [fontsLoaded] = useFonts({
PinyonScript_400Regular,
});
const continueBtn = async () => {
try {
await AsyncStorage.setItem('@viewedOnboarding', 'true');
} catch (error) {
console.log('Error @setItem: ', error);
};
};
if (!fontsLoaded) {
return <Text>...Loading</Text>;
} else {
return (
<View style={containerStyle(height, width).container}>
<ImageBackground
resizeMode={"cover"}
style={styles.image}
source={require("../assets/model.jpg")}
>
<LinearGradient
colors={["#00000000", "#000000"]}
style={styles.gradient}
>
<View style={styles.container}>
<View style={styles.logoTextContainer}>
<Text style={styles.logoText}>{title}</Text>
<Text style={styles.logoText}>{subTitle}</Text>
</View>
<ContinueBtn label={btnText} callback={continueBtn} />
</View>
</LinearGradient>
</ImageBackground>
</View>
);
}
};
在异步存储中设置一个值不会触发 AppNavigator
的重新渲染。因此,如果用户按下 continue button
,那么视觉上不会发生任何事情,因为 AppNavigator
的状态没有改变。如果您刷新应用程序,您之前使用 setItem
函数设置的标志将在 AppNavigator
初始渲染时重新加载。这就是刷新应用程序后它起作用的原因。
对于这种问题,我建议你使用Context
来触发AppNavigator
中的状态变化。
这是一个关于其工作原理的最小示例。我在代码中添加了注释来指导您。
为了简单起见,我们将做出以下假设:
我们在 Stack 中有两个屏幕,一个是 WelcomeScreen
,另一个是 HomeScreen
。
请注意,我们根据应用程序上下文对屏幕使用条件渲染。您可以添加任何您想要的屏幕,甚至是整个导航器(如果您的导航器是嵌套的,这将是必要的,但模式保持不变)。
应用程序
export const AppContext = React.createContext()
const App = () => {
// it is important that the initial state is undefined, since
// we need to wait for the async storage to return its value
// before rendering anything
const [hasViewedOnboarding, setHasViewedOnboarding] = React.useState()
const appContextValue = useMemo(
() => ({
hasViewedOnboarding,
setHasViewedOnboarding,
}),
[hasViewedOnboarding]
)
// retrieve the onboarding flag from the async storage in a useEffect
React.useEffect(() => {
const init = async () => {
const value = await AsyncStorage.getItem('@viewedOnboarding')
setHasViewedOnboarding(value != null ? JSON.parse(value) : false)
}
init()
}, [])
// as long as the flag has not been loaded, return null
if (hasViewedOnboarding === undefined) {
return null
}
// wrap everything in AppContext.Provider an pass the context as a value
return (
<AppContext.Provider value={appContextValue}>
<NavigationContainer>
<Stack.Navigator>
{!hasViewedOnboarding ? (
<Stack.Screen name="Welcome" component={WelcomeScreen} />
) : (
<Stack.Screen
name="Home"
component={HomeScreen}
/>
)}}
</Stack.Navigator>
</NavigationContainer>
</AppContext.Provider>
)
}
现在,在您的 WelcomeScreen
中,您需要在存储异步值后访问上下文并设置状态。
const WelcomeScreen: FC<IWelcomeScreen> = ({ navigation }) => {
// access the context
const { setHasViewedOnboarding } = useContext(AppContext)
const { width, height } = Dimensions.get("window");
const btnText = "Contiunue";
const title = "Book";
const subTitle = "Fab";
let [fontsLoaded] = useFonts({
PinyonScript_400Regular,
});
const continueBtn = async () => {
try {
await AsyncStorage.setItem('@viewedOnboarding', 'true');
setHasViewedOnboarding(true)
} catch (error) {
console.log('Error @setItem: ', error);
};
};
if (!fontsLoaded) {
return <Text>...Loading</Text>;
} else {
return (
<View style={containerStyle(height, width).container}>
<ImageBackground
resizeMode={"cover"}
style={styles.image}
source={require("../assets/model.jpg")}
>
<LinearGradient
colors={["#00000000", "#000000"]}
style={styles.gradient}
>
<View style={styles.container}>
<View style={styles.logoTextContainer}>
<Text style={styles.logoText}>{title}</Text>
<Text style={styles.logoText}>{subTitle}</Text>
</View>
<ContinueBtn label={btnText} callback={continueBtn} />
</View>
</LinearGradient>
</ImageBackground>
</View>
);
}
};
我正在尝试将值保存到异步存储,然后根据异步存储的值结果导航到正确的页面。我可以将数据存储在 AsyncStorage 中,但我的状态不会更新,我必须重新加载应用程序才能更新状态。这是我的代码:
我这里有一个 Welcome/Obnoarding 屏幕。我希望此屏幕只显示给新的应用程序用户。因此,当用户按下继续按钮时,我想将一个值保存到异步存储中,以便他们下次登录时不必再次看到登录页面。这是我的入职页面:
const WelcomeScreen: FC<IWelcomeScreen> = ({ navigation }) => {
const { width, height } = Dimensions.get("window");
const btnText = "Contiunue";
const title = "Book";
const subTitle = "Fab";
let [fontsLoaded] = useFonts({
PinyonScript_400Regular,
});
const continueBtn = async () => {
try {
await AsyncStorage.setItem('@viewedOnboarding', 'true');
} catch (error) {
console.log('Error @setItem: ', error);
};
};
if (!fontsLoaded) {
return <Text>...Loading</Text>;
} else {
return (
<View style={containerStyle(height, width).container}>
<ImageBackground
resizeMode={"cover"}
style={styles.image}
source={require("../assets/model.jpg")}
>
<LinearGradient
colors={["#00000000", "#000000"]}
style={styles.gradient}
>
<View style={styles.container}>
<View style={styles.logoTextContainer}>
<Text style={styles.logoText}>{title}</Text>
<Text style={styles.logoText}>{subTitle}</Text>
</View>
<ContinueBtn label={btnText} callback={continueBtn} />
</View>
</LinearGradient>
</ImageBackground>
</View>
);
}
};
在我的 AppNavigator 中,我想决定用户应该看到哪个导航。但是当我按下继续页面时,我的应用程序不会导航到我的 TabsNavigator。它保留在我的入职页面上,但如果我刷新该应用程序,该应用程序将导航到我的选项卡导航器。这是我确定用户应该在哪里的代码,具体取决于他们是新用户还是“旧”用户:
const WelcomeScreen: FC<IWelcomeScreen> = ({ navigation }) => {
const { width, height } = Dimensions.get("window");
const btnText = "Contiunue";
const title = "Book";
const subTitle = "Fab";
let [fontsLoaded] = useFonts({
PinyonScript_400Regular,
});
const continueBtn = async () => {
try {
await AsyncStorage.setItem('@viewedOnboarding', 'true');
} catch (error) {
console.log('Error @setItem: ', error);
};
};
if (!fontsLoaded) {
return <Text>...Loading</Text>;
} else {
return (
<View style={containerStyle(height, width).container}>
<ImageBackground
resizeMode={"cover"}
style={styles.image}
source={require("../assets/model.jpg")}
>
<LinearGradient
colors={["#00000000", "#000000"]}
style={styles.gradient}
>
<View style={styles.container}>
<View style={styles.logoTextContainer}>
<Text style={styles.logoText}>{title}</Text>
<Text style={styles.logoText}>{subTitle}</Text>
</View>
<ContinueBtn label={btnText} callback={continueBtn} />
</View>
</LinearGradient>
</ImageBackground>
</View>
);
}
};
在异步存储中设置一个值不会触发 AppNavigator
的重新渲染。因此,如果用户按下 continue button
,那么视觉上不会发生任何事情,因为 AppNavigator
的状态没有改变。如果您刷新应用程序,您之前使用 setItem
函数设置的标志将在 AppNavigator
初始渲染时重新加载。这就是刷新应用程序后它起作用的原因。
对于这种问题,我建议你使用Context
来触发AppNavigator
中的状态变化。
这是一个关于其工作原理的最小示例。我在代码中添加了注释来指导您。
为了简单起见,我们将做出以下假设:
我们在 Stack 中有两个屏幕,一个是 WelcomeScreen
,另一个是 HomeScreen
。
请注意,我们根据应用程序上下文对屏幕使用条件渲染。您可以添加任何您想要的屏幕,甚至是整个导航器(如果您的导航器是嵌套的,这将是必要的,但模式保持不变)。
应用程序
export const AppContext = React.createContext()
const App = () => {
// it is important that the initial state is undefined, since
// we need to wait for the async storage to return its value
// before rendering anything
const [hasViewedOnboarding, setHasViewedOnboarding] = React.useState()
const appContextValue = useMemo(
() => ({
hasViewedOnboarding,
setHasViewedOnboarding,
}),
[hasViewedOnboarding]
)
// retrieve the onboarding flag from the async storage in a useEffect
React.useEffect(() => {
const init = async () => {
const value = await AsyncStorage.getItem('@viewedOnboarding')
setHasViewedOnboarding(value != null ? JSON.parse(value) : false)
}
init()
}, [])
// as long as the flag has not been loaded, return null
if (hasViewedOnboarding === undefined) {
return null
}
// wrap everything in AppContext.Provider an pass the context as a value
return (
<AppContext.Provider value={appContextValue}>
<NavigationContainer>
<Stack.Navigator>
{!hasViewedOnboarding ? (
<Stack.Screen name="Welcome" component={WelcomeScreen} />
) : (
<Stack.Screen
name="Home"
component={HomeScreen}
/>
)}}
</Stack.Navigator>
</NavigationContainer>
</AppContext.Provider>
)
}
现在,在您的 WelcomeScreen
中,您需要在存储异步值后访问上下文并设置状态。
const WelcomeScreen: FC<IWelcomeScreen> = ({ navigation }) => {
// access the context
const { setHasViewedOnboarding } = useContext(AppContext)
const { width, height } = Dimensions.get("window");
const btnText = "Contiunue";
const title = "Book";
const subTitle = "Fab";
let [fontsLoaded] = useFonts({
PinyonScript_400Regular,
});
const continueBtn = async () => {
try {
await AsyncStorage.setItem('@viewedOnboarding', 'true');
setHasViewedOnboarding(true)
} catch (error) {
console.log('Error @setItem: ', error);
};
};
if (!fontsLoaded) {
return <Text>...Loading</Text>;
} else {
return (
<View style={containerStyle(height, width).container}>
<ImageBackground
resizeMode={"cover"}
style={styles.image}
source={require("../assets/model.jpg")}
>
<LinearGradient
colors={["#00000000", "#000000"]}
style={styles.gradient}
>
<View style={styles.container}>
<View style={styles.logoTextContainer}>
<Text style={styles.logoText}>{title}</Text>
<Text style={styles.logoText}>{subTitle}</Text>
</View>
<ContinueBtn label={btnText} callback={continueBtn} />
</View>
</LinearGradient>
</ImageBackground>
</View>
);
}
};