You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在TypeScript中为基于路径解析的watchData回调正确类型标注

问题:为watchData函数实现正确的类型标注,让回调参数自动推断类型

背景与代码基础

首先定义包含Widget和Sprocket的数据库类型及实例:

type Widget = { foo: unknown, bar: unknown }
type Sprocket = { baz: unknown, qux: unknown }
type Widgets = Record<string, Widget>
type Sprockets = Record<string, Sprocket>

const database: {widgets: Widgets, sprockets: Sprockets} = {
    widgets: {
        123: { foo: 'something', bar: 'something' },
        234: { foo: 'something', bar: 'something' }
    },
    sprockets: {
        345: { baz: 'something', qux: 'something' },
        456: { baz: 'something', qux: 'something' }
    }
}

期望实现watchData函数,通过传入路径订阅数据,回调的data参数能根据路径自动推断类型:

watchData('widgets/123', data => { 
    // 编译器应自动推断data为Widget类型
    console.log(data)
})

已定义Data类型,能正确解析路径对应的类型:

type Data<Path> = Path extends `${infer Table}/${infer _Key}`
    ? (Table extends 'widgets'
        ? Widget
        : (Table extends 'sprockets' ? Sprocket : undefined))
    : Path extends `${infer Table}`
    ? (Table extends 'widgets'
        ? Widgets
        : Table extends 'sprockets' ? Sprockets : undefined)
    : undefined

// 测试验证:类型推断正确
type TestSomeWidget = Data<'widgets/123'>     // Widget
type TestSomeSprocket = Data<'sprockets/456'> // Sprocket
type TestAllWidgets = Data<'widgets'>         // Widgets
type TestAllSprockets = Data<'sprockets'>     // Sprockets

尝试的基础实现报错,因为TypeScript无法关联回调参数类型与运行时逻辑:

const watchData = <Path extends string>(path: Path, cb: (val: Data<Path>) => void): unknown => {
    const [table, key] = path.split('/')

    if (table === 'widgets' || table === 'sprockets') {
        const tableData = database[table]

        if (key) return cb(tableData[key]) // 类型不匹配报错
        return cb(tableData) // 类型不匹配报错
    }

    return cb(undefined)
}

解决方案:使用函数重载实现精确类型推断

函数重载能直接为不同路径模式定义对应的回调参数类型,外部调用时自动推断,内部逻辑无需复杂泛型关联:

// 重载1:订阅整个widgets表,回调参数为Widgets类型
function watchData(table: 'widgets', cb: (val: Widgets) => void): unknown;
// 重载2:订阅整个sprockets表,回调参数为Sprockets类型
function watchData(table: 'sprockets', cb: (val: Sprockets) => void): unknown;
// 重载3:订阅单个widget条目,回调参数为Widget类型
function watchData(path: `${'widgets'}/${string}`, cb: (val: Widget) => void): unknown;
// 重载4:订阅单个sprocket条目,回调参数为Sprocket类型
function watchData(path: `${'sprockets'}/${string}`, cb: (val: Sprocket) => void): unknown;
// 重载5:处理无效路径,回调参数为undefined
function watchData(path: string, cb: (val: undefined) => void): unknown;

// 函数实现(内部可安全使用类型兼容的逻辑)
function watchData(path: string, cb: (val: any) => void): unknown {
    const [table, key] = path.split('/');

    if (table === 'widgets') {
        const tableData = database.widgets;
        return key ? cb(tableData[key]) : cb(tableData);
    }
    if (table === 'sprockets') {
        const tableData = database.sprockets;
        return key ? cb(tableData[key]) : cb(tableData);
    }
    return cb(undefined);
}

使用验证

调用时编译器会自动推断回调参数类型:

// data 类型为 Widget
watchData('widgets/123', data => {
    console.log(data.foo); // 可正常访问Widget属性
});

// data 类型为 Widgets
watchData('widgets', data => {
    console.log(data['123'].bar); // 可正常访问嵌套属性
});

// data 类型为 Sprocket
watchData('sprockets/456', data => {
    console.log(data.baz);
});

// data 类型为 undefined
watchData('invalid/path', data => {
    console.log(data);
});

原实现报错原因

TypeScript无法将运行时的table/key值与泛型Path做关联推导,比如当Path是'widgets/123'时,编译器无法确认table就是'widgets'、key就是'123',因此无法认定tableData[key]的类型等于Data<Path>,导致类型不匹配报错。函数重载通过预先定义路径与类型的对应关系,避开了这种复杂的泛型关联问题。

内容的提问来源于stack exchange,提问作者akivajgordon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 12:25:22