zoukankan      html  css  js  c++  java
  • 小二助手-react.js分块加载

    小二助手在线演示地址:http://118.25.217.253:8000  账号test 密码123

    小二助手是用material-ui开发的,感觉国内使用的人数不是特别多,所以创建了一个qq交流群,欢迎加入:836719189

     在网上找了多种方案,这种采纳了下面的方案,简单有效。

    一、为什么需要代码分片

    Facebook 的 create-react-app 是一款非常优秀的开发脚手架。它为我们生成了 React 开发环境,自带 webpack 默认配置。 它会通过 webpack 打包我们的应用,产生一个 bundle.js 文件。随着我们的项目越写越复杂,bundle.js 文件会随之增大。 

    由于该文件是唯一的,所以不管用户查看哪个页面、使用哪个功能,都必须先下载所有的功能代码。 

    当 bundle.js 大到一定程度,就会明显影响用户体验。

    此时,我们就需要 code splitting ,将代码分片,实现按需异步加载,从而优化应用的性能。

    二、代码分片的原理
    ES模块(ECMAScript modules)都是静态的。编译时就必须指明 确定的导入(import)和导出(export)。 这也是规定 import 声明必须出现在模块顶部的原因所在。

    但是我们可以通过 dynamic import() 来实现动态加载的功能。 dynamic import() 是 stage 3 中的一个提案。这是一个 运算符 operator 而非函数 function 。 我们把模块的名字作为参数传入,它会返回一个 Promise ,当模块加载完成后,该 Promise 就会 fulfilled。

    当你在代码中新增了一个 import() ,用它动态导入模块时, Webpack 2 会自动据此完成代码分片,不需要任何额外的手动配置。

    三、以路由为中心进行代码分片
    React 项目中的路由一般用 React Router,它可以将多页面的应用构建为 SPA ,即单页面应用。 

    此处,我们以其最新版 React Router v4 为例。

    分片前

    import {requireAuthentication} from './CheckToken'
    import Home from '../components/Home/Home'
    import Login from './LoginContainer'
    import Signup from './SignupContainer'
    import Profile from './ProfileContainer'
    ... ...
    <Router>
    <Switch>
    <Route exact path='/' component={Home} />
    <Route path='/login' component={Login} />
    <Route path='/signup' component={Signup} />
    <Route path='/profile' component={requireAuthentication(Profile)} />

    分片后

    新增 AsyncComponent,它将接受一个函数作为参数,实现异步地动态加载组件。例如:

    const AsyncLogin = asyncComponent(() => import('./LoginContainer'))
    至于为什么是以 () => import('./LoginContainer') 这样的箭头函数为参数,而非 './LoginContainer' 这样的字符串,和 Webpack 的进行代码分片的机制有关。 

    这么写看起来啰嗦,但可以让我们控制生成多少个 .chunk.js 这样的分片文件。

    代码:

    import React, { Component } from 'react'
    
    export default function asyncComponent(importComponent) {
    class AsyncComponent extends Component {
    constructor(props) {
    super(props)
    
    this.state = {
    component: null
    }
    }
    
    async componentDidMount() {
    const { default: component } = await importComponent()
    
    this.setState({
    component: component
    })
    }
    
    render() {
    const C = this.state.component
    
    return C ? <C {...this.props} /> : null
    }
    }
    
    return AsyncComponent
    }

    路由

    import {requireAuthentication} from './CheckToken'
    import asyncComponent from './AsyncComponent'
    
    const AsyncHome = asyncComponent(() => import('../components/Home/Home'))
    const AsyncLogin = asyncComponent(() => import('./LoginContainer'))
    const AsyncSignup = asyncComponent(() => import('./SignupContainer'))
    const AsyncProfile = asyncComponent(() => import('./ProfileContainer'))
    ... ...
    <Router>
    <Switch>
    <Route exact path='/' component={AsyncHome} />
    <Route path='/login' component={AsyncLogin} />
    <Route path='/signup' component={AsyncSignup} />
    <Route path='/profile' component={requireAuthentication(AsyncProfile)} />

    此时再运行 npm run build,看编译的log,以及 build/static/js/ 目录下的 js 文件,会发现多出了若干文件名 .chunk.js 结尾的文件。

      npm start 把项目跑起来,在 chrome 的 devTool 中,打开 Network ,查看 JS ,就可以看到异步动态按需加载分片文件的效果了。

    四、以组件为中心进行代码分片
    上面一小节是以路由为中心进行代码分片的思路与实现。但是 React Router 官网说得明白,React Router 是导航组件的集合。 

    即,路由本身并没有什么特别的,它们也是组件。

    如果以组件为中心进行代码分片,会带来额外的好处:

    除了路由此外,还有很多地方可以进行代码分片。广阔天地,大有作为。
    同一个组件中,针对不急着显示的东西,可以延迟其加载。
    ... ...
    这里介绍 React Loadable 。

    通过它,我们可以用使用 React 高阶组件 (Higher Order Component / HOC)实现异步加载 React 组件的功能,同时处理操作失败、网络错误等等边缘情况。

    注:一个高阶组件,简言之就是一个函数,它接受的参数是 React 组件,返回的结果也是 React 组件。

    React Loadable 可以通过 npm 安装 react-loadable。

    首先,我们用 React Loadable 来重构刚才的代码

    处理边缘情况的组件

    import React from 'react'
    
    const MyLoadingComponent = ({isLoading, error}) => {
    // 加载中
    if (isLoading) {
    return <div>Loading...</div>
    }
    // 加载出错
    else if (error) {
    return <div>Sorry, there was a problem loading the page.</div>
    }
    else {
    return null
    }
    }
    
    export default LoadingComponent

    路由

    import {requireAuthentication} from './CheckToken'
    import Loadable from 'react-loadable'
    import LoadingComponent from '../components/common/Loading'
    
    const AsyncHome = Loadable({
    loader: () => import('../components/Home/Home'),
    loading: LoadingComponent
    })
    const AsyncSignup = Loadable({
    loader: () => import('./SignupContainer'),
    loading: LoadingComponent
    })
    const AsyncLogin = Loadable({
    loader: () => import('./LoginContainer'),
    loading: LoadingComponent
    })
    const AsyncProfile = Loadable({
    loader: () => import('./ProfileContainer'),
    loading: LoadingComponent
    })
    <Router>
    <Switch>
    <Route exact path='/' component={AsyncHome} />
    <Route path='/login' component={AsyncLogin} />
    <Route path='/signup' component={AsyncSignup} />
    <Route path='/profile' component={requireAuthentication(AsyncProfile)} />

    五、进一步优化

    重新运行项目,发现了可以进一步改进的地方。

    防止 Loading 组件闪现
    在页面跳转的时候,屏幕上会短暂的闪过 LoadingComponent 组件。

    我们添加该组件的初衷,是在网络差的时候,给用户一个提示:“应用运行正常,只是正在加载中,请稍等。”

    显然,如果网络良好,跳转足够快,LoadingComponent 组件根本没有必要出现。

    React Loadable 可以很容易地实现这个功能。

    LoadingComponent 组件接收一个 pastDelay 属性,该属性仅仅在延迟超过一个规定的值后才为 true 。

    默认的延迟是 200ms,我们也可以自己指定别的时长。操作如下,我们将其设置为 300ms。

    const AsyncLogin = Loadable({
    loader: () => import('./LoginContainer'),
    loading: LoadingComponent,
    delay: 300
    })

    LoadingComponent 组件做相应调整。同时增加一些简单的样式。

    import React from 'react'
    import Footer from '../Footer/Footer'
    import styled from 'styled-components'
    
    const Wrap = styled.div`
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    background-color: #B2EBF2;
    text-align: center;
    `
    
    const LoadingComponent = (props) => {
    if (props.error) {
    return (
    <Wrap>
    <div>Error!</div>
    <Footer />
    </Wrap>
    )
    } else if (props.pastDelay) {
    // 300ms 之后显示
    return (
    <Wrap>
    <div>信息请求中...</div>
    <Footer />
    </Wrap>
    )
    } else {
    return null
    }
    }
    
    export default LoadingComponent

    同一个组件中,延迟加载不急着显示的内容

    例如这个组件,TopHeader 是优先显然的内容,Notification 是不一定显示的内容。我们可以推迟后者的加载。

    ... ...
    import TopHeader from '../components/Header/TopHeader'
    import Notification from './NotificationContainer'
    
    class TopHeaderContainer extends Component {
    ... ...
    
    return (
    <div>
    <TopHeader
    sideButtons={tempIsAuthenticated}
    logout={this.logout}
    />
    <Notification />
    </div>
    
    )
    }
    ... ...
    export default connect(mapStateToProps, { logout })(TopHeaderContainer)
    优化后
    
    ... ...
    import TopHeader from '../components/Header/TopHeader'
    
    import Loadable from 'react-loadable'
    import LoadingComponent from '../components/common/Loading'
    
    const AsyncNotification = Loadable({
    loader: () => import('./NotificationContainer'),
    loading: LoadingComponent,
    delay: 300
    })
    ... ...
    class TopHeaderContainer extends Component {
    ... ...
    
    return (
    <div>
    <TopHeader
    sideButtons={tempIsAuthenticated}
    logout={this.logout}
    />
    <AsyncNotification />
    </div>
    )
    }
    }
    ... ...
    export default connect(mapStateToProps, { logout })(TopHeaderContainer)
    ... ...

    此外, 还可以实现 预加载(如 click 按钮显示某组件,那么在 hover 事件时就预先加载之)、服务端渲染 等等。

    在此就不多做介绍了。
    ---------------------
    作者:Beijiyang999
    来源:CSDN
    原文:https://blog.csdn.net/beijiyang999/article/details/78591398 

  • 相关阅读:
    游戏文字自动断行需要,还得从 UTF-8 讲起
    史上最明白的 NULL、0、nullptr 区别分析(老师讲N篇都没讲明白的东东),今天终于明白了,如果和我一样以前不明白的可以好好的看看...
    django -- ORM实现作者增删改查
    selenium--操作JS弹框
    selenium--多窗口操作
    django -- ORM实现图书增删改查
    django -- ORM实现出版社增删改查
    selenium--等待的三种方式
    postman使用--Monitor
    django -- 实现ORM登录
  • 原文地址:https://www.cnblogs.com/liuhaili/p/10000191.html
Copyright © 2011-2022 走看看