【问题标题】:track UI elements states with one object, but the states are not reserved once leaving the screen and coming back使用一个对象跟踪 UI 元素的状态,但离开屏幕并返回时不会保留状态
【发布时间】:2020-09-07 15:26:24
【问题描述】:

在我的 react-native 项目中,我有三个复选框,我需要跟踪这些复选框的状态,因此我使用具有键值的对象(值是布尔值)来表示所有三个复选框的状态并使用 @987654322 @hook 来管理它们。这是我的代码:

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的状态。在CheckboxonPress 回调中,我切换每个复选框状态并通过钩子方法setCheckBoxesState 整体更新checkBoxesState

在运行时一切正常,我的屏幕在切换复选框状态时重新渲染,UI 正确显示复选框的状态。但是当我导航回上一个屏幕并导航回此屏幕时,问题就出现了,所有复选框状态都恢复到初始状态。

那么,为什么不保留复选框状态?

P.S. 前一个屏幕和MyScreen 在同一个堆栈导航器下。用户按下前一屏的按钮导航到MyScreen。从MyScreen 用户可以通过按“headerLeft”按钮转到上一个屏幕

【问题讨论】:

  • 因为这里没有提供我需要的代码部分,您可以在按下返回按钮时在您所在页面的componentWillUnmount 中添加一个console.log('unmounted')。只要告诉我您是否在控制台中看到此日志
  • 您可以将checkBoxesState setCheckBoxesState 作为道具传递给MyScreen,并且可以从MyScreen 调用,对于持久状态,您可以使用github.com/leoafarias/use-state-persist 这个包
  • @Amas,我更新了我的代码 sn-p ,现在我有控制台日志来检查组件是否已卸载。我测试过,每次离开屏幕时都能看到控制台日志。但这是为什么呢?据我了解,屏幕会在内存堆栈中停留一段时间,但似乎屏幕会立即卸载。无论如何如何解决我的问题?

标签: react-native react-hooks react-native-navigation


【解决方案1】:

首先让我们回答这个问题:

为什么不保留复选框状态?

这个组件完全独立地处理它的状态,状态是在内部创建和处理的,没有值是从外部传入的。这是什么意思?这个组件内部有它的初始状态值,它不使用任何道具或其他任何东西来初始化状态。每次创建此组件时,都会再次使用该值初始化状态。所以这就是你丢失对checkboxes所做的所有更改的原因,因为当你离开这个屏幕(组件)时,它会被卸载(我们将在下一个问题中讨论这个问题)并且因为所有值都只是在内部处理,每个数据(包含复选框状态)将丢失。

那么现在让我们谈谈这个:

react-native 是否应该在返回屏幕时保留状态?

简短的回答是否定的。每个组件在卸载时都会被销毁,包括它们的状态和数据。

现在让我们来回答原因

屏幕仍在内存中的堆栈上,没有被销毁?

通常开发人员使用类似react-navigationRNRF(基于react-navigation)的包来进行反应导航,大多数时候我们并不关心他们如何处理这个导航逻辑,我们只是使用提供给我们的界面。这些包中的每一个都可能有自己的方式来处理导航。提供完整的答案来确定为什么仍然在内存中的屏幕需要完整的代码审查并确保进行大量调试,但我想有两种可能性。首先,正如我所说,您使用的软件包可能出于某种原因将未安装的屏幕至少保留一段时间。第二个是常见的react 社区问题,即Unmounted component still in memory,您可以查看:https://github.com/facebook/react/issues/16138

最后让我们回答这个问题:

即使返回并丢失包含其状态的组件,我如何保持复选框状态?

这不仅仅是一种方法,但简单而简短的答案是将您的状态移出该组件,例如将其移出父组件或全局变量。 为了更清楚,让我们这样解释:想象屏幕A 总是挂载,然后你进入B,在那里你可以看到一些复选框,你可以修改状态。如果状态完全在B 内部处理,如果您从屏幕B 导航回A,您将丢失所有更改,因为B 现在已卸载。所以你应该怎么做才能将复选框状态放在A屏幕中,然后将值传递给B。并且在修改值时,您修改A 状态。因此,当B 被卸载时,所有更改都是持久的,因为您将它们放在A 中。

也存在其他方法,您可以创建一个名为globalState 的全局单例对象。然后将需要在多个屏幕之间共享的值放在那里。如果您更喜欢reduxmobx,您可以使用它们。它们的一种用法是当您有一些数据需要在多个屏幕之间共享时,这些数据与您所处的位置无关并且会持续存在。

【讨论】:

  • 注意:AsyncStorage 通常用于在应用程序中存储token 之类的数据,而不是用于状态持久化
  • AsyncStorage 正是为了状态持久化,之所以需要 AsyncStorage 仅仅是因为无论出于何种原因,即使在应用程序重新启动后也需要 state,因此得出结论 AsyncStorage 只是为了存储数据喜欢token不合适
  • @Isaac 虽然有很多方法可以保持状态,但我承认说我没有考虑应用程序重启和重新启动的情况,只是在谈论一般的状态处理。对于这个特定的问题,不仅不推荐使用 async-storage,而且是一种不好的做法。但总的来说是的,为了持久化数据而对对象进行字符串化和解析是很常见的,但不是这个
【解决方案2】:

这个解释来自官方react-navigation文档:

考虑一个带有屏幕 A 和 B 的堆栈导航器。导航到 A、它的componentDidMount被调用。当推 B 时,它的 componentDidMount 也被调用,但 A 仍然安装在堆栈上 因此它的 componentWillUnmount 不会被调用。

从B回到A时,调用B的componentWillUnmount,但是 A 的 componentDidMount 不是因为 A 保持整个挂载 时间。

https://reactnavigation.org/docs/navigation-lifecycle/#example-scenario

您的 MyScreen 屏幕等同于示例中的屏幕 B,这意味着如果您向前导航,但不能向后导航,您的屏幕可以保持安装状态。

【讨论】:

    【解决方案3】:

    很简单,只需将 keyExtractor 添加到您的 SectionList 组件中,它将唯一标识每个复选框,以便 react 知道在更新时要重新渲染哪个复选框。

    【讨论】:

      【解决方案4】:

      您需要使用 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() 在组件被卸载和销毁之前立即调用。在此方法中执行任何必要的清理,例如使计时器无效、取消网络请求或清理在 componentDidMount() 中创建的任何订阅。 ...一旦组件实例被卸载,它将永远不会被再次装载。

      这意味着存储在状态中的任何值也将被销毁,并且在导航回屏幕时ComponentDidMount 将被调用,您可能希望将持久值分配回状态。

      除了 AsyncStorage 之外,还有两种可能的方法是使用 Context 或单例,这两种方法可能适用于某些用例跨屏幕持久化数据。

      【讨论】:

      • 不,我没有。我的问题是两个屏幕仍在内存中,每次返回屏幕时状态都重置为初始状态,但 react-native 应该在返回屏幕时保留状态。我想了解为什么在不了解我面临的问题的情况下不直接切换到 AsyncStorage。不过谢谢你的回答。
      • 我添加了一个更新,希望这有助于澄清事情。
      • 谢谢,但如果我理解正确,您更新后的答案仍然假设该组件既已卸载又已销毁。就像我在我的情况下所说的那样,两个屏幕仍然在内存中的堆栈上,没有被破坏。状态应该是保留的。
      猜你喜欢
      • 2018-07-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多