mobx-cookbook/mobx-cookbook.github.io

Про нюанс генераторов в сторах

Opened this issue · 7 comments

Mobx оборачивает функции-генераторы в сторах в промис (mobxjs/mobx/src/types/modifiers.ts#L44) что скорее всего не нужно, если они были добавлены туда умышленно как в примере ниже. Возможно стоит про это добавить. Либо в главу про асинхронные действия, либо в продвинутые темы. Следствием этого является необходимость вручную задавать аннотации для полей и методов стора

class Notifications {
    items = [/* some notifications */]

    constructor() {
        makeObservable(this, {
            items: observable,
            shiftNotification: action
        })
    }

    // Mobx оборачивает функции-генераторы в промис (https://vk.cc/c6k7Ua), а тут этого не надо
    // поэтому получение ивента вынесено в отдельную ф-цию
    shiftNotification = () => this.items.shift()

    getNextNotification = function* () {
        yield this.shiftNotification()
    }
}
kubk commented

А можешь привести пример без отдельной функции? Что поломается?

kubk commented

В контексте Mobx и makeAutoObservable действительно функции со звёздочкой - это генераторы, для того чтобы прочитать значение оттуда нужны .next() / yield / await:

function *getNext() {
  yield 1;
  yield 2;
}

const generator = getNext();

console.log(generator.next());
console.log(generator.next());
console.log(generator.next());

https://jsfiddle.net/z7hr8ov4/

Вот (почти) полный листинг компонента который показывает уведомления. В реальности он ещё отвечает за снекбары aka Notifications.

Схема такая: на закрытие попапа вызывается метод onRequestClose который как раз и использует генератор из systemEventsStore. Соответственно, если там будет промис этот код работать не будет

let eventsGenerator

const SystemEvents = () => {
    const {systemEventsStore} = useStore()
    const [isOpened, setOpened] = useState(false)
    const [currentEvent, setCurrentEvent] = useState()

    useEffect(() => {
        if (systemEventsStore.messages.length > 0 && !isOpened) {
            eventsGenerator = systemEventsStore.getMessage()
            setCurrentEvent(eventsGenerator.next().value)
            setOpened(true)
        }
    }, [systemEventsStore.messages.length, isOpened])

    const closePopup = () => setOpened(false)

    const onRequestClose = () => {
        const {done, value} = eventsGenerator.next()
        if (!done) {
            setCurrentEvent(value)
            return false
        } else {
            return true
        }
    }

    return <>
        <Popup
            isOpened={isOpened}
            className={css.notify}
            onRequestClose={onRequestClose}
            onClose={closePopup}
        >
            <NotifyHeader closeCallback={closePopup}>{currentEvent?.header}</NotifyHeader>
            {
                currentEvent?.body
                ? <NotifyBody>{currentEvent?.body}</NotifyBody>
                : undefined
            }
            {
                currentEvent?.footer
                ? <NotifyFooter actions={currentEvent?.footer} closeCallback={closePopup}/>
                : undefined
            }
        </Popup>
    </>
}
kubk commented

@inoyakaigor

Следствием этого является необходимость вручную задавать аннотации для полей и методов стора

А если использовать makeAutoObservable(this, { generatorMethod: false })?

Эта функция следует правилам - конвертировать геттеры в computed, поля в observable, методы в actions, генераторы в flow

@kubk так работает без лишнего метода

UPD: но вообще мой пример был больше про факт трансформации генератора в промис, нежели про то, что придётся городить какие-то дополнительные костыли

kubk commented

Понимаю. Было бы классно как-то ужать всё это в простенький воспроизводимый пример, иначе в книгу тяжело это будет добавить. И объяснить зачем тут именно генераторы

Если я правильно понял, то можно назвать "особенности использования функций генераторов"