我一直在经常使用信号,但遇到了一个无法找到合理答案的用例。
许多在线示例和指南都展示了如何创建具有单个值的信号并通过 .set() 或 .update() 更新它。例如:
public value = signal<int>(0); value.set(1);
这对于简单的输入来说非常有意义,但是当我们想要将信号与具有多个值的对象一起使用时会发生什么?或者我们想要多个输入?例如,如果我有一个 User 对象
export interface User { firstName: string, lastName: string }
该对象位于信号内部,
public currentUser = signal<User>({firstName: 'Doug', lastName: 'The Cat'});
我想要一个表单供用户更新他们的姓名,我如何更新各个值而不完全覆盖现有的用户对象?
我尝试创建信号输出的浅表副本,然后附加表单,然后调用信号上的 set 来更新属性,但这感觉不太好,因为我只是覆盖了那里的内容。
我还考虑过将每个值、firstName、lastName 转换为它们自己的信号,但是当值添加到诸如 streetAddress、poBox 等对象时,这会变得失控且不灵活。
使用 update 方法来修补信号的值将是你最好的选择。在您的用例中,它的工作原理如下:
this.currentUser.update(x => ({ ...x, firstName: 'Danny' }));
使用扩展运算符创建新对象并仅设置修补的属性是一种常见做法,并且比直接更新属性有很多好处。信号特有的一个好处是,将信号用作“计算”信号的一部分时不会导致任何问题。如果您要直接更新属性,则计算出的信号将不会更新,因为对象引用从未更改。 如果您愿意,您还可以开发自己的可写信号版本,该信号专门用于对象并具有自己的
patch 方法。这将具有可在模板中使用的好处。
export type PatchableSignal<T extends {}> = Signal<T> & WritableSignal<T> &
{
/** Updates properties on an object */
patch(value: Partial<T>): void;
};
export function patchableSignal<T extends {}>(initialValue: T, options?: CreateSignalOptions<T>): PatchableSignal<T> {
const internal = signal<T>(initialValue) as SignalGetter<T> & WritableSignal<T>;
const node = internal[SIGNAL];
return Object.assign(internal, {
patch: (value: Partial<T>) => signalUpdateFn(node, (x) => ({ ...x, ...value })),
});
}
<button (click)="currentUser.patch({ firstName: 'New Name' })">Click Me</button>