当前位置: 代码网 > it编程>前端脚本>AngularJs > Angular实践之将Input与Lifecycle转换成流示例详解

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

2024年05月18日 AngularJs 我要评论
将 input 和生命周期函数转换成流在 angular 中一直有一个期待,就是希望能够将 input 和生命周期函数转换成流,实际上我们可以通过比较简单的方式实现,比如说:class namecom

将 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() {
  ...
  }
}

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

重写生命周期函数

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

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转流的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com