我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Angular实践之将Input与Lifecycle转换成流示例详解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Angular实践之将Input与Lifecycle转换成流示例详解

将 Input 和生命周期函数转换成流

在 Angular 中一直有一个期待,就是希望能够将 Input 和生命周期函数转换成流,实际上我们可以通过比较简单的方式实现,比如说:

class NameComponent {
  @Input() name: string;
}

我们要实现一个 input 为 name, output 为 hello name 的简单 component。如果将 input 看成是一个流的话,就会比较容易实现。常见的流转换方式是通过 set 方法实现:

class NameComponent {
  private name$ = new Subject(1);
  private _name: string;
  @Input() set name(val: string) {
    this.name$.next(val);
    this._name = val;
  }
  get name() {
    return this._name;
  }
  @Output() helloName = this.name$.pipe(
    map(name => `hello ${name}`),
  );
}

这样写是可以,不过你也看出来了,有一个问题,就是麻烦。

对于生命周期函数,我们也有类似的需求。比如说,我们经常需要在 Angular 销毁的时候,unsubscribe 所有的 subscription。

class NameComponent implements OnDestroy {
  private destory$ = new Subject<void>();
  ngOnDestroy(): void {
    destory$.next();
    destory$.complete();
  }
}

如果需要使用其他的生命周期函数的话,每个函数都需要这样手动调用一次。

思路

如果回到 input 的问题的话,我们知道,获取 input 的变化,除了 set 方法,还有 ngOnChanges 函数。我们很容易想到一个思路:

  • 将 ngOnChanges 转换成一个 stream, onChanges$
  • 通过 onChanges$ map 成 input stream
private onChanges$ = new Subject<SimpleChanges>();
@Input() name: string;
name$ = this.onChanges$.pipe(
  switchMap(simpleChanges => {
    if ('name' in simpleChanges) {
      return of(simpleChanges?.name?.currentValue);
    }
    return EMPTY;
  }),
)
ngOnChanges(simpleChanges: SimpleChanges) {
  this.onChanges$.next(simpleChanges);
}

当然,ngOnChanges 只会在 input 变化的时候触发,所以我们还需要加上 init 以后的初始值。(当然,我们也要将 afterViewInit 转换成 stream)

name$ = afterViewInit$.pipe(
  take(1),
  map(() => this.name),
  switchMap(value => this.onChanges$.pipe(
    startWith(value),
    if ('name' in simpleChanges) {
      return of(simpleChanges?.name?.currentValue);
    }
    return EMPTY;
  )),
) 

抽离成一个方法

很明显,如果 input 比较多的话,这样写就比较冗余了。很容易想到我们可以把它抽离成一个方法。

export function getMappedPropsChangesWithLifeCycle<T, P extends (keyof T & string)>(
  target: T,
  propName: P,
  onChanges$: Observable<SimpleChanges>,
  afterViewInit$: Observable<void>) {
  if (!onChanges$) {
    return EMPTY;
  }
  if (!afterViewInit$) {
    return EMPTY;
  }
  return afterViewInit$.pipe(
    take(1),
    map(() => target?.[propName]),
    switchMap(value => target.onChanges$.pipe(
      startWith(value),
      if (propName in simpleChanges) {
        return of(simpleChanges?.[propName]?.currentValue);
      }
      return EMPTY;
    ))
  ) 
}

看到这里,你可能很容易就想到了,我们可以把这个方法变成一个 decorator,这样看起来就简洁多了。比如我们定义一个叫做 InputMapper 的装饰器:

export function InputMapper(inputName: string) {
  return function (target: object, propertyKey: string) {
    const instancePropertyKey = Symbol(propertyKey);
    Object.defineProperty(target, propertyKey, {
      get: function () {
        if (!this[instancePropertyKey]) {
          this[instancePropertyKey] = getMappedPropsChangesWithLifeCycle(this, inputName, this['onChanges$']!, this['afterViewInit$']!);
        }
        return this[instancePropertyKey];
      }
    });
  };
}

值得注意的是,因为 target 会是 component instance 的 proto,会被所有的 instance 共享,所以我们在定义变量的时候,可以通过 defineProperty 中的 get 函数将变量定义到 this 上。这样 component instance 在调用的时候就可以成功将内容 apply 到 instance 而费 component class 的 prototype 上。

当然,使用的时候就会方便很多了:

class NameComponent {
  private onChanges$ = new Subject<SimpleChanges>();
  private afterViewInit$ = new Subject<void>();
  @Input() name: string;
  @InputMapper('name') name$!: Observable<string>;
  ngOnChanges() {
  ...
  } 
  ngAfterViewInit() {
  ...
  }
}

当然,因为对于生命周期函数,也是重复性工作,我们很容易想到,是否能够也能通过装饰器实现。

重写生命周期函数

我们只需要重写生命周期函数就可以巧妙的实现了:

  • 用类似的方法,在 this 定义 subject
  • 保存原有的函数
  • 通过 prototype 重写原来的函数,调用 subject.next。
  • 可以参考这里的实现: Angular2+ Observable Life-cycle Events (induro.io)
export function LifeCycleStream(lifeCycleMethodName: LifeCycleMethodName) {
  return (target: object, propertyKey: string) => {
    const originalLifeCycleMethod = target.constructor.prototype[lifeCycleMethodName];
    const instanceSubjectKey = Symbol(propertyKey);
    Object.defineProperty(target, propertyKey, {
      get: function () {
        if (!this[instanceSubjectKey]) {
          this[instanceSubjectKey] = new ReplaySubject(1);
        }
        return this[instanceSubjectKey].asObservable();
      }
    });
    target.constructor.prototype[lifeCycleMethodName] = function () {
      if (this[instanceSubjectKey]) {
        this[instanceSubjectKey].next.call(this[instanceSubjectKey], arguments[0]);
      }
      if (originalLifeCycleMethod && typeof originalLifeCycleMethod === 'function') {
        originalLifeCycleMethod.apply(this, arguments);
      }
    };
  }
}

那么我们可以将之前工作简化为:

class NameComponent {
  @LifeCycleStream('ngOnChanges') onChanges$: Observable<SimpleChanges>;
  @LifeCycleStream('ngAfterViewInit') ngAfterViewInit$: Observable<void>;
  @Input() name: string;
  @InputMapper('name') name$!: Observable<string>;
  ...
  ...
}

然后,因为我们已经实现了 InputMapper,那么很容易想到,有没有可能把 onChanges$afterViewInit$ 放进 InputMapper,这样我们就可以减少重复的调用了。我们可以把 LifeCycleStream 中的主体逻辑抽离成一个方法:applyLifeCycleObservable,然后在 InputMapper 调用就可以了:

if (!('afterViewInit$' in target)) {
  applyLifeCycleObservable('ngAfterViewInit', target, 'afterViewInit$');
}
if (!('onChanges$' in target)) {
  applyLifeCycleObservable('ngOnChanges', target, 'onChanges$');
}

当然,我们在调用前需要检查这个 stream 是否已经存在。注意,这里不要直接调用 target['ngAfterViewInit'], 因为我们之前写了 get 函数。可以思考下为什么。(防止将 ngAfterViewInit apply 到 target 上去)

最后,我们来看一下最终的代码:

class NameComponent {
  @Input() name: string;
  @InputMapper('name') name$!: Observable<string>;
}

这样,既没有破坏已有的 angular input,又能够很快的实现,input to stream 的转换,还是比较方便的。

以上就是Angular实践之将Input与Lifecycle转换成流示例详解的详细内容,更多关于Angular将Input Lifecycle转流的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Angular实践之将Input与Lifecycle转换成流示例详解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Angular实践之将Input与Lifecycle转换成流示例详解

这篇文章主要为大家介绍了Angular实践之将Input与Lifecycle转换成流示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-02-17

编程热搜

目录