Асинхронные действия MobX State Tree и повторный рендеринг компонента React

Я новичок в MST, и мне трудно найти больше примеров с асинхронными действиями. У меня есть api, который будет возвращать разные данные в зависимости от параметров, которые вы ему передаете. В этом случае api может вернуть массив фотографий или руководств. Я установил свои начальные значения для магазина следующим образом:

data: {
   photos: [],
   tutorials: []
}

В настоящее время я использую applySnapshot для обновления хранилища и, в конечном итоге, это вызовет повторный рендеринг моего компонента React. Чтобы отображать и фотографии, и учебные пособия, мне нужно дважды вызвать api (один раз с параметрами для фотографий и второй раз для руководств). Я столкнулся с проблемой, когда снимок из первого обновления показывает, что фотографии и учебные пособия имеют одинаковые значения, и только при втором обновлении я получаю правильные значения. Я, вероятно, неправильно использую applySnapshot для повторного рендеринга моих компонентов React. Я хотел бы знать, как лучше / правильно это сделать. Каков наилучший способ повторно отобразить мои компоненты React после того, как API дал ответ. Любые предложения очень ценятся

Я настроил свой магазин так:

import { RootModel } from '.';
import { onSnapshot, getSnapshot, applySnapshot } from 'mobx-state-tree';

export const setupRootStore = () => {
  const rootTree = RootModel.create({
    data: {
      photos: [],
      tutorials: []
    }
  });
  // on snapshot listener
  onSnapshot(rootTree, snapshot => console.log('snapshot: ', snapshot));

  return { rootTree };
};

Я создал следующую модель с асинхронным действием с использованием генераторов:

import {types,Instance,applySnapshot,flow,onSnapshot} from 'mobx-state-tree';

const TestModel = types
  .model('Test', {
    photos: types.array(Results),
    tutorials: types.array(Results)
  })
  .actions(self => ({
    fetchData: flow(function* fetchData(param) {

      const results = yield api.fetch(param);

      applySnapshot(self, {
        ...self,
        photos: [... results, ...self.photos],
        tutorials: [... results, ...self.tutorials]
      });
    })
  }))
  .views(self => ({
    getPhoto() {
      return self.photos;
    },
    getTutorials() {
      return self.tutorials;
    }
  }));

const RootModel = types.model('Root', {
  data: TestModel
});

export { RootModel };

export type Root = Instance<typeof RootModel>;
export type Test = Instance<typeof TestModel>;

Компонент React для Photos.tsx

import React, { Component } from 'react';
import Spinner from 'components/Spinner';
import { Root } from '../../stores';
import { observer, inject } from 'mobx-react';

interface Props {
  rootTree?: Root
}

@inject('rootTree')
@observer
class Photos extends Component<Props> {

  componentDidMount() {
      const { rootTree } = this.props;
      if (!rootTree) return null;
      rootTree.data.fetchData('photo');
  }

  componentDidUpdate(prevProps) {
    if (prevProps.ctx !== this.props.ctx) {
      const { rootTree } = this.props;
      if (!rootTree) return null;
      rootTree.data.fetchData('photo');
    }
  }

  displayPhoto() {
    const { rootTree } = this.props;
    if (!rootTree) return null;
    // calling method in MST view
    const photoResults = rootTree.data.getPhoto();

    if (photoResults.$treenode.snapshot[0]) {
      return (
        <div>
          <div className='photo-title'>{'Photo'}</div>
          {photoResults.$treenode.snapshot.map(Item => (
            <a href={photoItem.attributes.openUrl} target='_blank'>
              <img src={photoItem.url} />
            </a>
          ))}
        </div>
      );
    } else {
      return <Spinner />;
    }
  }

  render() {
    return <div className='photo-module'>{this.displayPhoto()}</div>;
  }
}

export default Photos;

Точно так же Tutorials.tsx выглядит так:

import React, { Component } from 'react';
import Spinner from '';
import { Root } from '../../stores';
import { observer, inject } from 'mobx-react';

interface Props {
  rootTree?: Root;
}

@inject('rootTree')
@observer
class Tutorials extends Component<Props> {

  componentDidMount() {
    if (this.props.ctx) {
      const { rootTree } = this.props;
      if (!rootTree) return null;
      rootTree.data.fetchData('tuts');
    }
  }

  componentDidUpdate(prevProps) {
    if (prevProps.ctx !== this.props.ctx) {
      const { rootTree } = this.props;
      if (!rootTree) return null;
      rootTree.search.fetchData('tuts');
    }
  }

  displayTutorials() {
    const { rootTree } = this.props;
    if (!rootTree) return null;
    // calling method in MST view
    const tutResults = rootTree.data.getTutorials();

    if (tutResults.$treenode.snapshot[0]) {
      return (
        <div>
          <div className='tutorials-title'>{'Tutorials'}</div>
          {tutResults.$treenode.snapshot.map(tutorialItem => (
            <a href={tutorialItem.attributes.openUrl} target='_blank'>
              <img src={tutorialItem.url} />
            </a>
          ))}
        </div>
      );
    } else {
      return <Spinner />;
    }
  }

  render() {
    return <div className='tutorials-module'>{this.displayTutorials()}</div>;
  }
}

export default Tutorials;

person user5844628    schedule 06.02.2020    source источник
comment
Хотя этот комментарий не имеет ничего общего с MobX, я настоятельно рекомендую вам использовать отдельные конечные точки для учебных пособий и фотографий. Это два разных типа сущностей, и это приведет только к путанице (и затруднит поддержку кода). Извините, я ничего не мог с собой поделать. Я надеюсь, что кто-нибудь сможет получить больше опыта в MobX, удачи!   -  person Jack    schedule 06.02.2020


Ответы (1)


Почему вы вообще используете applySnapshot в этом случае? Я не считаю это необходимым. Просто назначьте свои данные по мере необходимости в вашем действии:

.actions(self => ({
     //If you're fetching both at the same time
    fetchData: flow(function* fetchData(param) {

      const results = yield api.fetch(param);

      //you need cast() if using Typescript otherwise I think it's optional
      self.photos = cast([...results.photos, ...self.photos])
      //do you really intend to prepend the results to the existing array or do you want to overwrite it with the sever response?
      self.tutorials = cast(results.tutorials)

    })
  }))

Или, если вам нужно сделать два отдельных запроса для получения ваших данных, вероятно, лучше всего сделать это двумя разными действиями.

.actions(self => ({
    fetchPhotos: flow(function* fetchPhotos(param) {
      const results = yield api.fetch(param)
      self.photos = cast([... results, ...self.photos])      
    }),
    fetchTutorials: flow(function* fetchTutorials(param) {
      const results = yield api.fetch(param)
      self.tutorials = cast([... results, ...self.tutorials])      
    }),
  }))

Тем не менее, похоже, вам не нужно applySnapshot. Просто назначьте свои данные в своих действиях по мере необходимости. Нет ничего особенного в назначении данных в асинхронном действии.

person imagio    schedule 19.06.2020