angular - 使用变量突变器改变 RxJS 可观察对象 - Angular
问题描述
我做了一个从远程源获取一些用户数据的服务。该服务是一种获取多个用户的方法,以及一种获取特定用户的方法。从这两种方法返回的 observables 通过 map() 获得 .pipe(ed) 以便能够在用户对象被消耗之前对其进行变异。
我想要的是只为多用户流和单用户流定义一次修改器,但我目前的方法遇到了范围问题。
请注意,我称用户为“英雄”。这是一个设计方面。以下是我的 HeroService 类中的相应方法:
export class HeroService {
// [...]
getHeroes(): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl).pipe(
map((heroes: Hero[]) => this.mutateHeroes(heroes, this.addSkillsToHero)),
map((heroes: Hero[]) => this.mutateHeroes(heroes, this.splitName))
);
}
getHero(id): Observable<Hero> {
return this.http.get<Hero>(this.heroesUrl + "/" + id).pipe(
map((hero: Hero) => this.addSkillsToHero(hero)),
map((hero: Hero) => this.splitName(hero))
);
}
private mutateHeroes(heroes: Hero[], mutator) {
heroes.forEach((hero: Hero) => {
hero = mutator(hero);
});
return heroes;
}
private splitName(hero: Hero) {
let heroNames: string[] = hero.name.split(" ");
hero.firstname = heroNames.splice(0, 1).join(" ");
hero.lastname = heroNames.splice(heroNames.length - 1, 1).join(" ");
hero.middlename = heroNames.join(" ");
return hero;
}
private addSkillsToHero(hero: Hero) {
hero.skills = this.getSkills(Math.ceil(Math.random() * 10));
return hero;
}
private getSkills(count: number): string[] {
let skills: string[] = [];
let i;
for (i = 0; i < count; i++) {
skills.push(this.getRandomSkill());
}
return skills;
}
private getRandomSkill(): string {
return SKILL_TAGS[Math.floor(Math.random() * SKILL_TAGS.length)];
}
// [...]
}
Observable(s) 的 catchError() 返回:Cannot read property 'getSkills' of undefined
我怀疑 mutator 没有在类范围内被调用,因此无法找到。
我将如何在 JS 中做这样的事情?
整个项目可以在以下位置进行检查:
解决方案
用于救援的箭头函数 (Lamdas)
如前所述,您的问题归结为如何this
确定函数的上下文 ( )。这是在运行时完成的。
this
要扩展此处给出的另一个答案,您可以定义一个已绑定其上下文 ( ) 的函数。诀窍是尽早调用该函数并使用该函数的部分应用版本。一种方法是使用bind
.
const functionName = (function (args){
/* Some code */
return /*something*/
}).bind(this);
这有点难看,所以 JavaScript 有箭头函数可以做同样的事情。
const functionName = (args) => {
/* Some code */
return /*something*/
}
这样漂亮多了。更好的是单行单表达式 lamdas 有一个隐式返回:
const functionName = (args) => {
return /*something*/;
}
// Is the same as
const functionName = (args) => /*something*/;
普通函数在调用时会给出一个上下文。箭头函数总是将它们定义的范围绑定为它们的上下文。如果您不使用 Javascript 的原型继承,通常没有太多理由需要从定义它的范围更改函数的上下文。
我发现如果你愿意稍微了解一下这些知识,你真的可以整理你的代码。
下面是我将如何编写完全使用箭头函数的 HeroService 类。如果这是最好的方法,这是有争议的。当然,这种方法允许更多的声明式/函数式风格。在实践中,两者的混合让你两全其美。
export class HeroService {
// [...]
public getHeroes = (): Observable<Hero[]> =>
this.http.get<Hero[]>(this.heroesUrl).pipe(
map(this.mutateHeroes(this.addSkillsToHero)),
map(this.mutateHeroes(this.splitName))
);
public getHero = (id): Observable<Hero> =>
this.http.get<Hero>(this.heroesUrl + "/" + id).pipe(
map(this.addSkillsToHero),
map(this.splitName)
);
private mutateHeroes = transformer =>
(heroes: Hero[]) => heroes.map(transformer);
private splitName = (hero: Hero) => {
let heroNames: string[] = hero.name.split(" ");
return ({
...hero,
firstName: heroNames.splice(0, 1).join(" "),
lastname: heroNames.splice(heroNames.length - 1, 1).join(" "),
middlename: heroNames.join(" ")
});
}
private addSkillsToHero = (hero: Hero) => ({
...hero, skills: this.getSkills(Math.ceil(Math.random() * 10))
});
private getSkills = (count: number): string[] =>
Array.from({length: count}).map(this.getRandomSkill);
private getRandomSkill = (): string =>
SKILL_TAGS[Math.floor(Math.random() * SKILL_TAGS.length)];
// [...]
}
额外:组合函数
正确实现的映射函数的一个很酷的属性(无论您是映射数组还是流)是:
map(x => f(g(x)))
===
map(g).map(f)
这告诉我们,如果我们 compose addSkillsToHero
,splitName
那么你可以用一个map
而不是两个来做到这一点。RxJS 流实际上是转换器,因此它们(在某种程度上)为您执行此操作,但数组映射不会,因此您也可以通过组合获得一些边际性能(您只遍历数组一次!)。
通常你会给你一个像 Ramda/Redux/Lodash/etc 这样的库,你可以写类似的东西
private addSkillsAndSplitName =
compose(this.addSkillsToHero, this.splitName);
但是使用 vanilla JavaScript 组合两个函数也很容易。
private addSkillsAndSplitName =
hero => this.addSkillsToHero(this.splitName(hero))
然后你可以像这样使用这个新功能:
export class HeroService {
// [...]
public getHeroes = (): Observable<Hero[]> =>
this.http.get<Hero[]>(this.heroesUrl).pipe(
map(this.mutateHeroes(this.addSkillsAndSplitName))
);
public getHero = (id): Observable<Hero> =>
this.http.get<Hero>(this.heroesUrl + "/" + id).pipe(
map(this.addSkillsAndSplitName)
);
而且您还没有对可测试性、灵活性或关注点分离产生影响,因为功能仍然存在于单独的函数中。
推荐阅读
- java - 为什么 Spring Data 会进行过多的查询?
- python - PythonShell.run 在结果 nodejs 中返回 null
- c# - 在 C# 中将一种对象类型转换为另一种对象类型的通用自定义方法
- c# - 如何使用 Linq 在 Ranked Choice Vote 中选择前/后 N 名参赛者?
- ruby-on-rails - 希望在 rails 上传时显示图片
- laravel - Laravel 无法打开流:CentOs7 上 Dompdf lib 字体权限的权限被拒绝
- html - PrimeNG
如何保持单击菜单项处于选中状态(Angular 7) - azure - 发布 Azure 域
- django - 当文件池在 React 上发布文件时,Django Rest Framework 返回错误请求
- c# - Asp.net Core RequestSizeLimit 仍然执行动作