太多的 React 上下文提供者

Too many React Context providers

新手在这里做出反应并试图围绕新的 Context API(我还没有研究 Redux 等)。

似乎我可以做很多我需要做的事情,但我最终会遇到很多很多供应商,所有供应商都需要一个标签来包装我的主要应用程序。

我将为 Auth 提供一个提供程序,一个用于主题化,一个用于聊天消息(vis Pusher.com)等。另外使用 React Router 是另一个包装器元素。

我是否必须以这个(以及更多)结束......

<BrowserRouter>
    <AuthProvider>
        <ThemeProvider>
            <ChatProvider>
                <App />
            </ChatProvider>
        </ThemeProvider>
    </AuthProvider>
</BrowserRouter>

或者有更好的方法吗?

使用@rista404 的回答 -
因为 react-context-composer 已弃用。

感谢@AO17,the ping


免责声明:我从未使用过这个,只是研究了一下。

FormidableLabs(他们为许多 OSS projects) has a project called, react-context-composer

这似乎解决了您的问题。

React is proposing a new Context API. The API encourages composing. This utility component helps keep your code clean when your component will be rendering multiple Context Providers and Consumers.

一个简单的解决方案是使用组合函数,如the one Redux uses,将所有提供程序组合在一起。然后组合函数将像这样调用:

const Providers = compose(
    AuthProvider,
    ThemeProvider,
    ChatProvider
);

我还没有使用过这个解决方案,但是使用 React 的新钩子功能,而不是渲染你的上下文,你可以使用反应钩子在函数定义中访问它。

几行代码解决你的问题。

import React from "react"
import _ from "lodash"

/**
 * Provided that a list of providers [P1, P2, P3, P4] is passed as props,
 * it renders
 *
 *    <P1>
        <P2>
          <P3>
            <P4>
              {children}
            </P4>
          </P3>
        </P2>
      </P1>
 *
 */

export default function ComposeProviders({ Providers, children }) {
  if (_.isEmpty(Providers)) return children

  return _.reverse(Providers)
    .reduce((acc, Provider) => {
      return <Provider>{acc}</Provider>
    }, children)
}

如果您想要一种无需任何第三方库即可编写 Provider 的解决方案,这里有一个带有 Typescript 注释的解决方案:

// Compose.tsx

interface Props {
    components: Array<React.JSXElementConstructor<React.PropsWithChildren<any>>>
    children: React.ReactNode
}

export default function Compose(props: Props) {
    const { components = [], children } = props

    return (
        <>
            {components.reduceRight((acc, Comp) => {
                return <Comp>{acc}</Comp>
            }, children)}
        </>
    )
}

用法:

<Compose components={[BrowserRouter, AuthProvider, ThemeProvider, ChatProvider]}>
    <App />
</Compose>

如果你不使用 Typescript,你当然可以删除注释。

for 循环的解决方案:

export const provider = (provider, props = {}) => [provider, props];

export const ProviderComposer = ({providers, children}) => {
    for (let i = providers.length - 1; i >= 0; --i) {
        const [Provider, props] = providers[i];
        children = <Provider {...props}>{children}</Provider>
    }
    return children;
}

用法:

<ProviderComposer
    providers={[
        provider(AuthProvider),
        provider(ThemeProvider),
        provider(MuiPickersUtilsProvider, {utils: DateFnsUtils}),
    ]}
>
    <App/>
</ProviderComposer>

如果您需要将外部道具注入提供者元素,请重构 js 嵌套助手,使用 withprops hoc

我没有足够的声誉来发表评论,但集成 rrista404 答案将组件包装在 useCallback() 挂钩中以确保上下文数据在某些情况下(如页面切换)的完整性可能很有用。

// Compose.tsx

interface Props {
    components: Array<React.JSXElementConstructor<React.PropsWithChildren<any>>>
    children: React.ReactNode
}

const Compose = useCallback((props: Props) => {
    const { components = [], children } = props

    return (
        <>
            {components.reduceRight((acc, Comp) => <Comp>{acc}</Comp>, children)}
        </>
    )
}, [])

export default Compose