typescript类型保护


当遇到需要告诉编译器某个值是指定类型的场景时,我们可以使用类型断言,比如这个例子:

const valueList = [123, "hello"]

// getValue 函数随机返回数字类型或者字符串类型
function getValue() {
    const num = Math.random() * 10
    if (num < 5) {
        return valueList[0]
    } else {
        return valueList[1]
    }
}

const v = getValue()
if (v.length) { // error,类型“string | number”上不存在属性“length”。类型“number”上不存在属性“length”。
    console.log(v.length)
} else {
    console.log(v.toFixed()) // 类型“string | number”上不存在属性“toFixed”。类型“string”上不存在属性“toFixed”。
}

这种情况在编译阶段报错,可以使用类型断言解决:

if ((v as string).length) {
    console.log((v as string).length)
} else {
    console.log((v as number).toFixed()) 
}

使用类型断言虽然可以解决这种需要指定类型的情况,但是显得有些繁琐,我们尝试类型保护的方式来优化。

自定义类型保护

类型保护就是一些表达式,它们会在运行时检查以确保在某个作用域里的类型。 要定义一个类型保护,我们只要简单地定义一个函数,它的返回值是一个 “类型谓词”。比如可以这样定义一个类型保护函数:

function isString(value: number | string): value is string{
    const num = Math.random() * 10
    return num > 5
}

例子中的 value is string 就是类型谓词,value 必须是参数中的一个。使用它也很方便:

const v = getValue()
if (isString(v)) { 
    console.log(v.length)
} else {
    console.log(v.toFixed())
}

可以看到这比类型断言更简洁,只要检查过一次类型,后续分支就不用检查了,并且会自动推断出 else 分支中的 v 是 number 类型。

typeof 类型保护

自定义类型保护需要定义一个函数来判断类型,难免还是有些复杂,其实在 ts 中,如果是基本类型而不是复杂类型,可以直接使用 typeof 来做类型保护,如:

if (typeof v === 'string') { 
    console.log(v.length)
} else {
    console.log(v.toFixed())
}

这样写是可以的,效果和自定义类型保护一样的。但它有一些限制,这些 typeof 类型保护只有两种形式能被识别: typeof v === "typename"和 typeof v !== "typename", "typename"必须是 "number", "string", "boolean"或 "symbol"。 但是TypeScript并不会阻止你与其它字符串比较,语言不会把那些表达式识别为类型保护。

instanceof 类型保护

instanceof 操作符是 JS 中的原生操作符,用来判断一个实例是不是某个构造函数创建的,或者是不是使用 es6 语法的某个类创建的。在 ts 中,使用 instanceof 操作符可以达到类型保护的效果。例子:

class Class1 {
    constructor(public name: string = 'aa') { }
}

class Class2 {
    constructor(public age: number = 18) { }
}

function getRandomItem() {
    return Math.random() > 0.5 ? new Class1() : new Class2()
}

const item = getRandomItem()
if (item instanceof Class1) {
    console.log(item.name)
} else {
    console.log(item.age)
}

if 分支中使用 instanceof 判断了 item,如果是 Class1 创建的,那么应该有 name 属性,如果不是,那它就有 age 属性。