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