我首先学习了 Vue.js,现在在 Angular 4 中有一个项目,所以我刚刚学习了 Angular。我发现除了“计算属性”之外,一切都与 Vue 没有什么不同。在 Vue 中,我可以创建一个计算属性来监听其他属性的变化并自动运行计算。
例如(在 Vue 2 中):
computed: {
name(){
return this.firstname + ' ' + this.lastname;
}
}
name 属性只会在 firstname 或 lastname 中的一个更改时重新计算。如何在 Angular 2 或 4 中处理这个问题?
get name() { return ... }
。
subscribe
在组件中展开 observable 或使用 | async
管道直接绑定到视图。如您所见,根本没有计算属性,数据流是通过可观察对象执行的。如果您有特定的案例,请随时提出问题。
虽然这已经得到了回答,但我认为这不是一个很好的答案,用户不应该使用 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 属性将被缓存并仅在需要时重新计算
我将尝试改进 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>
每当用户更改 a
或 b
的输入值时,product
和 productFunc
都会登录到控制台。如果用户更改 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>
如果更改了 a
或 b
,则调用 AB
一次,然后调用 getAB
几次。如果 c
更改,则仅调用 getAB
。因此,即使必须执行计算,此解决方案也会更加高效。
在某些情况下,使用 Pure Pipe 可能是一个合理的选择,显然它有一些限制,但它至少避免了在任何事件上执行函数的成本。
@Pipe({ name: 'join' })
export class JoinPipe implements PipeTransform {
transform(separator: string, ...strings: string[]) {
return strings.join(separator);
}
}
在您的模板而不是全名属性中,您也许可以只使用 ' ' | join:firstname:lastname
。很遗憾,角度的计算属性仍然不存在。
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
,但这也意味着您依赖于另一个您可能不感兴趣使用的库。对于您解释的特定用例,管道替代方案在这里是最有趣的,因为它同时解决了反应性和记忆化问题,但并非对所有情况都有效。
是的你可以。
在 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;
}
}
console.log
放在此 get 函数的开头,并放置几个重复的 {{name}}
表达式,您将看到每个额外的 {{name}}
调用都会调用该函数。计算属性的全部意义在于只计算一次(每次更改基础值),并多次使用它。
我想再添加一个选项(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>
您可以从 Vue 组合 API 导入两个函数,它工作得非常好。这可能是个坏主意,但很有趣。只需从 Vue 导入 ref
和 computed
,您就可以在 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";
}
}
不定期副业成功案例分享