我说我有一些盒子,我想在屏幕上以某种布局定位,其中每个盒子的位置取决于所有其他盒子的位置(想想力导向布局或类似) . 还有一个重新布局发生取决于一些其他 @observable
变量,例如 sorting
按框的大小或 filtering
按类别 . 此外,这些框是交互式的,如果用鼠标悬停,则获得 highlighted
. 我也希望这个在框数据中反映为框上设置的 highlighted
属性 .
来自redux,我最初的方法是拥有一些可观察的ui状态变量,如 sortBy
和 filterBy
,然后有一个 @compute
getter返回 fresh 数组的 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 best practices,建议将整个主题略有不同 . 我理解它的方式,在mobx中你应该创建每个都有 @observable
属性的盒子的实际实例 . 另外一个应该只在盒子中保存每个盒子的一个实例 . 例如,如果我要添加一个额外的 @computed
getter来计算不同类型的布局,我现在技术上在我的商店中持有同一个盒子的多个版本,对吧?
所以我试图找到一个解决方案,为每个盒子项创建一个 Box
类实例,并为 highlighted
属性设置一个 @computed
getter . 这有效,但我无法知道如何以及何时设置方框的位置 . 一种方法是采用与上述方法类似的方法 . 计算 @computed
getter中的位置,然后在同一函数中设置每个框的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;
}
}
另一种方法是计算boxStore上@compute中的所有位置,然后在框内访问这些位置 .
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
或派生和观察位置 .首先,我将保留这个观察到的数据结构而不是计算框,因此不需要在更改时重新创建类:
计算的
案例 .
如果calculateLayout()只是根据前一个Box用x和y设置坐标,我建议只使用链表结构:
观察的案例
如果计算很复杂并且只需要在添加或删除框时部分重新计算,我建议使用属性观察器:https://mobx.js.org/refguide/observe.html#observe
通过这种方式,您可以获得每次都不会完全重新计算的优势 . 但是有一个优势,即该位置仍然是“派生的” . (你不必手动设置它) . 一旦你得到了正确的公式,你就再也不用考虑它了 .