angular如何实现computed-prop/ useMemo

1,259 阅读3分钟

之前在一个ng的项目中,有个列表,其中有几列的值是动态计算的,仅仅可能是30多条数据,就感觉到明显毫秒级的卡顿了,当时只想到了组件分割这种优化方式,因为我不知道如何在ng中缓存值避免刷新视图时重新计算(ng的class成员方法是没有缓存效果的,视图刷新时总会被重新计算)

现在来给出一个在ng中类似vue计算属性和react中useMemo的功能实现思路:

既然在angular中,那就要用rxjs做事件响应了,这是自带的,用这个就很干净

  1. 首先假设这个“计算属性”依赖3个变化的值

  2. 然后把这三个变化的值用BehaviourSubject来封装(这个能有缓存最新值功能,还有初始值,当值发生改变还能通知订阅者,非常适合做计算属性)

  3. 在ngDoCheck中进行检测,如果用diff服务,详见在angular中如何深度watch一个对象? - 掘金 (juejin.cn) 可能会方便一点。在这里发现值发生改变,再把值注入BehaviourSubject (虽说ngDoCheck会被高频调用,但实际上你控制好点的话,就不必过度担心)

  4. 然后用const calcValue$ = combineLatest([bs1, bs2, bs3]).pipe(map(([v1, v2, v3]) => {// 写计算方法})) combineLatest可以在任意一个信号源发出信号的时候,都能带上其他“没变的信号”combine到一起,一次性吐出来给你,太好了

  5. 最后页面就用 {{calcValue$ | async}} 这个管道操作符,把值解出来,这个async的使用其实好像js中的await,都是可以将异步的值解出来

  6. 锵锵,就是这样了

但是回到长列表这个场景下,如果列表有30行,每行依赖3个值,那就是90个值可能发生变化,那可能就得批量创建subject了。所以这个方法硬伤是需要创建大量的subject去存依赖。其实ngDoCheck有点像react的render行为,useMemo就是利用render多次执行来实现依赖收集并且计算出变化,最终其实是异曲同工。

不过react的useMemo不能写在循环里,所以不能批量动态地返回缓存对象,但react拆组件轻而易举,特别是函数式组件,相比之下angular的组件总要配modules就显得真是大费周章了。

最后如果引入angular-mobx那么确实可以提供一个装饰器函数@computed来实现这个功能。

更强大的实现

发现github上 angular想要实现computed的讨论 这个issue 5年过去了,还是discussion,一位contributor也说了在预期的计划中没有这项任务,但是大家可以继续讨论,然后!给出了一个easy又amazing的实现👇

function memonizer(...args) {
    const calcFunc = args.pop()
    let lastValue
    let deps
    return () => {
        let changed = false
        if (!deps) {
            deps = args.slice()
            changed = true        
        }
        args.forEach((getter, i) => {
            const newVal = getter()
            if(deps[i] !== newVal) {
                deps[i] = newVal
                changed = true
            }
        })
        return changed ? lastValue = calcFunc(...deps) : lastValue
    }
}

class MyComp {
    firstName = 'hello'
    lastName = 'react'
    fullNameMemonizer = memonizer(
        () => this.firstName,
        () => this.lastName,
        (first, last) => `${first} ${last}`
    )
    get fullName() {
        return this.fullNameMemonizer()
    }
}

闭包使用得真🐂啊!巧妙的把标量传入函数内,还能保持引用,好像学到什么了不得了东西 🐂🐂🐂