React16:按需加载和异步组件

React16按需加载组件和异步组件

Posted by wang chong on April 14, 2019

按需加载

随着前端工程化的发展,我们更倾向于使用自动化构建工具构建项目,构建工具可以完全使用模块化的方式完成项目的构建,便于维护和开发。我们在使用自动化构建工具通常会把代码打包到一个文件中去main.js,这样的好处在与当请求网页只需要加载这一个文件就可以展示整个应用。但是,随着网页功能的不断扩展和项目的不断增大,这反而带来了网页加载速度慢、交互卡顿的问题。原因是整个应用都在一个文件里面导致文件很大,从而加载很慢。于是就必须把代码分割开来,按需加载。

Webpack的按需加载

在使用Webpack构建项目的时候可以采用webpack的按需加载功能,Webpack采用动态import的方式按需加载模块。

import(/* webpackChunkName: 'module'*/"module").then(() => {
        //todo
}).catch(_ => console.log('It is an error'))

React16之前的按需加载

React16之前也可以使用Webpack的方式进行按需加载,但当时最流行的一种方式是React-loadable库提供的按需加载React组件,它利用动态import的语法,使用Promise语法加载React组件。同时,React-loadable支持React的服务端渲染。

例如:一个按需加载组件

export default  function DemoComponent() {

return (
        <div>
            <p>demo component</p>

        </div>
    )
}

在加载的时候我们想让上面的代码不显示,同时把它单独打包到一个文件中,我们来看一个React-loadable官网的例子:

import Loadavle from "react-loadable";
import Loading from "./my-loading-component";
const LoadableComponent = Loadable({
    loader: () => import('./my-component'),
    loading: Loading
})
export default class extends React.Component{
    render(){
        return <LoadableComponent/>
    }
}

上面代码中react-loadable使用动态import()方法,并将导入的组件分配给loader属性。同时,react-loadable提供了一个loading属性,以设置在加载组件时将展示的组件。

React16:Lazy组件

React16中Lazy组件使用动态import的方式加载组件,首先需要在.babelrc里面配置动态import的插件。

注意:只有lazy组件才能在suspense组件中支持。什么是Suspense组件继续往下读。

{
    "presets": ["@babel/preset-env","@babel/preset-react"],
    "plugins":[
        "@babel/plugin-syntax-dynamic-import"
    ]
}

Lazy按需加载组件很简单

首先定义一个简单的组件

import React from "react";
export default ()=> <p className="text-success">Lazy Component</p>

使用lazy组件的方式引入。

import React, {lazy} from "react";
const LazyComp = lazy(() => import("./lazy.jsx"));

使用

export default class App extends Component {
    constructor(props) {
        super(props);
        this.state = {
            message: "Hello World"
        }
    }
    render() {
        return (
           <Suspense fallback={"fdas"}>
                <LazyComp />
                
            </Suspense>
        )
    }
}

使用控制台检查一下 我们发现,多了一个0.js文件,这个文件就是那个按需加载的组件。代码拆分成功了,但是并没有到按需加载,反而是直接加载了。

变量控制加载

采用在状态中定义一个boolean类型的变量,使用这个变量来控制否加载。

export default class Suspenses extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            isload: false
        }
    }
    render() {
        return (
            <Suspense fallback={"fdas"}>
                <button onClick={() => { this.setState({ isload: !this.state.isload }) }}>click</button>
                {this.state.isload ? <LazyComp /> : void 0}
            </Suspense>

        )
    }
}

这时候发现并没有加载0.js,点击以下click试试。 点击之后发现,组件加载出来了,同时js文件也加载出来了。

异步组件Suspense

在Lazy组件中使用到了这个Suspense组件,这个组件是什么?

Suspense组件React16提供的一种支持异步组件的方式。

Suspense组件调用异步数据必使用一个fetcher,这个是必须的,这个是必须的,这个是必须的。

创建一个fetcher

const createFetcher = promiseTask => {
    var cached = {};
    let ref = cached;
    return ()=> {
        const task = promiseTask();
        task.then(res=>{
            ref = res;
        });
        console.log("-----".ref);
        console.log("=====",cached);
        //当ref!=== cached 就表示加载完成了,
        //如果 ref === cached 也就是没有加载完,throw一个task,就相当于报错。内部使用轮训的方式
        if(ref === cached){
            throw task; 
        }
        //得到结果输出
        console.log("++++++",ref);
        return ref;
    }
}

首先模拟一异步数据

const fetchApi = ()=> {
    const promise = new Promise((resolve) => {
        setTimeout(()=>{
            resolve('Data resolved');
        },3000)
    })
    return promise;
}

创建一个Suspenseomp组件,用于获取显示异步信息。

const requestData = createFetcher(fetchApi);

const SuspenseComp = () => {
    const data = requestData();
    return <p className ="text-warngin"> {data} </p>
}

创建一个组件,并使用Suspense组件

export default () => (
    <Suspense fallback={ <div>替换的数据</div> }>
        <SuspenseComp />
        <LazyComp />
    </Suspense>
)

Suspense组件必须接受一个fallback作为一个属性。当数据还未请求过来使用fallback中的数据来作为dom渲染,请求完成后会数据替换掉fallback的内容。

需要注意的地方

Suspense组件中可以写多个异步组件,它相当于Promise.all方法,必须等所有的组件数据都请求过来之后才会渲染,也可以说成渲染时间为请求时间最长的那个异步组件的时间。

上面这种方式开发起来真的挺不舒服的,还需要写fetcher之类的东西,很麻烦,还有官方提供了一个小的hook,让开发变得非常简单。

官方的库–react-hooks-fetch

引入

import {useFetch } from "react-hooks-fetch";

这个库提供了一个useFetch方法用于网络请求,我以一个网上的请求接口为例:

const SuspenseComp = () => {
    const {error,data} = useFetch('http://jsonplaceholder.typicode.com/posts');
    console.log(data);
    //如果error存在,返回出错。
    if(error){
        return "出错了";
    }
    //如果data不存在返回null
    if(!data){
        return null;
    }
    //最后返回信息。
    return <p>{data}</p>
}

useFetch方返回值有两个属性,一个error,一个是data。error用来判断请求过程中是否出错,data是请求的数据。就这么简单就ok了。

使用和使用普通方式相同。

export default () => (
    <Suspense fallback={ <div>替换的数据</div> }>
        <SuspenseComp />
        <LazyComp />
    </Suspense>
)