We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[zh]
如题,实现一个将 Angular 组件 Input 自动转化为 Observable 的自定义拦截器:
Input
Observable
@Component({}) export class DemoComponent { @ObservableInput() @Input('name') name$$: Observable<string>; }
通过上面的 ObservableInput 装饰器,我们将父组件传递的 Input name 自动转化成了一个 Observable 对象。
ObservableInput
name
Angular 组件中我们使用 @Input 获取父组件传递的上下文数据,类似 React/Vue 中 props 的概念。通常我们为了支持 Input 动态变化并做出一些相关操作的情况,会将 @Input 定义为 setter 的方式,同时我们为了取到最新的 Input 值又需要定义一个内部私有变量和一个对应的 getter:
@Input
props
setter
getter
@Component({}) export class DemoComponent { private _name: string; @Input() get name() { return this._name; } set name(name: string) { this._name = name; // do something } }
很明显,如果项目里的组件的 Input 越来越多且我们都需要支持动态 Input 的话可能会有很多这样的模板代码,且类似 _name 这样的中间变量放在代码里既显得丑陋又影响代码阅读体验,而实际上 Angular 社区对 ObservableInput 的需求已经由来已久:Proposal: Input as Observable,但官方一直未提供相应的实现。
_name
目前社区里类似的 ObservableInput 实现也都是通过自定义 getter/settter 劫持的方案来完成数据的转换,但是依然存在一些问题:
getter/settter
解决一下:
// 使用方式一 @Component({}) export class DemoComponent { @ObservableInput(true) // 自动绑定 name 值,即去除 `name$$` 末尾的 `$` 符号 @Input('name') name$$: Observable<string>; name: string; } // 使用方式二 @Component({}) export class DemoComponent { @ObservableInput(true, 'Hello World') // 自动绑定 name Input 的值并设置默认值为 Hello World name$$: Observable<string>; @Input() name: string; } // 使用方式三 @Component({}) export class DemoComponent { @ObservableInput('nameValue') // 自动绑定 nameValue Input 的值 name$$: Observable<string>; @Input() nameValue: string; }
即我们提供更加灵活的 ObservableInput 使用方式满足相对更多的使用需求。
本质上实现这样的数据劫持并不是什么黑魔法,只需要 ES5 环境支持(Symbol 可以换成其他实现):
export function ObservableInput< T = any, SK extends keyof T = any, K extends keyof T = any >(propertyKey?: K | boolean, initialValue?: SubjectType<T[SK]>) { return (target: T, sPropertyKey: SK) => { const symbol = Symbol(); type ST = SubjectType<T[SK]>; type Mixed = T & { [symbol]: BehaviorSubject<ST>; } & Record<SK, BehaviorSubject<ST>>; Object.defineProperty(target, sPropertyKey, { enumerable: true, configurable: true, get(this: Mixed) { return ( this[symbol] || (this[symbol] = new BehaviorSubject<ST>(initialValue)) ); }, set(this: Mixed, value: ST) { this[sPropertyKey].next(value); }, }); if (!propertyKey) { return; } if (propertyKey === true) { propertyKey = (sPropertyKey as string).replace(/\$+$/, '') as K; } Object.defineProperty(target, propertyKey, { enumerable: true, configurable: true, get(this: Mixed) { return this[sPropertyKey].getValue(); }, set(this: Mixed, value: ST) { this[sPropertyKey].next(value); }, }); }; }
使用类似的方案我们可以实现一个 ValueHook 装饰器来实现不需要多增加私有变量而自定义 Input 的 settter 和 getter:
ValueHook
settter
@Component({}) export class DemoComponent { @ValueHook(function(name) { // do something }) @Input() name: string; }
如果只是为了拦截 setter,ValueHook 的使用似乎更加有效。
const checkDescriptor = <T, K extends keyof T>(target: T, propertyKey: K) => { const descriptor = Object.getOwnPropertyDescriptor(target, propertyKey); if (descriptor && !descriptor.configurable) { throw new TypeError(`property ${propertyKey} is not configurable`); } return { oGetter: descriptor && descriptor.get, oSetter: descriptor && descriptor.set, }; }; export function ValueHook<T = any, K extends keyof T = any>( setter?: (this: T, value?: T[K]) => boolean | void, getter?: (this: T, value?: T[K]) => T[K], ) { return (target: T, propertyKey: K) => { const { oGetter, oSetter } = checkDescriptor(target, propertyKey); const symbol = Symbol(); type Mixed = T & { [symbol]: T[K]; }; Object.defineProperty(target, propertyKey, { enumerable: true, configurable: true, get(this: Mixed) { return getter ? getter.call(this, this[symbol]) : oGetter ? oGetter.call(this) : this[symbol]; }, set(this: Mixed, value: T[K]) { if ( value === this[propertyKey] || (setter && setter.call(this, value) === false) ) { return; } if (oSetter) { oSetter.call(this, value); } this[symbol] = value; }, }); }; }
@ObservableInput 和 @ValueHook 实际上可以组合使用,但大部分情况下你没必要也不应该这么做,如果你有这种需求,可能你更应该重构一下代码了。:)
@ObservableInput
@ValueHook
The text was updated successfully, but these errors were encountered:
No branches or pull requests
[zh]
What
如题,实现一个将 Angular 组件
Input
自动转化为Observable
的自定义拦截器:通过上面的
ObservableInput
装饰器,我们将父组件传递的Input
name
自动转化成了一个Observable
对象。Why
Angular 组件中我们使用
@Input
获取父组件传递的上下文数据,类似 React/Vue 中props
的概念。通常我们为了支持Input
动态变化并做出一些相关操作的情况,会将@Input
定义为setter
的方式,同时我们为了取到最新的Input
值又需要定义一个内部私有变量和一个对应的getter
:很明显,如果项目里的组件的
Input
越来越多且我们都需要支持动态Input
的话可能会有很多这样的模板代码,且类似_name
这样的中间变量放在代码里既显得丑陋又影响代码阅读体验,而实际上 Angular 社区对ObservableInput
的需求已经由来已久:Proposal: Input as Observable,但官方一直未提供相应的实现。How
目前社区里类似的 ObservableInput 实现也都是通过自定义
getter/settter
劫持的方案来完成数据的转换,但是依然存在一些问题:Observable
对象后无法直接获取原来Input
的值了Input
设置默认值了解决一下:
即我们提供更加灵活的
ObservableInput
使用方式满足相对更多的使用需求。本质上实现这样的数据劫持并不是什么黑魔法,只需要 ES5 环境支持(Symbol 可以换成其他实现):
基本实现
One more thing
使用类似的方案我们可以实现一个
ValueHook
装饰器来实现不需要多增加私有变量而自定义Input
的settter
和getter
:如果只是为了拦截
setter
,ValueHook
的使用似乎更加有效。基本实现
Last But Not Least
@ObservableInput
和@ValueHook
实际上可以组合使用,但大部分情况下你没必要也不应该这么做,如果你有这种需求,可能你更应该重构一下代码了。:)The text was updated successfully, but these errors were encountered: