状态对象发生变化时更新上下文
Posted
技术标签:
【中文标题】状态对象发生变化时更新上下文【英文标题】:Update context when state object mutates 【发布时间】:2020-01-27 07:20:55 【问题描述】:我有一个PageContext
,它将User
对象的状态保存为数组。每个User
对象都包含一个ScheduledPost
对象,当用户决定添加新帖子时,该对象会发生变化。我不知道如何在我的PageContext
发生更新时触发更新(我想避免forceUpdate()
调用)。我需要以某种方式收到通知,以便重新渲染帖子、维护计时器等。
请看代码:
class User
name: string;
createTime: number;
scheduledPosts: ScheduledPost[] = [];
/*
* Creates a new scheduled post
*/
public createScheduledPost(title : string, content : string, date : number): void
this.scheduledPosts.push(Object.assign(new ScheduledPost(),
title,
content,
date
));
class ScheduledPost
title: string;
content: string;
date: number;
public load(): void
// Create timers etc.
public publish(): void
// Publish post
// PageContext/index.tsx
export default React.createContext(
users: [],
editingUser: null,
setEditingUser: (user: User) =>
);
// PageContextProvider.tsx
const PageContextProvider: React.FC = props =>
const [users, setUsers] = useState<User[]>([]);
const [editingUser, setEditingUser] = useState<User>(null);
// Load users
useEffect(() =>
db.getUsers()
.then(result => setUsers(result));
, []);
return (
<PageContext.Provider value=
users,
editingUser,
setEditingUser
>
props.children
</PageContext.Provider>
);
;
我想要实现的是,当使用 useContext
钩子使用我的提供程序时:
const ctx = useContext(PageContext);
我想从任何组件创建一个时间表帖子,如下所示:
// Schedule post (1 hour)
ctx.editingUser.createScheduledPost("My post title", "The post content", (new Date).getTime() + 1 * 60 * 60);
但是,这不起作用,因为 React 不知道 User
属性刚刚发生了变异。
问题:
如何让 React 收到任何User
对象实例中的更改的通知?有什么方法可以正确解决(不包括 forceUpdate)?
我做得对吗?我是 React 新手,我觉得我在这里使用的结构很麻烦而且不正确。
【问题讨论】:
你能提供一个可行的例子吗?喜欢codesanbox? 使用Redux并将用户存储为对象(可序列化)。 【参考方案1】:用户在哪里发生变异?如果您将它们存储在显示的状态中,则应检测到更改。但是,如果您使用 User 类中内置的方法让它们自己 directly update,那么 React 将不会接受它们。你需要在你的状态下更新整个 users
数组,以确保 React 可以响应更改。
如果不知道您当前在何处/如何更新用户,很难给出更具体的示例,但一般化的突变可能会像这样(如果需要,您仍然可以使用类方法):
const newUsers = Array.from(users); // Deep copy users array to prevent direct state mutation.
newUsers[someIndex].createScheduledPost(myTitle, myContent, myDate);
setUsers(newUsers); // Calling the setX function tied to your useState call will automatically trigger updates/re-renders for all (unless otherwise specified) components/operations that depend on it
【讨论】:
但是在这种情况下更新数组是没有意义的。这真的是每个人在 React 中的方式吗?这违背了性能如此糟糕。有一些 C# 背景和基于实例的对象,这变得很麻烦。你能举个例子说明我应该如何添加一个预定的帖子,这样每个使用上下文的组件都会收到更新的数组吗? 在这种情况下,我同意它来自像 C# 这样的语言可能是反直觉的,并且不如单一更新或拼接等性能好。但是,是的,这是一个基本原则React 的:存储在 state 中的数据必须用setState
(或在钩子 setX
中)更新,以便 React 可以正确地“响应”更改。它还提供了大量的性能benefits,并且渲染 DOM 的复杂性稍微改变了演算,而不是后端的静态数据对象。
我将更新我的答案,说明如何做到这一点,但要回答您的问题:用户数组将在您直接从状态下传递时在您的上下文中自动更新,前提是您正在使用setUsers
函数对其进行更新。
是的,如果你能详细说明这个update the entire users array in your state to make sure React can respond to the changes
,那将是惊人的987654331@ 类不是 React 组件,我不能在该类中使用 useState
挂钩。也许通过适当的订阅事件可以实现某种存储?
我添加了一个通用示例,说明如何使用类方法更新 users
状态,但您可以使用克隆数组在第一步和第三步之间执行几乎任何操作。
【参考方案2】:
在 React 中重新渲染是通过在组件内调用 setState
(或通过使用 hooks,但关键是您需要调用特定方法)或通过更改组件 props
引起的。这意味着您的状态的手动突变永远不会导致重新渲染 - 即使您有简单的组件并调用了
this.state.something = somethingElse;
不会发生重新渲染。同样适用于上下文。
对于您的情况,这意味着您不应改变 editingUser
状态,而是调用 setEditingUser
更改用户状态,例如:
const user = ...ctx.editingUser ;
user.createScheduledPost("My post title", "The post content", (new Date).getTime() + 1 * 60 * 60);
ctx.setEditingUser(user);
我不确定您的内部结构,但如果同一用户也在 users
数组中,那么您需要通过调用 setUsers
方法来更新该部分状态,在该方法中您维护整个数组并且仅更新更改数据的单个用户 - 如果是这种情况,那么我会考虑重组应用程序,因为对于这种简单的状态更改,它已经变得复杂了。您还应该考虑使用redux、mobx 或其他一些状态管理库来代替反应上下文(我的个人建议)。
编辑
请你看一下这个:
在典型的 React 应用程序中,数据是自顶向下传递的(父级 child) 通过道具,但这对于某些类型的 许多人需要的道具(例如区域设置偏好,UI主题) 应用程序中的组件。上下文提供了一种共享方式 组件之间的此类值,而无需显式传递 贯穿树的每一层的道具。
如您所见,React 团队建议对许多组件中所需的一些全局首选项使用上下文。使用上下文的主要问题(在我看来)是您不编写 natural react 组件 - 它们不通过 props 接收依赖数据,而是从上下文 api 本身接收数据。这意味着如果不集成应用程序的上下文部分,您将无法重用您的组件。
虽然 redux 有类似的将状态保存在一个地方的概念,但它仍然通过 props 将该状态(及其更改)传播给组件,使您的组件不依赖于 redux、上下文或其他任何东西。
您可以坚持响应上下文并让整个应用程序使用它,但我只是说这样做不是最佳做法。
【讨论】:
以上是关于状态对象发生变化时更新上下文的主要内容,如果未能解决你的问题,请参考以下文章
如果它们的状态在 React useEffect 中发生变化,如何区分上下文值
SwiftUI上下文菜单(Context Menu)内容不随对象状态刷新的解决