Skip to content

Commit

Permalink
DEV: Introduce @dedupeTracked (#27084)
Browse files Browse the repository at this point in the history
Same as `@tracked`, but skips notifying consumers if the value is unchanged. This introduces some performance overhead, so should only be used where excessive downstream re-evaluations are a problem.

This is loosely based on `@dedupeTracked` in the `tracked-toolbox` package, but without the added complexity of a customizable 'comparator'. Implementing ourselves also avoids the need for pulling in the entire package, which contains some tools which we don't want, or which are now implemented in Ember/Glimmer (e.g. `@cached`).
  • Loading branch information
davidtaylorhq committed May 20, 2024
1 parent 32aaf2e commit 23b02a3
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 0 deletions.
46 changes: 46 additions & 0 deletions app/assets/javascripts/discourse/app/lib/tracked-tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,49 @@ export function resettableTracked(prototype, key, descriptor) {
},
};
}

/**
* @decorator
*
* Same as `@tracked`, but skips notifying about updates if the value is unchanged. This introduces some
* performance overhead, so should only be used where excessive downstream re-evaluations are a problem.
*
* @example
*
* ```js
* class UserRenameForm {
* ⁣@dedupeTracked fullName;
* }
*
* const form = new UserRenameForm();
* form.fullName = "Alice"; // Downstream consumers will be notified
* form.fullName = "Alice"; // Downstream consumers will not be re-notified
* form.fullName = "Bob"; // Downstream consumers will be notified
* ```
*
*/
export function dedupeTracked(target, key, desc) {
let { initializer } = desc;
let { get, set } = tracked(target, key, desc);

let values = new WeakMap();

return {
get() {
if (!values.has(this)) {
let value = initializer?.call(this);
values.set(this, value);
set.call(this, value);
}

return get.call(this);
},

set(value) {
if (!values.has(this) || values.get(this) !== value) {
values.set(this, value);
set.call(this, value);
}
},
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { cached } from "@glimmer/tracking";
import { module, test } from "qunit";
import { dedupeTracked } from "discourse/lib/tracked-tools";

module("Unit | tracked-tools", function () {
test("@dedupeTracked", async function (assert) {
class Pet {
initialsEvaluatedCount = 0;

@dedupeTracked name;

@cached
get initials() {
this.initialsEvaluatedCount++;
return this.name
?.split(" ")
.map((n) => n[0])
.join("");
}
}

const pet = new Pet();
pet.name = "Scooby Doo";

assert.strictEqual(pet.initials, "SD", "Initials are correct");
assert.strictEqual(
pet.initialsEvaluatedCount,
1,
"Initials getter evaluated once"
);

pet.name = "Scooby Doo";
assert.strictEqual(pet.initials, "SD", "Initials are correct");
assert.strictEqual(
pet.initialsEvaluatedCount,
1,
"Initials getter not re-evaluated"
);

pet.name = "Fluffy";
assert.strictEqual(pet.initials, "F", "Initials are correct");
assert.strictEqual(
pet.initialsEvaluatedCount,
2,
"Initials getter re-evaluated"
);
});
});

0 comments on commit 23b02a3

Please sign in to comment.