PERF: Optimize Category.subcategories (#31802)

At the moment, every call to `Category.subcategories` causes an
iteration through every single category. For sites with thousands of
categories, this is incredibly expensive, and makes things like the
category dropdown & sidebar rendering very slow.

To improve things, we can create a map of parent_category => [child
categories] in a single iteration, cache it, and then use that when
finding subcategories.

The `@computed` decorator ensures that this cache will be recalculated
whenever the list of categories changes (e.g. when sideloading new
categories in the experimental lazy-loaded-categories mode)
This commit is contained in:
David Taylor
2025-03-13 22:52:54 +00:00
committed by GitHub
parent 0da10c7639
commit c307e5aa47
2 changed files with 13 additions and 2 deletions

View File

@ -503,9 +503,9 @@ export default class Category extends RestModel {
this.set("parent_category_id", newParentCategory?.id);
}
@computed("site.categories.[]")
@computed("site.categoriesByParentId")
get subcategories() {
return this.site.categories.filterBy("parent_category_id", this.id);
return this.site.categoriesByParentId.get(this.id) || [];
}
@computed("subcategories")

View File

@ -147,6 +147,17 @@ export default class Site extends RestModel {
return map;
}
@computed("categories.@each.parent_category_id")
get categoriesByParentId() {
const map = new Map();
for (const category of this.categories) {
const siblings = map.get(category.parent_category_id) || [];
siblings.push(category);
map.set(category.parent_category_id, siblings);
}
return map;
}
@discourseComputed("notification_types")
notificationLookup(notificationTypes) {
const result = [];