我们试图拥有一个非常严格的前端。 DeVexTreme支持数据源的通用类型,但是根本没有验证。
sissue:const myDataSource = new DataSource<MyResponseDto, number>({
load: (options) => myFunction(options) //returns Promise<LoadResult<MyOtherResponseDto>>
})
目前,为了避免此问题,我为数据源做了一个包装程序类,因此我从
load
函数中推断出类型并将其传递给数据源,此外,我也必须覆盖几种类型,因为它们始终具有
any
的类型。但是我想知道是否有一个更好且本地的解决方案,这不会让我写额外的包装器并维护它...我们正在使用devextreme
&
devextreme-angular
版本:24.1.7
我也在DeVexpress支持中心发布了一张支持票。
电流解决方案(包装器):/**
* A factory class to produce strongly typed DevExtreme data sources.
*/
export class DataSourceFactory<TLoadOptions extends Pick<CustomStoreOptions<any, any>, "load">> {
private readonly storeOptions: TLoadOptions;
constructor(storeOptions: TLoadOptions) {
this.storeOptions = storeOptions;
}
/**
* Creates an `ExtendedDataSource` by merging the given `storeOptions` and any
* additional custom store options (except `load` which is enforced in the constructor).
*/
public createDataSource(
additionalOptions?: Omit<
CustomStoreOptions<ExtractItemType<TLoadOptions>, ExtractKeyType<TLoadOptions>>,
"load" | "key"
> & { key: keyof ExtractItemType<TLoadOptions> | Array<keyof ExtractItemType<TLoadOptions>> }
): ExtendedDataSource<ExtractItemType<TLoadOptions>, ExtractKeyType<TLoadOptions>> {
const mergedOptions: CustomStoreOptions<ExtractItemType<TLoadOptions>, ExtractKeyType<TLoadOptions>> = {
...this.storeOptions,
...(additionalOptions as CustomStoreOptions),
};
return new ExtendedDataSource<ExtractItemType<TLoadOptions>, ExtractKeyType<TLoadOptions>>(mergedOptions);
}
}
ExtendedDataSource.ts:
/**
* An extension of DevExtreme's DataSource with a typed `columns()` helper.
*
* @internal
* @deprecated do not use this method. Use `DataSourceFactory` instead. This is for internal purposes only
*/
export class ExtendedDataSource<TItem, TKey = number> extends DataSource<
TItem,
TKey
> {
constructor(options: CustomStoreOptions<TItem, TKey>) {
super(options);
}
/**
* Returns a proxy-based column accessor for type-safe property paths.
*
* generic type T is required
*/
public columns = <T extends TItem = never>() => {
return getColumns<T>();
};
// The overrides below simply ensure TypeScript picks up TItem[] correctly.
public override items(): TItem[] {
return super.items();
}
public override load(): DxExtendedPromise<TItem[]> {
return super.load();
}
public override reload(): DxExtendedPromise<TItem[]> {
return super.reload();
}
}
types.ts:
/**
* @internal
* Extracts the 'item' type from an object whose `load` method returns a `Promise<LoadResult<T>>`.
*/
export type ExtractItemType<T> = T extends {
load: (options: any) => Promise<LoadResult<infer U>>;
}
? U
: unknown;
/**
* @internal
* Extracts the 'key' type from an object whose `byKey` method accepts `key`.
* Fallback to `number` if not defined.
*/
export type ExtractKeyType<T> = T extends {
byKey?: (key: infer K, extraOptions?: any) => PromiseLike<any>;
}
? K
: number;
/**
* @internal
*/
export interface StringCallable {
(): string;
}
/**
* @internal
*/
export type PropertiesOf<T> = T extends any[]
? StringCallable
: T extends object
? { [K in keyof Required<T>]-?: PropertiesOf<T[K]> } & StringCallable
: StringCallable;
GetColumns.ts:
dataField
/**
* @internal
* @deprecated do not use this method. Use `DataSourceFactory` instead. This is for internal purposes only
*/
export function getColumns<T>(path: string = ""): PropertiesOf<T> {
return new Proxy((() => path) as StringCallable, {
get: (_target, prop: string) => {
const newPath = path ? `${path}.${prop}` : prop;
return getColumns(newPath);
},
apply: (_target, _thisArg, _args) => path,
}) as PropertiesOf<T>;
}
readonly #dataSourceFactory = new DataSourceFactory({
load: (options) => myFunction(options), //returns Promise<LoadResult<MyResponseDto>>
});
readonly dataSource = this.#dataSourceFactory.createDataSource({
key: "id", //key can be keyof MyResponseDto or an array of those keys
insert: (values) => myPostFunction(values), //values has the type: MyResponseDto and return value must be Promise<MyResponseDto>
update: (key, values) =>myPutFunction(key, JSON.stringify(values))), //values has the type: MyResponseDto & key has the type: number
});
readonly columns = this.dataSource.columns<MyResponseDto>(); //if the right generic is not passed, error is thrown. The reason to pass the generic is because of the limitations in angular html template
看起来,您已经完成了围绕DeVextreme的数据源的类型限制工作的彻底工作。包装器解决方案是强制执行类型安全性的绝佳方法,但我可以理解如何保持繁琐。希望DeVextreme能够在未来版本中增强其类型检查,以避免使用这些解决方法。对于从事其他类型项目的任何人,请不要忘记查看custom研究论文服务它们可能是有效完成学术任务的宝贵资源!