将 Angular 组件从多个输入/输出重构为单个配置对象 [英] Refactoring Angular components from many inputs/outputs to a single config object
问题描述
我的组件通常从具有多个 @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(...)
这样的回调,这会导致配置对象重新分配新值.所以看起来我们仍然有单向的数据流.此外,添加和删除属性不需要更改中间层.
将单个配置对象作为组件的输入而不是多个输入和输出有什么缺点吗?像这样避免 @Output
和 EventEmitter
会导致任何可能在以后赶上我的问题吗?
我认为对 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
中有名为 data
的 Output
名为 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 Input
s but you should stick to Output
s at all the time. Input
defines what your component requires from outside and some of those may be optional. However, Output
s 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 Output
s 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 Input
s 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 Input
s 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 Input
s. 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 Input
s, 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 function
s 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屋!