Как и когда устанавливать свойства объекта домена mobx?
У меня возникают проблемы с поиском хорошего решения конкретной проблемы в моем приложении реакции / MOBX. Допустим, у меня есть несколько блоков, которые я хочу расположить на экране в определенном макете, в котором положение каждого блока зависит от положений всех других блоков (представьте, что вы должны использовать макет с направлением силы или подобное). Существует также изменение макета в зависимости от некоторых других @observable
переменные, такие как sorting
по размеру коробки или filtering
по категориям. Кроме того, коробки являются интерактивными и получают highlighted
если один завис с мышью. Я также хотел бы, чтобы это было отражено в данных коробок как highlighted
свойство установлено на коробки.
Исходя из избыточности, мой первоначальный подход состоял бы в том, чтобы иметь некоторые наблюдаемые переменные состояния пользовательского интерфейса, такие как sortBy
а также filterBy
а затем есть @compute
геттер, который возвращает свежий массив свежих объектов box с их positions
а также highlighted
свойство устанавливается каждый раз, когда изменяется одна из переменных.
const boxes = [{ id: '1', category: 'bla', size: 5 }, ...];
class UiStore {
@observable sortBy = 'size';
@observable filterBy = undefined;
@observable hoveredBox = undefined;
/* Some @action(s) to manipulate the properties */
}
const uiStore = new UiStore();
class BoxesStore {
@computed
get positionedBoxes() {
const filteredBoxes = boxes.filter(box =>
box.category === uiStore.filterBy
);
// An array of positions based on the filtered boxes etc.
const positions = this.calculateLayout(filteredBoxes);
return boxes.map((box, i) => {
const { x, y } = positions[i];
return {
...box,
highlighted: uiStore.hoveredBox === box.id,
x,
y,
};
});
}
}
В теории это работает нормально. К сожалению, я не думаю, что это самое эффективное решение с точки зрения производительности. С множеством блоков создание новых объектов при каждом изменении и при каждом наведении не может быть лучшим решением. Также, в соответствии с лучшими практиками mobx, рекомендуется думать о теме немного по-другому. Насколько я понимаю, в mobx вы должны создавать реальные экземпляры блоков, каждый из которых имеет @observable
Сами свойства. Также предполагается, что каждый может хранить только один экземпляр каждого ящика в BoxStore. Если бы я, например, добавить дополнительный @computed
getter, который вычисляет другой тип компоновки. Сейчас я технически держу в своем магазине несколько версий одной и той же коробки, верно?
Поэтому я попытался найти решение с созданием Box
экземпляр класса для каждого элемента коробки с @computed
добытчик для highlighted
имущество. Это работает, но я не могу обернуть голову, как и когда устанавливать положение ящиков. Одним из способов было бы пойти с подобным подходом к тому, что выше. Расчет позиций в @computed
получить, а затем установить положение x, y каждого блока в той же функции. Это может сработать, но определенно кажется неправильным устанавливать позиции в @compute.
const boxes = [{ id: '1', category: 'bla', size: 5 }, ...];
/* uiStore... */
class Box {
constructor({id, category, size}) {
this.id = id;
this.category = category;
this.size = size;
}
@computed
get highlighted() {
return this.id === uiStore.highlighted
}
}
class BoxesStore {
@computed
get boxes() {
return boxes.map(box => new Box(box));
}
@computed
get positionedBoxes() {
const filteredBoxes = this.boxes.filter(box =>
box.category === uiStore.filterBy
);
// An array of positions based on the filtered boxes etc.
const positions = this.calculateLayout(filteredBoxes);
const positionedBoxes = filteredBoxes.map(box => {
const { x, y } = positions[i];
box.x = x;
box.y = y;
})
return positionedBoxes;
}
}
Другой подход состоит в том, чтобы вычислить все позиции в @compute на boxStore и затем получить доступ к этим позициям внутри блока.
const boxes = [{ id: '1', category: 'bla', size: 5 }, ...];
/* uiStore... */
class Box {
constructor({id, category, size}, parent) {
this.id = id;
this.category = category;
this.size = size;
this.parent = parent;
}
@computed
get position() {
return _.find(this.parent.positionedBoxes, {id: this.id})
}
}
class BoxesStore {
@computed
get boxes() {
return boxes.map(box => new Box(box));
}
@computed
get positionedBoxes() {
const filteredBoxes = this.boxes.filter(box =>
box.category === uiStore.filterBy
);
// An array of positions based on the filtered boxes etc.
return this.calculateLayout(filteredBoxes);
}
}
Опять же, это может сработать, но это кажется супер сложным. В качестве последнего подхода я думал о том, чтобы что-то сделать с помощью реакции mobx или автозапуска, чтобы прослушать изменения в uiStore, а затем установить положение ящиков с помощью такого действия:
const boxes = [{ id: '1', category: 'bla', size: 5 }, ...];
/* uiStore... */
class Box {
@observable position = {x: 0, y: 0};
@action
setPosition(position) {
this.position = position;
}
}
class BoxesStore {
constructor() {
autorun(() => {
const filteredBoxes = this.boxes.filter(box =>
box.category === uiStore.filterBy
);
this.calculateLayout(filteredBoxes).forEach((position, i) =>
filteredBoxes[i].setPosition(position);
)
})
}
@computed
get boxes() {
return boxes.map(box => new Box(box));
}
}
Мне до сих пор нравится этот подход, но я не чувствую, что ни одно из этих решений не является идеальным или элегантным. Я упускаю какой-то очевидный подход к этому типу проблемы? Есть ли более простой и практичный подход к решению этой проблемы?
Спасибо за чтение всего этого:)
1 ответ
В зависимости от реализации calculateLayout
я бы предложил либо использовать computed
или производная и наблюдаемая позиция.
во-первых, вместо блоков вычислений я бы оставил эту наблюдаемую структуру данных, чтобы не нужно было заново создавать классы при изменении:
class BoxStore {
@observable
boxes;
}
чехол для вычислений.
Если метод CalculayLay () просто увеличивает координаты с помощью набора x и y на основе предыдущего блока, я бы предложил использовать только структуру связанного списка:
class Box {
@observable prevBox;
@computed
get position()
if(this.prevBox)
return this.prevBox.clone().add(10,20);
return {x:0,y:0};
}
}
Чехол для наблюдаемого
Если вычисление является сложным и требует лишь частичного пересчета при добавлении или удалении ящиков, я бы предложил использовать наблюдатель свойства: https://mobx.js.org/refguide/observe.html
class BoxStore {
@observable
boxes = [];
constructor(){
mobx.observe(this.boxes, (change) => {
// efficiently compute new locations based on changes here.
})
}
}
Таким образом, вы получаете преимущество, заключающееся в том, что оно не полностью пересчитывается каждый раз. но имейте преимущество, что позиция все еще "выведена". (вам не нужно вручную устанавливать его). как только вы получили правильную формулу, вам больше не придется об этом думать.