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

Fra*_*ica 19 javascript angular-components angular

我的部件通常由具有多开始了@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));
    }
}
Run Code Online (Sandbox Code Playgroud)

它的用法:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

模板:

<using-event-emitter 
    [(prop1)]='prop1'
    (prop1Change)='onProp1Changed()'
    [(prop2)]='prop2'
    (prop2Change)='onProp2Changed()'>
</using-event-emitter>
Run Code Online (Sandbox Code Playgroud)

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

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

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

它的用法:

export class AppComponent {
    config = {
        prop1: 1,

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

        prop2: 2,

        onProp2Changed(val: number) {
            this.prop2 = val;
        }
    };
}
Run Code Online (Sandbox Code Playgroud)

模板:

<using-config [config]='config'></using-config>
Run Code Online (Sandbox Code Playgroud)

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

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

mol*_*ikh 10

我个人如果看到我需要超过4个输入+输出,我将检查我的方法再次创建我的组件,也许它应该是多个组件,我做错了.无论如何,即使我需要那么多的输入和输出,我也不会在一个配置中进行,原因如下:

1-更难以了解输入和输出内部应该是什么,如下所示:(考虑使用html输入元素和标签的组件)

想象一下,如果你只有3个这个组件,你应该在1或2个月后回来参与这个项目,或者其他人会与你合作或使用你的代码!你的代码真的很难理解.

2-缺乏表现.角度观察单个变量而不是观察阵列或物体的方式更便宜.除了考虑我给你的第一个例子,为什么你应该强制跟踪标签,其中可能永远不会改变总是在变化的值.

3-更难跟踪变量和调试.角度本身带有令人困惑的错误,难以调试,为什么我要更难.跟踪和修复任何错误的输入或输出对我来说更容易,而不是在一个配置变量中进行这一组数据.

我个人更喜欢将我的组件分解为尽可能小并测试每个组件.然后用小的组件制造更大的组件,而不是只有一个大的组件.

更新: 我使用此方法进行一次输入,没有更改数据(如标签)

@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() {
 }
}
Run Code Online (Sandbox Code Playgroud)

HTML:

<icon-component name="fa fa-trash " color="white"></icon-component>
Run Code Online (Sandbox Code Playgroud)

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


Bun*_*ner 6

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

另外,我认为单身config对于Inputs 不是最佳做法。它隐藏了真正的输入,用户可能必须查看您的代码或文档内部才能找出他们应该传递的内容。 但是,如果您的Inputs 是单独定义的,则用户可以使用语言服务等工具获得一些智能感知

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

让我们看看下面的例子

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

让我们用它

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

对于单独的输入

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

让我们用这个

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

正如我上面所说的,您必须查看 的代码YourComponent以了解您正在传递哪些值。此外,您必须在config任何地方键入以使用这些Inputs。另一方面,您可以更清楚地看到在第二个示例中传递了哪些值。如果您使用语言服务,您甚至可以获得一些智能感知

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

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

<your-comp [(data)]="value">
Run Code Online (Sandbox Code Playgroud)

value当您使用发出事件时,这将在父组件上更新

this.dataChange.emit(someValue)
Run Code Online (Sandbox Code Playgroud)

希望这能澄清我对单身的看法 Input

编辑

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

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

...

@Input() config: ChartConfig;
Run Code Online (Sandbox Code Playgroud)


Mil*_*lad 6

  • 除了其明显的功能之外,其目的Input还在于使您的组件具有声明性且易于理解。

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

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

  • 你会倒退并像 jQuery 插件过去的工作方式一样思考,你会调用一个名为的函数init,然后你提供一大堆配置,你甚至不记得是否应该提供,然后你继续将这个未知且不断增长的对象复制粘贴到您的组件中,甚至可能不需要它们

  • 使用简单的方法创建默认值非常容易和清晰,Input而使用对象创建默认值则变得有点混乱。

如果你有太多相似的Input,Output你可以考虑以下:

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()}
}
Run Code Online (Sandbox Code Playgroud)

2-如果你不喜欢这个,你可以使用组合并创建一个可重用的mixin并应用你所有的Input/Output。

下面是一个可用于应用 mixin 的函数示例,注意可能不一定正是您想要的,您需要根据您的需要进行调整。

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

然后创建你的 mixins :

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

applyMixins(MyComponent, [MyMixin]);
Run Code Online (Sandbox Code Playgroud)

3-您可以为输入设置默认属性,以便仅在需要时覆盖它们:

export class MyComponent{
    @Input() prop1: number = 10; // default 
}
Run Code Online (Sandbox Code Playgroud)