宣布 NgRx Signals v18:状态封装、私有存储成员、增强实体管理等等!
我们很高兴地宣布 NgRx Signals 的最新主要版本,其中包含令人兴奋的新功能、错误修复和其他更新。
NgRx 信号现已稳定!🎉
从版本 18 开始,该@ngrx/signals
软件包已不再是开发者预览版,并且已准备好投入生产。
状态封装
在以前的版本中,SignalStore 的状态可以从外部更新。
// v17:
export const CounterStore = signalStore(
withState({ count: 0 }),
withMethods((store) => ({
setCount(count: number): void {
patchState(store, { count }); // ✅
},
}))
);
// ===
const store = inject(CounterStore);
patchState(store, { count: 10 }); // ✅
在版本 18 中,状态默认受到保护,不被外部修改,从而确保一致且可预测的数据流。
// v18:
const CounterStore = signalStore(
withState({ count: 0 }),
withMethods((store) => ({
setCount(count: number): void {
patchState(store, { count }); // ✅
},
}))
);
// ===
const store = inject(CounterStore);
patchState(store, { count: 10 }); // ❌ compilation error
但是,为了在某些情况下获得更大的灵活性,可以在创建 SignalStore 时通过设置protectedState
选项来启用状态的外部更新。false
const CounterStore = signalStore(
{ protectedState: false }, // 👈
withState({ count: 0 })
);
// ===
const store = inject(CounterStore);
// ⚠️ The state is unprotected from external modifications.
patchState(store, { count: 10 });
protectedState: false
💡 为了向后兼容,添加到所有现有信号存储的迁移示意图会在升级时自动执行。
确保商店会员的诚信👨⚕️
从版本 18 开始,不允许覆盖 SignalStore 成员。如果意外覆盖了任何存储成员,开发模式下将显示警告。
const CounterStore = signalStore(
withState({ count: 0 }),
withMethods(() => ({
// ⚠️ @ngrx/signals: SignalStore members cannot be overridden.
count(): void {},
}))
);
私人商店会员🛡️
SignalStore 允许使用前缀定义无法从存储外部访问的私有成员_
。这包括根级状态切片、计算信号和方法。
const CounterStore = signalStore(
withState({
count1: 0,
// 👇 private state slice
_count2: 0,
}),
withComputed(({ count1, _count2 }) => ({
// 👇 private computed signal
_doubleCount1: computed(() => count1() * 2),
doubleCount2: computed(() => _count2() * 2),
})),
withMethods((store) => ({
increment1(): void { /* ... */ },
// 👇 private method
_increment2(): void { /* ... */ },
})),
);
// ===
const store = inject(CounterStore);
store.count1(); // ✅
store._count2(); // ❌ compilation error
store._doubleCount1(); // ❌ compilation error
store.doubleCount2(); // ✅
store.increment1(); // ✅
store._increment2(); // ❌ compilation error
💡 在私人商店会员指南中了解有关私人商店会员的更多信息。
状态追踪🕵
状态跟踪支持实现自定义 SignalStore 功能,例如日志记录、状态撤消/重做和存储同步。
在以前的版本中,可以使用getState
带有的函数来跟踪 SignalStore 状态变化effect
。
const CounterStore = signalStore(
withState({ count: 0 }),
withMethods((store) => ({
increment(): void { /* ... */ },
})),
withHooks({
onInit(store) {
effect(() => {
// 👇 The effect is re-executed on state change.
const state = getState(store);
console.log('counter state', state);
});
setInterval(() => store.increment(), 1_000);
},
})
);
由于 effect 行为无故障,如果状态在同一 tick 内多次更改,effect 函数将仅使用最终状态值执行一次。虽然异步 effect 执行有利于性能,但诸如状态撤销/重做等功能需要跟踪 SignalStore 的所有状态更改,而无需在同一 tick 内合并状态更新。
在此版本中,该@ngrx/signals
软件包提供了watchState
同步跟踪 SignalStore 状态变化的函数。它接受一个 SignalStore 实例作为第一个参数,并接受一个 watcher 函数作为第二个参数。
默认情况下,该watchState
函数需要在注入上下文中执行。它与函数的生命周期紧密相关,并在注入器销毁时自动清理。
const CounterStore = signalStore(
withState({ count: 0 }),
withMethods((store) => ({
increment(): void { /* ... */ },
})),
withHooks({
onInit(store) {
watchState(store, (state) => {
console.log('[watchState] counter state', state);
}); // logs: { count: 0 }, { count: 1 }, { count: 2 }
effect(() => {
console.log('[effect] counter state', getState(store));
}); // logs: { count: 2 }
store.increment();
store.increment();
},
})
);
在上面的例子中,watchState
函数将执行提供的观察器 3 次:一次使用初始计数器状态值,其余两次在每次递增后执行。相反,effect
函数将仅使用最终计数器状态值执行一次。
💡
watchState
在状态跟踪指南中了解有关该功能的更多信息。
增强实体管理🧩
在版本 18 中,该@ngrx/signals/entities
插件获得了多项增强。
selectId
在以前的版本中,使用自定义标识符更新实体集合是使用idKey
属性执行的。
// v17:
type Todo = { _id: number; text: string };
const TodosStore = signalStore(
withEntities<Todo>(),
withMethods((store) => ({
addTodo(todo: Todo): void {
patchState(store, addEntity(todo, { idKey: '_id' }));
},
}))
);
如果实体具有复合标识符(两个或多个属性的组合),idKey
则不能使用。
在此版本中,selectId
引入了 而不是idKey
以获得更好的灵活性。
// v18:
type Todo = { _id: number; text: string };
const selectId: SelectEntityId<Todo> = (todo) => todo._id;
const TodosStore = signalStore(
withEntities<Todo>(),
withMethods((store) => ({
addTodo(todo: Todo): void {
patchState(store, addEntity(todo, { selectId }));
},
}))
);
entityConfig
该entityConfig
函数减少了定义自定义实体配置时的重复代码,并确保了强类型。它接受一个配置对象,其中实体类型是必需的,而集合名称和自定义 ID 选择器是可选的。
const todoConfig = entityConfig({
entity: type<Todo>(),
collection: 'todo',
selectId: (todo) => todo._id,
});
const TodosStore = signalStore(
withEntities(todoConfig),
withMethods((store) => ({
addTodo(todo: Todo): void {
patchState(store, addEntity(todo, todoConfig));
},
}))
);
💡
@ngrx/signals/entities
在实体管理指南中了解有关该插件的更多信息。
升级到 NgRx Signals 18
要开始使用 NgRx Signals 18,请确保安装以下最低版本:
- Angular 版本 18.x
- Angular CLI 版本 18.x
- TypeScript 版本 5.4.x
NgRx 支持使用 Angular CLIng update
命令更新 NgRx 软件包。要将@ngrx/signals
软件包更新到最新版本,请运行以下命令:
ng update @ngrx/signals@18
即将举行的 NgRx 研讨会
随着 NgRx 的使用率与 Angular 的持续增长,许多开发者和团队仍然需要关于如何架构和构建企业级 Angular 应用的指导。我们非常高兴地宣布即将推出由 NgRx 团队直接提供的研讨会!
我们将提供一到三场全天研讨会,涵盖从 NgRx 的基础知识到最高级的主题。无论您的团队是刚开始使用 NgRx,还是已经使用一段时间,都能在这些研讨会中学习到新的概念。
访问我们的工作坊页面,从我们即将举办的工作坊列表中报名。下一场工作坊将于9月18日至20日(美国时间)和10月16日至18日(欧洲时间)举行。早鸟优惠仍然有效!
欢迎新成员加入 NgRx 团队💪
经过长时间的休整,Mike Ryan重返 NgRx 核心团队!作为 NgRx 最初的联合创始人之一,Mike 为@ngrx/effects
、@ngrx/entity
和@ngrx/store-devtools
软件包编写了第一行代码。我们非常高兴他能回归团队!欢迎回来,Mike!💜
我们还有另一个激动人心的消息:Rainer Hahnekamp已加入 NgRx 团队,成为我们值得信赖的合作伙伴!Rainer 是 Google Angular 开发专家,NgRx 代码库的积极贡献者,NgRx Toolkit社区插件的维护者,以及众多关于 NgRx 的深刻文章和视频的创作者。欢迎加入,Rainer!🚀
感谢所有贡献者和赞助商!🏆
NgRx 始终是一个社区驱动的项目。设计、开发、文档和测试——所有这些都是在社区的帮助下完成的。访问我们的社区贡献者页面,查看所有为该框架做出贡献的人员。
如果您有兴趣贡献代码,请访问我们的GitHub 页面,浏览我们的未解决问题,其中一些问题已标记为专门针对新贡献者。我们还在 GitHub 上积极讨论新功能和增强功能。
我们要衷心感谢我们的金牌赞助商Nx!Nx 长期以来一直大力推广 NgRx 作为构建 Angular 应用程序的工具,并致力于支持其所依赖的开源项目。
我们要感谢我们的铜牌赞助商House of Angular!
最后,我们还要感谢一次或每月捐款的个人赞助商。
赞助 NgRx 🤝
如果您有兴趣赞助 NgRx 的持续开发,请访问我们的GitHub 赞助商页面了解不同的赞助选项,或者直接联系我们讨论其他赞助机会。
在Twitter和LinkedIn上关注我们,了解有关 NgRx 平台的最新更新。
链接:https://dev.to/ngrx/announcing-ngrx-signals-v18-state-encapsulation-private-store-members-enhanced-entity-management-and-more-2lo6