将Angular组件从许多输入/输出重构为单个配置对象

时间:2017-12-10 20:46:11

标签: javascript angular angular-components

我的组件通常首先拥有多个@Input@Output属性。当我添加属性时,将单个配置对象切换为输入似乎更简洁。

例如,这是一个具有多个输入和输出的组件:

export class UsingEventEmitter implements OnInit {
    @Input() prop1: number;
    @Output() prop1Change = new EventEmitter<number>();
    @Input() prop2: number;
    @Output() prop2Change = new EventEmitter<number>();

    ngOnInit() {
        // Simulate something that changes prop1
        setTimeout(() => this.prop1Change.emit(this.prop1 + 1));
    }
}

及其用法:

export class AppComponent {
    prop1 = 1;

    onProp1Changed = () => {
        // prop1 has already been reassigned by using the [(prop1)]='prop1' syntax
    }

    prop2 = 2;

    onProp2Changed = () => {
        // prop2 has already been reassigned by using the [(prop2)]='prop2' syntax
    }
}

模板:

<using-event-emitter 
    [(prop1)]='prop1'
    (prop1Change)='onProp1Changed()'
    [(prop2)]='prop2'
    (prop2Change)='onProp2Changed()'>
</using-event-emitter>

随着属性数量的增加,似乎切换到单个配置对象可能更清晰。例如,这是一个采用单个配置对象的组件:

export class UsingConfig implements OnInit {
    @Input() config;

    ngOnInit() {
        // Simulate something that changes prop1
        setTimeout(() => this.config.onProp1Changed(this.config.prop1 + 1));
    }
}

及其用法:

export class AppComponent {
    config = {
        prop1: 1,

        onProp1Changed(val: number) {
            this.prop1 = val;
        },

        prop2: 2,

        onProp2Changed(val: number) {
            this.prop2 = val;
        }
    };
}

模板:

<using-config [config]='config'></using-config>

现在我可以通过多层嵌套组件传递配置对象引用。使用配置的组件将调用config.onProp1Changed(...)之类的回调,这会导致配置对象重新分配新值。所以我们似乎仍然有单向数据流。另外,添加和删除属性并不需要在中间层中进行更改。

将单个配置对象作为组件的输入,而不是具有多个输入和输出,是否有任何缺点?避免@OutputEventEmitter这样会导致以后可能遇到的任何问题吗?

5 个答案:

答案 0 :(得分:9)

如果我个人认为我需要4个以上的输入和输出,我将检查我的方法来再次创建我的组件,也许应该是多个组件,而我做错了什么。 无论如何,即使我需要大量的输入和输出,也不会在一个配置中使用它,因为这个原因:

1-很难知道输入和输出中应该包含什么,像这样: (考虑一个具有html input元素和标签的组件)              

想象一下,如果您只有3个组件,而您应该在1或2个月后回来从事该项目,或者其他人将与您合作或使用您的代码! 真的很难理解您的代码。

2-缺乏表现。对于angular来说,观看单个变量比观看数组或对象便宜得多。除了考虑我在第一个例子中给您的示例之外,为什么还要强制跟踪那些永远不会改变的标签以及始终在变化的值。

3-更难跟踪变量和调试。 angular本身带有难以调试的令人困惑的错误,为什么我要使其变得更难。对我来说,逐个跟踪和修复任何错误的输入或输出要比在一个配置变量中完成一堆数据更容易。

我个人更喜欢将组件分解得尽可能小并测试每个组件。然后从小的组件中组成更大的组件,而不是只有一个大组件。

更新: 我将这种方法用于一次输入,而没有更改数据(如label)

@Component({
selector: 'icon-component',
templateUrl: './icon.component.html',
styleUrls: ['./icon.component.scss'],
inputs: ['name', 'color']
});

export class IconComponent implements OnInit {
 name: any;
 color: any;

 ngOnInit() {
 }
}

HTML:

<icon-component name="fa fa-trash " color="white"></icon-component>

使用此方法,角度不会跟踪组件内部或外部的任何变化。 但是如果您的变量在父组件中发生更改,则使用@input方法,您也将在组件内部进行更改。

答案 1 :(得分:3)

  • 使用Input除了具有明显的功能外,还可以使您的组件具有声明性且易于理解。

  • 将所有配置放入一个庞大的对象中,该对象肯定会增长(相信我),出于上述所有原因以及进行测试,这是一个坏主意。

  • 使用简单的input属性测试组件的行为要容易得多,而不是提供一个巨大的令人困惑的对象。

  • 您会往回想,就像jQuery插件的工作方式一样,您会调用一个名为init的函数,然后提供一整套甚至没有的配置记住是否应该提供,然后将副本不断复制并粘贴到组件中甚至不需要它们的所有未知且不断增长的对象

  • 使用简单的Input可以很容易地创建默认值,而对于创建默认值的对象则有点混乱。

如果您有太多类似的InputOutput,则可以考虑以下内容:

1-您可以创建一个Base类,并放置所有类似的Input/Output,然后从中扩展所有组件。

export class Base{
    @Input() prop1: number;
    @Output() prop1Change = new EventEmitter<number>();
    @Input() prop2: number;
    @Output() prop2Change = new EventEmitter<number>();
}

@Component({})
export class MyComponent extends from Base{
      constructor(){super()}
}

2-如果您不喜欢这样,则可以使用组合并创建可重复使用的mixin并像这样应用所有Input/Output

下面是一个可用于应用mixin的函数的示例,NOTE不一定完全是您想要的,而您需要根据需要进行调整。

export function applyMixins(derivedCtor: any, baseCtors: any[]) {
  baseCtors.forEach(baseCtor => {
    Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
      derivedCtor.prototype[name] = baseCtor.prototype[name];
    });
  });
}

然后创建您的mixins:

export class MyMixin{
    @Input() prop1: number;
    @Output() prop1Change = new EventEmitter<number>();
    @Input() prop2: number;
    @Output() prop2Change = new EventEmitter<number>();
}

applyMixins(MyComponent, [MyMixin]);

3-您可以为输入提供默认属性,因此只有在需要时才覆盖它们:

export class MyComponent{
    @Input() prop1: number = 10; // default 
}

答案 2 :(得分:2)

  

将单个配置对象作为组件的输入,而不是具有多个输入和输出,是否有不利之处?

是的,当您要切换到onpush change detection strategy时(大型项目通常需要使用它来缓解由过多渲染周期引起的性能问题),角度不会检测到在内部发生的更改。 strong>您的配置对象。

  

这样会避免@Output和EventEmitter引起任何可能稍后引起我注意的问题吗?

是的,如果您开始远离@Output,并且在模板中直接对config对象本身进行操作,那么就会在视图中引起副作用,这将是难以理解的根源将来发现错误。您的视图永远都不要修改注入的数据。从这种意义上讲,它应该保持“纯粹”,并且仅通过事件(或其他回调)通知控制组件发生了什么事情。

更新:在再次查看了帖子中的示例之后,看起来您并不是要直接对输入模型进行操作,而是直接通过config对象传递事件发射器。通过@input传递回调(这是您的隐式操作)也具有it's drawbacks,例如:

  • 您的组件越来越难以理解和推理(其输入与输出是什么?)
  • 不能再使用banana box syntax

答案 3 :(得分:2)

我想说可以为Input使用单个配置对象,但是您应该始终坚持OutputInput定义您的组件从外部需要什么,其中一些可能是可选的。但是,Output完全是组件的业务,应在其中定义。如果您依靠用户来传递这些功能,则要么必须检查undefined函数,要么就继续调用这些函数,就好像它们总是在config中传递一样,如果有的话,使用它们可能很麻烦即使用户不需要它们,也有太多事件无法定义。因此,始终在组件中定义Output并发出您需要发出的任何东西。如果用户不将那些事件绑定到函数,那很好。

此外,我认为对config使用一个Input并不是最佳实践。它隐藏了真实的输入,用户可能必须查看代码或文档中的内容以了解应该传递的内容。但是,如果Input分别定义,则用户可以使用{ {3}}

此外,我认为这也可能会破坏变更检测策略。

让我们看下面的示例

@Component({
    selector: 'my-comp',
    template: `
       <div *ngIf="config.a">
           {{config.b + config.c}}
       </div>
    `
})
export class MyComponent {
    @Input() config;
}

让我们使用它

@Component({
    selector: 'your-comp',
    template: `
       <my-comp [config]="config"></my-comp>
    `
})
export class YourComponent {
    config = {
        a: 1, b: 2, c: 3
    };
}

对于单独的输入

@Component({
    selector: 'my-comp',
    template: `
       <div *ngIf="a">
           {{b + c}}
       </div>
    `
})
export class MyComponent {
    @Input() a;
    @Input() b;
    @Input() c;
}

让我们用这个

@Component({
    selector: 'your-comp',
    template: `
       <my-comp 
          [a]="1"
          [b]="2"
          [c]="3">
       </my-comp>
    `
})
export class YourComponent {}

如上所述,您必须查看YourComponent的代码以查看要传递的值。此外,还必须在各处键入config才能使用这些{{1} } s。另一方面,您可以清楚地看到在第二个示例中更好地传递了哪些值。如果您使用的是语言服务

,甚至可以获取一些智能提示。

另一件事是,第二个示例将更好地扩展。如果您需要添加更多Input,则必须一直编辑Input,这可能会破坏您的组件。但是,在第二个示例中,很容易添加另一个config,并且您无需触摸工作代码。

最后但并非最不重要的一点是,您无法真正以自己的方式提供双向绑定。您可能知道,如果在Input中有Inputdata中有Output,则组件的使用者可以使用双向绑定糖语法和简单类型

dataChange

当您使用

发出事件时,这将更新父组件上的<your-comp [(data)]="value">
value

希望这阐明了我对单个this.dataChange.emit(someValue)

的看法

修改

我认为单个Input存在一个有效的情况,其中还定义了一些Input。如果要开发类似图表组件的程序,而该程序通常需要复杂的选项/配置,那么最好使用单个function。这是因为该输入设置一次且永不更改,因此最好将图表选项放在一个位置。此外,用户可能会传递一些功能来帮助您绘制图例,工具提示,x轴标签,y轴标签等。 像这样的输入在这种情况下会更好

Input

答案 4 :(得分:2)

如果您要将输入参数捆绑为一个对象,我建议这样做:

export class UsingConfig implements OnInit {
    @Input() config: any;
    @Output() configChange = new EventEmitter<any>();


    ngOnInit() {
        // Simulate something that changes prop1
        setTimeout(() => 
          this.configChange.emit({
              ...this.config, 
              prop1: this.config.prop1 + 1
          });
        );
    }
}
  • 更改属性时,您正在创建新的配置对象。
  • 您正在使用Output-Event发出更改的配置对象。

这两点都确保ChangeDetection可以正常工作(假设您使用更有效的OnPush策略)。此外,在调试时更容易遵循逻辑。

修改: 这是父组件中显而易见的部分。

模板:

<using-config [config]="config" (configChange)="onConfigChange($event)"></using-config>

代码:

export class AppComponent {
    config = {prop1: 1};

    onConfigChange(newConfig: any){
      // if for some reason you need to handle specific changes 
      // you could check for those here, e.g.:
      // if (this.config.prop1 !== newConfig.prop1){...

      this.config = newConfig;
    }
  }