在TypeScript使用优化链式调用的时候, 被告知this不可分配给该返回值类型?

Tcosfish
  • 2
广东新手上路,请多包涵

问题来源

各位大佬们, 如题目所示, 我想要让 typescript 中的链式调用可以按照基本顺序去提示, 而过程的某些顺序可以任意, 如下图所示

graph TD
    A[开始运行] --> C1[setUrl]
    A[开始运行]--> C2[setMethod]
    C1 --> D[send]
    C2 --> D[send]
    C1 --> C2
    C2 --> C1

就像上面的流程图这样,
可以是 实例.setUrl().setMethod().send() 这样的方式,
也可以是 实例.setMethod().setUrl().send() 这样的方式,
但是不能是 实例.send().setMethod() 或 实例.setUrl().send()


在下面的代码中, 我勉强实现了这个功能,
但是, 对于 return this as any 这一行不是很理解,
此时的 this 类型应该是 BaseCon, 是作为 ISetCon 和 ISend 的联合类型存在,
那应该可以符合 T extends ISetCon ? Omit<T, 'setUrl'> : ISend 这个才对,
即如果当前的 this 属于 ISetCon 的子类型, 则返回筛选后的类型, 否则返回 ISend 类型,

问题

但是直接 return this 却会报错(类型T不可分配给该返回值类型),
为什么还需要做类型断言才能不报错呢?

具体代码

type Methodtype = 'POST' | 'GET'

interface ISetData {
    setData(): ISetCon
}

interface ISetCon {
    setUrl<T>(this: T, url?: string): T extends ISetCon ? Omit<T, 'setUrl'> : ISend
    setMethod<T>(this: T, method?: Methodtype): T extends ISetCon ? Omit<T, 'setMethod'> : ISend 
}

interface ISend {
    send(): void
}

type BaseCon = ISetCon & ISend

class Container implements BaseCon {
    setUrl<T>(this: T, url?: string): T extends ISetCon ? Omit<T, 'setUrl'> : ISend {
        console.log(url)
        return this as any  // ???
    }
    setMethod<T>(this: T, method?: Methodtype): T extends ISetCon ? Omit<T, 'setMethod'> : ISend {
        console.log(method)
        return this as any  // ???
    }
    send() { console.log('GOGO') }
}

const test: ISetCon = new Container()
test.setUrl('123456').setMethod('POST').send()
回复
阅读 785
2 个回答

em...感觉就非常的奇怪,这种逻辑不应该用类型系统去控制
image.png
你看一眼提示,抛开ISetCon这个接口,setUrl已经指明了参数this的类型“T”,又指明了返回类型“T extends ISetCon ? Omit<T, 'setUrl'> : ISend”,怎么看都是冲突的。

从需求来看,其实就是setUrl和setMethod返回this,send不返回this而已。

class Container {
    setUrl(url?: string){
        console.log(url)
        return this  // ???
    }
    setMethod(method?: Methodtype) {
        console.log(method)
        return this as any  // ???
    }
    send() { console.log('GOGO') }
}

const test = new Container()
test.setUrl('123456').setMethod('POST').send()

所以这样不就够了吗

另外,试想,test是Container的实例,ISetCon接口和ISend接口,是Container类的两种行为集合,并不是两种“类型”,更不可能让test或者说this反复横跳。

在定义类的时候,本身就带有一定的类型系统,最起码this自身已经蕴含了“Container的实例”这个约束,最多对参数进行一定的约束。比如setUrl<T>的这个T在调用的时候也并没有给出约束。

如果接口是必须的,建议可以这么些,同样能够表达必要的约束

type Methodtype = 'POST' | 'GET'

interface ISetData {
    setData(): ISetCon
}

interface ISetCon {
    setUrl( url?: string): this
    setMethod(method?: Methodtype): this
}

interface ISend {
    send(): void
}

type BaseCon = ISetCon & ISend

class Container implements ISend,ISetCon {
    setUrl(url?: string){
        console.log(url)
        return this  // ???
    }
    setMethod(method?: Methodtype) {
        console.log(method)
        return this as any  // ???
    }
    send() { console.log('GOGO') }
}

const test = new Container()
test.setUrl('123456').setMethod('POST').send()

为什么要写as any

因为你已经指定了,函数的返回值是

T extends ISetCon ? Omit<T, 'setUrl'> : ISend

那函数的返回值必须符合这个类型,你直接返回this,this的类型是T,肯定不满足的。但any肯定是满足的,如果不想写any,那就得写

return this as unknown as (T extends ISetCon ? Omit<T, 'setUrl'> : ISend) 

目的是一样的,都是为了让这个函数的返回值类型符合你指定的返回值类型。

宣传栏