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

时间:2016-04-01 14:17:21

标签: javascript reactjs redux react-redux

我有一个概念证据,这似乎有效,但我的一部分想知道这是否真的是一个好主意,如果有一个更好的解决方案,使用像Redux或替代策略。

问题

基本上,我的整个应用程序都有一个基本的React组件,它有一些你可能期望的典型组件,标题,菜单,页脚等等。

继续我的树(更进一步)我有一个组件,如果我可以在我的标题组件中安装一个新的菜单项,那将是非常棒的。当然,标题组件位于我的应用程序的顶部,因此拒绝访问。

这只是一个这样的例子,但这是我从多个角度来看的一个问题。

我的疯狂解决方案

我研究了使用React的上下文,以便公开允许子组件声明他们想要在标题中出现的任何其他元素的函数。

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

1。提供者

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

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删除元素(如果是组件卸载)。

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。消费者

更高阶的组件。每个实例都配置为" watch"对于附加到给定命名空间的元素。它使用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;
  };
}

这是对我打算做什么的粗略概述。当你看它时,它基本上是一个消息传递系统。也许可以进一步抽象出来。

我已经玩过redux,猜猜Redux有什么用?所以我无法帮助,但我觉得虽然这对我有用,但也许这不是一个好的设计,而且我无意中站在了Redux的脚趾上或产生了一般的反模式。 / p>

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

任何智慧的话语?

更新否1

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

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

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

即。

消费者实施:

<InjectableHeader />

生产者实施:

InjectIntoHeader(<FooButton />)(FooPage)

我非常明确地认为并且很容易理解。我确实喜欢这样我可以创建最关心的按钮,这使我有能力与它的同伴建立更牢固的关系。

我也知道redux流程可能是正确的想法。感觉就像我让自己变得更难 - 我无能为力,但认为这种技术可能有一些优点。

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

更新否2

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

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

如果我觉得它有效,我会发布任何解决方案作为答案。如果做不到这一点,我会去Redux,因为没有早点听你的话。

其他示例

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

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

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

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

2 个答案:

答案 0 :(得分:3)

关于何时使用redux / flux / reflux / anothingelseux以及何时使用上下文的想法:

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

我会说在你的情况下-ux方式是他们的方式去,没有理由你的包装器组件应该处理它与之无关的逻辑,并且代码将是模糊的。想象一下开发人员稍后会看到你的代码并看到你通过上下文得到这个。任何家长都可以发送它,所以他需要检查它的发送地点。你的包装器组件也会发生同样的情况,如果类似的操作开始繁殖,你将会处理其中没有任何关系的许多方法和处理程序。

拥有一个带有动作和减少器的商店可以分离您案件中的问题,并且是最易读的做事方式。

答案 1 :(得分:1)

好的,智慧可能会说使用Redux及其单向数据流是最好的解决方案。因此我将@Mijamo的答案设定为答案。

我最终创建了我在帖子中讨论的注射剂解决方案。到目前为止,它非常有用。它实际上非常有用,而且我已经能够使用其他技术制作一些太复杂的东西了。

最重要的是,我的注射目标不需要明确知道将注入其中的每种可能成分。

我对我所做的事情非常满意,因此我创建了一个库:

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

正如您所看到的,我尝试使组件绑定(目标/源)尽可能明确。您必须实际导入并绑定代码中的所有相应目标。这很有用,因为您可以获得目标/源绑定的编译时检查(well sorta)。比基于魔法字符串的绑定更有帮助。

无论如何,它可能仍然是一个疯狂的想法,但也许我疯了,这就是为什么我如此喜欢它。 :)