TS中,当函数传参可能是多种类型时,应该怎么正确处理类型判断?

新手上路,请多包涵

比如:

interface AudioEngineRemoteOpts {
    url: string;
……
}
interface AudioEngineOpts {
    loop?: boolean;
……
}
playBGM_COCOS(opts: AudioEngineOpts | AudioEngineRemoteOpts) {
      if(opts是AudioEngineOpts ) ……
     else if(opts是AudioEngineRemoteOpts)……
}

我尝试过用‘url’ in opts来判断,但是这样可能会访问到原型链,用opts.url === undefined也会被ts提醒说可能没有url属性。或者用断言来回避这个问题,但是我想知道有没有更好的代码组织方式,让这个问题不发生,而不是回避它

阅读 1.5k
avatarAI BotBETA

在TypeScript中,当你有一个函数接收多种类型的参数时,并且需要根据参数的类型来执行不同的逻辑,你可以使用几种不同的策略来优雅地处理这种情况。以下是一些建议:

  1. 类型守卫 (Type Guards):
    类型守卫是一种表达式,它在运行时评估,并且返回一个布尔值,该值可以用于缩小类型检查器的类型范围。

    function isAudioEngineOpts(opts: any): opts is AudioEngineOpts {
      return !(opts as AudioEngineOpts).url; // 假设AudioEngineOpts没有url属性
    }
    
    function isAudioEngineRemoteOpts(opts: any): opts is AudioEngineRemoteOpts {
      return (opts as AudioEngineRemoteOpts).url !== undefined;
    }
    
    function playBGM_COCOS(opts: AudioEngineOpts | AudioEngineRemoteOpts) {
      if (isAudioEngineOpts(opts)) {
        // opts 现在是 AudioEngineOpts
      } else if (isAudioEngineRemoteOpts(opts)) {
        // opts 现在是 AudioEngineRemoteOpts
      }
    }

注意,在这个例子中,我们假设AudioEngineOpts不包含url属性,所以可以使用!(opts as AudioEngineOpts).url来判断。如果AudioEngineOpts有其他独特的属性或方法可以用来区分,你应该使用那些。

  1. 类型断言 (Type Assertions):
    如果你确信你知道参数的类型,并且想要告诉TypeScript忽略类型检查,你可以使用类型断言。但是,类型断言应该谨慎使用,因为它们会绕过TypeScript的类型系统。

    function playBGM_COCOS(opts: AudioEngineOpts | AudioEngineRemoteOpts) {
      if ('url' in opts) {
        // 使用类型断言,但确保你确实知道这是安全的
        const remoteOpts = opts as AudioEngineRemoteOpts;
        // ...
      } else {
        // 假设这里除了AudioEngineRemoteOpts就是AudioEngineOpts
        const engineOpts = opts as AudioEngineOpts;
        // ...
      }
    }

注意,这里使用'url' in opts作为类型检查的基础,但是仍然使用了类型断言。

  1. 使用联合类型与类型谓词 (Type Predicates):
    如果你想要在类型系统中更清晰地表达这种情况,你可以定义一个类型谓词函数,该函数返回一个联合类型的缩小子集。
  2. 重载 (Overloads):
    如果函数的行为在每种类型上都非常不同,你也可以考虑使用函数重载。但是,请注意,重载主要是用于函数签名,而不是用于在函数体内部进行类型区分。
  3. 可选链 (Optional Chaining) 和空值合并 (Nullish Coalescing):
    虽然它们不直接用于类型检查,但在处理可能为undefinednull的属性时,它们是很有用的工具。
  4. discriminated unions (带区分联合):
    如果你可以修改接口定义,添加一个区分属性(通常是一个字面量类型或枚举)到每个接口中,TypeScript 就可以更容易地区分它们。

选择哪种方法取决于你的具体需求和偏好。在大多数情况下,类型守卫和类型断言是处理这种情况的有效方法。但是,如果可能的话,最好避免在函数体内部进行复杂的类型检查,并尽量通过设计来减少这种需要。

3 个回答

可以增加一个 type 字段:

interface AudioEngineRemoteOpts {
    type: 'engine-remote'
}

interface AudioEngineOpts {
    type: 'engine'
}

if(opts.type === 'engine') {
    /** code */
}

我觉得直接用 'url' in opts 就可以了。

方法1:使用自定义类型保护

interface AudioEngineRemoteOpts {
    url: string;
    // 其他属性
}

interface AudioEngineOpts {
    loop?: boolean;
    // 其他属性
}

function isAudioEngineRemoteOpts(opts: AudioEngineOpts | AudioEngineRemoteOpts): opts is AudioEngineRemoteOpts {
    return (opts as AudioEngineRemoteOpts).url !== undefined;
}

function playBGM_COCOS(opts: AudioEngineOpts | AudioEngineRemoteOpts) {
    if (isAudioEngineRemoteOpts(opts)) {
        // opts 是 AudioEngineRemoteOpts 类型
        console.log("Playing remote audio from URL:", opts.url);
    } else {
        // opts 是 AudioEngineOpts 类型
        console.log("Playing local audio with loop option:", opts.loop);
    }
}

方法2:使用标记属性

interface AudioEngineRemoteOpts {
    kind: 'remote';
    url: string;
    // 其他属性
}

interface AudioEngineOpts {
    kind: 'local';
    loop?: boolean;
    // 其他属性
}

function playBGM_COCOS(opts: AudioEngineOpts | AudioEngineRemoteOpts) {
    if (opts.kind === 'remote') {
        // opts 是 AudioEngineRemoteOpts 类型
        console.log("Playing remote audio from URL:", opts.url);
    } else if (opts.kind === 'local') {
        // opts 是 AudioEngineOpts 类型
        console.log("Playing local audio with loop option:", opts.loop);
    }
}
推荐问题
logo
Microsoft
子站问答
访问
宣传栏