mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -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