Typescript - 泛型接口扩展后属性不存在

4
场景:我正在尝试创建一个基础方法,以便我可以重复使用(works/doesNotWork)它来进行几个不同的调用。由于我不想重复多次设置,所以这个基础方法应该做一些设置。问题是,我传递给我的设置调用的泛型告诉我它期望不同的类型。我需要的所有属性都存在于基础泛型类型中,但是我遇到了 TypeScript 的问题。 错误类型“{ SyncCount: number; }”的参数不能赋值给类型“DeepPartial<T>”的参数 目标:我希望使用泛型找到一种解决方案,允许我调用一个基础方法进行设置。理想情况下,doesNotWork会被修改以正常工作。请参见下面的示例代码和 fiddle 中的期望代码(目标)。

这是我在issue中的代码片段。

type DeepPartial<T> = T extends object ? {
    [P in keyof T]?: DeepPartial<T[P]>;
} : T;

interface IDbRecord {
    readonly _id: string;
    readonly _rev: string;
    readonly DocumentType: string;
}

interface IBuilder<TEntity extends IDbRecord> {
    defaults(value: DeepPartial<TEntity>): Builder<TEntity>
}

class Builder<TEntity extends IDbRecord> implements IBuilder<TEntity> {

    defaults(value: DeepPartial<TEntity>): IBuilder<TEntity> {
        return this;
    }

}

interface IBaseEntity extends IDbRecord {
    SyncCount: number;
    SyncStatus: string
}

interface ICar extends IBaseEntity {
    Model: string;
}

interface IPerson extends IBaseEntity {
    Name: string
}

class Test {

    protected builder<TEntity extends IDbRecord>() {
        return new Builder<TEntity>();
    }

    private works<T extends IBaseEntity>() {
        // not using the generic type works
        return this.builder<IBaseEntity>().defaults({ SyncCount: 0 })
    }

    private doesNotWork<T extends IBaseEntity>() {
        // using the generic type does not work here
        return this.builder<T>().defaults({ SyncCount: 0 }) // ERROR
    }

    private someWorkingImplmentation<T extends IBaseEntity>() {
        return this.builder<T>().defaults({ SyncCount: 0 });
    }

    // I want to avoid duplicate code below from defaults.
    // A base setup method would be best
    people = this.builder<IPerson>().defaults({ SyncCount: 0 });
    cars = this.builder<ICar>().defaults({ SyncCount: 0 });

    // GOAL
    _people = this.someWorkingImplmentation<IPerson>();
    _cars = this.someWorkingImplmentation<ICar>();
}

你是在寻找解释还是解决方案?我有解决方案,但没有具体的原因描述和文档作为我的来源。 - ug_
我可以通过 return this.builder<T>().defaults({ SyncCount: 0 } as DeepPartial<T>) 来解决它。 - tom10271
更新了目标 @ug_。我希望能得到一个解决方案 :) - Agrejus
什么样的解决方案?由于解决方案可能是不使用通用方法,我不知道你想要什么:/ - zenly
更新了目标 @kelly,我想在解决方案中使用泛型。 - Agrejus
1个回答

2
doesNotWork<T extends IBaseEntity>中的类型限制似乎没有传递到defaults方法中。这似乎导致Typescript认为泛型T可以是任何东西,尽管它被限制为IBaseEntity。请参见此SO问题获取更多详细信息。
现在约束流如下:
doesNotWork<ICar> // return type inferred from builder<T>
  builder<T> - this is where the constraints of IBaseEntity fall off
    defaults<T>()

一种简单的解决方案是通过固定函数的返回类型来反转约束条件:
private doesNotWork<T extends IBaseEntity>(): IBuilder<T> {
    return this.builder<IBaseEntity>().defaults({ SyncCount: 0 })
}

通过添加IBuilder<T>的返回类型,我们消除了需要通过构建器传递泛型T来确定返回类型的要求。
有了这个,下面的代码现在可以正确地识别接口的字段:
_people = this.works<IPerson>().defaults({ Name: 'Foo' });
_cars = this.works<ICar>().defaults({ Model: 'jetta' });

1
使用您的示例,我能够让解决方案正常工作。我的实际解决方案要复杂一些,而这个演示只是为了更简单地解释问题。尽管如此,我还是能够让我的复杂解决方案正常工作。奇怪的是为什么约束会失效。属性存在于类型“T”上,但无法访问。不管怎样,这就是我需要的!谢谢!!! - Agrejus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接