全局[动态]加载React组件的五种方式

239 阅读2分钟

项目结构

npx create-react-app test-dynamic --template=typescript

cd test-dynamic

mkdir src/Components utils

touch src/Components/index.tsx src/utils/dynamic.ts

code . && exit

文件内容

Components/index.tsx

const DDD = () => <div>你好!</div>

export default DDD;

utils/dynamic.ts

import React from "react";

import ReactDOM from "react-dom";

type IDDD = {

  current: null | undefined | any;

};

const DDDRef: IDDD = {

  current: undefined,

};


export const initialDDD = async () => {

  if (DDDRef?.current) return DDDRef?.current;

  const module = await import( **/* webpackChunkName: "DDD" */** "../Components") as unknown as any;

  console.log("module: ", module); // Module {__esModule: true, Symbol(Symbol.toStringTag): 'Module'}

  DDDRef.current = module;

  return DDDRef.current;

};

  

**//** **不注释** **就是全局加载,** **注释** **就是动态加载**

// initialDDD();

  
export async function dynamicImportInner(config: {

  type: string;

  params?: { id: string };

}) {

  const type = config?.type;

  const params = config?.params;

  const id = params?.id;

  const module = await initialDDD();

  console.log("module2: ", module); // Module {__esModule: true, Symbol(Symbol.toStringTag): 'Module'}

  const Def = module.default;

  console.log("def: ", Def); // const DDD = () => <div>你好!</div>

  


  switch (type) {

    case "click":

      // 此处的def的本质就是一个函数,就是const DDD = () => <div>你好!</div>本身!

      return Def;

    case "method":

      if (id) {

        const instance = React.createElement(Def, {});

        ReactDOM.render(instance, document.getElementById(id));

        // 由于babel.js的原因,这里直接写成<Def></Def>或者<Def />都是可以的;

        //   ReactDOM.render(<Def></Def>, document.getElementById(id));

        return () =>

          ReactDOM.unmountComponentAtNode(document.getElementById(id)!);

      }

  }

}

  
  (window as any).dynamicImport = (id: string) => dynamicImportInner({type: 'method', params: {id}}); // **方案0**

src/App.tsx

import React from 'react';

import { dynamicImportInner } from './utils/dynamic';

  


function App() {

  // 方案一和方案二使用**useState**方法

  const [count, setCount] = React.useState(0);

  const DynamicComponent = React.useRef<any>(null);

  


  // 方案三和方案四使用**useRef**方法

  // const [DynamicComponent2, setDynamicComponent2] = React.useState(null);
  

  return (

    <div className="App">

      <button

        onClick = { () => {

          (async _ => {

            const def = await dynamicImportInner({type: 'click'});

            **// 方案1:**

            // {

            //   const element = React.createElement(def);

            //   DynamicComponent.current = element;

            //   setCount(count+1)

            // }

  


            **// 方案2:**

            {

              DynamicComponent.current = def;

              setCount(count+1)

            }

  


            **// 方案3**

            // {

            //   setDynamicComponent2(def());

            // }

  


            **// 方案4:** 虽然def没有执行,按理来说应该是函数而不是执行之后的虚拟dom,但是会自动变成虚拟dom,非常的神奇;useRef就没有这样的能力

            // {

            //   setDynamicComponent2(def);

            // }

  


            // setDynamicComponent(def());

          })();

        } }

      >点我动态加载</button>

      <div id="container"></div>

      {/* 总而言之,render这里必须放一个**虚拟dom** */}

      {/* **在这个地方使用/混用babel之前的jsx语法和babel之后的js代码**是完全ok的 */}

      {/* **方案一的渲染方式** */}

      {/* {DynamicComponent.current} */}

      {/* **方案二的渲染方式** */}

      {React.createElement(DynamicComponent.current || (()=><></>))}

      {/* **方案三的渲染方式** */}

      {/* {DynamicComponent2} */}

      {/* **方案四的渲染方式** */}

      {/* {DynamicComponent2} */}

    </div>

  );

}

  
export default App;

测试

yarn start

// 方案 0

控制台运行.效果为渲染之后两秒钟卸载

dynamicImport('container').then(_=>setTimeout(_, 2000))

// 方案1-4

点击按钮

// 观察devtool网络面板是否重复请求