【问题标题】:How can I avoid the React/JSX Pyramid of Doom?如何避免 React/JSX 末日金字塔?
【发布时间】:2021-10-03 14:05:54
【问题描述】:

我喜欢在我的 React 和 React Native 项目中使用上下文,这意味着每个项目有多个上下文提供者。结果,我的应用程序的根目录通常如下所示:

<ContextA.Provider value={valueA}>
    <ContextB.Provider value={valueB}>
        <ContextC.Provider value={valueC}>
            // ...and so on until rendering actual app content
        </ContextC.Provider>
    </ContextB.Provider>
</ContextA.Provider>

这会创建一个看起来和感觉都像是不良风格/做法的提供商金字塔。

我可以将我的上下文值集中到一个大提供者中:

<BigContext.Provider value={ valueA, valueB, valueC }>
   /// app content
</BigContext.Provider>

...但是有一些充分的理由希望保持上下文分离 - 主要是防止仅对 valueA 感兴趣的组件在仅 valueB 更改时重新渲染。

即使没有上下文,您仍然可以让来自不同软件包的提供程序堆叠到它们自己的金字塔中。这是我的一个 React Native 应用程序的根目录,例如:

<DataContext.Provider value={data}>
    <AppearanceProvider>
        <SafeAreaProvider>
            <NavigationContainer>
                <Tab.Navigator>
                    // tab screens here
                </Tab.Navigator>
            </NavigationContainer>
        </SafeAreaProvider>
    </AppearanceProvider>
</DataContext.Provider>

有没有一种干净的方法来“崩溃”或以某种方式避免这些末日金字塔?

【问题讨论】:

  • 我个人将我的提供者保存在一个名为Providers 的单独组件中,并且仅使用该组件来包装我的应用程序内容。我仍然创造了那个金字塔,但让它远离眼睛。不幸的是,没有办法避免这种情况。唯一的选择是使用像 redux 或 mobx 这样的状态管理库,但对于小型项目来说这可能是一种矫枉过正
  • 唯一一种不好的风格/做法可能是您将不同类型的提供者混为一谈。例如,假设您有多个用于状态管理的提供程序和多个用于导航的提供程序。您可以做的是创建一个名为StateManagementProviders 的组件并将所有相关的提供程序放在那里,另一个名为NavigationProviders。然后,您可以将这两个提供程序嵌套在您的 App 组件中。
  • 在某种意义上,你可以说拥有多个嵌套的 div 就是 Doom 的 div,看不到尝试折叠它的有价值的理由
  • @DennisVash 你没看错,但在这种情况下,没有一个嵌套的提供者只有一个孩子,从而创建了一个直金字塔。如果您有 5 个以上的嵌套 div,每个 div 只有一个孩子,那么您通常会做错事。

标签: reactjs react-native jsx use-context


【解决方案1】:

“清理”所谓的末日金字塔并没有任何性能优势。拥有多个级别完全很好

在您实现下面的代码之前,请确保您不要仅仅因为可以就提供全局级别的上下文。上下文提供程序必须包装在使用它的组件附近,这意味着并不总是在根级别。

也就是说,这是一个简单的高阶组件,它可以包装多个 Provider 以提供 Redux compose esque API。

/**
 * Check if the object has a property matching the key
 * NOTE: Works only for shallow objects.
 * @param object
 * @param key
 * @returns {boolean|boolean}
 */
const hasProperty = (object, key) =>
  object ? Object.hasOwnProperty.call(object, key) : false;
const hasProps = (arg) =>
  hasProperty(arg, 'provider') && hasProperty(arg, 'props');

export const withContextProviders = (...providers) => (Component) => (props) =>
  providers.reduceRight((acc, prov) => {
    let Provider = prov;
    if (hasProps(prov)) {
      Provider = prov.context;
      const providerProps = prov.props;
      return <Provider {...providerProps}>{acc}</Provider>;
    }
    return <Provider>{acc}</Provider>;
  }, <Component {...props} />);

用法:

const Component = () => {...}
export withContextProviders(ThemeProvider, I8nProvider)(Component)
// OR
export withContextProviders({provider: ThemeProvider, props: {darkMode: true}}, I8nProvider)(Component)
// OR
export withContextProviders(ThemeProvider, {provider: I8nProvider, props: {lang: 'en'}})(Component)
// OR
const providers = [{provider: ThemeProvider, props: {darkMode: true}}, {provider: I8nProvider, props: {lang: 'en'}}]
export withContextProviders(...providers)(Component)

注意:这不会产生单例,即如果两个组件都用这个 HoC 包装,它们都将获得自己的上下文实例。对于这种情况,建议在根级别使用 provider 包装组件

同样,拥有多个级别是完全很好

【讨论】:

  • 感谢您的详细回答。我认识到“清理”金字塔并没有真正的性能优势,因为这主要是一个风格问题。但该代码看起来是一个不错的替代品,可以替代文字金字塔的代码。
猜你喜欢
  • 2016-10-14
  • 1970-01-01
  • 2018-06-03
  • 2017-06-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-04
相关资源
最近更新 更多