2

我对此有一个概念证明,这似乎可行,但我的一部分想知道这是否真的是一个好主意,以及是否有使用 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 来创建特定于用例且更加明确的额外包装器。

IE

消费者实现:

<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

4

2 回答 2

3

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

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

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

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

于 2016-04-01T15:07:08.117 回答
1

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

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

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

我对我所做的一切感到非常满意,以至于我创建了一个库:

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

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

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

于 2016-04-06T16:21:13.707 回答