У меня проблемы с поиском хорошего решения конкретной проблемы в моем приложении реакции / мобксе. Допустим, у меня есть несколько блоков, которые я хочу расположить на экране в определенном макете, в котором положение каждого блока зависит от позиций всех других блоков (представьте, что вы должны использовать макет с направлением силы или подобный). Также происходит изменение макета в зависимости от некоторых других @observable
переменных, таких как sorting
по размеру поля или filtering
по категории. Кроме того, ящики интерактивны и получают highlighted
, если навести курсор мыши. Я также хотел бы, чтобы это было отражено в данных блоков как свойство highlighted
, установленное для блоков.
Исходя из избыточности, мой первоначальный подход состоял бы в том, чтобы иметь некоторые наблюдаемые переменные состояния пользовательского интерфейса, такие как sortBy
и filterBy
, а затем получить геттер @compute
, который возвращает массив fresh свежие объекты-боксы с их свойствами 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
, который вычисляет другой тип макета, я теперь технически держу несколько версий одной и той же коробки в моем магазине, верно?
Итак, я попытался найти решение с созданием экземпляра класса 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));
}
}
Мне больше всего нравится этот подход, но я не чувствую, что какое-либо из этих решений является идеальным или элегантным. Я упускаю какой-то очевидный подход к этому типу проблемы? Есть ли более простой и практичный подход к решению этой проблемы?
Спасибо за чтение всего этого:)