Angular 2+/4/5/6/7:智能、愚蠢和深度嵌套的组件通信

Posted

技术标签:

【中文标题】Angular 2+/4/5/6/7:智能、愚蠢和深度嵌套的组件通信【英文标题】:Angular 2+/4/5/6/7: Smart, dumb and deeply nested component communication 【发布时间】:2017-10-05 14:06:47 【问题描述】:

注意:为简单起见,将组件深度视为:

- Smart (grand)parent level 0
  - dumb child level 1
   ....
    - dumb grandchild level 2
      ....)

关于智能/大/父/子组件如何在多级(至少 3 级)链上进行通信和传递数据的方式有多种选择和条件。我们希望将我们的“智能”(祖)父组件保留为唯一可以访问我们的数据服务(或原子/不可变存储)的组件,它将推动与“哑”(孙)子级的信息交换。我们看到的选项是:

    Anti-pattern(?):通过@Input/@Output 绑定向下和向上传递数据。这就是一些人所说的“无关属性”或“自定义事件冒泡问题”(例如:here 和here。)。不行。 反模式:智能组件通过@ViewChildren 或@ContentChilden 访问哑(大)子级。这再次硬连线孩子,仍然没有为(孙)孩子创建一个干净的机制将数据向上传递给智能组件。 如 angular.io 食谱 here 和一篇出色的帖子 here 中所述的共享消息服务。 ?

现在在“3”的情况下,哑(孙)子必须注入消息服务。这让我想到了我的问题:

Q1:每个“笨”(孙)子都注入消息服务在直觉上似乎很奇怪。消息服务的最佳做法是成为这个家庭的专用服务,还是搭载上面提到的“聪明”祖父母负责的数据服务?

Q1A:此外,如果所有组件都将注入服务,这比在链上添加@Input/@Output 绑定要好得多吗? (我看到“愚蠢”组件需要某种方式来获取信息的论点)

Q2:如果“聪明”的祖父母正在与类似 redux 的商店(对我们来说是 ngrx)进行通信怎么办?再一次,与“哑”组件的通信最好通过注入/专用消息服务进行,还是最好将存储注入每个“哑”组件......或者?请注意,组件间通信是“操作”(例如:表单验证、禁用按钮等)和数据(即,将数据添加到/更新存储或服务)的组合。

非常感谢您的想法!

【问题讨论】:

【参考方案1】:

(更新:02-07-2019:这篇文章已经过时了——添加了“store/ngrx”模式)

因此,在进一步研究之后,当谈到如何最好地向下和向上通信嵌套组件链时,似乎真的只有两种选择——浮士德式的讨价还价:

任何一个

在嵌套组件链中向上、向下和整个传递 @Input/@Output 绑定(即处理“自定义事件冒泡”或“无关属性”的问题)

使用消息传递/订阅服务在这一系列组件之间进行通信(很好的描述 here),并为链中的每个组件注入该服务。

或者:

反应式存储模式(例如“ngrx”)是另一种选择。请注意,IMO,智能组件和哑组件的概念仍然适用。也就是说,哑组件永远不会直接访问商店。同样,智能组件是通过商店获取数据的主要部分。

我个人是使用智能和演示(“哑”)组件的支持者。添加“商店”也应该有选择地完成,因为它显着增加了从架构、一致的实施模式、开发和维护到新员工入职的流程成本。名义上,“哑”组件只需要 @Inputs 和 @Outputs 就可以了。它不关心它在组件树中有多深或多浅——这就是应用程序的问题。事实上,它并不关心什么应用程序首先使用它。同时,如果将特定于应用程序的服务注入其中,则深层组件不是很笨拙或可移植的。顺便说一句,对应的“智能”组件实际上是向其家族树中需要它的任何哑组件提供中间服务(通过一流的 @Injectable 服务或类似 redux 的存储)。只要孙子以某种方式发出需要采取服务/存储操作的信号(再次通过@Input/@Output 链),智能组件也不关心其直接子代@Inputs 之外的组件。这样,智能组件也可以跨应用程序线传输。

鉴于此,浮士德式的交易 IMO 倾向于使用 @Input/@Output 链来解决它带来的所有提到的问题。也就是说,我会密切关注这一点,如果有人知道的话,我欢迎干净和解耦的替代方案。

【讨论】:

@Input/@Output 方法禁止您在父母和孩子之间引入第 3 方组件(例如:Angular 的 <router-outlet></router-outlet> 或 Angular Material 的 <mat-card>...</mat-card>)。 (更准确地说,您可以使用@Input 但不能使用@Output;@Output 事件不会冒泡。) 当您有子组件并且您需要为子组件使用<router-outlet> 时,这种方法很糟糕。 我通常会遇到一个问题,即我的子组件是可路由的,但我也可以让它们变得愚蠢。由于通过路由器插座激活它们不允许我使用@Input/@Output,因此替代方法是使用 *ngIf 来激活这些组件。在父组件的路由更改时使用 *ngIf 激活组件是一个好习惯吗,因为这允许我使用 @Input/@Output 创建哑组件?【参考方案2】:

为什么 #1 是反模式?祖父组件拥有数据并通过@Input 参数将其传递给哑子组件。当事件发生时(通过@Output 事件发射器),哑子组件简单地调用回调,从而导致祖父组件操作数据。对我来说似乎很干净。

编辑:我明白您关于通过许多中间层重复传递值(如提交处理程序)的观点。也许可以在父组件中创建代表您的组件树的嵌套结构。然后每个组件可以传递它需要的属性,加上一个对象传递给下一个组件。然后每个组件只知道它下面的那个:

// Parent component builds this object (or gets a service to do it)

viewModelForChildComponent: 

    property1NeededForChildComponent,

    property2NeededForChildComponent,

    viewModelForGrandChildComponent: 
        property1NeededForGrandChildComponent,

        property2NeededForGrandChildComponent,

        viewModelForGrandGrandChildComponent: 
            property1NeededForGrandGrandChildComponent,

            submitHandlerNeededForGrandGrandChildComponent
        
    

【讨论】:

感谢您的意见。请注意,再次就脱钩而言,IMO ***组件不应该真正关心或了解其孙辈,而不仅仅是提供间接服务,这是其主要职责。顺便说一句,这与我原来问题中的选项“2”没有太大区别。 不客气。我想我明白你在说什么——***父组件现在不仅要知道子组件需要什么,还要知道它们的确切层次结构。我个人对此表示同意,因为我将这种协调视为父组件的工作。或者我创建一个服务来将 JSON 转换为包含演示数据/回调的分层视图模型(适用于单元测试)。你是对的,这有点像你的第二个选项,但实际上并没有触及视图子项。无论如何,我很想知道你最终是如何解决这个问题的。祝你好运!【参考方案3】:

Input() 和 Output() 绑定也是处理这个问题的一种完全合法的方式。让智能组件处理生成值的逻辑,然后使用 Input() 和 Output() 沿着组件链简单地传递和接收值。

当然,这指出了智能/视图方法的缺点之一:更多文件;更多样板。这就是为什么我不会主张一种万能的单一方法。相反,请选择一种在您当前环境中有意义的方法(对于应用程序和您的组织)。

【讨论】:

我添加了为什么最好在嵌套组件链中解耦属性绑定的链接。但简单地说,如果一个提交/清除按钮组件嵌套 4 层深,其全部存在的理由是发出“提交”,为什么具有其他关注点分离的中间组件需要知道或关心?我同意主流观点,这是一种反模式。但是,如果关卡深度只有 1,那么我看不到通过 @Input/@Outputs 传递数据的问题。 编辑了我的答案,以进一步扩大赞成/反对讨论并解决您提出的问题。

以上是关于Angular 2+/4/5/6/7:智能、愚蠢和深度嵌套的组件通信的主要内容,如果未能解决你的问题,请参考以下文章

旋转数组

旋转数组

1,2,3,4,5,6,7最小比较和交换次数的排序次序为?

数组反转

189. 旋转数组

189-旋转数组