如何为Svelte Writable Store的Getter/Setter设置不同TypeScript类型?
解决SvelteKit中Writable Store的类型差异问题(Setter无ID,Getter带ID)
首先明确两个核心类型:用户传入的无ID对象,以及Store内部存储/返回的带ID对象:
// 用户传入的无ID对象类型 type ItemWithoutId = { type: "MyType"; properties: { foo: string; }; }; // Store内部维护的带ID对象类型 type ItemWithId = ItemWithoutId & { id: string };
原生writable的set/update与subscribe的类型绑定一致,无法直接实现「输入无ID、输出有ID」的类型差异,因此需要封装自定义Store,在内部处理ID生成逻辑,同时对外暴露符合要求的类型接口:
import { writable, type Writable } from 'svelte/store'; // 实现唯一ID生成逻辑(可替换为uuid等库) function generateUniqueId(): string { return Math.random().toString(36).slice(2, 10); } // 自定义Store工厂函数 export function createItemStore(initialItems: ItemWithoutId[] = []) { // 初始化时为初始数据自动添加ID const initialWithIds: ItemWithId[] = initialItems.map(item => ({ ...item, id: generateUniqueId() })); // 内部使用原生Writable维护带ID的数据 const internalStore: Writable<ItemWithId[]> = writable(initialWithIds); // 自定义set方法:仅接受无ID数组,自动添加ID后更新内部Store function set(items: ItemWithoutId[]) { const itemsWithIds = items.map(item => ({ ...item, id: generateUniqueId() })); internalStore.set(itemsWithIds); } // 自定义update方法:支持基于现有带ID数据更新,新增项自动补全ID function update( updater: (current: ItemWithId[]) => (ItemWithoutId | ItemWithId)[] ) { internalStore.update(current => { return updater(current).map(item => { // 已有ID的项直接保留,无ID的项自动生成ID return 'id' in item ? item as ItemWithId : { ...item, id: generateUniqueId() }; }); }); } // 对外暴露subscribe(返回带ID数据)和自定义的set/update(接受无ID输入) return { subscribe: internalStore.subscribe, set, update }; }
使用示例
// 创建Store,初始传入无ID数组 const appStore = createItemStore([ { type: "MyType", properties: { foo: "bar" } } ]); // ✅ 调用set时,TypeScript会强制检查输入必须无ID appStore.set([ { type: "MyType", properties: { foo: "baz" } } ]); // ✅ 调用update时,可直接访问当前带ID的数据,新增项允许无ID appStore.update(current => { console.log(current[0].id); // 这里能获得正确的IntelliSense提示 return [...current, { type: "MyType", properties: { foo: "new-item" } }]; }); // ✅ 订阅时拿到的是带ID的数组,类型提示完全正确 appStore.subscribe(items => { items.forEach(item => { console.log(item.id); // 自动提示id字段 console.log(item.type, item.properties.foo); }); });
关键说明
- 避免了原代码中
subscribe内调用set导致的无限循环问题,ID生成逻辑统一放在set和update阶段处理。 - 通过封装实现了「输入输出类型分离」:对外的
set/update仅接受无ID数据,subscribe返回带ID数据,完全符合需求。 - 全程未使用联合类型,TypeScript类型检查和IntelliSense提示均正常工作。
内容的提问来源于stack exchange,提问作者Laaouatni Anas
相关产品推荐
相关产品推荐

