简体   繁体   English

使用react的上下文允许将子组件呈现为父/祖父母/曾祖父母...反模式?

[英]Using react's context to allow rendering of a child component into a parent/grandparent/great-grandparent… Anti-pattern?

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. 我有一个概念证明,似乎可行,但是我的一部分想知道这是否真的是一个好主意,或者是否存在使用Redux或替代策略之类的更好的解决方案。

The Problem 问题

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. 基本上,我的整个应用程序都有一个基本的React组件,其中包含一堆您可能期望的典型组件,标题,菜单,页脚等。

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. 那只是一个这样的例子,但这是一个我从多个角度都遇到问题的案例。

My Crazy Solution 我的疯狂解决方案

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的上下文,以公开允许子组件声明希望出现在标头中的任何其他元素的函数。

After playing around with the concept I eventually refactored it into a pretty generic solution that is essentially a React Element messaging system. 在研究了这个概念之后,我最终将其重构为一个非常通用的解决方案,该解决方案本质上是一个React Element消息传递系统。 There are three parts to this solution. 此解决方案包括三个部分。

1. The Provider 1.提供者

Single instance component much in the same vein as Redux's Connect component. 单实例组件与Redux的Connect组件在很大程度上相同。 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. The Producer 2.生产者

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 . 每个实例都可以通过produceElements上下文项“产生”元素,为特定名称空间提供元素,然后通过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. The Consumer 3.消费者

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. 它使用consumeElements通过回调函数注册“开始”监听,并使用stopConsumingElements注销消耗。

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. 也许可以进一步抽象。

I already have redux in play, and guess what Redux is good for? 我已经在玩Redux,猜猜Redux有什么用? 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的脚趾上或制作了一般的反图案。

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. 我想我没有直接使用Redux的唯一原因是因为我在生产Elements,而不是简单的状态。 I could go down the route of creating element descriptor objects and then pass that down through Redux, but that's complicated in itself. 我可以遵循创建元素描述符对象的方法,然后将其传递给Redux,但这本身很复杂。


Any words of wisdom? 有什么智慧的话吗?


UPDATE No 1 更新1

Some additional clarification on the above. 关于上述内容的一些其他说明。

This allows me to inject Elements both up and down, and even left to right, on my full component tree. 这使我可以在整个组件树上上下,甚至从左向右注入Elements。 I know most React Context examples describe the injection of data from a Grandparent into a Grandchild component. 我知道大多数React Context示例都描述了从祖父母到孙子组件的数据注入。

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. 实际上,我很可能会使用这些HOFS创建针对用例的更明确的其他包装。

ie

A consumer implementation: 消费者实现:

<InjectableHeader />

A producer implementation: 生产者实现:

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. 我确实喜欢这样,我可以在最关心的地方创建按钮,这使我能够与同伴建立更牢固的关系。

I also get that redux flow is probably the right idea. 我也认为redux流可能是正确的想法。 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? 有什么理由这是个坏主意吗?


UPDATE No 2 更新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. 我基本上打破了应用程序的可预测性,并且使单向数据模型提供的所有好处无效。

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是这种情况下的最佳解决方案,而且我梦dream以求的是更明确的单向解决方案,该解决方案使用上面的一些概念,但是没有任何上下文魔术。

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. 否则,我将去Redux踢自己,以免自己早点听别人的话。


Other examples 其他例子

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://joecritchley.svbtle.com/portals-in-reactjs

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

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

My idea on when to use redux/flux/reflux/anothingelseux and when to use context: 我关于何时使用redux / flux / reflux / anothingelseux以及何时使用上下文的想法:

  • -ux stores are useful to store information shared between component in a transversal way. -ux存储对于以横向方式存储组件之间共享的信息很有用。 This is typically your use case: communicating between components that don't have any other obvious connexion and that are far away from each other in the tree. 这通常是您的用例:在没有其他明显连接的组件之间相互通信,并且这些组件在树中彼此远离。
  • Context is useful to provide information to children when you don't know where they will be or how many of them will need them. 当您不知道孩子会在哪里或有多少需要他们时,上下文可以为孩子提供信息。 For example I use context for a map to provide information to its children on the current viewport. 例如,我为地图使用上下文,以在当前视口上向其子级提供信息。 I don't know if all the children will use it but they are all potentially interested and they are not supposed to change it. 我不知道所有的孩子是否都会使用它,但是他们都有潜在的兴趣,他们不应该更改它。

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. 在您的情况下,我想说的是-ux方法是他们要走的路,没有理由为什么您的包装器组件应该处理与它无关的逻辑,并且代码会变得晦涩难懂。 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. 包装器组件也会发生同样的情况,如果相似的操作开始相乘,您将处理许多与该组件无关的方法和处理程序。

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. 拥有一个带有动作和简化程序的商店可以区分您的情况,这将是最易读的处理方式。

Okay, so, wisdom probably says that using Redux and it's uni-directional data flow is the best solution. 好的,所以智慧可能说使用Redux及其单向数据流是最好的解决方案。 Therefore I have set the answer by @Mijamo as the answer. 因此,我将@Mijamo的答案设置为答案。

I did end up creating the injectables solution that I was talking about in my post. 最后,我确实创建了我在帖子中谈论的可注射解决方案。 So far it has been immensely useful. 到目前为止,它已经非常有用。 It's really really useful actually and I have already been able to produce some awesome stuff that would be too complicated using other techniques. 实际上,它真的非常有用,我已经能够产生一些很棒的东西,而使用其他技术则太过复杂了。

The best thing of all is that my injectable targets don't need to explicitly know about every possible component that will be injected in them. 最好的事情是,我的可注射目标不需要明确知道将要注射到其中的每种可能的组分。

I've been so happy with what I did that I created a library: 我对自己所做的事感到非常满意,以至于我创建了一个库:

https://github.com/ctrlplusb/react-injectables https://github.com/ctrlplusb/react-injectables

As you can see I try to make the component binding (target/source) as explicit as possible. 如您所见,我尝试使组件绑定(目标/源)尽可能明确。 You have to actually import and bind all respective targets in your code. 您实际上必须在代码中导入并绑定所有各自的目标。 This is helpful as you can get compile time checks (well sorta) for your target/source bindings. 这很有用,因为您可以获取目标/源绑定的编译时检查(很好的排序)。 Much more helpful than magic string based bindings. 比基于魔术字符串的绑定有用得多。

Anyways, it's probably still a crazy idea, but maybe I am crazy and that's why I love it so much. 无论如何,这可能仍然是一个疯狂的主意,但也许我疯了,这就是为什么我如此爱它。 :) :)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 从Parent调用子组件的方法反应是否反模式,为什么? - Is it anti-pattern to call child component's method from Parent in react and why? CSS-设置div宽度等于其“曾祖父母”的宽度 - CSS - Set div width equal to that of its “great-grandparent” 反应组件加载模式(或反模式?) - React component loading pattern (or anti-pattern?) 这是一个 React 反模式吗? - Is this a React anti-pattern? 访问 GrandParent 组件中的子组件 - Accessing Child Component in GrandParent component 试图找到曾祖父母的类名时使用Javascript / jQuery [object Object] - Javascript/jQuery [object Object] when trying to find great-grandparent class name 从父组件或祖父组件中清除 React 组件中的输入 - Clearing input within React Component from a parent or grandparent Component KnockoutJS-ViewModel祖父母-父母-使用ko.computed访问父/祖父母值的孩子 - KnockoutJS - ViewModel Grandparent - Parent - Child using ko.computed to access Parent/Grandparent Value 在子级上设置父状态是否反模式? - Is setting parent state on child mount an anti-pattern? 如何在 Angular 中将子组件的 @inputs 和 @outputs 隐式公开给祖父母? - How to expose child component's @inputs and @outputs implicitly to grandparent in Angular?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM