【问题标题】:Using react's context to allow rendering of a child component into a parent/grandparent/great-grandparent.... Anti-pattern?使用 react 的上下文允许将子组件呈现为父/祖父/曾祖父.... 反模式?
【发布时间】:2016-04-01 14:17:20
【问题描述】:

我对此有一个概念证明,这似乎可行,但我的一部分想知道这是否真的是一个好主意,以及是否有使用 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.消费者

更高阶的组件。每个实例都配置为“监视”附加到给定名称空间的元素。它使用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 的脚趾上,或者产生了一个普遍的反模式。

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


有什么智慧之言吗?


第 1 号更新

对上述内容的一些补充说明。

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

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

消费者实现:

<InjectableHeader />

生产者实现:

InjectIntoHeader(<FooButton />)(FooPage)

我认为这很明确,也很容易理解。我确实喜欢我可以在它最关心的地方创建按钮,这使我能够与同行建立更牢固的关系。

我也明白 redux flow 可能是正确的想法。感觉就像我让自己变得更加困难 - 我不禁认为这种技术可能有一些优点。

这有什么特别的坏主意吗?


第 2 条更新

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

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

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


其他示例

这里有一些其他项目/想法试图使用各种技术来解决相同(ish)的问题:

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

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

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

【问题讨论】:

  • 我更喜欢使用 Redux,如果还有其他原因,因为一致性,这对以后维护代码有很大帮助。
  • 我可以理解最好保持单一模式,但我不知道我的生产者/消费者模式是否会增加那么多额外的维护开销。我必须将两个字符串匹配在一起(查找全部)以查看生产的产品在哪里被消耗。使用 Redux,我必须执行操作,然后检查正在调用哪些 reducer,然后通过 state 进行选择,当组件卸载时,我需要对我的 state 执行清理过程。虽然,即使说了所有这些,我认为你可能仍然是对的。我只是想尝试进一步探索,看看是否还有更多内容。

标签: javascript reactjs redux react-redux


【解决方案1】:

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

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

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

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

【讨论】:

  • 不错的总结。有兴趣看到第三个项目符号“何时使用内部状态和道具”,尽管我想这很明显:当父母和孩子之间存在明确的关系时,其他人不需要知道。
  • 不错的建议,我可能会在我有空的时候添加这个:)
  • 我明白了,我真的明白了。对于行为流,它总是对我来说是 redux。在我真正想要的只是一个可以渲染组件的占位符/容器的情况下,我不禁觉得这有点矫枉过正。行为只属于被渲染的组件。
  • 我明白了,redux 是一个相当庞大的东西,包含许多不同的组件(动作、reducers、容器、存储、中间件......),如果你只将它用于这么小的东西,它可能看起来很重应用程序的一部分。在那种情况下,如果我是你,我会研究 Reflux,它更轻量级,更适合处理单个项目/变量和触发动作。它可能会在不到 10 行代码中解决您的问题。
【解决方案2】:

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

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

最好的一点是我的可注入目标不需要明确知道将要注入其中的每个可能的组件。

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

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

如您所见,我尝试使组件绑定(目标/源)尽可能明确。您必须在代码中实际导入和绑定所有相应的目标。这很有帮助,因为您可以获得目标/源绑定的编译时检查(以及排序)。比基于魔术字符串的绑定更有帮助。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-18
    • 2022-10-23
    • 2018-04-28
    相关资源
    最近更新 更多