在评估现代 Angular 中应用程序返工的架构时,我发现 Signals 是状态管理的绝佳候选者。
但是,我们的应用程序相当大,并且尝试了一些想法,我发现了一些陷阱。
AppState
包装在 Signal
@Injectable()
export class AppStateService {
private appState = signal(new AppState());
public getState() {
return this.appState.asReadonly();
}
public setForm(form: FormGroup) {
this.appState.update(state => {
state.form = form;
return state;
})
}
public addError(error: AppError) {
this.appState.update(state => {
state.errors.push(error)
return state;
})
}
}
但我知道这效率不高,因为更改状态的一部分将重新评估每个依赖代码。
@ngrx/signals
,认为“细粒度反应性”和嵌套/深层信号的概念可以解决这个问题:export const AppStore = signalStore(
withState(initialAppState),
withMethods((store) => ({
addError: (error: AppError): void => {
patchState(store, (state) => ({ errors: [...state.errors, error] }));
},
setForm: (form: FormGroup): void => {
patchState(store, (state) => ({ form: form }));
},
debug: (): any => {
const state = store.form().getRawValue(); // Trying a "partial" state use
return state;
},
})),
);
但是在第二个示例中,如果我在代码中使用
debug()
函数,我至少注意到两个问题:
form
的部分使用,如果我在errors
中推送一个值,该方法也会被重新评估,而我们知道form
有没有更好的状态管理方式?我是否应该放弃集中的状态管理并根据关注划分到不同的状态?我担心这可能会导致意大利面条式代码,因为我需要更改一个子状态才能对其他子状态产生副作用。我不认为
@ngrx/signals
相对于单个大状态对于性能/反应性的价值。
我会按顺序回答你的问题。
问题 1 - 信号 如果您想使用信号进行状态管理(好主意!),您应该只在信号内使用不可变值。
setForm
和 addError
方法会改变状态,但不会替换引用。信号不会注意到变化,也不会调用变化检测。因此,在无区域应用程序中(这是使用信号背后的全部意义),UI 将不会检测到更改。
正确的实现是:
setForm(form: FormGroup) {
this.appState.update(state => ({
...state,
form
}))
}
但即使这样的实现也不是一个好主意,因为 FormGroup 本身并不是不可变的。更好的方法是在信号中保存表单组的值,而不是表单组本身。
如果您想了解有关如何使用信号实现有状态服务的更多信息,有一个 udemy 课程 详细介绍了它以及最佳实践演示。
问题2-信号存储 不使用方法,而是使用计算信号
withComputed
将形式转换为其原始值。计算信号确实使用记忆化。
再说一遍 - 将表单组保持在信号存储状态是一个坏主意,因为状态应该是不可变的,而表单组则不是。