简体   繁体   English

MobX 状态树异步操作和重新渲染 React 组件

[英]MobX State Tree async actions and re-rendering React component

I am new to MST and is having a hard time finding more examples with async actions.我是 MST 的新手,很难找到更多带有异步操作的示例。 I have an api that will return different data depending on the params you pass to it.我有一个 api,它将根据您传递给它的参数返回不同的数据。 In this case, the api can either return an array of photos or tutorials.在这种情况下,api 可以返回一组照片或教程。 I have set up my initial values for the store like so:我已经为商店设置了我的初始值,如下所示:

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

Currently, I am using applySnapshot to update the store and eventually, that will trigger a re-render of my React component.目前,我正在使用applySnapshot来更新商店,最终,这将触发我的 React 组件的重新渲染。 In order to display both photos and tutorials, I need to call the api twice (Once with the params for photos and the second time for tutorials).为了同时显示照片和教程,我需要调用 api 两次(一次是照片参数,第二次是教程)。 I am running into an issue where the snapshot from the first update shows that photos and tutorials have the same values and only on the second update, do I get the correct values.我遇到了一个问题,第一次更新的快照显示照片和教程具有相同的值,只有在第二次更新时,我才能获得正确的值。 I am probably misusing applySnapshot to re-render my React components.我可能误用了applySnapshot来重新渲染我的 React 组件。 I would like to know the better/proper way of doing this.我想知道这样做的更好/正确方法。 What is the best way to re-render my React components after the api has yielded a repsonse.在 api 产生响应后重新渲染我的 React 组件的最佳方法是什么? Any suggestions are much appreciated任何建议都非常感谢

I have set up my store like this:我是这样设置我的商店的:

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 };
};

I have created the following model with an async action using generators:我使用生成器创建了以下带有异步操作的模型:

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 component for Photos.tsx用于Photos.tsx React 组件

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;

Similarly, Tutorials.tsx is like so:同样,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;

Why are you using applySnapshot at all in this case?在这种情况下,您为什么要使用applySnapshot I don't think it's necessary.我认为没有必要。 Just assign your data as needed in your action:只需在您的操作中根据需要分配您的数据:

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

    })
  }))

Or if you need to make two separate requests to fetch your data it's probably best to make it two different actions或者,如果您需要发出两个单独的请求来获取数据,最好将其设为两个不同的操作

.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])      
    }),
  }))

Regardless, it doesn't seem like you need applySnapshot .无论如何,您似乎不需要applySnapshot Just assign your data in your actions as necessary.只需根据需要在您的操作中分配您的数据。 There's nothing special about assigning data in an async action.在异步操作中分配数据没有什么特别之处。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM