ReactJS - 如何将“全局”数据传递给深度嵌套的子组件?

52

人们通常如何处理React应用程序中的“全局”数据?

例如,假设我在用户登录我的应用程序后拥有以下数据。

user: {
  email: 'test@user.com',
  name: 'John Doe'
}

这是几乎任何应用程序组件都可能想要了解的数据 - 因此它可以在已登录或已注销状态下呈现,或者如果已登录,则可以显示用户的电子邮件地址。

根据我的理解,React 在子组件中访问此数据的方式是让顶层组件拥有数据,并使用属性将其传递给子组件,例如:

<App>
  <Page1/>
  <Page2>
    <Widget1/>
    <Widget2 user={user}/>
  </Page2>
</App>

但这对我来说似乎不太方便,因为这意味着我必须通过每个组合件传递数据,才能将其传递到需要它的子组件中。

在React中是否有管理此类数据的方法?

注意:此示例非常简化 - 我喜欢将意图封装为组合件,以便可以根据需要彻底更改整个UI功能的实现细节。

编辑:我知道,默认情况下,在我的顶层组件上调用setState会导致重新渲染所有子组件,并且在每个子组件中,我可以使用任何我喜欢的数据进行渲染(例如全局数据,而不仅仅是状态或props)。但是人们选择如何通知仅应呈现某些子组件呢?


1
许多人(包括我自己)使用Backbone来处理React中的数据,然后在模型更改时触发重新渲染。或者,如果您愿意,可以只使用全局对象。React本身不处理数据,它只提供了重新渲染应用程序的方法。 - David Hellsing
1
谢谢您的反馈!那么您是否只是在“主”组件上调用setState,让React重新渲染所有内容,而不管它的状态是否已更改?这当然有效,但并不适合渲染(https://dev59.com/wWAf5IYBdhLWcg3wXhyz#24719289). - Brad Parks
1
我们在backbone处理程序内的顶层组件上使用this.forceUpdate()。我认为这是适当的方法,但您可以在此处找到更多替代方法和有关优缺点的信息:https://dev59.com/mWEi5IYBdhLWcg3wDoaV - David Hellsing
有趣...绝对值得一读...听起来仍然会导致渲染,但如果渲染结果与原来相同,则不会进行实际的DOM更改...好好知道! - Brad Parks
6个回答

20

自从我最初回答这个问题以来,我发现React本身不支持任何意义上的“全局”数据 - 它真正的目的是管理UI。你的应用程序的数据需要存储在其他地方。话虽如此,它现在确实支持访问全局context数据如此页面上的另一个答案所详细说明的那样这篇由Kent Dodds撰写的好文章介绍了context API的演变,并且现在已经被React官方支持。

context方法只应用于真正的全局数据。如果您的数据属于其他任何类别,则应按以下方式操作:

  • Facebook使用他们自己的Flux库来解决这个问题。
  • MobxRedux与Flux类似,但似乎更受欢迎。它们可以以更清晰、更直观的方式完成相同的工作。

https://github.com/dustingetz/react-cursor

和这个类似的库:

https://github.com/mquan/cortex

重要提示:我认为这是Facebook的Flux或类似机制(上述内容)需要处理的工作。当数据流变得过于复杂时,除了回调之外,需要另一种机制来在组件之间进行通信,而Flux及其克隆似乎就是解决方案...

2
好消息:肯特·多兹的'好文章'链接似乎需要多层注册和登录,但只需向下滚动即可跳过所有内容。 - Michael Scheper
2
好的建议 - 我刚刚去更改了链接,使用了一篇来自Kent Dodds的更新文章,并直接链接到他的网站。 - Brad Parks

13

使用 React Context 属性。这个属性专门用于在不需要显式地转发数据的情况下向下传递全局数据集。虽然会增加组件生命周期函数的复杂性,但请注意我链接页面上提供的注意事项。


3

我认为React.createContext()是您目的的完美解决方案。使用useContext hook监听上下文变化的组件将被重新渲染。
这是您代码的简单片段:

export const CurrentUser = React.createContext({})
 
const App = () =>
{
    const User = getUser() // any authorisation method 
    return <>
        <CurrentUser.Provider value={User}> 
            <App>
                <Page1/>
                <Page2>
                    <Widget1/>
                    <Widget2/>
                </Page2>
            </App>
        </CurrentUser.Provider>
    </>
}  

const Widget2 = () =>
{
    const User = useContext(CurrentUser)
    return <>{User?.name}</>
}

如果你想直接控制重新渲染,可以在嵌套组件中使用React.memo。例如,如果你需要仅在特定属性更改后重新渲染组件。

此外,通过嵌套上下文值,您可以实现应用程序的良好灵活性。您可以为应用程序的不同部分传递不同的上下文值。

export const CurrentUser = React.createContext({})
 
const App = () =>
{
    const User = getUser() // any authorisation method 
    const AnotherUser = getAnotherUser() // any authorisation method 
 
    return <>
        <CurrentUser.Provider value={User}> 
            <App>
                <Page1/>
                <CurrentUser.Provider value={AnotherUser}>  
                  <Page2>
                      <Widget1/>
                      <Widget2/>
                  </Page2>
                </CurrentUser.Provider>
            </App>
        </CurrentUser.Provider>
    </>
}  

const Widget2 = () =>
{
    const User = useContext(CurrentUser)
    return <>{User?.name}</>
}


3
您可以使用 React Context API 将全局数据传递给嵌套的子组件。Kent C. Dodds 在 Medium 上撰写了一篇详尽的文章,介绍了如何使用这个新的API。React’s ⚛️ new Context API。它将帮助您更好地理解如何使用该API。

2

为什么不能通过在渲染所有子组件时使用 {...restOfProps} 将数据一路传递到组件链底部?

render(){
  const {propIKnowAbout1, propIKnowAbout2, ...restOfProps} = this.props;
  return <ChildComponent foo={propIKnowAbout1} bar={propIKnowAbout2} {...restOfProps}/>
}

1

这里有一个叫 Reactn 的库 https://www.npmjs.com/package/reactn,与本地状态一样,你可以使用 this.global 和 this.setGlobal 来获取和设置全局状态。
为此,你只需要
import React from 'reactn';


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接