track UI elements states with one object, 但一旦离开屏幕并返回,这些状态就不会被保留
track UI elements states with one object, but the states are not reserved once leaving the screen and coming back
在我的 react-native 项目中,我有三个复选框,我需要跟踪这些复选框的状态,所以我使用一个具有键值(值为布尔值)的对象来表示所有三个复选框的状态并使用useState
钩子来管理它们。这是我的代码:
import React, { useState, useEffect } from 'react';
...
const MyScreen = ({ navigation }) => {
// initially, all checkboxes are checked
const initialCheckBoxState = {
0: true,
1: true,
2: true,
};
const [checkBoxesState, setCheckBoxesState] = useState(initialCheckBoxState);
useEffect(() => {
return () => {
console.log('Screen did unmount');
};
}, [checkBoxesState]);
return (
<View>
...
<SectionList
sections={options}
renderItem={({ index, item }) => (
<CheckBox
onPress={() => {
const checkBoxesStateCopy = { ...checkBoxesState };
checkBoxesStateCopy[index] = !checkBoxesStateCopy[index];
setCheckBoxesState(checkBoxesStateCopy);
}}
/>
)}
/>
...
</View>
);
};
我省略了与我的问题无关的代码。如您所见,我为每个项目绘制了一个 CheckBox
组件。
实际上,总是有三个项目(即显示三个复选框)。一开始我声明了initialCheckBoxState
,每个key-pair代表了各自checkbox的状态。在 Checkbox
的 onPress
回调中,我切换每个复选框状态并通过钩子方法 setCheckBoxesState
整体更新 checkBoxesState
。
运行时一切正常,切换复选框状态时我的屏幕重新呈现,UI 正确显示复选框的状态。但是当我导航回上一个屏幕并导航回此屏幕时,问题就来了,所有复选框状态都回到初始状态。
那么,为什么不保留复选框状态?
P.S. 上一个屏幕和 MyScreen
在同一个堆栈导航器下。用户按上一个屏幕的按钮导航到 MyScreen
。从 MyScreen
开始,用户可以通过按“headerLeft”按钮转到上一个屏幕
您需要使用 AsyncStorage 将数据保存到设备。组件卸载时状态变量将被清除。
AsyncStorage 文档:
https://react-native-community.github.io/asaync-storage/
import AsyncStorage from '@react-native-community/async-storage';
//You can only store string values so convert objects to strings:
const storeData = async (value) => {
try {
const jsonValue = JSON.stringify(value)
await AsyncStorage.setItem('@storage_Key', jsonValue)
} catch (e) {
// saving error
}
}
const getData = async () => {
try {
const jsonValue = await AsyncStorage.getItem('@storage_Key')
return jsonValue != null ? JSON.parse(jsonValue) : null;
} catch(e) {
// error reading value
}
}
更新 -
由于 React 组件生命周期的性质,状态不会被持久化。具体来说,当您离开屏幕时,会调用生命周期方法 componentWillUnmount
。
以下是 docs 的摘录:
componentWillUnmount() is invoked immediately before a component is unmounted and destroyed. Perform any necessary cleanup in this method, such as invalidating timers, canceling network requests, or cleaning up any subscriptions that were created in componentDidMount().
...Once a component instance is unmounted, it will never be mounted again.
这意味着存储在状态中的任何值也将被销毁,并且在导航回屏幕时将调用 ComponentDidMount
,您可能希望在此处将持久值分配回状态。
除了 AsyncStorage 之外的两种可能适用于某些用例以跨屏幕持久保存数据的方法是使用 Context 或单例。
首先让我们回答问题:
why the checkboxes states are not reserved?
该组件完全独立地处理其状态,状态是在内部创建和处理的,没有值是来自外部的 passed-in。这是什么意思?该组件在其自身内部具有初始状态值,它不使用任何道具或其他任何东西来初始化状态。每次创建此组件时,都会使用该值再次初始化状态。所以这就是你丢失对 checkboxes
所做的所有更改的原因,因为当你离开这个屏幕(组件)时,它会被卸载(我们将在下一个问题中讨论这个)并且因为所有值都只在内部处理,每个数据(包含复选框状态)将丢失。
那么现在让我们谈谈这个:
is react-native supposed to reserve the state when come back to the screen?
简短的回答是否定的。每个组件在卸载时都会被销毁,包括它们的状态和数据。
现在让我们回答为什么
screens are still on the stack in memory, not destroyed?
通常开发人员使用 react-navigation
或 RNRF
(构建在 react-navigation
之上)这样的包来进行 React 导航,大多数时候我们并不关心它们如何处理这个导航逻辑,我们只使用提供给我们的接口。这些包中的每一个都可能有自己的方式来处理导航。提供完整的答案以确定为什么仍然在内存中的屏幕需要完整的代码审查和肯定的大量调试,但我想有两种可能性。首先,正如我所说,也许您正在使用的软件包出于某种原因至少将未安装的屏幕保留在内存中一段时间。第二个是常见的 react
社区问题,即 Unmounted component still in memory
,您可以在以下位置查看:https://github.com/facebook/react/issues/16138
最后让我们回答问题:
how do i keep checkboxes state even with navigating back and losing component containing their state?
这并没有只有一种方法,但简单而简短的答案是将您的状态移出该组件,例如将其移出到父组件或全局变量。
为了更清楚,让我们这样解释:想象屏幕 A
总是挂载,然后你进入 B
,在那里你可以看到一些复选框,你可以修改状态。如果状态完全在 B
内处理,如果您从屏幕 B
导航回 A
,您将丢失所有更改,因为 B
现在已卸载。因此,您应该如何将复选框状态放入 A
屏幕,然后将值传递给 B
。并且在修改值时,您修改 A
状态。所以当 B
被卸载时,所有更改都是持久的,因为你在 A
.
中有它们
还有其他方法,您可以创建一个名为 globalState
的全局单例对象。然后将需要在多个屏幕之间共享的值放在那里。如果您喜欢 redux
或 mobx
,您可以使用它们。它们的用途之一是当您有一些数据需要在多个屏幕之间共享时,这些数据独立于您所在的位置并将持续存在。
此解释来自官方react-navigation
文档:
Consider a stack navigator with screens A and B. After navigating to
A, its componentDidMount is called. When pushing B, its
componentDidMount is also called, but A remains mounted on the stack
and its componentWillUnmount is therefore not called.
When going back from B to A, componentWillUnmount of B is called, but
componentDidMount of A is not because A remained mounted the whole
time.
https://reactnavigation.org/docs/navigation-lifecycle/#example-scenario
您的 MyScreen 屏幕等同于示例中的屏幕 B,这意味着如果您向前导航而不是向后导航,您的屏幕会保持挂载状态。
很简单,只需将一个 keyExtractor 添加到您的 SectionList 组件,它将唯一标识每个复选框,以便 React 知道在更新时 re-render 哪个。
在我的 react-native 项目中,我有三个复选框,我需要跟踪这些复选框的状态,所以我使用一个具有键值(值为布尔值)的对象来表示所有三个复选框的状态并使用useState
钩子来管理它们。这是我的代码:
import React, { useState, useEffect } from 'react';
...
const MyScreen = ({ navigation }) => {
// initially, all checkboxes are checked
const initialCheckBoxState = {
0: true,
1: true,
2: true,
};
const [checkBoxesState, setCheckBoxesState] = useState(initialCheckBoxState);
useEffect(() => {
return () => {
console.log('Screen did unmount');
};
}, [checkBoxesState]);
return (
<View>
...
<SectionList
sections={options}
renderItem={({ index, item }) => (
<CheckBox
onPress={() => {
const checkBoxesStateCopy = { ...checkBoxesState };
checkBoxesStateCopy[index] = !checkBoxesStateCopy[index];
setCheckBoxesState(checkBoxesStateCopy);
}}
/>
)}
/>
...
</View>
);
};
我省略了与我的问题无关的代码。如您所见,我为每个项目绘制了一个 CheckBox
组件。
实际上,总是有三个项目(即显示三个复选框)。一开始我声明了initialCheckBoxState
,每个key-pair代表了各自checkbox的状态。在 Checkbox
的 onPress
回调中,我切换每个复选框状态并通过钩子方法 setCheckBoxesState
整体更新 checkBoxesState
。
运行时一切正常,切换复选框状态时我的屏幕重新呈现,UI 正确显示复选框的状态。但是当我导航回上一个屏幕并导航回此屏幕时,问题就来了,所有复选框状态都回到初始状态。
那么,为什么不保留复选框状态?
P.S. 上一个屏幕和 MyScreen
在同一个堆栈导航器下。用户按上一个屏幕的按钮导航到 MyScreen
。从 MyScreen
开始,用户可以通过按“headerLeft”按钮转到上一个屏幕
您需要使用 AsyncStorage 将数据保存到设备。组件卸载时状态变量将被清除。
AsyncStorage 文档: https://react-native-community.github.io/asaync-storage/
import AsyncStorage from '@react-native-community/async-storage';
//You can only store string values so convert objects to strings:
const storeData = async (value) => {
try {
const jsonValue = JSON.stringify(value)
await AsyncStorage.setItem('@storage_Key', jsonValue)
} catch (e) {
// saving error
}
}
const getData = async () => {
try {
const jsonValue = await AsyncStorage.getItem('@storage_Key')
return jsonValue != null ? JSON.parse(jsonValue) : null;
} catch(e) {
// error reading value
}
}
更新 -
由于 React 组件生命周期的性质,状态不会被持久化。具体来说,当您离开屏幕时,会调用生命周期方法 componentWillUnmount
。
以下是 docs 的摘录:
componentWillUnmount() is invoked immediately before a component is unmounted and destroyed. Perform any necessary cleanup in this method, such as invalidating timers, canceling network requests, or cleaning up any subscriptions that were created in componentDidMount(). ...Once a component instance is unmounted, it will never be mounted again.
这意味着存储在状态中的任何值也将被销毁,并且在导航回屏幕时将调用 ComponentDidMount
,您可能希望在此处将持久值分配回状态。
除了 AsyncStorage 之外的两种可能适用于某些用例以跨屏幕持久保存数据的方法是使用 Context 或单例。
首先让我们回答问题:
why the checkboxes states are not reserved?
该组件完全独立地处理其状态,状态是在内部创建和处理的,没有值是来自外部的 passed-in。这是什么意思?该组件在其自身内部具有初始状态值,它不使用任何道具或其他任何东西来初始化状态。每次创建此组件时,都会使用该值再次初始化状态。所以这就是你丢失对 checkboxes
所做的所有更改的原因,因为当你离开这个屏幕(组件)时,它会被卸载(我们将在下一个问题中讨论这个)并且因为所有值都只在内部处理,每个数据(包含复选框状态)将丢失。
那么现在让我们谈谈这个:
is react-native supposed to reserve the state when come back to the screen?
简短的回答是否定的。每个组件在卸载时都会被销毁,包括它们的状态和数据。
现在让我们回答为什么
screens are still on the stack in memory, not destroyed?
通常开发人员使用 react-navigation
或 RNRF
(构建在 react-navigation
之上)这样的包来进行 React 导航,大多数时候我们并不关心它们如何处理这个导航逻辑,我们只使用提供给我们的接口。这些包中的每一个都可能有自己的方式来处理导航。提供完整的答案以确定为什么仍然在内存中的屏幕需要完整的代码审查和肯定的大量调试,但我想有两种可能性。首先,正如我所说,也许您正在使用的软件包出于某种原因至少将未安装的屏幕保留在内存中一段时间。第二个是常见的 react
社区问题,即 Unmounted component still in memory
,您可以在以下位置查看:https://github.com/facebook/react/issues/16138
最后让我们回答问题:
how do i keep checkboxes state even with navigating back and losing component containing their state?
这并没有只有一种方法,但简单而简短的答案是将您的状态移出该组件,例如将其移出到父组件或全局变量。
为了更清楚,让我们这样解释:想象屏幕 A
总是挂载,然后你进入 B
,在那里你可以看到一些复选框,你可以修改状态。如果状态完全在 B
内处理,如果您从屏幕 B
导航回 A
,您将丢失所有更改,因为 B
现在已卸载。因此,您应该如何将复选框状态放入 A
屏幕,然后将值传递给 B
。并且在修改值时,您修改 A
状态。所以当 B
被卸载时,所有更改都是持久的,因为你在 A
.
还有其他方法,您可以创建一个名为 globalState
的全局单例对象。然后将需要在多个屏幕之间共享的值放在那里。如果您喜欢 redux
或 mobx
,您可以使用它们。它们的用途之一是当您有一些数据需要在多个屏幕之间共享时,这些数据独立于您所在的位置并将持续存在。
此解释来自官方react-navigation
文档:
Consider a stack navigator with screens A and B. After navigating to A, its componentDidMount is called. When pushing B, its componentDidMount is also called, but A remains mounted on the stack and its componentWillUnmount is therefore not called.
When going back from B to A, componentWillUnmount of B is called, but componentDidMount of A is not because A remained mounted the whole time.
https://reactnavigation.org/docs/navigation-lifecycle/#example-scenario
您的 MyScreen 屏幕等同于示例中的屏幕 B,这意味着如果您向前导航而不是向后导航,您的屏幕会保持挂载状态。
很简单,只需将一个 keyExtractor 添加到您的 SectionList 组件,它将唯一标识每个复选框,以便 React 知道在更新时 re-render 哪个。