使用 react 的上下文允许将子组件渲染为父/祖父母/曾祖父母......反模式? [英] Using react's context to allow rendering of a child component into a parent/grandparent/great-grandparent.... Anti-pattern?

查看:53
本文介绍了使用 react 的上下文允许将子组件渲染为父/祖父母/曾祖父母......反模式?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对此有一个概念证明,这似乎有效,但我的一部分想知道这是否真的是一个好主意,以及是否有使用 Redux 或替代策略之类的更好的解决方案.

I have a proof of concept for this, which seems to work, but a part of me is wondering if this is really a good idea and if there is perhaps a better solution out there using something like Redux or an alternative strategy.

问题

基本上,我的整个应用程序都有一个基本的 React 组件,其中包含许多您可能期望的典型组件,页眉、菜单、页脚等.

Basically, I have a base React component for my entire application which has a bunch of typical components that you might expect, header, menu, footer etc etc.

在我的树的更深处(更远)我有一个组件,如果我可以在我的标题组件中安装一个新的菜单项,那将会很棒.标头组件当然就在我的应用程序的顶部,因此访问被拒绝.

Further down my tree (much further) I have a component for which it would be awesome if I could mount a new menu item for within my header component. The header component of course lives right at the top of my application so access is denied.

这只是一个这样的例子,但这是我从多个角度遇到的问题案例.

That is just one such example, but it's a problem case I have hit from many angles.

我的疯狂解决方案

我研究了使用 React 的上下文来公开允许子组件声明他们希望出现在标题中的任何其他元素的函数.

I looked into using React's context in order to expose functions that would to allow child components to declare any additional elements they would like to appear within the header.

在尝试了这个概念之后,我最终将它重构为一个非常通用的解决方案,它本质上是一个 React Element 消息传递系统.此解决方案分为三个部分.

After playing around with the concept I eventually refactored it into a pretty generic solution that is essentially a React Element messaging system. There are three parts to this solution.

1.提供者

单实例组件与 Redux 的 Connect 组件非常相似.她本质上是接收和传递消息的引擎.她的基本结构(以上下文为重点)是:

Single instance component much in the same vein as Redux's Connect component. She's essentially the engine that receives and passes the messages along. Her basic structure (Context focused) is:

class ElementInjectorProvider extends Component {
  childContextTypes: {

    // :: (namespace, [element]) -> void
    produceElements: PropTypes.func.isRequired,

    // :: (namespace, [element]) -> void
    removeElements: PropTypes.func.isRequired,

    // :: (listener, namespace, ([element]) -> void) -> void
    consumeElements: PropTypes.func.isRequired,

    // :: (listener) -> void
    stopConsumingElements: PropTypes.func.isRequired,

  }

  /* ... Implementation ... */
}

2.制作人

高阶组件.每个实例都可以通过 produceElements 上下文项生成"元素,为特定命名空间提供元素,然后通过 removeElements 移除元素(在组件卸载的情况下).

A higher order component. Each instance can "produce" elements via the produceElements context item, providing elements for a specific namespace, and then remove the elements (in case of component unmount) via removeElements.

function ElementInjectorProducer(config) {
  const { namespace } = config;

  return function WrapComponent(WrappedComponent) {
    class ElementInjectorConsumerComponent {
      contextTypes = {
        produceElements: PropTypes.func.isRequired,
        removeElements: PropTypes.func.isRequired
      }

      /* ... Implementation ... */
    }

    return ElementInjectorProducerComponent;
  };
}

3.消费者

高阶组件.每个实例都被配置为监视"附加到给定命名空间的元素.它使用 consumeElements 通过回调函数注册开始"监听,并使用 stopConsumingElements 取消注册消费.

A higher order component. Each instance is configured to "watch" for elements attached to a given namespace. It uses consumeElements to "start" the listening via a callback function registration and stopConsumingElements to deregister the consumption.

function ElementInjectorConsumer(config) {
  const { namespace } = config;

  return function WrapComponent(WrappedComponent) {
    class ElementInjectorConsumerComponent {
      contextTypes = {
        consumeElements: PropTypes.func.isRequired,
        stopConsumingElements: PropTypes.func.isRequired
      }

      /* ... Implementation ... */
    }

    return ElementInjectorConsumerComponent;
  };
}

<小时>

这是我打算做的事情的粗略概述.基本上,当您查看它时,它是一个消息传递系统.也许可以进一步抽象.


That's a rough overview of what I am intending on doing. Basically it's a messaging system when you look at it. And perhaps could be abstracted even further.

我已经在使用 redux,猜猜 Redux 有什么用?所以我不禁觉得虽然这对我有用,但也许这不是一个好的设计,我无意中站在了Redux的脚趾上或产生了一般的反模式.

I already have redux in play, and guess what Redux is good for? So I can't help but feel that although this is working for me, perhaps it's not a good design and that I have inadvertently stood on Redux's toes or produced a general anti-pattern.

我想我没有直接使用 Redux 的唯一原因是因为我正在生产元素,而不是简单的状态.我可以沿着创建元素描述符对象的路线,然后通过 Redux 向下传递,但这本身就很复杂.

I guess the only reason I didn't jump straight into using Redux for this is is that because I am producing Elements, not simple state. I could go down the route of creating element descriptor objects and then pass that down through Redux, but that's complicated in itself.

有什么好话吗?

更新 1 号

对上述内容进行一些补充说明.

Some additional clarification on the above.

这允许我在我的完整组件树上上下甚至从左到右注入元素.我知道大多数 React Context 示例都描述了从祖父组件到孙组件的数据注入.

This allows me to inject Elements both up and down, and even left to right, on my full component tree. I know most React Context examples describe the injection of data from a Grandparent into a Grandchild component.

此外,我希望上述实现能够从开发人员那里抽象出任何有关 Context 用法的知识.事实上,我很可能会使用这些 HOFS 来创建特定于用例且更加明确的其他包装器.

Also, I would want the above implementation to abstract away from the developer any knowledge of Context usage. In fact I would most likely use these HOFS to create additional wrappers that are specific to use cases and far more explicit.

消费者实现:

<InjectableHeader />

生产者实现:

InjectIntoHeader(<FooButton />)(FooPage)

我认为这很明确并且易于遵循.我确实喜欢我可以在它最关心的地方创建按钮,这使我能够与同行建立更牢固的关系.

It's pretty explicitly I think and easy to follow. I do like that I can create the button where it is most cared about which grants me the ability to create stronger relationships with it's peers.

我也知道 redux 流程可能是正确的想法.只是感觉我让自己变得更难了 - 我不禁想到这种技术可能有一些优点.

I also get that redux flow is probably the right idea. It just feels like I make it a lot harder for myself - I can't help but think there may be some merit to this technique.

有什么理由认为这是个坏主意吗?

Is there any reason this is specifically a bad idea?

更新第 2 号

好的,我现在确信这是一个坏主意.我基本上打破了应用程序的可预测性,并使单向数据模型提供的所有好处无效.

Ok, I am now convinced this is a bad idea. I am basically breaking the predictability of the application and null'ifying all the benefits that a uni-directional data model provides.

我仍然不相信使用 Redux 是这种情况下的最佳解决方案,而且我已经想出了一个更明确的单向解决方案,它使用了上面的一些概念,但没有任何上下文魔法.

I am still not convinced that using Redux is specifically the best solution for this case, and I have dreamt up a more explicit uni-directional solution that uses some of the concepts from above, without any context magic though.

如果我认为可行,我会发布任何解决方案作为答案.做不到这一点,我会去 Redux 并为自己没有早点听你而自责.

I'll post any solution as an answer if I think it works. Failing that, I'll go Redux and kick myself for not listening to you all sooner.

其他示例

以下是一些其他项目/想法,试图使用各种技术解决相同的(ish)问题:

Here are a few other projects/ideas trying to solve the same(ish) problem using a variety of techniques:

https://joecritchley.svbtle.com/portals-in-reactjs

https://github.com/davidtheclark/react-displace

https://github.com/carlsverre/react-outlet

推荐答案

我对何时使用 redux/flux/reflux/anothingelseux 以及何时使用 context 的想法:

My idea on when to use redux/flux/reflux/anothingelseux and when to use context:

  • -ux 存储可用于以横向方式存储组件之间共享的信息.这通常是您的用例:在树中没有任何其他明显连接且彼此相距很远的组件之间进行通信.
  • 当您不知道孩子会在哪里或有多少人需要这些信息时,上下文有助于为他们提供信息.例如,我使用地图的上下文在当前视口上向其子项提供信息.我不知道是否所有的孩子都会使用它,但他们都有潜在的兴趣,他们不应该改变它.

在您的情况下,我会说 -ux 方式是他们要走的路,没有理由您的包装器组件应该处理与它无关的逻辑,并且代码会变得晦涩难懂.想象一下,开发人员稍后会查看您的代码,并看到您通过上下文收到此信息.任何父母都可以发送它,因此他需要检查它的发送位置.您的包装器组件也会发生同样的情况,如果类似的操作开始成倍增加,您将处理其中的许多方法和处理程序,而这些方法和处理程序在那里无关紧要.

I would say in your case the -ux way is they way to go, there is no reason why your wrapper component should handle logic it has nothing to do with, and the code would be obscure. Imagine the dev going to your code later on and seeing that you receive this through the context. Any parent could have sent it, so he will need to check at where it has been sent. Just the same happens to your wrapper component, if similar operation start to multiply you will handle with many methods and handlers in it that have nothing to do there.

拥有一个包含 action 和 reducer 的 store 可以将您的案例中的关注点分开,这将是最易读的做事方式.

Having a store with action and reducers allows to separate the concerns in your case and would be the most readable way to do things.

这篇关于使用 react 的上下文允许将子组件渲染为父/祖父母/曾祖父母......反模式?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆