Angular 进阶之五: Signals到底用不用?

Angular 在V16的时候推出了Signals,在17正式作为主打功能之一强烈推荐,看过了各种博主的各种科普文章也没说明白,到底这东西值不值得用?毕竟项目大了,重构代码也不是闹着玩儿的。各种科普文章主要在说两点:

  1. 用了性能提高

  2. 用了方便,你改一个,使用的地方自动就获得通知。(听起来和Rxjs的Observer一样)

在Angular项目日常开发过程中主要的矛盾其实就两个。

  1. Object的成员改了,不会触发子组件(component)的ngOnChange。需要手动调用detectChanges。

  2. 性能,其中之一就是html template里头不能用function,不然会不停的调用。

这也是Signals可能可以解决的两个问题。我们用下面一个简单的例子来说明。

TypeScript 复制代码
import { Component, signal, computed } from '@angular/core';

interface Member {
    id: number;
    name: string;
    value: number;
}

@Component({
    template: `<div>
        <p>{{targetsSignal()[0].name}} has {{targetsSignal()[0].value}}</p>
        <button (click)="increase()">Increase</button>
        
        <button (click)="decrease()" [disabled]="canDecrease()">Decrease</button>
        <p>Name starts with M</p>
        <div *ngFor="let member of computedNames()">{{member.id}} - {{member.name}}</div>
        
        <div style="height: 300px; width: 300px; background-color: #2f487e;" (mousemove)="onMousemove()"></div>
        <test-signal-sub [members]="targetsSignal()"></test-signal-sub>
    </div>`,
    selector: 'test-signal',
})

export class SignalComponent {
    counter = 0;
    counter1 = 0;

    targets: Member[] = [{name: 'Maria', id: 1, value: 1}, {name: 'Michel', id: 2, value: 1}, {name: 'Jack', id: 3, value: 1}, {name: 'John', id: 4, value: 1}, 
                {name: 'Sam', id: 5, value: 1}, {name: 'Mila', id: 6, value: 1}];
    readonly targetsSignal = signal<Member[]>(this.targets);
    
    computedNames = computed(() => {
        this.counter++
        console.log("namesFiltered() called! " + this.counter + " times!");
        return this.targetsSignal().filter(item => item.name.startsWith('M'));
    })

    constructor() {

    }

    onMousemove() {
        // console.log('Mouse moved');
    }

    increase() {
        const newTargets = [].concat(this.targetsSignal());
        newTargets[0].value += 1;
        this.targetsSignal.set(newTargets);
    }

    decrease() {
        this.targetsSignal.update(val => { 
            val[0].value -= 1;
            return val;
        });
    }

    canDecrease() {
        this.counter1++;
        console.log("canDecrease() called! " + this.counter1 + " times!");
        return this.targetsSignal()[0].value <= 0;
    }
}
TypeScript 复制代码
import { Component, OnChanges, SimpleChanges, Input } from '@angular/core';

@Component({
    template: `<div>
        <p>Sub signal component below:</p>
        <p>{{name}} preferred {{color}}</p>
        <p>{{members[0].name}} has value {{members[0].value}}</p>
    </div>`,
    selector: 'test-signal-sub',
})

export class SignalSubComponent implements OnChanges {
    @Input() members: any;
    color = '';
    name = '';
    readonly preferedColor = [{name: 'Maria', color: 'red'}, {name: 'Michel', color: 'blue'}, {name: 'Jack', color: 'yellow'}, {name: 'John', color: 'green'}, 
    {name: 'Sam', color: 'black'}, {name: 'Mila', color: 'white'}];
    constructor() {

    }

    ngOnChanges(changes: SimpleChanges): void {
        if (changes.members?.previousValue !== changes.members?.currentValue) {
            const index = changes.members?.currentValue[0].value % changes.members?.currentValue.length ?? 0;
            this.color = this.preferedColor[index].color;
            this.name = this.preferedColor[index].name;
        }
    }
}

两个组件,父组件的html template里绑定了computedNames,和canDecrease,从外表也看不出来区别,但是如果使用鼠标在蓝色区域晃动,控制台就会打印canDecrease被不停调用,computedNames没事,这个例子说明了Signal可以被用在html template里,解决的主要场景之一就是button的状态,现实中不都是理想情况,button属于form,一个valid状态就解决了button是不是disable。很多情况button的[disabled]后面跟了一串判断,"a || b || !c || d"等等,现在有了signal就直接一个signal就好了。但是重构时需要把涉及到的a,b,c,d这些成员都改成signal。这就可能需要一次重构一整个component。

在上面例子中increase使用set每次用新数组更新signal,decrease使用update,每次只改原来的数组,在子组件中,会看到,increase会触发子组件的ngOnChange,但是decrease不会。这就和原来没什么区别了。还是Angular的老问题,对象引用不变,成员变,无法触发子组件的ngOnChange。

所以,Signals也没有宣传中吹的天花乱坠那么厉害,解决的问题有限,它更像是一个Rxjs的一个封装,一个别名,一个简化版本。是不是要重构代码就看大家自己的需求了。

PS:Angular在开发signal component和双向绑定的signal,都在他们的任务列表中,等这两个发布可能会带来更多的好处,

相关推荐
小小竹子8 分钟前
前端vue-实现富文本组件
前端·vue.js·富文本
小白小白从不日白17 分钟前
react hooks--useReducer
前端·javascript·react.js
下雪天的夏风29 分钟前
TS - tsconfig.json 和 tsconfig.node.json 的关系,如何在TS 中使用 JS 不报错
前端·javascript·typescript
diygwcom41 分钟前
electron-updater实现electron全量版本更新
前端·javascript·electron
Hello-Mr.Wang1 小时前
vue3中开发引导页的方法
开发语言·前端·javascript
程序员凡尘1 小时前
完美解决 Array 方法 (map/filter/reduce) 不按预期工作 的正确解决方法,亲测有效!!!
前端·javascript·vue.js
编程零零七5 小时前
Python数据分析工具(三):pymssql的用法
开发语言·前端·数据库·python·oracle·数据分析·pymssql
(⊙o⊙)~哦7 小时前
JavaScript substring() 方法
前端
无心使然云中漫步7 小时前
GIS OGC之WMTS地图服务,通过Capabilities XML描述文档,获取matrixIds,origin,计算resolutions
前端·javascript
Bug缔造者7 小时前
Element-ui el-table 全局表格排序
前端·javascript·vue.js