首页 文章

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

提问于
浏览
1

我说我有一些盒子,我想在屏幕上以某种布局定位,其中每个盒子的位置取决于所有其他盒子的位置(想想力导向布局或类似) . 还有一个重新布局发生取决于一些其他 @observable 变量,例如 sorting 按框的大小或 filtering 按类别 . 此外,这些框是交互式的,如果用鼠标悬停,则获得 highlighted . 我也希望这个在框数据中反映为框上设置的 highlighted 属性 .

来自redux,我最初的方法是拥有一些可观察的ui状态变量,如 sortByfilterBy ,然后有一个 @compute getter返回 fresh 数组的 fresh 框对象,每次其中一个设置了 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 类实例,并为 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 回答

  • 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.
        })
      }
    }
    

    通过这种方式,您可以获得每次都不会完全重新计算的优势 . 但是有一个优势,即该位置仍然是“派生的” . (你不必手动设置它) . 一旦你得到了正确的公式,你就再也不用考虑它了 .

相关问题