ChatGPT解决这个技术问题 Extra ChatGPT

angular 是否具有 vue.js 中的“计算属性”功能?

我首先学习了 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,如何根据可能随时间变化的另一个属性设置一个属性?
那么它应该是一个二传手。以这种方式更新的组件属性将在视图中正确更新。当坚持使用组件道具时,您会发现自己走投无路,Angular 大量使用 RxJS 可观察对象和主题来为数据观察和操作提供灵活的模式。
@estus 这样做的常见做法是什么?比如说当一个组件加载的时候,会有一个http请求,之后有一些计算才会真正显示在页面上。
一种常见的做法是分离关注点,在服务中发出 http 请求并从那里返回计算的 observable。可以使用 subscribe 在组件中展开 observable 或使用 | async 管道直接绑定到视图。如您所见,根本没有计算属性,数据流是通过可观察对象执行的。如果您有特定的案例,请随时提出问题。

A
Andzej Maciusovic

虽然这已经得到了回答,但我认为这不是一个很好的答案,用户不应该使用 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 的计算属性。谢谢。
A
Alexei - check Codidact

我将尝试改进 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 确实有助于解决这个问题:

使用 npm install --save mobx-angular mobx 安装它 为绑定属性使用可观察和计算属性

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。因此,即使必须执行计算,此解决方案也会更加高效


k
keyneom

在某些情况下,使用 Pure Pipe 可能是一个合理的选择,显然它有一些限制,但它至少避免了在任何事件上执行函数的成本。

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

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


R
R3ctor

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

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

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

const { BehaviorSubject, 运算符:{ 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 订阅 = fullName$.subscribe((fullName) => console.log(fullName)) setTimeout(() => { firstName$.next('Lana'); subscription.unsubscribe(); }, 2000);

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

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


J
Julia Passynkova

是的你可以。

在 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 计算属性特性在变化检测方面比这更智能,它只会在底层属性发生变化时评估函数。
k
kemsky

我想再添加一个选项(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>

K
Kevin K

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

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

使用至少版本 3 将 Vue 添加到您的项目添加到您的 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";
  }
}