"Cannot invoke an object which is possibly 'undefined'" 即使在确保它之后 !== undefined

"Cannot invoke an object which is possibly 'undefined'" even after ensuring it !== undefined

为什么我在检查 func 引用未定义后仍会出现 Cannot invoke an object which is possibly 'undefined' Typescript 错误?

type Hoge = {
    func?: (str: string) => boolean
}

const myFunc = (obj: Hoge) => {
    const data = ['AAA', 'BBB', 'CCC']

    if(obj.func !== undefined) {
        data.filter(obj.func) // ok
        data.filter(v => obj.func(v)) // ng Cannot invoke an object which is possibly 'undefined'.
    }
}

它在最后一行,您需要有条件地处理 obj.func 未定义的情况,以便将 v 传递给它:

func

的手写体
func?: ((str: string) => boolean) | undefined

使用三元有条件地处理 undefined 满足这个:

type Hoge = {
    func?: (str: string) => boolean
}

const myFunc = (obj: Hoge) => {
    const data = ['AAA', 'BBB', 'CCC']

    if(obj.func !== undefined) {
        data.filter(obj.func) // ok
        data.filter(v => obj.func ? obj.func(v) : console.log('obj.func undefined')) // ng Cannot invoke an object which is possibly 'undefined'.
    }
}

好吧 知道 data.filter 的回调会立即执行,但 typescript 应该如何知道?

考虑这个例子:

type Hoge = {
    func?: (str: string) => boolean
}

const myFunc = (obj: Hoge) => {
    if(obj.func !== undefined) {
        setTimeout(() => obj.func!('a'), 100); // Force assume func is not undefined
    }
    obj.func = undefined; // TS will allow this since func can be undefined, but this is a problem
}
myFunc({
    func: (str: string) => { 
        console.log(str); return true; 
    }
})

因为在您的情况下您确实知道函数是在 if 块中调用的,所以您应该使用:

data.filter(v => obj.func!(v))

这会让 TS 知道你知道函数在那个时候不是未定义的

Playground link

简答

Control flow analysis很复杂,Typescript的分析也就到此为止了。在这种情况下,很容易证明在 //ok 行上,data.func !== undefined。但是要证明 data.func 的值在它被调用之前不会改变并不容易 将来某个时候 在传递给 data.filter 的闭包中。

请参阅此答案末尾的解决方案。

长答案

Type narrowing is achieved by using control flow analysis 以证明特定行上的引用具有比其最初声明或先前已知类型更窄的类型。

换行

data.filter(obj.func) // ok

控制流分析很简单; obj.func 在检查为 !== undefined 后立即取消引用。

但在下一行

data.filter(v => obj.func(v))

obj.func 不会立即取消引用。它只出现在下一行 lexically。但实际上,它直到稍后才会被调用,在 data.filter 的执行“内部”。 Typescript 必须递归地进行控制流分析,直至 data.filter 的实现。显然,在这种情况下并非如此。也许未来版本的 Typescript 会(they keep improving it)。或者它可能太复杂或太昂贵。或者这不可能?

Help me improve this answer

Does Javascript's "single threaded architecture" mean that no other thread can change the value of obj.func before data.filter is finished?

自己看看

将以下代码放入您的 IDE 或尝试 in the Typescript Playground。观察 abcde 的类型。请注意 c 词法 出现在 bd 之间,如何具有不同的类型。这是因为 wrappingFunc 实际上并没有在 bd 之间执行。 c 的类型不能仅仅因为它按词法出现在 if 子句中而被缩小。注意在调用 wrappingFunc 之前如何修改 obj.func 的值:

type Hoge = {
    func?: (str: string) => boolean
}

const myFunc = (obj: Hoge) => {
    const data = ['AAA', 'BBB', 'CCC']

    const a = obj.func           // ((str: string) => boolean) | undefined

    if(obj.func !== undefined) {
        const b = obj.func       // (str: string) => boolean

        const wrappingFunc = function () {
            const c = obj.func   // ((str: string) => boolean) | undefined
            c()                  // ERROR
        }

        const d = obj.func       // (str: string) => boolean

        obj.func = undefined     // modify obj.func before calling wrappingFunc
        wrappingFunc()           // this call will fail; Typescript catches this possibility above
   }

    const e = obj.func           // ((str: string) => boolean) | undefined
}

解决方案

修复错误的一种方法是使用类型断言,这基本上是对 Typescript 说:“你可能不知道类型,但我知道,所以相信我。”:

const myFunc = (obj: Hoge) => {
    const data = ['AAA', 'BBB', 'CCC']

    if(obj.func !== undefined) {
        data.filter(obj.func) // ok
        data.filter(v => (obj.func as (str: string) => boolean)(v) ) 
    }
}

另一种方法是将 obj.func 的值赋给闭包中的一个变量,Typescript 可以很容易地证明它从未被修改过:

const myFunc = (obj: Hoge) => {
    const data = ['AAA', 'BBB', 'CCC']

    if(obj.func !== undefined) {
        data.filter(obj.func) // ok
        const filterFunc = obj.func
        data.filter(v => filterFunc(v)) // ok
    }
}