(二)React 进阶、Hooks、异步组件

255 阅读10分钟

参考资料:

  1. UmiJS
  2. React官网资料

一、高级组件 HOC

高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。

1、作用

  • 代码复用,逻辑抽象
  • 渲染劫持
  • state的抽象和更改
  • props的更改

2、写法

怎么写一个高阶组件?

  • 普通方式
  • 装饰器
  • 多个高阶组件的组合
1、普通写法
//属性代理:操作props
import React, {Component} from 'react';
class Count extends Component {
  state = {
    number: 0
  }
  render(){
    return (
      <div>
        {/* 对象会合并,hook不会 */}
        {this.state.number}
        <button onClick={()=>{this.setState({number: number + 1})}
      </div>
    )
  }
}

function HocNumber (Comp) {
    return class Temp extends Component {
      render(){
        let newProps = {
          ...this.props,
          age: '18'
        }
        return <Comp {...newProps}/>
      }
    }        
}

Count = HocNumber(Count);
export default Count;
2、装饰器
//定义
import React, {Component} from 'react';
export const decoratorWithNameHeight = (height?: number) => {
  //两个return用于获取传参
  return (WrappedComponent: any) => {
    return class extends Component<any, State> {
      public state: State = {
            name: ''
      }
      componentWillMount(){
        let username = localStorage.getItem('myName');
        this.setState({
          name: username
        })
      }
    
      render(){
        return (
          <div>
          	<WrappedComponent name={this.state.name} {...this.props}/>
    		<p>the height is {height || 0} </p>
          </div>
        )
      }
    }
  }
}

//使用装饰器的高阶组件
import React, {Component} from 'react';
import {decoratorWithNameHeight} from "../index";

@decoratorWithNameHeight(180)
class UglyWorld extends Component<Props, any>{
  render(){
    return <div>bye ugly world! my name is {this.props.name}</div>
  }
}

export default UglyWorld;

//使用
import {UglyWorld} from "../index"
<UglyWorld/>
3、多个高阶组件的组合
export const decoratorWithWidth = (width?: number) => {
  //两个return用于获取传参
  return (WrappedComponent: any) => {
    return class extends Component<any, any> {
      render(){
        return (
          <div>
          	<WrappedComponent {...this.props}/>
    		<p>the width is {width || 0} </p>
          </div>
        )
      }
    }
  }
}

//使用多个装饰器的高阶组件
import React, {Component} from 'react';
import {decoratorWithNameHeight, decoratorWithWidth} from "../index";

@decoratorWithWidth(100)
@decoratorWithNameHeight(180)
class UglyWorld extends Component<Props, any>{
  render(){
    return <div>bye ugly world! my name is {this.props.name}</div>
  }
}

export default UglyWorld;

//使用:先展示height、再展示width
import {UglyWorld} from "../index"
<UglyWorld/>

3、实现方法及技术作用

高级组件能用来做什么?从技术方面?

实现高阶组件的方法有两种,包括属性代理和反向继承

  • 属性代理:继承component,渲染使用原组件
    • 操作props
    • 操作组件实例
    • 抽象state和事件:将子组件的事件和state抽象至高阶组件中,通过props传值使用
      • 使用场景:将表单中的数据和事件抽象到高阶组件中,统一处理
    • 通过ref使用引用
  • 反向继承/劫持:继承传入的组件,从而使用传入组件的属性和方法
    • 渲染劫持
    • 控制state:可读入传入组件的state对其修改,建议不修改
//属性代理:通过ref使用引用,对子组件DOM进行处理
//1. 父组件使用高阶组件
import React, {Component} from 'react'
import Hoc from './hoc/index'
class App extends Component {
  refA = React.creatRef()
  componentDidMount(){
    //操作子组件的input
    this.refA.current.InputRef.current.focus();
  }
  render(){
    return (
      <div>
        <Hoc name="jian" ref={this.refA}/>
      </div>
    )
  }
}
export default App;

//2.高阶组件使用ref
// app组件传入refA,挂载至Count组件上,this.refA.current可获取到Count组件
// this.refA.current.InputRef.current可获取到input的dom
import React, {Component} from 'react'
class Count extends Component {
  state = {
    number: 0
  }
  InputRef = React.createRef()
  render(){
    return (
      <div>
        {/* 对象会合并,hook不会 */}
        {this.state.number}
        <input type="text" ref={this.InputRef}/>
        <button onClick={()=>{this.setState({number: number + 1})}
      </div>
    )
  }
}

function HocNumber (Comp) {
    class Temp extends Component {
      render(){
        let newProps = {
          ...this.props,
          age: '18'
        }
        let { forwardRef } = this.props;
        return <Comp {...newProps} ref={forwardRef}/>
      }
    }
    return React.forwardRef((props, ref) => {
      return <Temp forwardRef={ref} {...props}/>
    })
}

Count = HocNumber(Count);
export default Count;
//属性代理:操作组件实例
export const refHoc = () => {
  return (WrappedComponent: any) => {
    return class extends Component<any, any>{
      ref = null;
      componentDidMount(){
        console.log(this.ref.state);
      }
    	render(){
        return (
          <WrappedComponent
          	{...this.props}
    		ref={(instance: any)=>{
                  this.ref = instance;
                }}
          </WrappedComponent>
        )
      }
    }
  }
}

import {refHoc} from "../refHoc";
interface Props {
  name?: string;
}
interface State {
  weight?: number:
  height?: number;
}

@refHoc
class RefDemoComponent extends Component<Props, state>{
  state: State = {
    weight: 60,
    height: 170
  }
  render(){
    return <div>bye ugly world my name is {this.props.name}</div>
  }
}
  
export default RefDemoComponent;
//劫持:劫持方法
import React from 'react';
export function hijackHoc<T extends {new (...args: any[]):any}>(
	component: T
){
  return class extends component {
    //劫持点击事件,做处理
    handleClick(){
      console.log(this.handleClick)
      super.handleClick();
    }
    
    render(){
      const parent = super.render();
      return React.cloneElement(parent, {
        onClick: () => this.handleClick()
      })
    }
  }
}

//使用
@hijackHoc
class HijackComponent extends Component<Props, state>{
  state: State = {
    weight: 60,
    height: 170
  }
  handleClick(){
    this.setState({
      weight: this.state.weight + 1;
    })
  }
  render(){
    return (
      <div onClick={()=> this.handleClick()}>
        测试按钮 {this.state.weight}
      </div>
		)
  }
}
export default RefDemoComponent;
//渲染劫持
import React, {Component} from 'react';
class Count extends Component {
  render(){
    return <div>count</div>
  }
}
const MyContainer = (WrappedComponent) => {
  return class Temp extends WrappedComponent{
    render(){
      const elementsTree = super.render()
      let newProps = {}
      if(this.props.isShow){
        return super.render()
      }else{
        return null;
      }
    }
  }
}

Count = MyContainer(Count)
export default Count;

组件命名:Hoc创建的容器组件会与任何其他组件一样,为了方便调试,选择一个显示名称,表示他是HOC的产物,最常见的方式是用HOC保住被包装组件的显示名称,eg:高阶组件名为withSubscription,被包装的组件显示名称为CommentList,显示名称应为WithSubscription(CommentList)。

function withSubscription(WrappedComponent){
  class WithSubScription extends React.Component {/* */}
  WithSubScription.displayName = `WithSubscription(${getDisplayName(WrappedComponent)})`;
  return WithSubScription;
}

function getDisplayName(WrappedComponent){
  return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}

HOC 缺点⼩结

  • 增加了组件嵌套层级,过多时对于渲染的性能有⼀定影响;
  • ref 、displayName 等易被忽略,虽然我们不推荐使⽤ ref,但是类似上⾯的需求,尤其是组件库封 装,ref 的转发是必不可少的,在⼀些 Dev tool 中也徒增了 UI ⽆关的组件嵌套;
  • 对于已使⽤了 HOC 的业务,需求的扩展有⼀定的难度;
  • ⾼阶组件有相似的逻辑时,也会造成执⾏顺序、功能覆盖的⻛险……

4、HOC知名的应用案例

1、react-redux
connect(mapStateToProps, mapDispatchToProps, mergeProps)(App);

// 简化实现等价于:
export function connect(mapStateToProps, mapDispatchToProps) {
    return function (WrappedComponent) {
        class Connect extends React.Component {
            componentDidMount() {
                //从context获取store并订阅更新
                this.context.store.subscribe(this.forceUpdate.bind(this));
            }
            
            render() {
                return (<WrappedComponent 
                    // 该组件的 props,需要由 connect 这个阶组件原样传回原组件
                    { ...this.props }
                    
                    // 根据 mapStateToProps  state 挂到 this.props 
                    { ...mapStateToProps(this.context.store.getState()) }
                    
                    // 根据 mapDispatchToProps  dispatch(action) 挂到 this.props 
                    { ...mapDispatchToProps(this.context.store.dispatch) }
                />)
            }
        }
        // 接收 context 的固定写法
        Connect.contextTypes = {
            store: PropTypes.object
        }
        return Connect;
    }
}
// 因此, App 组件的 props 会被注⼊ action、state 等
2、react-router-dom withRouter
export default withRouter(App); // App 获得了 history,location 等 props

function withRouter(Component) {
    const displayName = `withRouter(${Component.displayName || Component.name})`;
    const C = props => {
        // 如果想要设置被 withRouter 包裹的组件的 ref,这⾥使⽤ wrappedComponentRef
        const { wrappedComponentRef, ...remainingProps } = props;
        
        return (
            <RouterContext.Consumer>
                {context => {
                    // 将 context 加⼊到 Component 中,注意 ref 的转发,这⾥注⼊了
                    // RouterContext 中定义的各种 props,其中就包括 history,location 对象
                    return (
                        <Component
                            {...remainingProps}
                            {...context}
                            ref={wrappedComponentRef}
                        />
                    );
                }}
            </RouterContext.Consumer>
        );
    };
    C.displayName = displayName;
    C.WrappedComponent = Component;
    
    // 当你给⼀个组件添加⼀个 HOC 时,原来的组件会被⼀个 container 的组件包裹。
    // 这意味着新的组件不会有原来组件任何静态⽅法。
    // 为了解决这个问题,可以在 return container 之前将 static ⽅法 copy 到 container 上⾯
    // ⽤ hoist-non-react-statics 来⾃动复制所有 non-React 的 static methods
    return hoistStatics(C, Component);
}

二、Hooks

⾼阶组件允许我们通过套娃的⽅式来增强组件,套娃套多了,维护起来会越来越难。hooks 的诞⽣也顺 带解决了这个问题。因此 hooks 的强⼤能⼒依然是代码逻辑的复⽤,同时也简化了⽣命周期,使得函数式 组件拥有了状态。注意, hooks 只能在函数式组件中使⽤,命名规范为 use 开头,且可以返回组件或任意 类型的数据(也可不返回)。

1、hooks优势

  • class的缺点
  1. 组件间的状态逻辑很难复用
    • 组件间如果有state的逻辑是相似的,class模式下基本使用高阶组件来解决。虽然能够解决问题,但是我们需要在组件外部再包一层元素,会导致层级非常冗余。
  2. 复杂业务的有状态组件会越来越复杂
  3. 监听和定时器的操作,被分散在多个区域:多个生命周期函数中存在同一业务逻辑,逻辑分散
  4. this指向问题
class App extends React.Component<any, any>{
  constructor(props){
    super(props);
    this.state = {
      num: 1,
      title: "Jian"
    }
    this.handleClick2 = this.handleClick1.bind(this);
  }
  
  handleClick1(){
    this.setState({
      num: this.state.num + 1
    })
  }

  handleClick3 = () => {
    this.setState({
      num: this.state.num + 1
    })
  }
	
  render(){
    return (
      <div>
          {/* 考点1: render里bind每次都会返回一个新的函数,造成ChildComponent每次会重新渲染 */}
          <ChildComponent onClick={this.handleClick1.bind(this)}></ChildComponent>
          <ChildComponent onClick={()=> this.handleClick1()}></ChildComponent>

          {/* 考点1解决方法:提取函数 */}
          <ChildComponent onClick={this.handleClick2}></ChildComponent>
          <ChildComponent onClick={this.handleClick3}></ChildComponent>
      </div>
    )
  }
}
  • hooks的优点
  1. 利用业务逻辑的封装和拆分,可以非常自由的组合各种定义hooks(自己封装的用到的hooks的逻辑)
  2. 可以在无需修改组件结构的情况下,复用状态逻辑
  3. 定时器、监听等都被聚合到同一块代码下

2、常用Api

1、useState
import React, {useState, Component} from 'react';
class Temp extends Component{
  state = {
    count: 0
  }
  render(){
    return (
      <div>
        {this.state.count}
        <button onClick={()=>{
            this.setState({
              count: this.state.count + 1
            })
          }}>+</button>
      </div>
    )
  }
}

//hooks写法
const Temp = () => {
  const [count, setCount] = useState(0); //初始值为0
  return (
    <div>
      {count}
      <button onClick={()=>{setCount(count+1)}}>+</button>
    </div>
  )
}

//复合值,setValue无法合并对象
const Temp = () => {
  const [counter, setValue] = useState(()=>{
    return {name: 'jian', age: 18}
  });
  return (
    <div>
      {counter.name}{counter.age}
      <button onClick={()=>{setValue({...counter, age: counter.age + 1})}}>+</button>
    </div>
  )
}
2、useEffect
import React, {useState, useEffect} from 'react';
const Temp = () => {
  const [number, setNumber] = useState(0);
  const [count, setCount] = useState(0);
  
  //Similar to componentDidMount,只执行一次
  useEffect(()=>{
    console.log('useEffect')
  }, [])
  
  //Similar to componentDidMount and componentDidUpdate
  useEffect(()=>{
    console.log('useEffect')
    //返回函数,清楚副作用,componentWillUnmount
    return function xx(){
    }
  })
  
  //Similar to componentDidUpdate,仅在依赖项更新时调用
  useEffect(()=>{
    console.log('useEffect')
  }, [number])
  
  return (
    <div>
      <h1>{number}</h1>
      <h2>{count}</h2>
      <button onClick={()=>{setNumber(number+1)}}>number+</button>
      <button onClick={()=>{setCount(count+1)}}>number+</button>
    </div>
  )
}
3、useRef
// 1. 挂载 dom 节点 
import React, { useRef, useEffect } from 'react'; 

export default function UseRef() { 
    const container = useRef(null); 
    console.log('container', container); // 第⼀次是拿不到的 
    
    useEffect(() => { 
        console.log('container', container); // current 属性引⽤着虚拟 DOM 节点 
    }, []); 
    return (Ref 容器); 
} 

// 2. 模拟类组件的 this,充当持久化数据对象 
export default function UseRef() { 
    const container = useRef(false); 
    
    useEffect(() => { 
        if (container.current) { 
            console.log('模拟 componentDidUpdate ,即除了初始化,之后的更新进到这⾥'); 
        } else { 
            container.current = true; // 初次挂载时⾛这⾥ 
        } 
    }); 
    return (Ref 容器); 
}
4、useCallback

性能优化,缓存方法 (函数)

import React, {useState, useCallback} from 'react'
/* 
解析:父组件更改,Child每次在渲染
使用React.memo包裹可优化,对函数组件进行优化
类似:shouldComponentUpdate和PureComponent
*/
const Child = React.memo((getCount) => {
  return <div onClick={getCount}>child</div>
})

function Parent(){
  const [num, setNum] = useState(0);
  const [val, setVal] = useState("");
  /* 
  未使用useCallback包裹时,val和num变时都渲染 
  使用useCallback包裹时,只有依赖项变动才会渲染
  */
  const getCount = useCallback(() => {
    console.log(num)
  }, [num]}
                               
  return (
    <div>
      <h1>num: {num}</h1>
      <h2>val: {val}</h2>
      <input type="text" onChange={(ev) => {setVal(ev.target.value)}}/>
      <button onClick={()=> {setNum(num + 1)}}>+</button>
      <Child getCount={getCount}/>
    </div>
  )
}
export default Parent;
5、useMemo

缓存计算值,性能优化

import React, { useState } from 'react';
const UseCallbackSub = ({ value, onChange }) => {
    console.log('⼦元素发⽣了渲染 value: ', value); 
    return ; 
}; 
export default function UseCallback() { 
    const [count, setCount] = useState(0); 
    const [value, setValue] = useState(0); 
    // 每次修改 count 时,本组件发⽣渲染⽆可厚⾮, 
    // 但是⼦组件 UseCallbackSub 也会进⾏不必要的渲染 
    const onClick = () => { 
        setCount(count + 1); 
    }; 
    
    const onChange = e => { 
        setValue(e.target.value); 
    }; 
    return (<> 
        count 发⽣变化{ count } 
        </>
    ); 
} 

// 通常优化这类情景,可以对⼦组件使⽤ memo 包裹 
import React, { useState, useCallback, memo } from 'react'; 
const UseCallbackSub = memo(({ value, onChange }) => { 
    console.log('⼦元素发⽣了渲染 value: ', value); 
    return ; 
}); 

// ⽗组件内: 
... 
const onChange = useCallback(e => { 
    setValue(e.target.value);
 }, []); 
 
 ... 
 // 总结⼀句,useCallback 可以对函数进⾏缓存,保证 onChange 不会随着组件更新⽽改变引⽤,
 //⽽ memo 会默认对所有的 props 进⾏对⽐,如果不发⽣变化则不更新组件,避免⽗级引起的⼦级渲染。
 //当然,上述⽅式也可以不使⽤useCallback 达到⽬的(组件的更新只取决于 value 的变化),使⽤⾃定义⽐对函数: 
 
 const UseCallbackSub = memo(({ value, onChange }) => {
     console.log('⼦元素发⽣了渲染 value: ', value); 
     return ; 
 }, (prev, next) => prev.value === next.value);
6、useContext

使用context解决父子数据传递问题:Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法

import React, {useState, useContext} from 'react'
let theme = {
  black: {
    background: "#000",
    color: "#fff"
  },
  pink: {
    background: "#eee",
    color: "#000"
  }
}

const ThemeContext = React.createContext()

//父组件
function App (){
  return <ThemeContext.Provider value={theme.black}>
    <Theme/>
  </ThemeContext.Provider>
}

//子组件
function Theme(){
  const ThemeCo = useContext(ThemeContext);
  return (
    <div style={background: ThemeCo.background, color: ThemeCo.color}>
    </div>
  )
}
export default App;
7、useReducer

使用reducer保存全局复合数据

import React, {useState, useReducer} from 'react'
const initState = {
  name: 'jian',
  age: 18
}
function reducer(state, action){
  switch(action.type){
    case 'increment':
      return {...state, age: age + 1}
    case 'decrement':
      return {...state, age: age - 1}
    default:
      return state
  }
}

function Temp(){
  const [state, dispatch] = useReducer(reducer, initState);
  return (
    <div>
      <h1>{state.name}</h1>
      <h2>{state.age}</h2>
      <button onClick={()=>{dispatch({type: 'increment'})}}>increment</button>
      <button onClick={()=>{dispatch({type: 'decrement'})}}>decrement</button>
    </div>
  )
}

export default Temp;
8、useCustom

使用use开头封装函数,内部使用原hook方法

import React, {useState, useMemo} from 'react'
function useCutDown(init = 60){
  const [count, setCount] = useState(init)
  useEffect(()=>{
    let timer = setInterval(()=> {
      if(count < 1){
        clearInterval(timer)
        return
      }
      setCount(count -1)
    }, 1000)
    return ()=>{
      clearInterval(timer)
    }
  }, [])
  return count
}

function App(){
  let Counter = useCutDown(120);
  return <div>{Counter}</div>
}

export default App;

3、自定义hooks实现

⾸次挂载不执⾏,更新时执⾏:

// 上⽂有提到使⽤ useEffect 模拟 componentDidUpdate 的例⼦,如果 
// 这个场景⽐较多,我们可以封装起来: 
import React, { useRef, useEffect } from 'react'; 
export default function useUpdated(callback) { 
    const didUpdate = useRef(false);
    useEffect(() => { 
        if (didUpdate.current) {
            callback?.();
        } else {
            didUpdate.current = true; 
            // 初次挂载时⾛这⾥ 
        } 
    }); 
} 

// 分隔线 --------------------------------------------------- 
// 使⽤⽅式 
import useUpdated from './useUpdated'; 

export default function UseRef() {
    useUpdated(() => { 
        console.log('模拟 componentDidUpdate ,即除了初始化,之后的更新进到这⾥'); 
    }); 
    return (<button>Ref 容器</button>);
 }

参数变化就发起请求,⾃动更新数据源

import { useState, useEffect } from 'react'; 
const defaultOptions = {}; // 根据实际情况写死⼀些默认值 

export default function useRequest(query, { url, method = 'GET' } = {}) { 
    // 务必保证 query 的变化时有条件的
    const [state, setState] = useState({ 
        data: [], error: false, loading: false
    });
    useEffect(() => {
        const opts = { ...defaultOptions, method };
        if (method === 'GET') { 
            opts.params = query; 
        } else {
            opts.body = JSON.stringify(query);
        } 
        setState(state => ({...state, loading: true})); 
        fetch(url, opts).then(json => json()).then(res => { // 异常处理⾃⼰做⼀下 
            setState(state => ({
                ...state, 
                loading: false,
                data: res.data || [], 
                error: false 
            })); 
        }).catch(() => { 
            setState(state => ({...state, loading: false, error: true })); 
        }); 
    }, [query]); 
    
    return state; 
} 

// 分隔线 ------------------------------------------------------------ 
// 使⽤⽅式 
import useRequest from './useRequest';
export default function List() { 
    const [query, setQuery] = useState({});
    const { data, error, loading } = useRequest(query, { url: '/list' });
    
    const onChangeQuery = params => setQuery(query => ({ ...query, ...params }));
    
    return <div>
        <SearchPanel onChange={onChangeQuery} />
        <ul>
            { 
                loading ? 'loading...' 
                : error ? <Empty description="出错了" /> 
                : data.map(item => <li key={item.id}>{item.name}</li>)
            }
        </ul>
        <Pagination onChange={onChangeQuery} />
    </div>
}

三、异步组件

动态导⼊ + Suspense 占位。下⾯的 About 组件将独⽴打包为⼀个⽂件,访问的那⼀刻开始下载,在此 之前并不会占⽤⽹络和系统资源。对于低优先级的任务,尤其是单⻚应⽤的⾸屏展示,异步组件显得⼗分必要。

  • 异步模式:请求数据 -> 渲染组件
  • 解决问题:优化性能、延迟加载

1、传统模式

渲染组件 -> 请求数据 -> 再渲染组件

function Index(){
  const [userInfo, setUserInfo] = React.useState(0);
  React.useEffect(()=>{
    getUserInfo().then(res => {
      setUserInfo(res)
    })
  }, [])
  return <div>
    <h1>{userInfo.name}</h1>
  </div>
}

//2. 使用Suspense + 异步组件
function AsyncComponent(){
  const userInfo = getUserInfo()
  return <div>
    <h1>{userInfo.name}</h1>
  </div>
}
export default function Home(){
  return <div>
    <React.Suspense fallback={<div>loading...</div>}>
      <AsyncComponent/>
    </React.Suspense>
  </div>
}

//3. 使用Suspense + lazy

//eg:import('./app.js').then()
code splitting和import

2、使用Suspense

//1. 使用Suspense + 异步组件
function AsyncComponent(){
  const userInfo = getUserInfo()
  return <div>
    <h1>{userInfo.name}</h1>
  </div>
}
export default function Home(){
  return <div>
    <React.Suspense fallback={<div>loading...</div>}>
      <AsyncComponent/>
    </React.Suspense>
  </div>
}

//2. 使用Suspense + lazy
//code splitting和import,eg:import('./app.js').then()
const LazyComponent = React.lazy(() => import('./test.js'))
export default function Index(){
   return <Suspense fallback={<div>loading...</div>} >
       <LazyComponent />
   </Suspense>
}

suspense1.png

suspense2.png

  • suspense原理
export class Suspense extends React.Component{
   state={ isRender: true  }
   componentDidCatch(e){
     /* 异步请求中,渲染 fallback */
     this.setState({ isRender:false })
     const { p } = e
     Promise.resolve(p).then(()=>{
       /* 数据请求后,渲染真实组件 */
       this.setState({ isRender:true })
     })
   }
   render(){
     const { isRender } = this.state
     const { children , fallback } = this.props
     return isRender ? children : fallback
   }
}
  • lazy原理
function lazy(ctor){
    return {
         $$typeof: REACT_LAZY_TYPE,
         _payload:{
            _status: -1,  //初始化状态
            _result: ctor,
         },
         _init:function(payload){
             if(payload._status===-1){ /* 第一次执行会走这里  */
                const ctor = payload._result;
                const thenable = ctor();
                payload._status = Pending;
                payload._result = thenable;
                thenable.then((moduleObject)=>{
                    const defaultExport = moduleObject.default;
                    resolved._status = Resolved; // 1 成功状态
                    resolved._result = defaultExport;/* defaultExport 为我们动态加载的组件本身  */ 
                })
             }
            if(payload._status === Resolved){ // 成功状态
                return payload._result;
            }
            else {  //第一次会抛出Promise异常给Suspense
                throw payload._result; 
            }
         }
    }
}

3、实现一个异步组件

import React from 'react'; 

export default function lazy(loadComponent) { 
    const Fallback = () => <h1>loading...</h1>;
    const [Component, setComponent] = useState(() => Fallback); 
    
    useEffect(() => {
        loadComponent().then(res => { 
            setComponent(res.default);
        });
    }, []); 
    return ; 
} 

// 或者使⽤⾼阶函数 
export default function lazy(loadComponent) { 
    return class WrapComponent extends React.Component { 
        state = { 
            Component: () =><h1>loading...</h1>;
        } 
        async componentDidMount() { 
            const { default: Component } = await loadComponent(); 
            this.setState({ Component });
        }
        render() {
            const Component = this.state.Component;
            return <Component />;
        }
    }
} 

// 分隔线 ----------------------------------------------------- 
// 使⽤⽅式 
const AsyncAbout = lazy(() => import('./About'));