NgRx SignalStore 的事件公告插件:Flux 架构的现代诠释
由 Mux 赞助的 DEV 全球展示挑战赛:展示你的项目!
我们很高兴地宣布推出 NgRx SignalStore 的 Events 插件,该插件已作为 NgRx v19.2 的一部分发布!这一新增功能为 SignalStore 带来了基于事件的状态管理功能,使开发人员能够更有效地创建可扩展的应用程序。
实验阶段🚧
Events 插件目前处于实验阶段。这意味着其 API 可能会发生变化,并且在版本稳定之前,未来的版本可能会进行一些修改,而不会发布标准的重大变更通知。我们鼓励您试用该插件,提供反馈,并帮助我们塑造它的未来!
Flux架构🔄
该插件从最初的 Flux 架构中汲取灵感,并融合了 NgRx Store、NgRx Effects 和 RxJS 的最佳实践和模式。
带有 Events 插件的应用程序架构由以下构建模块组成:
- 事件:描述系统内发生的事件。事件被分发以触发状态更改和/或副作用。
- 调度器:一个事件总线,它将事件转发给商店中相应的处理程序。
- Store:包含用于管理状态和处理副作用的 reducer 和 effects,从而保持清晰可预测的应用程序流程。
- 视图:反映状态变化并分发新事件,从而实现用户界面与底层系统之间的持续交互。
通过分发事件并对事件做出反应,事件的发生(事件本身)与事件的发生方式(状态变化或由此产生的副作用)解耦,从而实现可预测的数据流和更易于维护的代码。
攻略🛠️
Events 插件提供了一组 API,用于以响应式和声明式的方式定义和处理事件。以下示例演示了如何使用这些 API 通过事件实现状态和行为编排。
定义事件
事件是使用该eventGroup函数创建的,该函数将相关事件组织到同一来源下。此函数接受一个包含两个属性的对象:
source:标识事件组的来源(例如'Users Page',,'Users API')。events:一个命名事件创建者的字典,其中每个键定义事件名称,每个值使用type核心@ngrx/signals包中的辅助函数定义有效负载类型。
import { type } from '@ngrx/signals';
import { eventGroup } from '@ngrx/signals/events';
export const usersPageEvents = eventGroup({
source: 'Users Page',
events: {
opened: type<void>(),
refreshed: type<void>(),
},
});
export const usersApiEvents = eventGroup({
source: 'Users API',
events: {
loadedSuccess: type<User[]>(),
loadedFailure: type<string>(),
},
});
执行状态更改
为了处理事件引起的状态变化,SignalStore 提供了相应的withReducer功能。Case reducer 通过函数定义on,该函数将一个或多个事件映射到一个 case reducer 函数。每个 case reducer 接收事件并返回状态更新。它支持三种返回值形式:部分状态对象、部分状态更新器以及部分状态对象和/或更新器的数组。
import { signalStore } from '@ngrx/signals';
import { setAllEntities, withEntities } from '@ngrx/signals/entities';
import { on, withReducer } from '@ngrx/signals/events';
export const UsersStore = signalStore(
{ providedIn: 'root' },
withEntities<User>(),
withRequestStatus(),
withReducer(
on(usersPageEvents.opened, usersPageEvents.refreshed, setPending),
on(usersApiEvents.loadedSuccess, ({ payload }) => [
setAllEntities(payload),
setFulfilled(),
]),
on(usersApiEvents.loadedFailure, ({ payload }) => setError(payload)),
),
);
💡 该功能的实现
withRequestStatus及其状态更新器可在SignalStore 官方文档中找到。
副作用
副作用通过该withEffects特性进行处理。该特性接受一个函数,该函数接收 store 实例作为参数,并返回一个副作用字典。每个副作用都被定义为一个可观察对象,它会对Events服务中的特定事件做出反应。如果某个副作用发出新事件,该事件将被自动分发。
import { Events, withEffects } from '@ngrx/signals/events';
import { mapResponse } from '@ngrx/operators';
export const UsersStore = signalStore(
/* ... */
withEffects(
(
store,
events = inject(Events),
usersService = inject(UsersService),
) => ({
loadUsers$: events
.on(usersPageEvents.opened, usersPageEvents.refreshed)
.pipe(
exhaustMap(() =>
usersService.getAll().pipe(
mapResponse({
next: (users) => usersApiEvents.loadedSuccess(users),
error: (error: { message: string }) =>
usersApiEvents.loadedFailure(error.message),
}),
),
),
),
logError$: events
.on(usersApiEvents.loadedFailure)
.pipe(tap(({ payload }) => console.log(payload))),
}),
),
);
雷丁州
该插件不会改变状态的暴露或使用方式。因此,组件可以通过使用 store 实例来访问状态和计算信号。
@Component({
selector: 'app-users',
template: `
<h1>Users</h1>
@if (usersStore.isPending()) {
<p>Loading...</p>
}
<ul>
@for (user of usersStore.entities(); track user.id) {
<li>{{ user.name }}</li>
}
</ul>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UsersComponent {
readonly usersStore = inject(UsersStore);
}
调度事件
要触发状态变更或副作用,可以使用该Dispatcher服务分发事件。它提供了一个dispatch以事件作为输入的方法。
import { Dispatcher } from '@ngrx/signals/events';
@Component({
/* ... */
template: `
<h1>Users</h1>
<button (click)="refresh()">Refresh</button>
`,
})
export class UsersComponent {
readonly usersStore = inject(UsersStore);
readonly dispatcher = inject(Dispatcher);
constructor() {
this.dispatcher.dispatch(usersPageEvents.opened());
}
refresh(): void {
this.dispatcher.dispatch(usersPageEvents.refreshed());
}
}
为了方便起见,可以使用该injectDispatch函数生成自分发事件,该函数返回一个对象,该对象具有与提供的事件创建器匹配的方法。
import { injectDispatch } from '@ngrx/signals/events';
@Component({
/* ... */
template: `
<h1>Users</h1>
<button (click)="dispatch.refreshed()">Refresh</button>
`,
})
export class UsersComponent {
readonly usersStore = inject(UsersStore);
readonly dispatch = injectDispatch(usersPageEvents);
constructor() {
this.dispatch.opened();
}
}
扩大规模
随着应用程序复杂性的增加,可以将 reducer 和 effect 提取到独立的功能中,以提高可维护性。
提取还原剂
export function withUsersReducer() {
return signalStoreFeature(
{ state: type<EntityState<User> & RequestStatusState>() },
withReducer(
on(usersPageEvents.opened, usersPageEvents.refreshed, setPending),
on(usersApiEvents.loadedSuccess, ({ payload }) => [
setAllEntities(payload),
setFulfilled(),
]),
on(usersApiEvents.loadedFailure, ({ payload }) => setError(payload)),
),
);
}
提取效应
export function withUsersEffects() {
return signalStoreFeature(
withEffects(
(
store,
events = inject(Events),
usersService = inject(UsersService),
) => ({
loadUsers$: events
.on(usersPageEvents.opened, usersPageEvents.refreshed)
.pipe(
exhaustMap(() =>
usersService.getAll().pipe(
mapResponse({
next: (users) => usersApiEvents.loadedSuccess(users),
error: (error: { message: string }) =>
usersApiEvents.loadedFailure(error.message),
}),
),
),
),
logError$: events
.on(usersApiEvents.loadedFailure)
.pipe(tap(({ payload }) => console.log(payload))),
}),
),
);
}
最终商店组成
export const UsersStore = signalStore(
{ providedIn: 'root' },
withEntities<User>(),
withRequestStatus(),
withUsersReducer(),
withUsersEffects(),
);
要点总结🔑
事件插件:
- 结合了 Flux、NgRx Store、NgRx Effects 和 RxJS 中经过验证的模式。
- 与现有 SignalStore 功能无缝集成。
- 通过强大的自定义选项扩展 Flux 架构。
- 通过单一方法统一管理本地和全局状态。
即将举办的 NgRx 工作坊🎓
随着 NgRx 在 Angular 中的应用日益广泛,许多开发者和团队仍然需要关于如何架构和构建企业级 Angular 应用的指导。我们很高兴地宣布,NgRx 团队即将推出一系列研讨会!
我们提供一到三个全天的 NgRx 工作坊,涵盖从基础知识到高级主题的所有内容。无论您的团队是刚刚开始使用 NgRx 还是已经使用了一段时间,我们都保证他们能从这些工作坊中学习到新的概念。
请访问我们的工作坊页面了解更多详情。下一场工作坊安排如下:
感谢所有贡献者和赞助商!🏆
NgRx 仍然是一个社区驱动的项目。设计、开发、文档编写和测试——所有这些都离不开社区的帮助。请访问我们的社区贡献者页面,查看所有为该框架做出贡献的人。
如果您有兴趣贡献代码,请访问我们的GitHub 页面,查看我们待解决的问题,其中一些问题专门针对新贡献者。我们还在 GitHub 上设有活跃的讨论区,用于讨论新功能和改进建议。
我们要衷心感谢我们的黄金赞助商Nx!Nx 长期以来一直推广 NgRx 作为构建 Angular 应用程序的工具,并致力于支持他们所依赖的开源项目。
我们要感谢我们的铜牌赞助商House of Angular!
最后,我们还要感谢那些一次性或每月捐款的个人赞助商。
赞助 NgRx 🤝
如果您有兴趣赞助 NgRx 的持续开发,请访问我们的GitHub Sponsors 页面了解不同的赞助选项,或直接联系我们讨论其他赞助机会。
请在Twitter和LinkedIn上关注我们,获取有关 NgRx 平台的最新动态。
文章来源:https://dev.to/ngrx/announcing-events-plugin-for-ngrx-signalstore-a-modern-take-on-flux-architecture-4dhn
