Angular监听StorageEvent报No overload matches this call错误
问题场景
开发Angular应用时需要将应用状态持久化存储到localStorage,计划通过监听storage事件实现跨标签页状态同步:当同一应用在多个标签页打开时,任意标签页修改存储数据,其余标签页可自动同步更新应用状态。
原有问题代码
storage事件订阅代码
this.storageListenSub = fromEvent(window, 'storage').subscribe( (event: StorageEvent) => { if (event.key === 'notes') this.loadState(); } );
状态读取函数代码
loadState() { try { const notesInStorage = JSON.parse(localStorage.getItem('notes')!); // if (!notesInStorage) return; this.notes.length = 0; //clear the notes array while keeping referenece this.notes.push(...notesInStorage); this.notes = notesInStorage; } catch (e) { console.log('There was an error retrieving the notes from local storage'); console.log(e); } }
触发的TypeScript报错
No overload matches this call. Overload 1 of 3, '(observer?:
Partial| undefined): Subscription', gave the
following error.
Type '(event: StorageEvent) => void' has no properties in common with type 'Partial'. Overload 2 of 3, '(next:
(value: Event) => void): Subscription', gave the following error.
Argument of type '(event: StorageEvent) => void' is not assignable to parameter of type '(value: Event) => void'.
Types of parameters 'event' and 'value' are incompatible.
Type 'Event' is missing the following properties from type 'StorageEvent': key, newValue, oldValue, storageArea, and 2 more.
Overload 3 of 3, '(next?: ((value: Event) => void) | null | undefined,
error?: ((error: any) => void) | null | undefined, complete?: (() =>
void) | null | undefined): Subscription', gave the following error.
Argument of type '(event: StorageEvent) => void' is not assignable to parameter of type '(value: Event) => void'.
Types of parameters 'event' and 'value' are incompatible.
Type 'Event' is not assignable to type 'StorageEvent'.ts(2769)
报错原因
RxJS的fromEvent方法默认会根据传入的事件目标、事件名推断事件对象为通用Event类型,不会自动识别为StorageEvent。直接在回调参数中标注类型为StorageEvent时,TypeScript会检测到类型不兼容:通用Event类型上不存在key、newValue、oldValue、storageArea等StorageEvent独有的属性,因此抛出类型错误。
解决方法
1. 为fromEvent指定泛型类型(推荐写法)
调用fromEvent时传入泛型参数,明确告知RxJS该事件返回的对象类型为StorageEvent,TypeScript会自动对回调参数做类型推断,不需要额外标注参数类型:
// 注意引入fromEvent import { fromEvent } from 'rxjs'; this.storageListenSub = fromEvent<StorageEvent>(window, 'storage').subscribe( (event) => { // event自动推断为StorageEvent类型,可直接访问key等属性 if (event.key === 'notes') this.loadState(); } );
2. 回调内做类型断言
如果不需要修改fromEvent的调用,也可以在回调函数内部先将通用Event类型断言为StorageEvent再使用:
this.storageListenSub = fromEvent(window, 'storage').subscribe( (event: Event) => { const storageEvt = event as StorageEvent; if (storageEvt.key === 'notes') this.loadState(); } );
额外代码优化建议
原loadState函数存在冗余逻辑:先清空原数组再push新数据的操作,会被后续直接赋值this.notes = notesInStorage覆盖,完全不会生效,可删除这部分无意义代码;同时增加空值判断,避免JSON.parse解析null抛出异常:
loadState() { try { const storageVal = localStorage.getItem('notes'); if (!storageVal) return; const notesInStorage = JSON.parse(storageVal); this.notes = notesInStorage; } catch (e) { console.log('There was an error retrieving the notes from local storage'); console.error(e); } }
注意:浏览器的storage事件只会在其他标签页/窗口修改localStorage时触发,当前标签页自身修改localStorage不会触发该事件,当前页修改存储后需要手动更新状态,不要依赖该事件做当前页的状态刷新。
内容的提问来源于stack exchange,提问作者CryptoMike

