【问题标题】:does angular have the "computed property" feature like in vue.js?角度是否具有像 vue.js 中的“计算属性”功能?
【发布时间】:2017-09-28 08:51:22
【问题描述】:

我首先学习了 Vue.js,现在在 Angular 4 中有一个项目,所以我刚刚学习了 Angular。我发现除了“计算属性”之外,一切都与 Vue 没有什么不同。在 Vue 中,我可以创建一个计算属性来监听其他属性的变化并自动运行计算。

例如(在 Vue 2 中):

computed: {
    name(){
        return this.firstname + ' ' + this.lastname;
    }
}

name 属性只会在 firstname 或 lastname 中的一个更改时重新计算。如何在 Angular 2 或 4 中处理这个问题?

【问题讨论】:

  • @Pano angularjs 标签用于 Angular 1 问题。 Angular 使用完全不同的方法,它检测区域的变化。所以基本上 Angular 中的对应物是基本的 get 属性访问器,get name() { return ... }
  • 谢谢@estus,如何根据可能随时间变化的另一个属性设置一个属性?
  • 那么它应该是一个setter。以这种方式更新的组件属性将在视图中正确更新。坚持使用组件道具时,您会发现自己走投无路,Angular 大量使用 RxJS 可观察对象和主题来为数据观察和操作提供灵活的模式。
  • @estus 这样做的常见做法是什么?比如说当一个组件加载的时候,会有一个http请求,之后会进行一些计算,然后才会真正显示在页面上。
  • 一种常见的做法是分离关注点,在服务中发出 http 请求并从那里返回计算的 observable。可以使用subscribe 将可观察对象解包在组件中,或者使用| async 管道直接绑定到视图。如您所见,根本没有计算属性,数据流是通过可观察对象执行的。如果您有特定的案例,请随时提出问题。

标签: javascript angular vue.js


【解决方案1】:

是的,你可以。

在 TS 文件中:

export class MyComponent {

  get name() {
     return  this.firstname + ' ' + this.lastname;
  }
}

然后在 html 中:

<div>{{name}}</div>

这是一个例子:

@Component({
  selector: 'my-app',
  template: `{{name}}`,
})
export class App  {
  i = 0;
  firstN;
  secondN;

  constructor() {
    setInterval(()=> {
      this.firstN = this.i++;
      this.secondN = this.i++;
    }, 2000);
  }
  get name() {
    return  this.firstN + ' ' + this.secondN;
  }
}

【讨论】:

  • 不,这不是 OP 所指的。
  • 为什么? name 将在视图中更新,然后 firstName 或 secondName 被更改。这不是 vue.js 的发明。 Knockout.js 已经在 7 年前计算了 prop。
  • 这不是计算属性:只要把console.log放在这个get函数的开头,再放几个重复的{{name}}表达式,你就会看到每个函数都被调用了额外的{{name}} 电话。计算属性的全部意义在于只计算一次(每次更改基础值),并多次使用它。
  • VueJS 计算属性特性在变化检测方面比这更智能,它只会在底层属性发生变化时评估函数。
【解决方案2】:

虽然已经回答了这个问题,但我认为这不是一个很好的答案,用户不应该使用 getter 作为 angular 中的计算属性。为什么你可能会问? getter 只是函数的糖语法,它将被编译为普通函数,这意味着它将在每次更改检测检查时执行。这对性能来说很糟糕,因为任何更改都会重新计算属性数百次。

看看这个例子:https://plnkr.co/edit/TQMQFb?p=preview

@Component({
    selector: 'cities-page',
    template: `
        <label>Angular computed properties are bad</label>

        <ng-select [items]="cities"
                   bindLabel="name"
                   bindValue="id"
                   placeholder="Select city"
                   [(ngModel)]="selectedCityId">
        </ng-select>
        <p *ngIf="hasSelectedCity">
            Selected city ID: {{selectedCityId}}
        </p>
        <p><b>hasSelectedCity</b> is recomputed <b [ngStyle]="{'font-size': calls + 'px'}">{{calls}}</b> times</p>
    `
})
export class CitiesPageComponent {
    cities: NgOption[] = [
        {id: 1, name: 'Vilnius'},
        {id: 2, name: 'Kaunas'},
        {id: 3, name: 'Pabradė'}
    ];
    selectedCityId: any;

    calls = 0;

    get hasSelectedCity() {
      console.log('hasSelectedCity is called', this.calls);
      this.calls++;
      return !!this.selectedCityId;
    }
}

如果你真的想要计算属性,你可以使用像 mobx 这样的状态容器

class TodoList {
    @observable todos = [];
    @computed get unfinishedTodoCount() {
        return this.todos.filter(todo => !todo.finished).length;
    }
}

mobx 具有 @computed 装饰器,因此 getter 属性将被缓存并仅在需要时重新计算

【讨论】:

  • 感谢您指出使用 get 属性对性能的影响。非常有用的信息。
  • 这应该是公认的答案,因为它是唯一一个指出原始答案不模拟来自 VueJS 的计算属性。谢谢。
【解决方案3】:

在某些情况下,使用纯管道可能是一个合理的替代方案,显然它有一些限制,但它至少避免了在任何事件上执行函数的成本。

@Pipe({ name: 'join' })
export class JoinPipe implements PipeTransform {
  transform(separator: string, ...strings: string[]) {
    return strings.join(separator);
  }
}

在您的模板而不是全名属性中,您也许可以只使用' ' | join:firstname:lastname。很遗憾,Angular 的计算属性仍然不存在。

【讨论】:

    【解决方案4】:

    我将尝试改进Andzej Maciusovic 的希望,以获得一个规范的答案。确实,VueJS 有一个称为计算属性的功能,可以使用示例快速显示:

    <template>
      <div>
        <p>A = <input type="number" v-model="a"/></p>
        <p>B = <input type="number" v-model="b"/></p>
        <p>C = <input type="number" v-model="c"/></p>
        <p>Computed property result: {{ product }}</p>
        <p>Function result: {{ productFunc() }}</p>
      </div>
    </template>
    
    <script>
    export default {
      data () {
        return {
          a: 2,
          b: 3,
          c: 4
        }
      },
    
      computed: {
        product: function() {
          console.log("Product called!");
          return this.a * this.b;
        }
      },
    
      methods: {
        productFunc: function() {
          console.log("ProductFunc called!");
          return this.a * this.b;
        }
      }
    }
    </script>
    

    每当用户更改ab 的输入值时,productproductFunc 都会登录到控制台。如果用户更改c,则仅调用productFunc

    回到 Angular,mobxjs 确实有助于解决这个问题:

    1. 使用npm install --save mobx-angular mobx 安装它
    2. observablecomputed 属性用于绑定属性

    TS 文件

        import { observable, computed } from 'mobx-angular';
    
        @Component({
           selector: 'home',
           templateUrl: './home.component.html',
           animations: [slideInDownAnimation]
        })
        export class HomeComponent extends GenericAnimationContainer {
           @observable a: number = 2;
           @observable b: number = 3;
           @observable c: number = 4;
    
           getAB = () => {
               console.log("getAB called");
               return this.a * this.b;
           }
    
           @computed get AB() {
               console.log("AB called");
               return this.a * this.b;
           }
        }
    

    标记

    <div *mobxAutorun>
        <p>A = <input type="number" [(ngModel)]="a" /> </p>
        <p>B = <input type="number" [(ngModel)]="b" /> </p>
        <p>C = <input type="number" [(ngModel)]="c" /> </p>
        <p> A * B = {{ getAB() }}</p>
        <p> A * B (get) = {{ AB }}</p>
    </div>
    

    如果ab 被更改,AB 被调用一次,getAB 被调用多次。如果更改了c,则仅调用getAB。因此,即使必须执行计算,此解决方案也会更加高效

    【讨论】:

      【解决方案5】:

      Vue 中的computed 属性有两个巨大的好处:反应性和记忆。

      在 Vue 中,我可以创建一个计算属性来监听其他属性的变化并自动运行计算。

      您在这里专门询问 Angular 中的反应性系统。似乎人们已经忘记了什么是 Angular 基石:Observables。

      const { 
          BehaviorSubject, 
          operators: {
            withLatestFrom,
            map
          }
      } = rxjs;
      const firstName$ = new BehaviorSubject('Larry');
      const lastName$ = new BehaviorSubject('Wachowski');
      
      const fullName$ = firstName$.pipe(
          withLatestFrom(lastName$),
          map(([firstName, lastName]) => `Fullname: ${firstName} ${lastName}`)
      );
      
      const subscription = fullName$.subscribe((fullName) => console.log(fullName))
      
      setTimeout(() => {
          firstName$.next('Lana');
          subscription.unsubscribe();
      }, 2000);
      &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.3/rxjs.umd.js"&gt;&lt;/script&gt;

      我的代码没有解决记忆部分,它只处理反应性。

      人们给出了一些有趣的答案。虽然mobX computed 属性可能(我从未使用过mobX)更接近Vue 中的computed,但这也意味着您依赖于另一个您可能不感兴趣的库。对于您解释的特定用例,管道替代方案在这里是最有趣的,因为它同时解决了反应性和记忆化问题,但并非对所有情况都有效。

      【讨论】:

        【解决方案6】:

        我想再添加一个选项(TypeScript 4),因为上述方法不能 100% 满足所有需求。它不是反应性的,但仍然足够好。这个想法是显式声明一个检测变化的函数和一个计算属性值的函数。

        export class ComputedProperty<TInputs extends any[], TResult> {
            private readonly _changes: (previous: TInputs) => TInputs;
            private readonly _result: (current: TInputs) => TResult;
            private _cache: TResult;
            private _inputs: TInputs;
        
            constructor(changes: (previous: TInputs) => TInputs, result: (current: TInputs) => TResult) {
                this._changes = changes;
                this._result = result;
            }
        
            public get value(): TResult {
                const inputs = this._changes(this._inputs);
                if (inputs !== this._inputs) {
                    this._inputs = inputs;
                    this._cache = this._result(this._inputs);
                }
                return this._cache;
            }
        }
        

        声明:

        // readonly property
        this.computed = new ComputedProperty<[number], number>(
                (previous) => {
                    return previous?.[0] === this.otherNumber ? previous : [this.otherNumber];
                },
                (current) => {
                    return current[0] + 1;
                }
            );
        

        用途:

         <label>Angular computed property: {{computed.value}}</label>
        

        【讨论】:

          【解决方案7】:

          您可以从 Vue 组合 API 导入两个函数,它工作得非常好。这可能是个坏主意,但很有趣。只需从 Vue 导入 refcomputed,就可以在 Angular 中拥有计算属性。

          我有一个示例 PR,我将 Vue 添加到 Angular 项目中:https://github.com/kevin-castify/vue-in-angular/pull/1/files

          1. 至少使用版本 3 将 Vue 添加到您的项目中
          2. 像这样添加到您的 component.ts 中:
          import { Component, OnInit } from '@angular/core';
          import { ref, watch } from 'vue';
          
          ...
          
          export class FullNameComponent implements OnInit {
            firstName = ref('');
            lastName = ref('');
            fullName = computed(() => this.firstName.value + this.lastName.value;
          
            ngOnInit(): void {
              // Might need to seed the refs here to trigger the first computation
              firstName.value = "Jane";
              lastName.value = "Doe";
            }
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2017-08-26
            • 1970-01-01
            • 2017-09-24
            • 2020-06-12
            • 2021-11-08
            • 2018-01-30
            • 1970-01-01
            • 2019-08-08
            相关资源
            最近更新 更多