首页 文章

如何在 react-router 中调用 getComponent 时显示加载 UI?

提问于
浏览
6

我是 React 的新手,在使用 getComponent 加载路径时,我无法弄清楚如何渲染“loading ...”屏幕。 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);

如果我使用动作手动设置加载状态,这似乎有效,这是我想要做的。但是(我觉得这将是一个真正的 noob 问题)我无法弄清楚如何从路由器中访问 store/dispatcher。

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

任何人都可以指出我正确的方向(也让我知道我正在做什么是最好的做法?)?

编辑:我会尝试澄清这一点。在第一个代码块中,您可以看到如果单击<Link to="/about">元素,则将触发 getComponent 函数,这将 lazy-load About.jsx 组件。我遇到的问题是我无法弄清楚如何显示某种加载 indicator/spinner,它会在点击链接后立即显示,然后在组件加载后立即更换。

更多编辑:我已经尝试创建一个包装器组件来加载异步路由,它似乎工作,但它感觉真的很 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;

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

4 回答

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

    我已经制作了一个显示 Loading/Loaded 的基本组件,具体取决于 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;
    

    这似乎有效。只要单击<Link/>转到/about 路径,就会调度操作以在主存储中将“加载”状态设置为 true。这导致<Loading/>组件自行更新(我设想它最终会在窗口的角落或类似的东西中渲染一个微调器)。运行奇怪的require.ensure([])函数以使 webpack 执行其奇特的代码拆分,并且一旦加载了组件,就会调度另一个操作以将加载状态设置为 false,并呈现组件。

    我仍然是 React 的新手,虽然这似乎有用,但我不确定这是否是正确的方法。如果有人有更好的方法,请加入!

  • 1

    按照相同的方法 as_1_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
    
  • 0

    好的,让我们看看我是否可以对此有所了解:

    我无法弄清楚如何从路由器中访问 store/dispatcher

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

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

    关于loading状态:我认为这是一个错误的方向,有一个 slow-loading 组件,并在加载时显示一个等待指示器。我宁愿有一个 fast-loading 组件从后端异步加载它的所有数据,虽然数据还没有,但组件呈现一个等待指示器。数据可用后,即可显示。这基本上就是你在第二次编辑中描绘的内容。

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

    因此,我宁愿为加载屏幕创建一个独立的组件,而不是让包装器变得通用,而是在每个单独的组件都需要时显示它。 (这些需求是不同的,所以似乎很难在通用 way.)这样的事情中处理这个:

    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
            );
          }
        }
      });
    };
    
  • 0

    动态加载异步路由器正在使用require.ensure,它使用jsonp从网络下载脚本。由于网络速度慢,有时 UI 块,屏幕仍显示预览反应组件。

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

相关问题