首页 文章

使用Redux ReactRouter在React App中进行异步数据流?

提问于
浏览
20

我正在使用ReduxReact-router制作一个简单的邮件应用程序 . 由于我不太了解Redux路由器中的实际数据流 .

我想要的是什么

  • 页面启动后( / ), MailListComponent 从服务器获取消息数组 . 此时 MessageComponent 未显示,因为它没有单个消息来获取数据 .

  • 获取 state.messages:[] 后,app将导航到 state.messages:[]/messages/1 )`的第一条消息 .

  • 转换完成后,将显示 MessageComponent 并获取id = 1 info且's in a separate request it'附件的消息 .

这是组件模型:

Component model

我在做什么

// MailListActions.js
export function loadMessages() {
  return {
    type:    'LOAD_MESSAGES',
    promise: client => client.get('/messages')
  };
}

// MailListReducer.js
import Immutable from 'immutable';

const defaultState = { messages: [], fetchingMessages: false };

export default function mailListReducer(state = defaultState, action = {}) {
  switch (action.type) {
    case 'LOAD_MESSAGES_REQUEST':
      return state.merge({fetchingMessages: true});

    case 'LOAD_MESSAGES':
        return state.merge({fetchingMessages: false, messages: action.res.data || null});

    case 'LOAD_MESSAGES_FAILURE':
        // also do something

    default:
      return state;
  }
}

当我使用promiseMiddleware时, LOAD_MESSAGESLOAD_MESSAGES_REQUESTLOAD_MESSAGES_FAILURE 将作为请求 /messages 结束 .

现在:

  • 可以在 MailListComponent 的componentDidMount中调度 loadMessages() 吗?

  • 如何正确转换为 /messages/1

  • 我应该在我的州创建 activeMessageId<Integer> 吗?

  • 所有这些组件应如何与React-Router连接?

这是我目前的尝试:

export default (store) => {
  const loadAuth = (nextState, replaceState, next) => { ... };

  return (
    <Route name="app" component={App} path="/" onEnter={loadAuth}>
      <IndexRoute component={Content}/> // <== THIS IS A DUMMY COMPONENT. It diplays pre-loader until the app is transitioned to real first message
      <Route path="messages/:id" component={Message}/>
    </Route>
  );
};

你能给我一些观点,如何连接点?什么是poper异步数据流逻辑?

我使用isomorphic-redux示例作为我的应用程序的基础 . 虽然是同构的,但正常的Redux应用程序之间不应该有太大的区别

谢谢 .

更新

其中一个想法 - 为 <IndexRoute component={Content}/> 设置onEnter挂钩,它将获取消息,设置为状态和初始化转换 . 是redux路由器的方式吗?

但是,这种方式也可能相当棘手,因为 /messages 仅适用于经过身份验证的用户(其中 store.getState().auth.get('loaded') == true

3 回答

  • 4

    在我看来,服务器端渲染很重要 . 没有它,您将提供只在客户端生活的空白页面 . 它会严重影响你的SEO . 因此,如果我们认为服务器端呈现很重要,我们需要一种方法来获取适合服务器端呈现的数据 .

    the docs for server side rendering i.c.w. react-router,这是我们发现的:

    • 首先我们调用 match ,将它传递给当前位置和我们的路线

    • 然后我们调用 ReactDOMServer.render ,将 matchmatch 传递给它

    很明显,在进入渲染阶段之前,我们需要访问所获取的数据 .

    这意味着我们 cannot use component lifecycle . 我们也不能使用 onEnter 或任何其他仅在渲染已经开始时触发的钩子 . 在服务器端,我们需要在渲染开始之前获取数据 . 这意味着我们需要能够确定从 match 获取的 renderProps 中取得的内容 .

    常见的解决方案是在顶层组件上放置一个静态 fetchData 函数 . 在你的情况下,它可能看起来像这样:

    export default class MailListComponent extends React.Component {
      static fetchData = (store, props) => {
        return store.dispatch(loadMessages());
      };
      // ....
    }
    

    我们可以在服务器端找到这个 fetchData 函数并在继续渲染之前在那里调用它,因为 match 给了我们包含匹配组件类的 renderProps . 所以我们可以循环遍历它们并获取所有 fetchData 函数并调用它们 . 像这样的东西:

    var fetchingComponents = renderProps.components
      // if you use react-redux, your components will be wrapped, unwrap them
      .map(component => component.WrappedComponent ? component.WrappedComponent : component)
      // now grab the fetchData functions from all (unwrapped) components that have it
      .filter(component => component.fetchData);
    
    // Call the fetchData functions and collect the promises they return
    var fetchPromises = fetchingComponents.map(component => component.fetchData(store, renderProps));
    

    fetchData 返回 store.dispatch 的结果,该结果为 Promise . 在客户端,这将只显示一些 loading 屏幕,直到Promise完成,但在服务器端,我们将需要等到发生这种情况,所以当我们进入渲染阶段时,我们实际上在商店中有数据 . 我们可以使用 Promise.all

    // From the components from the matched route, get the fetchData functions
    Promise.all(fetchPromises)
      // Promise.all combines all the promises into one
      .then(() => {
        // now fetchData() has been run on every component in the route, and the
        // promises resolved, so we know the redux state is populated
        res.status(200);
        res.send('<!DOCTYPE html>\n' +
          ReactDOM.renderToString(
            <Html lang="en-US" store={app.store} {...renderProps} script="/assets/bridalapp-ui.js" />
          )
        );
        res.end();
    })
    

    你去吧我们将完全填充的页面发送给客户端 . 在那里,我们可以使用 onEnter 或生命周期钩子或任何其他方便的方法来获取用户导航客户端时所需的后续数据 . 但是我们应该尝试确保我们在组件本身上有一个函数或注释(初始操作?),这样我们就可以预先获取服务器端渲染的数据 .

  • 17

    我一直在研究一个相当大的应用程序(React,Redux,React Router等),它具有非常相似的功能(消息浏览器带侧边栏搜索栏/工具等等) . 这是 almost identical structurally 对你的'已经摆好了以上 . 利用React's component lifecycle对我们来说已经很好了 .

    基本上由组件来决定,“鉴于这些数据(消息,加载等......),我应该看起来和/或做什么?” .

    我们开始混淆onEnter和其他"outside of the component"策略,但他们开始觉得过于复杂 . 还有关于存储 activeMessageId 的问题 . 如果我正确理解您的场景,则应该可靠地从示例中的当前路径 params.id 中获取 .

    为了解一些事情,这种方法正在为我们完成

    enter image description here

    当然,这个例子被简化了一些,但它总结了“请求消息”部分,并且非常接近于我们的实际方法 .

    const MailApp = React.createClass({
      componentWillMount() {
        this._requestIfNeeded(this.props);
      },
    
      componentWillUpdate(newProps) {
        this._requestIfNeeded(newProps);
      },
    
      _requestIfNeeded(props) {
        const {
          // Currently loaded messages
          messages,
    
          // From our route "messages/:id" (ReactRouter)
          params: {id},
    
          // These are our action creators passed down from `connect`
          requestMessage,
          requestMessages,
    
          // Are we "loading"?
          loading,
          } = props;
    
        if (!id) {
          // "messages/"
          if (messages.length === 0 && !loading)
            return requestMessages();
        } else {
          // "messages/:id"
          const haveMessage = _.find(messages, {id});
          if (!haveMessage && !loading)
            return requestMessage(id);
        }
      },
    
      render() {
        const {
          messages,
          params: {id},
        } = props;
    
        return (
          <div>
            <NavBar />
            <Message message={_.find(messages, {id})}/>
            <MailList message={messages} />
          </div>
        )
      }
    });
    

    我很想知道这是否有助于您或您是否在其他地方降落 . 我已经看到围绕这些主题的类似问题,并且会对你发现的内容感兴趣 .

  • 0

    React-Router 2 API打开有趣的数据获取可能性,让你在路由器和你的应用程序之间有一个层 . 这是一个额外的插件,如https://github.com/rackt/async-propshttps://github.com/raisemarketplace/ground-control的好地方 .

    GroundControl中的通用 fetchData 挂钩(特定于Redux)为您提供了相当大的功能 . 允许您阻止组件呈现客户端; handle render async;然后发送给你的减速机 .

    const fetchData = (done, { dispatch, clientRender }) => {
      // show blocking loading component
      setTimeout(() => {
        clientRender(); // non-blocking loader (props.loading)
        setTimeout(() => {
          dispatch(actions.load({ mydata });
          done();
        }, 1000);
      }, 1000)
    };
    

    在路由上声明Reducers,并调用每个嵌套路由的 fetchData 函数 . 因此,您可以使用父布局路由处理身份验证 . 然后嵌套路由中的 fetchData 处理它们关心的内容 .

相关问题