Angular中的开发技巧

写在前面 👣

angular是一个非常优秀的前端框架,但是各种语法和api也是相对较多,掌握一些开发技巧可以使我们写的代码看起来更优雅,我接触angular开发三年有余,加入我们worktile团队也有一段时间了,不管是通过自己学习还是参考研究前辈写的代码,学到了很多新的知识,正好借此机会来一波总结。

工具篇 🛠️

俗话说工欲善其事,必先利其器。先推荐几个我装机必备的软件和插件。

  1. utools 软件

提高生产力的工具,轻量、安全、简洁、无广告。

  1. FeHelper 浏览器插件

前端助手

  1. 沙拉查词

Saladict 沙拉查词是一款专业划词翻译扩展,为交叉阅读而生。大量权威词典涵盖中英日韩法德西语。

js/ts 篇 🎃

  1. 求数组中的总和、最大最小值
1
const array = [1,2,3,4,5];
  • 求和
1
array.reduce((pre,cur) => pre + cur);
  • 求最大值
1
array.reduce((pre,cur) => pre > cur ? pre : cur);
  • 求最小值
1
array.reduce((pre,cur) => pre < cur ? pre : cur);
  1. 过滤出数组中的真值

参数值为 0 、 -0 、 null 、 false 、 NaN 、 undefined ,或空字符串( “” ),则该对象具

有的初始值为 false

1
2
3
4
5
6
7
8
9
const array = [0,-0,null,false,NaN,undefined,"",1,2,3];

array.filter(Boolean);

// Boolean不仅是一个boolean类型的包装器,也可以作为转换函数使用,等同于双重非运算符!!
// 上行代码等同于:
// array.filter(item => Boolean(item));

// 输出:[1, 2, 3]
  1. 去除数组中重复值
1
2
3
4
5
const array = [a,b,c,d,1,2,3,a,e,f,1];

array.filter((item,idx) => array.indexOf(item) === idx);

array = [...new Set(array)]
  1. 三元运算符和空合并运算符(??)

三元运算符大家经常用,但是有时候或许空合并运算符才是最佳选择。

空合并运算符 (??) 是一个逻辑运算符,当其左侧操作数为空或未定义时返回其右侧操作数,否则返回其左侧操作数。

1
2
3
4
5
const item: any = helpers.find(okrObjectiveType, { value: value.type });
return item ? item.desc : '';

// 等同于:
return item?.desc ?? '';
  1. 展开语法
  • 合并对象属性不要再用 Object.assign() 属性啦

展开对象的语法和 Object.assign() 行为一致,执行的都是浅拷贝 (只遍历一层)。但是语法更简短,需要注

意的是 Object.assign() 函数会触发 setters ,而展开语法则不会。

1
2
3
4
5
6
7
8
9
10
11
const user = { 
name: 'Kapil Raghuwanshi',
gender: 'Male'
};

const college = {
primary: 'Mani Primary School',
secondary: 'Lass Secondary School'
};

const summary = {...user,...college};
  • 连接多个数组

没有展开语法的时候,只能组合使用 push , splice , concat 等方法,来将已有数组元素变成新数组的一部分。有了展开语法,通过字面量方式,构造新数组会变得更简单、更优雅!

1
2
3
4
5
const parts = ['shoulders', 'knees'];

const lyrics = ['head', ...parts, 'and', 'toes'];

// ["head", "shoulders", "knees", "and", "toes"]
  1. 可选链

可选的链接 ?. 如果值在 ? 之前,则停止评估。为 undefined 或 null 并返回。

1
2
3
4
5
6
7
8
<thy-avatar
class="styx-body-title-doc-avatar"
thyShowName="true"
[thySize]="24"
[thyName]="detailInfo?.created_by?.display_name"
[thySrc]="detailInfo?.created_by?.avatar"
[thyDisabled]="detailInfo?.created_by?.uid | isDisabledMember">
</thy-avatar>
  1. 参数默认值

给函数参数设置默认值有时会很方便。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Pipe({ name: 'taskStateClass' })
export class TaskStateClassPipe implements PipeTransform {
transform(type: number, prefix = 'wtf-') {
if (!type) {
return '';
}
const stateConstantMap = helpers.keyBy(stateConstant, 'value');
const state = stateConstantMap[type];
let className = state ? state.className : '';
if (prefix) {
className = prefix + className;
}
return className;
}
}
  1. 巧妙的使用结构赋值可以避免使用 any 类型
1
2
3
4
5
6
onViewFilter($event: { config: MissionViewFilterConfigurationInfo; originConfig: MissionViewFilterConfigurationInfo }) {
if ($event.config.date_unit !== $event.originConfig.date_unit) {
this.initDateRangeValue($event.config.date_unit);
this.setDateRange();
}
}
  1. padStart方法补0

getDate()、getMonth() 等api获取日期的值可能只有一位,常见的需求是格式化成两位数字

1
2
3
const date = new Date().getDate(); // 21

const month = `${new Date().getMonth()}`.padStart(2,0) // 08
  1. 字符串转number类型快捷方法

一元的 + 号运算符相当于 Number() 方法。

先来回顾一下 JavaScript 中的原始值都有哪些:

JavaScript 中的原始值是指数字、字符串、布尔值、null和undefined。

1
const a = +'3'; // 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
+ undefined; // NaN

+ null; // 0

+ true; // 1
+ false; // 0

+ '1'; // 1
+ '-1'; // -1
+ 'a1'; // NaN

// Object类型的值的转换:

+ new Date() // 1663912235982
+ {}; // => NaN
+ { valueOf: function () { return 0 } }; // => 0
+ {
a: () => {},
valueOf: function() {
return this.a;
},
toString: function() {
return 1;
}
}

Apply the following steps:
1.Let primValue be ToPrimitive(input argument, hint Number).
2.Return ToNumber(primValue).

ToPrimitive会调用 [[DefaultValue]]内部方法

1.Let valueOf be the result of calling the [[Get]] internal method of object O with argument “valueOf”.
2.If IsCallable(valueOf) is true then,
a.Let val be the result of calling the [[Call]] internal method of valueOf, with O as the this value and an empty argument list.
b.If val is a primitive value, return val.

简单来说就是:如果传入的值是 object 类型的话,会先调用 Object.valueOf() 函数,如果其返回值属于原始值类型,那么就继续调用Number() 函数,否则会去调用 Object.toString() 函数,输入其返回值。

rxjs篇 🐇

  1. 使用takeUntil取消订阅
1
2
3
4
5
6
7
8
9
10
destroy$: Subject<any> = new Subject<any>();
ngOnInit() {
request.pipe(takeUntil(this.destroy$)).subscribe();
request.pipe(takeUntil(this.destroy$)).subscribe();
request.pipe(takeUntil(this.destroy$)).subscribe();
}
ngOnDestroy() {
this.destroy$.next(null);
this.destroy$.complete();
}

扩展:其它批量取消订阅的方式

  • SubscriptionLike
1
2
3
4
5
6
7
8
9
10
subscriptions: SubscriptionLike[] = [];
ngOnInit() {
this.subscriptions.push(request.subscribe(...));
this.subscriptions.push(request.subscribe(...));
this.subscriptions.push(request.subscribe(...));
}
ngOnDestroy() {
this.subscriptions.forEach(
(subscription) => subscription.unsubscribe());
}

关于 SubscriptionLike 类型:

1
2
3
4
5
6
7
interface SubscriptionLike extends Unsubscribable {
get closed: boolean
unsubscribe(): void

// inherited from index/Unsubscribable
unsubscribe(): void
}

其实现类:

Subject、BehaviorSubject、ReplySubject、AsyncSubject、Subscription、Subscriber

  • Subscription
1
2
3
4
5
6
7
8
9
subscriptions: Subscription = new Subscription();
ngOnInit() {
this.subscriptions.add(request.subscribe(...));
this.subscriptions.add(request.subscribe(...));
this.subscriptions.add(request.subscribe(...));
}
ngOnDestroy() {
this.subscriptions.unsubscribe();
}
  1. forkJoin连接多个请求

当有一组 observables,但你只关心每个 observable 最后发出的值时,此操作符是最适合的。此操作符的一个常见用例是在页面加载(或其他事件)时你希望发起多个请求,并在所有请求都响应后再采取行动。

  1. distinctUntilChanged去除重复数据

只有当当前值与之前最后一个值不同时才会发出。distinctUntilChanged 默认使用 === 进行比较

  1. finalize/finally

当Observable完成或者报错时调用。

  1. switchMap

当前一个订阅未完成时,又发出新的订阅,则会取消之前订阅。

Demo------Typehead 搜索:

类型头搜索是一种通过文本逐步搜索和过滤的方法。它有时也被称为自动完成,增量搜索,搜索作为你的类型,内联搜索,即时搜索和字轮。

关于类型头搜索优化的demo之前文章有专门写过

扩展:concatMap、mergeMap、switchMap和exhaustMap的使用区别和使用场景

Angular语法篇 👼

  1. 合理使用 ngZone runOutsideAngular 来提升应用性能 我们知道Angular可以自动处理变化检测,这是因为它使用了 zone.js ,简单的来说, zone.js 就是通过打补丁的方式来拦截浏览器的事件,然后进行变化检测,但是变化检测是极其消耗资源的,如果绑定了大量的事件,那么就会造成性能问题,所以我们可以使用 runOutsideAngular 来减少不必要的变化检测。
1
2
3
4
5
6
7
8
9
10
11
this.ngZone.runOutsideAngular(() => {
this.renderer.listen(this.elementRef.nativeElement, 'keydown', event => {
const keyCode = event.which || event.keyCode;
if (keyCode === keycodes.ENTER) {
event.preventDefault();
this.ngZone.run(() => {
this.thyEnter.emit(event);
});
}
});
});
  1. @ViewChild 读取指定类型的实例
1
<input #input thyInput  [thyAutofocus]="true" />

上面这行代码有三个实例 ElementRef 、 ThyInputComponent 、 ThyAutoFocusDirective ,在某些情况下如果我们要获取指定类型的实例应该怎么做呢?

1
@ViewChild('input', { read:ThyInputComponent })  inputComponent : ThyInputComponent ;
  1. 动态绑定的样式或者类名如果只有一个时可以简写
1
2
3
<thy-list-item *ngFor="let item of context" [class.active]="item.isActive"
[innerHTML]="item.elementRef.nativeElement.innerHTML | bypassSecurityTrustHtml">
</thy-list-item>

其它 🍢

  1. 如何浏览器中调试元素的 hover 样式

  1. 使用 git 命令的 git cherry-pick 把一个分支的部分commit应用到其他的分支上。
1
2
3
4
5
git cherry-pick commitHash

// 转移多个提交

git cherry-pick hashA hashB