6

我对 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 教程似乎都跳过了我认为必须是一个常见问题的问题。

谁能指出我正确的方向(如果我正在做的事情是最佳实践,也让我知道?)?

编辑:我会尝试进一步澄清这一点。在第一个代码块中,您可以看到,如果我单击一个<Link to="/about">元素,则会触发 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;

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

4

4 回答 4

1

I think I have this figured out. It may or may not be the correct way to go about things, but it seems to work. Also I don't know why I didn't think of this earlier.

First up, move my createStore code to its own file (store.jsx) so I can import it into the main entry point as well as into my Routes.jsx file:

import {createStore} from 'redux';
import rootReducer from '../reducers/Root.jsx';

var store = createStore(rootReducer);

export default store;

Root.jsx looks like this (it's an ugly mess, but I'm just trying to get something that works on a basic level and then I'll clean it up):

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

I've made a basic component that shows Loading/Loaded depending on the Redux store's value of "loading":

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

And now my Routes.jsx file looks like this (note I've imported the Redux store):

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;

This seems to work. As soon as a <Link/> is clicked to go to the /about route, an action is dispatched to set the "loading" state to true in the main store. That causes the <Loading/> component to update itself (I envision it would eventually render a spinner in the corner of the window or something like that). That weird require.ensure([]) function is run to get webpack to do its fancy code splitting, and once the component is loaded then another action is dispatched to set the loading state to false, and the component is rendered.

I'm still really new to React and while this seems to work, I'm not sure if it's the right way to do it. If anyone has a better way, please chime in!

于 2016-04-03T11:07:57.290 回答
1

遵循与@David MI 相同的方法,实现了一个加载减速器和一个包装调度的函数。

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

加载减速器:

// ------------------------------------
// 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 隐式调度加载操作:

一些路线:

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
于 2016-06-10T22:06:46.220 回答
0

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

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

于 2017-02-24T15:22:35.157 回答
0

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

我不知道如何从路由器内访问商店/调度程序

没有必要这样做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
        );
      }
    }
  });
};
于 2016-04-01T07:32:23.497 回答