什么是 Angular Composable 概念
我们都知道 Lodash,它是一个在项目中重用无状态逻辑的库。那么,如果在 Angular 项目中我们有一个类似的工具包来重用有状态逻辑呢?
Composables 并不是一个新的概念,它是来自 Vue.js 的一个概念。我在这篇博客中使用的许多示例和想法直接来自 Vue.js Composables 文档。
在版本 16.0.0-next.0 中,Angular 团队引入了 Signals 的实现,Signals 是一种反应性的基本组件,可以在 Angular 中提供精细的反应性能力。随着这样的重大变化,还考虑到 Angular 团队在最新版本中引入的其他非常有用的功能,比如 inject 函数或 DestroyRef 的概念,不可避免地会出现新的模式。本文试图在 Angular 的上下文中探索这个模式。
在 Angular 自身中,我们已经看到了我们可以称之为“功能型服务(Functional Services)”的过渡。它始于版本 14.2.0 中功能型守卫(functional guards)和解析器(resolvers)的引入,继续于版本 15.0.0 中功能型拦截器(functional interceptors)的引入。但是什么是 Angular Composable,为什么以及如何在项目中使用它?
什么是 Angular Composable?
在 Angular 应用程序的上下文中,一个 composable 是一个使用 Signals API 封装有状态逻辑的函数。这些可组合函数可以在多个组件中重复使用,可以相互嵌套,并且可以帮助我们将组件的有状态逻辑组织成小型、灵活和简单的单元。
与我们创建 util 函数以在组件之间重用无状态逻辑的方式相同,我们创建 composable 以共享有状态逻辑。
但是让我们看看在 Angular 应用程序中如何编写一个 composable。在下面的示例中,我没有使用 Angular Signals RFC 中提议的 API。当此 API 的所有功能就位时(例如应用程序渲染生命周期钩子,基于 Signal 的查询),我们将能够以更好的方式编写这些可组合函数,并能够为它们提供更多功能。
让我们从一个非常简单的示例开始。
Mouse Tracker Example
使用 Signals 在 Angular Component 里实现 mouse tracking 功能:
@Component({
standalone: true,
template: ` {{ x() }} {{ y() }} `,
})
export class MouseTrackerComponent implements AfterViewInit, OnDestroy {
// injectables
document = inject(DOCUMENT);
// state encapsulated and managed by the composable
x = signal(0);
y = signal(0);
ngAfterViewInit() {
document.addEventListener('mousemove', this.update.bind(this));
}
// a composable can update its managed state over time.
update(event: MouseEvent) {
this.x.update(() => event.pageX);
this.y.update(() => event.pageY);
}
ngOnDestroy() {
document.removeEventListener('mousemove', this.update.bind(this));
}
}
可以对其重构,增加通用性:
// mouse-tracker.ts file
export function useMouse() {
// injectables
const document = inject(DOCUMENT);
// state encapsulated and managed by the composable
const x = signal(0);
const y = signal(0);
// a composable can update its managed state over time.
function update(event: MouseEvent) {
x.update(() => event.pageX);
y.update(() => event.pageY);
}
document.addEventListener('mousemove', update);
// lifecycle to teardown side effects.
inject(DestroyRef).onDestroy(() =>
document.removeEventListener('mousemove', update)
);
// expose managed state as return value
return { x, y };
}
其他 Component 也可以重用了:
@Component({
standalone: true,
template: ` {{ mouse.x() }} {{ mouse.y() }} `,
})
export class MouseTrackerComponent {
mouse = useMouse();
}
- 点赞
- 收藏
- 关注作者
评论(0)