请教下 angular computed 相关的问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Mirachael
V2EX    Angular

请教下 angular computed 相关的问题

  •  
  •   Mirachael 2024-04-17 16:31:59 +08:00 3776 次点击
    这是一个创建于 542 天前的主题,其中的信息可能已经有所发展或是发生改变。

    使用 computed 获取数组长度,但是视图不会更新

     addrLength = computed(() => { const user = this.dataService.user(); return user.addesses.length; }); 

    使用 computed 重新构建新的数组,视图也不会更新

     addresses = computed(() => { const user = this.dataService.user(); // transform data 后页面不会更新 // return user.addresses.map(addr => ({ address: addr, title: `Address-${addr.title}` })); // 直接返回 user.addresses ,页面会更新 return user.addresses; }); 

    这里是在线 demo

    第 1 条附言    2024-04-18 09:28:59 +08:00

    其实是想试下angular的新特性signal computed文档

    看文档,我理解为,signal更新后,任何依赖此signal的computed也会更新,文档里有原话。

    const count: WritableSignal<number> = signal(0); const doubleCount: Signal<number> = computed(() => count() * 2); 

    The doubleCount signal depends on count. Whenever count updates, Angular knows that anything which depends on either count or doubleCount needs to update as well.

    第 2 条附言    2024-04-19 10:11:57 +08:00
    结论: 使 signal 的 equal 方法始终返回 false ,这样就能保证触发更新
    27 条回复    2024-04-19 13:36:02 +08:00
    echoless
        1
    echoless  
       2024-04-17 18:08:31 +08:00
    感觉是 computed object/array signal tracking 算法的问题. 我查了半天文档也没有查到原因.

    代码在这, 暂时不太能看懂
    https://github.com/angular/angular/tree/16.2.12/packages/core/src/signals
    ktqFDx9m2Bvfq3y4
        2
    ktqFDx9m2Bvfq3y4  
       2024-04-17 18:11:05 +08:00   2
    你这种还不如这样:{{user.address.length}}。user 来自 userService ,这个对象一直不变,登录修改里面的值。这样肯定可行。你那个主要问题是不确定什么时间会改变,会导致 Angular 不断调用才能判断。
    ktqFDx9m2Bvfq3y4
        3
    ktqFDx9m2Bvfq3y4  
       2024-04-17 18:17:04 +08:00
    我用 Angular 这么多年,主要领悟就是能共享对象就不要传什么方法。然后在其他地方替换对象或更新它的值。比计算要高性能,也不要用 get/set 属性,这个会比一般的变量浪费性能因为需要计算才能拿到值,直接给字段,省去重复调用的过程。
    shakaraka
        4
    shakaraka  
    PRO
       2024-04-17 18:54:31 +08:00
    原因是你的 user 是同一个,你虽然设置了 addresses ,但是实际的 user 是没有变化的。

    你要做的是把 user 里的 addresses 也设置 signal 。
    tedding
        5
    tedding  
       2024-04-17 18:56:56 +08:00 via iPhone
    Create a computed Signal which derives a reactive value from an expression.
    angular 说了计算的是 signal
    参考 https://angular.io/guide/signals
    chnwillliu
        6
    chnwillliu  
       2024-04-18 06:17:49 +08:00 via Android
    你在 update user address 时,user 这个 signal 并没有变 dirty ,自然 computed 不会重新计算。
    Mirachael
        7
    Mirachael  
    OP
       2024-04-18 09:32:41 +08:00
    @chnwillliu 调用了 signal 的 update 方法,怎么变 dirty
    chnwillliu
        8
    chnwillliu  
       2024-04-18 09:47:53 +08:00 via Android
    signal 相当于默认有 rxjs 的 distinctUntilChanged ,你第二次 emit 同一个 object reference 会被 skip 掉的。
    chnwillliu
        9
    chnwillliu  
       2024-04-18 09:51:52 +08:00 via Android
    自定义 signal 判等方式,或者 update address 的时候把整个 user update 成另一个 object reference ,好比 redux / ngrx 处理的方式。
    Mirachael
        10
    Mirachael  
    OP
       2024-04-18 10:41:39 +08:00
    @Chad0000 #3 这是 angular 的新特性 signal ,如果在模板里获取 signal 的当前值,需要像方法一样调用。比如 dataService.user().addresses 。这个 user 是 signal ,不是方法。另外,你说模板不要传方法,其实是更新策略选择的问题。
    Mirachael
        11
    Mirachael  
    OP
       2024-04-18 10:45:00 +08:00
    @chnwillliu #9 我觉的跟 user 的 object reference 变没变没关系,因为这种写法是可以更新视图的

    ```
    addresses = computed(() => {
    const user = this.dataService.user();

    // 直接返回 user.addresses ,页面会更新
    return user.addresses;
    });
    ```
    ktqFDx9m2Bvfq3y4
        12
    ktqFDx9m2Bvfq3y4  
       2024-04-18 10:49:47 +08:00
    @Mirachael #11
    你这个返回的是 object 了,后面改变的也是 object ,那么不管它来自哪里,都能触发更新。

    我倒是没注意这个新的 Feature ,但我觉得它可能会性能差。怎么说呢,我在做一个白板应用,用 Angular 管理成千的节点渲染出来,目前没有使用 Push 模式,有一次我大量将 field 改成了 getter/setter ,结果性能直线下降产生了明显卡顿感。所以我后面的实践都是能直接给 Field 就直接给,哪怕 getter 这种不计算的也是需要调用后才知道是否改变,会导致性能下降。

    你的这个 Feature 就算没导致性能下降,但会导致写法过于混乱,跟直接绑定对象或属性比,不够简洁直观。
    Mirachael
        13
    Mirachael  
    OP
       2024-04-18 11:22:58 +08:00
    @chnwillliu #9 你说的对,浅拷贝还不行,必须使用深拷贝,因为在 computed 里使用的都是 addresses ,我使用 lodash 的 cloneDeep 方法就可以

    ```
    addAddress() {
    this.user.update((u) => {
    const addr = new Address('test', '20000');
    u.addAddress(addr);

    return cloneDeep(u);
    });
    }
    ```
    Mirachael
        14
    Mirachael  
    OP
       2024-04-18 11:27:41 +08:00
    @Chad0000 #12 你应该就是没有使用 push 模式,因为默认的更新策略就是会把组件树的所有节点都检查一遍,跟使用 getter/setter 没有关系,你可以看下这篇文章 https://juejin.cn/post/6844904017836032007
    Mirachael
        15
    Mirachael  
    OP
       2024-04-18 11:28:48 +08:00
    @Mirachael #13 但是又无法解释,不使用深拷贝,只返回 user.addresses 时,视图能更新这种情况。。。
    ktqFDx9m2Bvfq3y4
        16
    ktqFDx9m2Bvfq3y4  
       2024-04-18 11:45:00 +08:00
    @Mirachael #14
    我知道启用 Push 会更好,但我的问题是我在未遇到性能问题前我只想保持简洁高效,能用 Push 最好但比较繁琐,带来不必要的心智负担。我的 Default 模式下,将 Field 改成了 getter/setter 之后,直接导致性能严重下降:这已经明确证明了是与之前 Angular 能直接拿到对象或值相比,getter/setter 需要 Angular 每次都调用才能拿到值对比,而不是值就已经在它的处理层,只需要看值是否改变而已。

    我目前基于 Default 模式做了很多框架简化开发,并没有引起性能问题,我又不是做专业组件所以从来没弄过 Push 模式。
    ktqFDx9m2Bvfq3y4
        17
    ktqFDx9m2Bvfq3y4  
       2024-04-18 12:14:24 +08:00
    @Mirachael #14

    我做了很长时间的 WPF 和 Xamarin 应用,它们使用的就是 MVVM 设计,而且只有 Push 模式,我倒是羡慕 Angular 有主动检查而且不太影响性能的这种做法,这样能使很多事情大大简化。我做的白板需要同时渲染上千个节点在 SVG 面板中,实现各种效果,目前一点儿也不卡顿,同时还保留了架构和数据的简洁。在做这个项目过程中,最简单地检测是否卡顿的方式就是选中若干个对象,能拖多快就拖多快,看拖动是否平滑(因为会带着箭头什么的一起变动)。目前说真的,Default 模式就足够高性能。
    shakaraka
        18
    shakaraka  
    PRO
       2024-04-18 14:49:47 +08:00
    @Mirachael #11

    默认比较方法就是 Object.Is 呀,怎么没关系啊?

    https://github.com/angular/angular/blob/a5b5b7d5ef84b9852d2115dd7a764f4ab3299379/packages/core/primitives/signals/src/equality.ts#L17


    解决办法上面都说啦
    1 、返回一个新的 user
    2 、将 addresses 也设置为 signal
    shakaraka
        19
    shakaraka  
    PRO
       2024-04-18 14:50:38 +08:00
    你试试吧 user 的 equal 值设置为() => true ,保你每次都会更新
    shakaraka
        20
    shakaraka  
    PRO
       2024-04-18 15:00:41 +08:00
    在以前的 v16 ,signal 是有三个方法,set ,update ,mutate 。

    set 、update 这两个设置之后,是会运行 equal 方法比较值的引用是否变化,才决定更新。

    mutate 相当于使用后一定更新,但是这个 mutate 已经下线了

    https://github.com/angular/angular/issues/52735#issuecomment-1804195570
    https://github.com/angular/angular/pull/52348
    chnwillliu
        21
    chnwillliu  
       2024-04-18 18:17:10 +08:00 via Android   1
    @Mirachael 一个 signal 只有脏了才会 push dirty 的 notification 给下游的 computed ,effect 或者 view ,signal update 后脏不脏由 signal equality function 决定,默认用的 Object.is 检测前后两个值。所以并不需要 deep clone ,shallow clone 一样可以。

    直接返回 address 能 work 只是因为你在 app component 里没用 onPush strategy ,默认在 UI event 后更新 component 的 view ,而 view 中使用了 address ,address push 了新值自然能在 view 中体现出来,和 signal 没关系,你就是写个普通 get 函数一样 work 。
    chnwillliu
        22
    chnwillliu  
       2024-04-18 18:32:39 +08:00 via Android
    @Chad0000 这个 feature 就是前端大热的 signal ,就是为了做到细粒度更新 view 而引入的,将来可是要干掉 zonejs ,颠覆 Angular 自上而下 change detection 的。
    ktqFDx9m2Bvfq3y4
        23
    ktqFDx9m2Bvfq3y4  
       2024-04-18 20:09:42 +08:00 via iPhone
    @chnwillliu
    那它这么折腾侵入性太强了,反而不如 wpf 的实现优雅了。后者实现接口通知属性变更就行,我在实际使用过程中是偷懒反射然后动态触发通知,也算简洁和入侵不强。
    Mirachael
        24
    Mirachael  
    OP
       2024-04-19 09:32:26 +08:00
    @wunonglin #19

    试过了,不是设置 true ,应该时设置为 false ,确实可以更新视图

    ```

    user = signal<User>({} as User, {
    equal: () => false,
    });
    ```
    Mirachael
        25
    Mirachael  
    OP
       2024-04-19 10:07:24 +08:00
    @chnwillliu #21 shallow clone 确实可以,但是实际情况时 object 通常是带有方法的类实例,浅拷贝会导致 user signal 丢失方法。所以这种情况应该 deepClone
    shakaraka
        26
    shakaraka  
    PRO
       2024-04-19 11:24:41 +08:00
    @Mirachael #24 对,这里我写反了我勘误。但是你直接把 addresses 设置为 signal 不就好了吗
    chnwillliu
        27
    chnwillliu  
       2024-04-19 13:36:02 +08:00 via Android
    @Mirachael 可以了解下 ngrx 的 singal store 和 deep signal 的概念哦
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2735 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 11:37 PVG 19:37 LAX 04:37 JFK 07:37
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86