【问题标题】:How to show loading UI when calling getComponent in react-router?在 react-router 中调用 getComponent 时如何显示加载 UI?
【发布时间】:2016-07-20 21:07:27
【问题描述】:

我真的是 React 新手,我不知道如何在使用 getComponent 加载路由时呈现“正在加载...”屏幕。 getComponent 调用工作正常并显示组件,但 UI 上没有任何迹象表明请求和响应之间正在发生任何事情。这就是我想要弄清楚的。

import Main from './pages/Main.jsx';
import Test from './pages/Test.jsx';
import Home from './pages/Home.jsx';


var Routes = {
  path: "/",
  component: Main,
  indexRoute: {
    component: Home
  },
  childRoutes: [
    {
      path: "test",
      component: Test
    },
    {
      path: "about",
      getComponent: function(path, cb) {
        require.ensure([], (require) => {
          cb(null, require("./pages/about/About.jsx"));
        });
      }
    }
  ]
};

export default Routes;

在尝试使用 onEnter 或在 getComponent 函数中强制“加载”组件显示失败后,我想也许我应该尝试使用 Redux 将加载状态设置为 true/false 并让我的主视图组件显示加载屏幕:

import React from 'react';
import {connect} from 'react-redux';

import NavBar from '../components/Navigation/NavBar.jsx';
import Footer from '../components/Footer.jsx';
import Loading from './Loading.jsx';
import navItems from '../config/navItems.jsx';
import setLoading from '../actions/Loading.jsx';

var Main = React.createClass({
  renderPage: function() {
    if (this.props.loading) {
      return (
        <Loading/>
      );
    } else {
      return this.props.children;
    }
  },
  render: function() {
    return (
      <div>
        <header id="main-header">
          <NavBar navigation={navItems}/>
        </header>
        <section id="main-section">
          {this.renderPage()}
        </section>
        <Footer id="main-footer" />
      </div>
    );
  }
});

function mapStateToProps(state) {
  return {
    loading: state.loading
  }
}

export default connect(mapStateToProps)(Main);

如果我使用操作手动设置加载状态,这似乎可行,这正是我想要做的。但是(我觉得这将是一个真正的菜鸟问题)我不知道如何从路由器内部访问商店/调度程序。

我不确定我是否使用了错误的搜索词或其他什么,但我完全没有想法,而且每个 react-router/redux 教程似乎都跳过了我认为必须是一个常见问题的问题.

谁能指出我正确的方向(如果我正在做的事情是否是最佳做法,请告诉我?)?

编辑:我会尝试进一步澄清这一点。在第一个代码块中,您可以看到,如果我单击 &lt;Link to="/about"&gt; 元素,则会触发 getComponent 函数,该函数将延迟加载 About.jsx 组件。我遇到的问题是我无法弄清楚如何显示某种加载指示器/微调器,它会在单击链接后立即出现,然后在组件加载后将其替换。

更多编辑:我尝试创建一个用于加载异步路由的包装器组件,它似乎可以工作,但是感觉真的很hacky,我确信这不是正确的方法关于这样做。路由代码现在如下所示:

import Main from './pages/Main.jsx';
import Test from './pages/Test.jsx';
import Home from './pages/Home.jsx';
import AsyncRoute from './pages/AsyncRoute.jsx';


var Routes = {
  path: "/",
  component: Main,
  indexRoute: {
    component: Home
  },
  childRoutes: [
    {
      path: "test",
      component: Test
    },
    {
      path: "about",
      component: AsyncRoute("about")
    }
  ]
};

export default Routes;

AsyncRoute.jsx 页面如下所示:

import React from 'react';

function getRoute(route, component) {
  switch(route) {
    // add each route in here
    case "about":
      require.ensure([], (require) => {
        component.Page = require("./about/About.jsx");
        component.setState({loading: false});
      });
    break;
  }
}

var AsyncRoute = function(route) {
  return React.createClass({
    getInitialState: function() {
      return {
        loading: true
      }
    },
    componentWillMount: function() {
      getRoute(route, this);
    },
    render: function() {
      if (this.state.loading) {
        return (
          <div>Loading...</div>
        );
      } else {
        return (
          <this.Page/>
        );
      }
    }
  });
};

export default AsyncRoute;

如果有人有更好的想法,请告诉我。

【问题讨论】:

    标签: javascript reactjs react-router react-redux


    【解决方案1】:

    我想我已经弄清楚了。它可能是也可能不是正确的处理方式,但它似乎有效。另外我不知道为什么我没有早点想到这个。

    首先,将我的 createStore 代码移动到它自己的文件 (store.jsx) 中,这样我就可以将它导入到主入口点以及我的 Routes.jsx 文件中:

    import {createStore} from 'redux';
    import rootReducer from '../reducers/Root.jsx';
    
    var store = createStore(rootReducer);
    
    export default store;
    

    Root.jsx 看起来像这样(这是一个丑陋的烂摊子,但我只是想得到一些在基本层面上工作的东西,然后我会清理它):

    import {combineReducers} from 'redux';
    import user from './User.jsx';
    import test from './Test.jsx';
    
    var loading = function(state = false, action) {
      switch (action.type) {
        case "load":
          return true;
        case "stop":
          return false;
        default:
          return state;
      }
    };
    
    
    export default combineReducers({
      user,
      test,
      loading
    });
    

    我制作了一个基本组件,根据 Redux 商店的“正在加载”值显示加载/加载:

    import React from 'react';
    import {connect} from 'react-redux';
    
    var Loading = React.createClass({
      render: function() {
        if (this.props.loading) {
          return (
            <h1>Loading</h1>
          );
        } else {
          return (
            <h1>Loaded</h1>
          );
        }
      }
    });
    
    export default connect(state => state)(Loading);
    

    现在我的 Routes.jsx 文件看起来像这样(注意我已经导入了 Redux 存储):

    import Main from './pages/Main.jsx';
    import Test from './pages/Test.jsx';
    import Home from './pages/Home.jsx';
    import store from './config/store.jsx';
    
    var Routes = {
      path: "/",
      component: Main,
      indexRoute: {
        component: Home
      },
      childRoutes: [
        {
          path: "test",
          component: Test
        },
        {
          path: "about",
          getComponent: function(path, cb) {
            store.dispatch({type: "load"})
            require.ensure([], (require) => {
              store.dispatch({type: "stop"});
              cb(null, require("./pages/about/About.jsx"));
            });
          }
        }
      ]
    };
    
    export default Routes;
    

    这似乎有效。只要单击&lt;Link/&gt; 转到 /about 路由,就会调度一个动作以在主存储中将“正在加载”状态设置为 true。这会导致&lt;Loading/&gt; 组件自我更新(我设想它最终会在窗口的角落呈现一个微调器或类似的东西)。运行那个奇怪的 require.ensure([]) 函数来让 webpack 进行其花哨的代码拆分,一旦加载了组件,就会调度另一个动作来将加载状态设置为 false,然后渲染组件。

    我对 React 还是很陌生,虽然这似乎可行,但我不确定这是否是正确的方法。如果有人有更好的方法,请加入!

    【讨论】:

      【解决方案2】:

      按照与@David M 相同的方法,我实现了一个加载减速器和一个包装调度的函数。

      除去店铺创建和管理,基本如下:

      loadingReducer:

      // ------------------------------------
      // Constants
      // ------------------------------------
      export const LOADING = 'LOADING'
      
      // ------------------------------------
      // Actions
      // ------------------------------------
      const loadQueue = []
      export const loading = loading => {
          if (loading) {
              loadQueue.push(true)
          } else {
              loadQueue.pop()
          }
      
          return {
              type: LOADING,
              payload: loadQueue.length > 0
          }
      }
      
      export const actions = {
          loading
      }
      
      // ------------------------------------
      // Action Handlers
      // ------------------------------------
      
      const ACTION_HANDLERS = {
          [LOADING]: (state, action) => (action.payload)
      }
      
      // ------------------------------------
      // Reducer
      // ------------------------------------
      const initialState = false
      export default function reducer (state = initialState, action) {
          const handler = ACTION_HANDLERS[action.type]
          return handler ? handler(state, action) : state
      }
      

      请注意 loadingQueue 如何在有剩余模块要获取时保持加载消息处于活动状态,用于嵌套路由。

      withLoader功能:

      import { loading } from 'loadingReducer'
      
      const withLoader = (fn, store) => {
          return (nextState, cb) => {
              store.dispatch(loading(true))
      
              fn(nextState, (err, cmp) => {
                  store.dispatch(loading(false))
                  cb(err, cmp)
              })
          }
      }
      
      export default withLoader
      

      现在在定义新路由时,我们可以使用 withLoader 隐式调度加载操作:

      someRoute:

      import withLoader from 'withLoader'
      import store from 'store'
      
      const route = {
          path: 'mypath',
          getComponent: withLoader((nextState, cb) => {
              require.ensure([], require => {
                  cb(null, require('something').default)
              }, 'NamedBundle')
          }, store)
      }
      export default route
      

      【讨论】:

      • 您能否在store.js 中分享您的代码?您将如何获得全球商店?
      【解决方案3】:

      好的,让我们看看我是否可以在这里阐明这一点:

      我不知道如何从路由器中访问 store/dispatcher

      没有必要这样做 AFAIK。您可以指定所有路由,列出应该响应每个路由的组件(就像您在上面所做的那样),然后将每个组件连接到 redux 存储。对于连接,您的 mapStateToProps 函数可以以更简单的方式编写,如下所示:

      export default connect(state => state)(Main);
      

      关于loading 状态:我认为加载缓慢的组件并在加载时显示等待指示器是朝着错误方向迈出的一步。我宁愿有一个快速加载的组件,它从后端异步加载它的所有数据,当数据还不可用时,该组件会呈现一个等待指示器。一旦数据可用,就可以显示它。这基本上就是您在第二次编辑中所描绘的内容。

      如果你能从你的实际数据中删除它会更好,即没有数据存在 -> 显示加载屏幕/数据存在 -> 显示真实屏幕。这样,您可以避免加载标志不同步时出现的问题。 (从技术上讲:避免冗余。)

      因此,我宁愿为加载屏幕创建一个独立的组件,并在每个单独的组件需要它时显示它,而不是使包装器通用。 (这些需求是不同的,所以似乎很难以通用的方式处理。)像这样:

      var Page = function(route) {
        return React.createClass({
          getInitialState: function() {
            // kick off async loading here
          },
          render: function() {
            if (!this.props.myRequiredData) {
              return (
                <Loading />
              );
            } else {
              return (
                // display this.props.myRequiredData
              );
            }
          }
        });
      };
      

      【讨论】:

      • 谢谢,两者都是,但目前我的代码有更多问题,我觉得我可能在尝试解决它时走错了方向。我将编辑我的问题以尝试澄清。
      • 为什么 About.jsx 组件需要这么长时间才能加载?您是否正在同步加载某些内容?你能添加那个组件的草图吗?
      • About.jsx 不会花费很长时间来加载,因为它所做的只是渲染一页文本(它只是一个具有渲染功能的组件),但是我计划制作一个包含 70 的大型应用程序+ 路由——其中一些是简单的页面,而另一些则是更复杂的巨大事物,它们会引入大量依赖项,因此我想在进一步讨论之前先弄清楚这些异步内容。我在想我也许可以制作一个包装器组件来代替异步加载,但感觉不是“正确”的方式。
      • 我认为您需要为屏幕异步加载数据,但组件本身的加载不应该是异步的。如果您的组件本身需要很长时间才能加载,那么肯定会有一些可疑的事情发生。我在上面的回答中给出了一个例子的草图。
      • @Nicole 数据不是唯一需要异步加载的东西;使用代码拆分时,您通常会加载一个代码包,该代码包在用户第一次前往该路线时为给定路线提供组件(有时还有其他东西)。这确实是 react-router 提供异步方法的主要原因。这并不是因为他们认为您会在渲染路线之前加载大量数据。
      【解决方案4】:

      动态负载异步路由器使用require.ensure,它使用jsonp从网络下载脚本。 由于网络缓慢,有时 UI 阻塞,屏幕仍然显示预览反应组件。

      @Nicole,真正慢的不是组件内部的数据加载,而是组件本身,因为 jsonp

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-09-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-05-09
        • 2013-09-28
        • 1970-01-01
        相关资源
        最近更新 更多