将 Angular 组件从多个输入/输出重构为单个配置对象 [英] Refactoring Angular components from many inputs/outputs to a single config object

查看:20
本文介绍了将 Angular 组件从多个输入/输出重构为单个配置对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的组件通常从具有多个 @Input@Output 属性开始.当我添加属性时,切换到单个配置对象作为输入似乎更清晰.

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

导出类 UsingEventEmitter 实现 OnInit {@Input() prop1: 数字;@Output() prop1Change = new EventEmitter();@Input() prop2: 数字;@Output() prop2Change = new EventEmitter();ngOnInit() {//模拟改变 prop1 的东西setTimeout(() => this.prop1Change.emit(this.prop1 + 1));}}

及其用法:

导出类 AppComponent {道具1 = 1;onProp1Changed = () =>{//已经使用 [(prop1)]='prop1' 语法重新分配了 prop1}道具2 = 2;onProp2Changed = () =>{//已经使用 [(prop2)]='prop2' 语法重新分配了 prop2}}

模板:

</using-event-emitter>

<小时>

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

导出类 UsingConfig 实现 OnInit {@Input() 配置;ngOnInit() {//模拟改变 prop1 的东西setTimeout(() => this.config.onProp1Changed(this.config.prop1 + 1));}}

及其用法:

导出类 AppComponent {配置 = {道具1:1,onProp1Changed(val: number) {this.prop1 = val;},道具2:2,onProp2Changed(val: number) {this.prop2 = val;}};}

模板:

<小时>

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

将单个配置对象作为组件的输入而不是多个输入和输出有什么缺点吗?像这样避免 @OutputEventEmitter 会导致任何可能在以后赶上我的问题吗?

解决方案

我认为对 Input 使用单个配置对象是可以的,但你应该坚持使用 Output s 在所有的时间.Input 定义了您的组件从外部需要什么,其中一些可能是可选的.但是,Output 完全是组件的业务,应该在其中定义.如果您依赖用户传递这些函数,您要么必须检查 undefined 函数,要么继续调用这些函数,就好像它们总是在配置中传递一样,这可能会很麻烦使用您的如果有太多事件需要定义,即使用户不需要它们.因此,始终在组件中定义 Output 并发出您需要发出的任何内容.如果用户不绑定函数那些事件,那很好.

另外,我认为 Input 使用单个 config 不是最佳做法.它隐藏了真正的输入,用户可能需要查看您的代码或文档的内部以找出他们应该传入的内容.但是,如果您的 Input 是单独定义的,则用户可以获得一些智能感知语言服务

等工具

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

我们来看下面的例子

@Component({选择器:'my-comp',模板:`<div *ngIf="config.a">{{config.b + config.c}}

`})导出类 MyComponent {@Input() 配置;}

让我们使用它

@Component({选择器:'你的补偿',模板:`<my-comp [config]="config"></my-comp>`})导出类 YourComponent {配置 = {a: 1, b: 2, c: 3};}

对于单独的输入

@Component({选择器:'my-comp',模板:`<div *ngIf="a">{{b + c}}

`})导出类 MyComponent {@Input() 一个;@Input() b;@Input() c;}

让我们用这个

@Component({选择器:'你的补偿',模板:`<my-comp[a]="1"[b]="2"[c]="3"></my-comp>`})导出类 YourComponent {}

正如我上面所说的,您必须查看 YourComponent 的代码以了解您正在传入哪些值.此外,您必须在任何地方键入 config 以使用那些 Input .另一方面,您可以更清楚地看到在第二个示例中传递了哪些值.如果您使用语言服务

,您甚至可以获得一些智能感知

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

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

当您使用

发出事件时,这将更新父组件上的 value

this.dataChange.emit(someValue)

希望这能澄清我对单个 Input

的看法

编辑

我认为对于单个 Input 有一个有效的情况,其中还定义了一些 function .如果您正在开发类似图表组件的东西,它通常需要复杂的选项/配置,实际上最好使用单个 Input.这是因为,该输入设置一次且永不更改,最好将图表选项放在一个地方.此外,用户可能会传递一些函数来帮助您绘制图例、工具提示、x 轴标签、y 轴标签等.对于这种情况,像下面这样的输入会更好

导出接口 ChartConfig {宽度:数量;高度:数量;传奇: {位置:字符串,标签:(x, y) =>细绳};工具提示:(x, y) =>细绳;}...@Input() 配置:图表配置;

My components often start out by having multiple @Input and @Output properties. As I add properties, it seems cleaner to switch to a single config object as input.

For example, here's a component with multiple inputs and outputs:

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));
    }
}

And its usage:

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
    }
}

Template:

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


As the number of properties grows, it seems that switching to a single configuration object might be cleaner. For example, here's a component that takes a single config object:

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

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

And its usage:

export class AppComponent {
    config = {
        prop1: 1,

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

        prop2: 2,

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

Template:

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


Now I can just pass the config object reference through multiple layers of nested components. The component using the config would invoke callbacks like config.onProp1Changed(...), which causes the config object to do the reassignment of the new value. So it seems we still have one-way data flow. Plus adding and removing properties doesn't require changes in intermediate layers.

Are there any downsides to having a single config object as an input to a component, instead of having multiple input and outputs? Will avoiding @Output and EventEmitter like this cause any issues that might catch up to me later?

解决方案

I would say it could be OK to use single config objects for Inputs but you should stick to Outputs at all the time. Input defines what your component requires from outside and some of those may be optional. However, Outputs are totally component's business and should be defined within. If you rely on users to pass those functions in, you either have to check for undefined functions or you just go ahead and call the functions as if they are ALWAYS passed within config which may be cumbersome to use your component if there are too many events to define even if the user does not need them. So, always have your Outputs defined within your component and emit whatever you need to emit. If users don't bind a function those event, that's fine.

Also, I think having single config for Inputs is not the best practice. It hides the real inputs and users may have to look inside of your code or the docs to find out what they should pass in. However, if your Inputs are defined separately, users can get some intellisense with tools like Language Service

Also, I think it may break change detection strategy as well.

Let's take a look at the following example

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

Let's use it

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

And for separate inputs

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

And let's use this one

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

As I stated above, you have to look at the code of YourComponent to see what values you are being passed in. Also, you have to type config everywhere to use those Inputs. On the other hand, you can clearly see what values are being passed in on the second example better. You can even get some intellisense if you are using Language Service

Another thing is, second example would be better to scale. If you need to add more Inputs, you have to edit config all the time which may break your component. However, on the second example, it is easy to add another Input and you won't need to touch the working code.

Last but not least, you cannot really provide two-way bindings with your way. You probably know that if you have in Input called data and Output called dataChange, consumers of your component can use two-way binding sugar syntax and simple type

<your-comp [(data)]="value">

This will update value on the parent component when you emit an event using

this.dataChange.emit(someValue)

Hope this clarifies my opinions about single Input

Edit

I think there is a valid case for a single Input which also has some functions defined inside. If you are developing something like a chart component which often requires complex options/configs, it is actually better to have single Input. It is because, that input is set once and never changes and it is better to have options of your chart in a single place. Also, the user may pass some functions to help you draw legends, tooltips, x-axis labels, y-axis labels etc. Like having an input like following would be better for this case

export interface ChartConfig {
    width: number;
    height: number;
    legend: {
       position: string,
       label: (x, y) => string
    };
    tooltip: (x, y) => string;
}

...

@Input() config: ChartConfig;

这篇关于将 Angular 组件从多个输入/输出重构为单个配置对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆