在 react-router 中调用 getComponent 时如何显示加载 UI? [英] How to show loading UI when calling getComponent in react-router?

查看:37
本文介绍了在 react-router 中调用 getComponent 时如何显示加载 UI?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对 React 真的很陌生,我不知道如何在使用 getComponent 加载路由时呈现正在加载..."屏幕.getComponent 调用工作正常并显示组件,但 UI 上没有指示请求和响应之间正在发生任何事情.这就是我想弄清楚的.

import Main from './pages/Main.jsx';从'./pages/Test.jsx'导入测试;从'./pages/Home.jsx'导入主页;无功路线= {小路: "/",组件:主要,索引路由:{组件:首页},子路由:[{路径:测试",组件:测试},{路径:关于",getComponent:函数(路径,cb){require.ensure([], (require) => {cb(null, require("./pages/about/About.jsx"));});}}]};导出默认路由;

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

从'react'导入React;从'react-redux'导入{connect};从'../components/Navigation/NavBar.jsx'导入导航栏;从'../components/Footer.jsx'导入页脚;从'./Loading.jsx'导入加载;从'../config/navItems.jsx'导入navItems;从'../actions/Loading.jsx'导入setLoading;var Main = React.createClass({渲染页面:函数(){如果(this.props.loading){返回 (<加载/>);} 别的 {返回 this.props.children;}},渲染:函数(){返回 (<div><header id="main-header"><NavBar navigation={navItems}/></标题><section id="main-section">{this.renderPage()}</节><页脚 id="main-footer"/>

);}});函数 mapStateToProps(state) {返回 {加载:state.loading}}导出默认连接(mapStateToProps)(主);

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

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

任何人都可以指出我正确的方向(并告诉我我正在做的是否是最佳实践?)?

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

更多编辑:我尝试创建一个用于加载异步路由的包装器组件,它似乎可以工作,但感觉真的很糟糕,我确定这不是正确的方法关于这样做.路由代码现在看起来像这样:

import Main from './pages/Main.jsx';从'./pages/Test.jsx'导入测试;从'./pages/Home.jsx'导入主页;从'./pages/AsyncRoute.jsx'导入AsyncRoute;无功路线= {小路: "/",组件:主要,索引路由:{组件:首页},子路由:[{路径:测试",组件:测试},{路径:关于",组件:AsyncRoute("about")}]};导出默认路由;

AsyncRoute.jsx 页面如下所示:

从'react'导入React;函数getRoute(路由,组件){开关(路由){//在这里添加每个路由案例关于":require.ensure([], (require) => {component.Page = require("./about/About.jsx");component.setState({loading: false});});休息;}}var AsyncRoute = 函数(路由){返回 React.createClass({getInitialState:函数(){返回 {加载:真实}},componentWillMount:函数(){getRoute(路线,这个);},渲染:函数(){如果(this.state.loading){返回 (<div>正在加载...</div>);} 别的 {返回 (<this.Page/>);}}});};导出默认的 AsyncRoute;

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

解决方案

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

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

loadingReducer:

//------------------------------------//常量//------------------------------------导出常量加载 = '加载'//------------------------------------//动作//------------------------------------常量加载队列 = []导出常量加载 = 加载 =>{如果(加载){loadQueue.push(true)} 别的 {loadQueue.pop()}返回 {类型:加载,有效载荷:loadQueue.length >0}}导出常量动作 = {加载}//------------------------------------//动作处理程序//------------------------------------const ACTION_HANDLERS = {[加载]:(状态,动作)=>(动作.有效载荷)}//------------------------------------//减速器//------------------------------------const initialState = false导出默认函数reducer (state = initialState, action) {const handler = ACTION_HANDLERS[action.type]返回处理程序?处理程序(状态,动作):状态}

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

withLoader 函数:

import { loading } from 'loadingReducer'const withLoader = (fn, store) =>{返回 (nextState, cb) =>{store.dispatch(加载(真))fn(nextState, (err, cmp) => {store.dispatch(加载(假))cb(错误,cmp)})}}使用Loader导出默认值

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

someRoute:

从 'withLoader' 导入 withLoader从商店"导入商店常量路由 = {路径:'我的路径',getComponent: withLoader((nextState, cb) => {require.ensure([], require => {cb(null, require('something').default)}, 'NamedBundle')}, 店铺)}导出默认路由

I'm really new to React and I can't figure out how to render a "loading..." screen when a route is being loaded with getComponent. The getComponent call works fine and displays the component, but there's no indication on the UI that anything is happening between the request and the response. That's what I'm trying to figure out.

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;

After trying to unsuccessfully force a "loading" component to display using onEnter or within the getComponent function, I thought maybe I should try using Redux to set a loading state to true/false and getting my main view component to display a loading screen:

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

This seems to work if I manually set the loading state using an action, which is what I was looking to do. But (and I feel this is going to be a real noob question) I can't figure out how to access the store/dispatcher from within the router.

I'm not sure if I'm using the wrong search terms or whatever, but I'm completely out of ideas and every react-router/redux tutorial seems to skip over what I feel like has to be a common problem.

Can anyone point me in the right direction (and also let me know if what I'm doing is best practice?)?

EDIT: I'll try and clarify this a bit more. In the first code block, you can see that if I click a <Link to="/about"> element then the getComponent function will fire, which will lazy-load the About.jsx component. The problem I am having is I can't figure out how to show some sort of loading indicator/spinner that would appear immediately after clicking the link and then have it get replaced once the component loads.

MORE EDITING: I've tried creating a wrapper component for loading async routes and it seems to work, however it feels really hacky and I'm sure it isn't the right way to go about doing this. Routes code now looks like this:

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;

The AsyncRoute.jsx page looks like this:

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;

If anyone has a better idea, please let me know.

解决方案

Following the same approach as @David M I implemented a loading reducer and a function to wrap the dispatches.

Excluding the store creation and manage, they are basically as follows:

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
}

Notice how loadingQueue keeps the loading message active while there are remaining modules to fetch, for nested routes.

withLoader function:

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

Now when defining new routes we can dispatch the loading action implicitly using 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

这篇关于在 react-router 中调用 getComponent 时如何显示加载 UI?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆