Как и когда устанавливать свойства объекта домена mobx? - PullRequest
0 голосов
/ 15 мая 2018

У меня проблемы с поиском хорошего решения конкретной проблемы в моем приложении реакции / мобксе. Допустим, у меня есть несколько блоков, которые я хочу расположить на экране в определенном макете, в котором положение каждого блока зависит от позиций всех других блоков (представьте, что вы должны использовать макет с направлением силы или подобный). Также происходит изменение макета в зависимости от некоторых других @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));
    }
}

Мне больше всего нравится этот подход, но я не чувствую, что какое-либо из этих решений является идеальным или элегантным. Я упускаю какой-то очевидный подход к этому типу проблемы? Есть ли более простой и практичный подход к решению этой проблемы?

Спасибо за чтение всего этого:)

1 Ответ

0 голосов
/ 15 мая 2018

В зависимости от реализации 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#observe

class BoxStore {
  @observable
  boxes = [];
  constructor(){
    mobx.observe(this.boxes, (change) => {
       // efficiently compute new locations based on changes here.
    })
  }
}

таким образом, вы получаете преимущество, заключающееся в том, что оно не пересчитывается полностью каждый раз.но имейте преимущество, что позиция все еще «выведена».(вам не нужно вручную устанавливать его).как только вы получили правильную формулу, вам больше не придется об этом думать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...