site infoHacknerd | Tech Blog
blog cover

🚧 [React] 迷你React: 环境构建

JavaScriptReact
image

项目目录

shellCopy
.
├── README.md
├── package           // react实现
│   ├── index.tsx
│   ├── interface.ts
│   └── utils.ts
├── package-lock.json
├── package.json
├── public
│   └── index.html
├── src                // 应用目录
│   └── index.tsx
├── tsconfig.json      // typescript配置
└── webpack.config.js  // webpack配置

目标

webapck 打包src/*和pacakge/* 生成main.js文件。
webpack 打包 public/index.hmtl 引入生成的main.js。
访问index.html; main.js 在root节点生成Dom、展示dom。

依赖安装

shellCopy
> npm install react react-dom --save
> npm intsall @types/react @types/react-dom --save-dev
> npm intsall typescript --save-dev
# webpack-dev-server 用于启动开发服务
> npm intsall webpack webpack-cli webpack-dev-server --save-dev
# ts-loader 用于调用typescrit编译器,编译ts tsx
# html-webpack-plugin 用于编译html
> npm install html-webpack-plugin ts-loader --save

pacakge.json

jsonCopy
{
  "name": "mini-react",
  "scripts": {
    "build": "webpack --mode=production --node-env=production",
    "serve": "webpack serve"
  },
  "devDependencies": {
    "@types/react": "latest",
    "@types/react-dom": "latest",
    "html-webpack-plugin": "^5.6.0",
    "ts-loader": "^9.5.1",
    "typescript": "^5.4.5",
    "webpack": "^5.91.0",
    "webpack-cli": "^5.1.4",
    "webpack-dev-server": "^5.0.4"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  }
}

应用入口

  • 1.public/index.html
  • id为root的div将作为根节点。

    htmlCopy
    <!DOCTYPE html>
    <html>
      <head>
        <title>React APP</title>
      </head>
      <body>
        <div id="root"></div> 
      </body>
    </html>

  • 1.src/index.tsx
  • typescript编译器将React语法编译成React.createElement(type, props, children) 。

    typescriptCopy
    import React from "../package/index";
    
    const element = (
      <div style="background: salmon">
        <h1>Hello World</h1>
        <h2 style="text-align:right">from myReact</h2>
      </div>
    );
    
    /**
     * 上述代码将会被编译成
     * React.createElement('div', { style: 'background: salmon' }, [ React.createElement('h1', null, "Hello World"),  React.createElement('h2', { style: "text-align:right"}, "from myReact")])
     */
    
    const container = document.getElementById("root");
    React.render(element, container as HTMLElement);

    React实现

  • 1.package/index.tsx
  • 实现createElement ,render (简单实现将节点挂载到Root上)。

    typescriptCopy
    
    export function createElement(type: any, props: any, ...children: any[]): JSX.Element {
      const result = {
        key: "",
        type,
        props: {
          ...props,
          children: children.map((child) => {
            return typeof child === "object" ? child : createTextElement(child);
          }),
        },
      };
      return result;
    }
    
    export function createTextElement(text: string): JSX.Element {
      return {
        key: "",
        type: "TEXT_ELEMENT",
        props: {
          nodeValue: text,
          children: [],
        },
      };
    }
    
    export function render(element: JSX.Element, container: HTMLElement) {
    	// document.createTextNode || createElement 创建
    	const dom = element.type == "TEXT_ELEMENT" ? document.createTextNode("") : document.createElement(element.type);
    
      // 设置props
      Object.keys(element.props)
        .filter((key) => key !== "children")
        .forEach((key) => {
          dom[key] = element.props[key];
        });
    
    	// 深度优先遍历children
      if (element.props.children) {
        for (const iterator of element.props.children) {
          render(iterator, dom);
        }
      }
    
    	// 添加到dom
      container.appendChild(dom);
    }
    
    const React = {
      createElement,
      render,
    };
    
    export default React;

    Webpack

    javascriptCopy
    const path = require("path");
    const HtmlWebpackPlugin = require("html-webpack-plugin");
    
    const isProduction = process.env.NODE_ENV == "production";
    
    const config = {
      entry: "./src/index.tsx",                                      // 入口
      output: {
        path: path.resolve(__dirname, "dist"),                       // 输出
      },
      devServer: {                                                   // devServer配置
        open: true,
        host: "localhost",
      },
      plugins: [
        new HtmlWebpackPlugin({                                      // 解析HTML
          template: path.join(__dirname, "./public/index.html"),
        })
      ],
      module: {                                                      // 解析tsx
        rules: [
          {
            test: /\.(ts|tsx)$/i,
            loader: "ts-loader",
            exclude: ["/node_modules/"],
          }
        ],
      },
      resolve: {
        extensions: [".tsx", ".ts", ".jsx", ".js", "..."],
      },
    };
    
    module.exports = () => {
      if (isProduction) {
        config.mode = "production";
      } else {
        config.mode = "development";
      }
      return config;
    };

    React 实现

    typescriptCopy
    export function createElement(type: any, props: any, ...children: any[]): JSX.Element {
      const result = {
        key: "",
        type,
        props: {
          ...props,
          children: children.map((child) => {
            return typeof child === "object" ? child : createTextElement(child);
          }),
        },
      };
      debugger;
      return result;
    }
    
    export function createTextElement(text: string): JSX.Element {
      return {
        key: "",
        type: "TEXT_TEXT_ELEMENT",
        props: {
          nodeValue: text,
          children: [],
        },
      };
    }
    
    function render(element: JSX.Element, container: HTMLElement) {
      const dom: any =
        element.type === "TEXT_TEXT_ELEMENT" ? document.createTextNode("") : document.createElement(element.type);
      element.props.children.forEach((child: any) => {
        render(child, dom as any);
      });
    
      Object.keys(element.props)
        .filter((key) => key !== "children")
        .map((key) => {
          dom[key] = element.props[key];
        });
      container.appendChild(dom);
    }
    
    const React = {
      createElement,
      render,
    };
    
    export default React;

    tsconfig.json

    jsonCopy
    {
      "compilerOptions": {
        "allowSyntheticDefaultImports": true,
        "noImplicitAny": true,
        "module": "ES6",
        "target": "es5",
        "jsx": "react",           // 解析jsx
        "lib": ["DOM"],           // 提供DOM API
      },
      "include": ["src", "package"],
      "exclude": ["node_modules", "dist"]
    }
    

    webpack.config.js

    javascriptCopy
    const path = require("path");
    const HtmlWebpackPlugin = require("html-webpack-plugin");
    
    const isProduction = process.env.NODE_ENV == "production";
    
    const config = {
      entry: "./src/index.tsx",
      output: {
        path: path.resolve(__dirname, "dist"),
      },
      devServer: {
        open: true,
        host: "localhost",
      },
      plugins: [
        new HtmlWebpackPlugin({                                      // 编译html
          template: path.join(__dirname, "./public/index.html"),
        }),
      ],
      module: {
        rules: [
          {                                                          // 编译ts tsx
            test: /\.(ts|tsx)$/i,
            loader: "ts-loader",
            exclude: ["/node_modules/"],
          },
        ],
      },
      resolve: {
        extensions: [".tsx", ".ts", ".jsx", ".js", "..."],
      },
    };
    
    module.exports = () => {
      if (isProduction) {
        config.mode = "production";
      } else {
        config.mode = "development";
      }
      return config;
    };

    启动

    shellCopy
    > npm run serve

    结果如下:

    image

    Contents

    • 项目目录
    • 目标
    • 依赖安装
    • 应用入口
    • React实现
    • Webpack
    • React 实现
    • tsconfig.json
    • webpack.config.js
    • 启动

    2024/04/16 05:52