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

Angular 2+/4/5/6/7: Smart, dumb and deeply nested component communication

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

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

关于 smart/grand/parent/child 组件如何在 MULTI-LEVEL(至少 3 级)链上上下通信和传递数据的方式有多种选择和条件。我们希望将我们的 'smart' (grand)parent 组件作为唯一可以访问我们的数据服务(或 atomic/immutable 存储)的组件,它将推动与 'dumb'(盛大)children。我们看到的选项是:

  1. Anti-pattern(?):通过@Input/@Output 绑定在组件链中上下传递数据。这就是某些人所说的 'extraneous properties' 或 'custom event bubbling problem'(例如:here and here)。不行。
  2. Anti-pattern:智能组件通过@ViewChildren 或@ContentChilden 访问dumb (grand)children。这再次对 children 进行了硬连线,但仍然没有为(grand)children 创建一个干净的机制来将数据向上传递给智能组件。
  3. angular.io 食谱 here and an excellent post here 中描述的共享消息服务。
  4. ?

现在如果是“3”,哑巴(大)children 必须注入消息服务。这让我想到了我的问题:

Q1:每个 'dumb'(grand)children 都注入了​​消息服务,这在直觉上似乎很奇怪。消息服务的最佳做法是为这个家庭提供专用服务,还是依托于上述 'smart' grandparent 负责的数据服务?

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

Q2:如果 'smart' grand parent 正在与 redux-like 商店(对我们来说是 ngrx)通信怎么办?与 'dumb' 组件的通信最好通过 injected/dedicated 消息服务发生,还是最好将存储注入每个 'dumb' 组件......或者?请注意,inter-component 通信是 'actions'(例如:表单验证、禁用按钮等)和数据(即添加数据 to/update 商店或服务)的组合。

非常感谢您的想法!

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

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

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

viewModelForChildComponent: {

    property1NeededForChildComponent,

    property2NeededForChildComponent,

    viewModelForGrandChildComponent: {
        property1NeededForGrandChildComponent,

        property2NeededForGrandChildComponent,

        viewModelForGrandGrandChildComponent: {
            property1NeededForGrandGrandChildComponent,

            submitHandlerNeededForGrandGrandChildComponent
        }
    }
}

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

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

(更新:02-07-2019:这个 post 已经过时了——添加了 'store/ngrx' 模式)

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

任一个

  • 在整个嵌套组件链中向上、向下传递@Input/@Output 绑定(即处理 'custom event bubbling' 或 'extraneous properties' 的问题)

  • 使用 messaging/subscription 服务在该系列组件之间进行通信(很好的描述 here)并为链中的每个组件注入该服务。

或:

  • 响应式存储模式(例如 'ngrx')是另一种选择。请注意,IMO,智能组件和哑组件的概念仍然适用。即,哑组件从不直接访问商店。同样,智能组件是通过商店获取数据的主体。

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

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