如何以及何时设置mobx域对象属性?

时间:2018-05-15 12:48:44

标签: javascript reactjs mobx mobx-react

我无法在react / mobx应用程序中找到解决特定问题的好方法。假设我有一些盒子,我想在屏幕上以某种布局定位,其中每个盒子的位置取决于所有其他盒子的位置(想想力导向布局或类似)。还会根据其他@observable变量进行重新布局,例如sorting按框大小或filtering按类别划分。此外,这些框是交互式的,如果用鼠标悬停,则获得highlighted。我也希望这可以在框数据中反映为框上设置的highlighted属性。

来自redux,我最初的方法是拥有一些可观察的ui状态变量,例如sortByfilterBy,然后有一个@compute getter返回每当其中一个变量发生变化时,设置了新鲜框对象及其positionshighlighted属性的新鲜

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属性的@computed getter的框项目创建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;
    }
}

另一种方法是计算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 个答案:

答案 0 :(得分:0)

根据calculateLayout的实施情况,我建议使用computed或派生&观察位置。

首先,我将保留您观察到的数据结构,而不是计算框,因此不需要在更改时重新创建类:

class BoxStore {
  @observable
  boxes;
}

计算的案例。

如果calculateLayout()只是根据前一个Box设置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#observe

class BoxStore {
  @observable
  boxes = [];
  constructor(){
    mobx.observe(this.boxes, (change) => {
       // efficiently compute new locations based on changes here.
    })
  }
}
通过这种方式,您可以获得每次都不会完全重新计算的优势。但有一个优势,即该位置仍然是“派生”的。 (你不必手动设置它)。一旦你得到了正确的公式,你就再也不用考虑它了。