20 lines
1.5 KiB
TypeScript
20 lines
1.5 KiB
TypeScript
/**
|
|
* 外部只读快照的选择器适配。只缓存一次选中结果,不复制业务文档或另存操作状态。
|
|
* 依赖上游不可变快照:根对象身份未变时,选择器可以直接复用旧结果。
|
|
*/
|
|
/** One immutable source, one cached selection; no second document or operation state. */
|
|
/** 最小快照/订阅协议,可由 editor、session 或派生源实现,订阅返回释放函数。 */
|
|
export type ExternalSource<S>={getSnapshot():S;subscribe(notify:()=>void):()=>void};
|
|
/** 用 selector 与等价判断派生稳定快照;等价的新值沿用旧引用,避免 UI 的无效刷新。 */
|
|
export function createSelectorStore<S,T>(source:ExternalSource<S>,selector:(snapshot:S)=>T,isEqual:(a:T,b:T)=>boolean=Object.is){
|
|
let initialized=false,snapshot:S,selection:T;
|
|
/** 根未变则跳过计算;根变但结果等价也保持 selection 引用,只更新已观察的根身份。 */
|
|
function getSnapshot():T{
|
|
const current=source.getSnapshot();if(initialized&&Object.is(snapshot,current))return selection;
|
|
const next=selector(current),same=initialized&&isEqual(selection,next);
|
|
snapshot=current;if(!same)selection=next;initialized=true;return selection;
|
|
}
|
|
// 每个订阅单独跟踪 observed;计算失败仍通知,让使用方在自己的读取/错误边界重新处理。
|
|
return Object.freeze({getSnapshot,subscribe(notify:()=>void){let observed=getSnapshot();return source.subscribe(()=>{let next:T;try{next=getSnapshot();}catch{notify();return;}if(!Object.is(observed,next)){observed=next;notify();}});}});
|
|
}
|