之前在一个ng的项目中,有个列表,其中有几列的值是动态计算的,仅仅可能是30多条数据,就感觉到明显毫秒级的卡顿了,当时只想到了组件分割这种优化方式,因为我不知道如何在ng中缓存值避免刷新视图时重新计算(ng的class成员方法是没有缓存效果的,视图刷新时总会被重新计算)
现在来给出一个在ng中类似vue计算属性和react中useMemo的功能实现思路:
既然在angular中,那就要用rxjs做事件响应了,这是自带的,用这个就很干净
-
首先假设这个“计算属性”依赖3个变化的值
-
然后把这三个变化的值用BehaviourSubject来封装(这个能有缓存最新值功能,还有初始值,当值发生改变还能通知订阅者,非常适合做计算属性)
-
在ngDoCheck中进行检测,如果用diff服务,详见在angular中如何深度watch一个对象? - 掘金 (juejin.cn) 可能会方便一点。在这里发现值发生改变,再把值注入BehaviourSubject (虽说ngDoCheck会被高频调用,但实际上你控制好点的话,就不必过度担心)
-
然后用const calcValue$ = combineLatest([bs1, bs2, bs3]).pipe(map(([v1, v2, v3]) => {// 写计算方法})) combineLatest可以在任意一个信号源发出信号的时候,都能带上其他“没变的信号”combine到一起,一次性吐出来给你,太好了
-
最后页面就用 {{calcValue$ | async}} 这个管道操作符,把值解出来,这个async的使用其实好像js中的await,都是可以将异步的值解出来
-
锵锵,就是这样了
但是回到长列表这个场景下,如果列表有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()
}
}
闭包使用得真🐂啊!巧妙的把标量传入函数内,还能保持引用,好像学到什么了不得了东西 🐂🐂🐂