mirror of
https://github.com/discourse/discourse.git
synced 2024-11-22 08:57:10 -06:00
DEV: Introduce @dedupeTracked
(#27084)
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`).
This commit is contained in:
parent
32aaf2e8d3
commit
23b02a3824
@ -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);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -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"
|
||||
);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user