在 ngOnChanges 挂钩中对 SimpleChanges 接口进行类型检查
Type checking for SimpleChanges interface in ngOnChanges hook
如果我们在当前组件的 ngOnChanges 挂钩的 SimpleChanges typescript 参数中进行类型检查,那就太好了。
这将防止我们在检查属性时出现错误。
使用 TypeScript 2.1 和 keyof 功能我想出了以下类型声明(基于 SimpleChanges)这似乎给了我们对组件属性的必要类型化访问:
export type ComponentChange<T, P extends keyof T> = {
previousValue: T[P];
currentValue: T[P];
firstChange: boolean;
};
export type ComponentChanges<T> = {
[P in keyof T]?: ComponentChange<T, P>;
};
使用这些,声明 vscode 编辑器自动获取类型信息并自动完成更改属性:
一个问题是 changes 参数现在将列出每个 属性 组件(不仅是 @Input() 属性),但我还没有找到比这更好的方法。
我使用继承解决了这个问题。首先,我创建了扩展 SimpleChange class 的类型化接口。您只需要执行一次并将其导入到需要它的组件中。
interface TypedChange<T> extends SimpleChange
{
previousValue: T;
currentValue: T;
}
其次,我们将 SimpleChanges 接口扩展到我们的组件中预期会发生变化的接口。例如
interface MyComponentChanges extends SimpleChanges
{
RefreshQueue: TypedChange<number>
}
最后的实现是
public ngOnChanges(changes: MyComponentChanges): void
{
if (changes.RefreshQueue)
{
if (!changes.RefreshQueue.isFirstChange())
{
if (changes.RefreshQueue.currentValue == 1)
{
DoSomething();
}
}
}
}
下面是 Intellisense 屏幕截图
我建议采用以下解决方案,改编自 https://github.com/angular/angular/issues/17560#issuecomment-624161007
export type SimpleChangeTyped<T> = Omit<SimpleChange, SimpleChange['previousValue'] | SimpleChange['currentValue']>
& {
previousValue: T;
currentValue: T;
};
export type SimpleChangesTyped<T> = {
[K in keyof T]: SimpleChangeTyped<T[K]>;
};
该方案具有以下优点:
- 完全键入/使用智能感知
- 不存在 SimpleChange
上固有的 overwrite/union 属性
- 适用于
this
- 防止不良属性
- 如果 angular 开发者决定更改 SimpleChange
上的 previousValue
或 currentValue
属性,将会抛出错误
用法示例:
ngOnChanges(changes: SimpleChangesTyped<YourComponent>): void { // Or use SimpleChangesTyped<this>
changes.someComponentProperty; // GOOD: Will identify the changes object with correct types
changes.someOtherProperty; // BAD: Property 'someOtherProperty' does not exist on type 'SimpleChangesTyped<YourComponent>'.ts(2339)
changes.someComponentProperty.currentValue; // GOOD: Will identify the type of the someComponentProperty property
changes.someComponentProperty.firstChange; // GOOD: Will identify the type of the SimpleChange firstChange property (boolean)
changes.someComponentProperty.someOtherProperty; // BAD: Property 'someOtherProperty' does not exist on type 'SimpleChangeTyped<SomeComponentPropertyType>'.ts(2339)
}
此方法的唯一缺点是它不 inherit/union SimpleChanges,因此如果将来向此接口添加任何属性,它们将对 SimpleChangesTyped 无效。我尝试使用以下变体:
export type SimpleChangesTyped<T> =
Omit<SimpleChanges, keyof T> &
{
[K in keyof T]: SimpleChangeTyped<T[K]>;
};
然而遗憾的是这不起作用,因为您最终再次允许所有密钥。
a solution 来自 Netanel Basal
type MarkFunctionPropertyNames<T> = {
[Key in keyof T]: T[Key] extends Function | Subject<any> ? never : Key;
}
type ExcludeFunctionPropertyNames<T extends object> = MarkFunctionPropertyNames<T>[keyof T];
type ExcludeFunctions<T extends object> = Pick<T, ExcludeFunctionPropertyNames<T>>;
export type NgChanges<TComponent extends object, TProps = ExcludeFunctions<TComponent>> = {
[Key in keyof TProps]: {
previousValue: TProps[Key];
currentValue: TProps[Key];
firstChange: boolean;
isFirstChange(): boolean;
}
}
// ...
@Component({...})
export class MyComponent {
ngOnChanges(changes: NgChanges<MyComponent>) { }
}
如果我们在当前组件的 ngOnChanges 挂钩的 SimpleChanges typescript 参数中进行类型检查,那就太好了。
这将防止我们在检查属性时出现错误。
使用 TypeScript 2.1 和 keyof 功能我想出了以下类型声明(基于 SimpleChanges)这似乎给了我们对组件属性的必要类型化访问:
export type ComponentChange<T, P extends keyof T> = {
previousValue: T[P];
currentValue: T[P];
firstChange: boolean;
};
export type ComponentChanges<T> = {
[P in keyof T]?: ComponentChange<T, P>;
};
使用这些,声明 vscode 编辑器自动获取类型信息并自动完成更改属性:
一个问题是 changes 参数现在将列出每个 属性 组件(不仅是 @Input() 属性),但我还没有找到比这更好的方法。
我使用继承解决了这个问题。首先,我创建了扩展 SimpleChange class 的类型化接口。您只需要执行一次并将其导入到需要它的组件中。
interface TypedChange<T> extends SimpleChange
{
previousValue: T;
currentValue: T;
}
其次,我们将 SimpleChanges 接口扩展到我们的组件中预期会发生变化的接口。例如
interface MyComponentChanges extends SimpleChanges
{
RefreshQueue: TypedChange<number>
}
最后的实现是
public ngOnChanges(changes: MyComponentChanges): void
{
if (changes.RefreshQueue)
{
if (!changes.RefreshQueue.isFirstChange())
{
if (changes.RefreshQueue.currentValue == 1)
{
DoSomething();
}
}
}
}
下面是 Intellisense 屏幕截图
我建议采用以下解决方案,改编自 https://github.com/angular/angular/issues/17560#issuecomment-624161007
export type SimpleChangeTyped<T> = Omit<SimpleChange, SimpleChange['previousValue'] | SimpleChange['currentValue']>
& {
previousValue: T;
currentValue: T;
};
export type SimpleChangesTyped<T> = {
[K in keyof T]: SimpleChangeTyped<T[K]>;
};
该方案具有以下优点:
- 完全键入/使用智能感知
- 不存在 SimpleChange 上固有的 overwrite/union 属性
- 适用于
this
- 防止不良属性
- 如果 angular 开发者决定更改 SimpleChange 上的
previousValue
或 currentValue
属性,将会抛出错误
用法示例:
ngOnChanges(changes: SimpleChangesTyped<YourComponent>): void { // Or use SimpleChangesTyped<this>
changes.someComponentProperty; // GOOD: Will identify the changes object with correct types
changes.someOtherProperty; // BAD: Property 'someOtherProperty' does not exist on type 'SimpleChangesTyped<YourComponent>'.ts(2339)
changes.someComponentProperty.currentValue; // GOOD: Will identify the type of the someComponentProperty property
changes.someComponentProperty.firstChange; // GOOD: Will identify the type of the SimpleChange firstChange property (boolean)
changes.someComponentProperty.someOtherProperty; // BAD: Property 'someOtherProperty' does not exist on type 'SimpleChangeTyped<SomeComponentPropertyType>'.ts(2339)
}
此方法的唯一缺点是它不 inherit/union SimpleChanges,因此如果将来向此接口添加任何属性,它们将对 SimpleChangesTyped 无效。我尝试使用以下变体:
export type SimpleChangesTyped<T> =
Omit<SimpleChanges, keyof T> &
{
[K in keyof T]: SimpleChangeTyped<T[K]>;
};
然而遗憾的是这不起作用,因为您最终再次允许所有密钥。
a solution 来自 Netanel Basal
type MarkFunctionPropertyNames<T> = {
[Key in keyof T]: T[Key] extends Function | Subject<any> ? never : Key;
}
type ExcludeFunctionPropertyNames<T extends object> = MarkFunctionPropertyNames<T>[keyof T];
type ExcludeFunctions<T extends object> = Pick<T, ExcludeFunctionPropertyNames<T>>;
export type NgChanges<TComponent extends object, TProps = ExcludeFunctions<TComponent>> = {
[Key in keyof TProps]: {
previousValue: TProps[Key];
currentValue: TProps[Key];
firstChange: boolean;
isFirstChange(): boolean;
}
}
// ...
@Component({...})
export class MyComponent {
ngOnChanges(changes: NgChanges<MyComponent>) { }
}