繁体   English   中英

防止 React Native 在上下文值更改时重新安装组件

[英]Prevent React Native remount of component on context value change

在我正在构建的聊天应用程序中,每当用户发送消息和启动聊天时,我想从用户帐户中扣除积分。 用户帐户可在应用程序中作为上下文访问,并使用 firestore 文档上的快照侦听器在用户帐户文档中发生任何更改时进行更新。 请参阅底部的代码示例 1. 和 2.

现在,只要 userAccount 对象中的任何内容发生变化,所有上下文提供程序子项(NavigationStructure 及其所有子组件)都会根据React 的文档重新呈现。

然而,这会在也使用此上下文的聊天屏幕上造成巨大问题:只要上下文中的某些内容发生变化,就会重新初始化那里定义的状态。 例如,我有一个标志指示模态是否可见,默认值是可见的。 当我进入聊天屏幕时,隐藏模态,在 firestore 数据库中手动更改值(例如扣除积分)聊天屏幕重新呈现并且模态再次可见。 参见代码示例 3。

我很迷茫解决这个问题的最好方法是什么,有什么想法吗?

我想到的解决方案:

  • 将积分计数器移动到不同的 firestore 文档并每天扣除一次积分,但这感觉像是一个奇怪的解决方法。
  • 通过谷歌搜索,似乎可以使用 useCallback 或 React.memo 做一些事情,但我不确定如何做。
  • 放弃并成为一名木工……不过似乎是在逃避问题。
  • 也许它与我在 NavigationStructure 中使用的嵌套反应导航堆栈和选项卡导航器有关?

我尝试过的绝望的事情:

  • 将 NavigationStructure 的所有子组件包装在“React.memo(..)”中
  • 确保我没有在另一个组件的主体中定义一个组件。
  • 查看大量堆栈溢出帖子并尝试修复问题,但均无济于事。

代码示例

  1. 带上下文的应用程序设置
function App() {
  const userData = useUserData();
  ...
  return (
    <>
      <UserContext.Provider value={{ ...userData }}>
        <NavigationStructure />
      </UserContext.Provider>
    </>
}
  1. useUserData Hook 与 firestore 快照监听器
export const useUserData = () => {
  const [user, loading] = useAuthState(authFB);
  const [userAccount, setUserAccount] = useState<userAccount | null>();
  const [userLoading, setUserLoading] = useState(true);

  useEffect(() => {
      ...
      unsubscribe = onSnapshot(
        doc(getFirestore(), firebaseCollection.userAccount, user.uid),
        (doc) => {
          if (doc.exists()) {
            const data = doc.data() as userAccount & firebaseRequirement; 
            //STACK OVERFLOW COMMENT: data CONTAINES 'credits' FIELD
            ...
            setUserAccount(data);
          
          } 
           ...
        }
      );

  }, [user, loading]);
  ...
  return {
    user,
    userAccount,
    userLoading: userLoading || loading,
  };
};

  1. 代码示例:带模式的聊天屏幕
export const Chat = ({ route, navigation }: ChatScreenProps): JSX.Element => {
  const ctx = useContext(UserContext);
  const userAccount = ctx.userAccount as userAccount;
  ...
  
  //modal visibility
  const [modalVisible, setModalVisible] = useState(true); 
  // STACK OVERFLOW COMMENT: ISSUE IS HERE.
  // FOR SOME REASON THIS STATE GET'S RE-INITALIZED (AS true) WHENEVER 
  // SOMETHING IN THE userAccount CHANGES.
  ...

  return (
    <>
      ...
      <Modal
        title={t(tPrefix, 'tasklistModal.title')}
        visible={ModalVisible}
        onClose={() => setModalVisible(false)}
        footer={
          ...
        }
      >
       ....
      </Modal>
     ...
    </>)
}

这是 React 上下文的一个特征——上下文值的任何更改都会导致所有上下文的消费者重新渲染。 这在他们的文档的警告部分中有简要介绍,但在第三方博客中得到了扩展,例如: 如何使用 React Context 破坏您的应用程序的性能

您已经尝试过作者建议的记忆化。 记忆您的组件不会阻止重新初始化,因为当您更改用户对象时,组件中的值确实会发生变化。

解决方案是使用第三方状态管理解决方案,不依赖于 Context,而是依赖于它自己的 diffing。 Redux、Zustand 和其他流行的库会进行自己的比较,以便仅重新渲染受影响的组件。

上下文实际上只推荐用于不经常更改的值,并且无论如何都需要完整的应用程序重新呈现,例如主题更改或语言选择。 尝试用“真正的”状态管理解决方案代替它。

对上下文的任何更改确实会重新呈现所有消费者组件,无论它们是否使用更改后的属性。

但它不会卸载和安装组件,这就是您的本地状态被初始化为默认值的原因。

因此,问题不在于重新呈现(这种情况很少见),而是<Chat... />或其父组件之一由于上下文的变化而卸载。

从给出的部分代码示例中很难分辨,但我建议您查看如何使用loading loading? <div>loading..</div>: <Chat... /> loading? <div>loading..</div>: <Chat... />会导致此行为。

作为示例,这里有一个 codesandbox ,它说明了所提出的观点。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM