remove redundant guards before snapshot.merge (#32937)

* remove redundant guards before snapshot.merge

* fix(viewmodels): restore RedactedBodyViewModel setting guard

* docs(mvvm): note Snapshot.merge change detection
This commit is contained in:
Zack
2026-03-26 14:00:48 +01:00
committed by GitHub
parent cd429874db
commit 411eab9fd2
6 changed files with 9 additions and 35 deletions
@@ -86,8 +86,6 @@ export class DecryptionFailureBodyViewModel
}
public setDecryptionFailureCode(decryptionFailureCode: DecryptionFailureCode | null): void {
if (this.props.decryptionFailureCode === decryptionFailureCode) return;
this.props.decryptionFailureCode = decryptionFailureCode;
this.snapshot.merge({
decryptionFailureReason: DecryptionFailureBodyViewModel.getDecryptionReasonFromCode(decryptionFailureCode),
@@ -99,17 +99,10 @@ export class ReactionsRowViewModel
const nextIsVisible = this.props.isActionable && this.props.reactionGroupCount > 0;
const nextShowAllButtonVisible =
this.props.reactionGroupCount > MAX_ITEMS_WHEN_LIMITED + 1 && !this.props.showAll;
const updates: Partial<ReactionsRowViewSnapshot> = {};
if (this.snapshot.current.isVisible !== nextIsVisible) {
updates.isVisible = nextIsVisible;
}
if (this.snapshot.current.showAllButtonVisible !== nextShowAllButtonVisible) {
updates.showAllButtonVisible = nextShowAllButtonVisible;
}
if (Object.keys(updates).length > 0) {
this.snapshot.merge(updates);
}
this.snapshot.merge({
isVisible: nextIsVisible,
showAllButtonVisible: nextShowAllButtonVisible,
});
}
public setCanReact(canReact: boolean): void {
@@ -83,30 +83,15 @@ export class RedactedBodyViewModel
}
public setEvent(mxEvent: MatrixEvent): void {
if (this.props.mxEvent === mxEvent) return;
this.props = { ...this.props, mxEvent };
const text = RedactedBodyViewModel.computeText(this.props);
const tooltip = RedactedBodyViewModel.computeTooltip(this.props.mxEvent, this.showTwelveHour);
const updates: Partial<RedactedBodyViewSnapshot> = {};
if (this.snapshot.current.text !== text) {
updates.text = text;
}
if (this.snapshot.current.tooltip !== tooltip) {
updates.tooltip = tooltip;
}
if (Object.keys(updates).length > 0) {
this.snapshot.merge(updates);
}
this.snapshot.merge({ text, tooltip });
}
private updateTooltip(): void {
const tooltip = RedactedBodyViewModel.computeTooltip(this.props.mxEvent, this.showTwelveHour);
if (this.snapshot.current.tooltip !== tooltip) {
this.snapshot.merge({ tooltip });
}
this.snapshot.merge({ tooltip });
}
}
@@ -210,9 +210,7 @@ export class RoomListItemViewModel
*/
private async loadAndSetMessagePreview(): Promise<void> {
const messagePreview = await this.loadMessagePreview();
if (messagePreview !== this.snapshot.current.messagePreview) {
this.snapshot.merge({ messagePreview });
}
this.snapshot.merge({ messagePreview });
}
/**
@@ -98,6 +98,6 @@ export class ResizerViewModel
};
public onBlur = (): void => {
if (this.getSnapshot().isFocusedViaKeyboard) this.snapshot.merge({ isFocusedViaKeyboard: false });
this.snapshot.merge({ isFocusedViaKeyboard: false });
};
}
+1 -1
View File
@@ -92,7 +92,7 @@ export function FooView({ vm }: FooViewProps): JSX.Element {
1. A View model is a class extending [`BaseViewModel`](https://github.com/element-hq/element-web/blob/develop/src/viewmodels/base/BaseViewModel.ts).
2. Implements the interface defined in the view (e.g `FooViewModel` in the example above).
3. View models define a snapshot type that defines the data the view will consume. The snapshot is immutable and can only be changed by calling `this.snapshot.set(...)` or `this.snapshot.merge(...)` in the view model. This will trigger a re-render in the view.
4. Call [`this.snapshot.merge(...)`](https://github.com/element-hq/element-web/blob/develop/packages/shared-components/src/viewmodel/Snapshot.ts#L32) to only update part of the snapshot.
4. Call [`this.snapshot.merge(...)`](https://github.com/element-hq/element-web/blob/develop/packages/shared-components/src/viewmodel/Snapshot.ts#L32) to only update part of the snapshot. `merge(...)` already skips emitting when the merged fields are unchanged, so avoid extra equality guards that only duplicate that check.
5. Avoid recomputing the entire snapshot when you only need to update a single field. For performance reasons, only recompute the fields that have actually changed. For example, if only `title` has changed, call `this.snapshot.merge({ title: newTitle })` rather than rebuilding the full snapshot object with all fields recomputed.
6. View models can have props which are passed in the constructor. Props are usually used to pass in dependencies (eg: stores, sdk, etc) that the view model needs to do its work. They can also be used to pass in initial values for the snapshot.