基于 Webpack 从 0 到 1 启动一个 React 项目

之前写了了篇文章介绍了如何[基于 Webpack 从 0 到 1 启动一个 Vue 项目](https://juejin.cn/post/7170725391353511944),那么就很有必要介绍一下 `Vue` 的竞品 `React` 是如何基于 `Webpack` 从 0 到 1 启动

下面是这个项目运行效果

IMG

普通启动

如果你是刚开始接触 HTML/CSS/JavaScript 三件套开始接触的前端,那么你可能比较熟悉或者比较能接受的引入 React 的方式可能是使用 CDN 的方式,大概如下(下面这个是我要介绍的例子)

<head>
  <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
</head>
<body>
  <div id="root"></div>
</body>
<script>
  const useState = React.useState;
  const createElement = React.createElement;

  function App() {
    const [count, setCount] = useState(0);

    const handleClick = () => {
      setCount(++count);
    };

    return createElement("div", {
      children: [
        "count:" + count,
        createElement(
          "button",
          {
            key: "2",
            onClick: handleClick,
          },
          "click + 1"
        ),
      ],
    });
  }

  const root = ReactDOM.createRoot(document.getElementById("root"));
  root.render(
    createElement(React.StrictMode, {
      children: [createElement(App, { key: "1" })],
    })
  );
</script>

但这种方式是基于 React.createElement 去编写 DOM 的代码的,React 中比较常用的是 JSX 语法,它们的文档认为 JSX

它有助于编写UI代码 – 无论是使用 React 还是其他库。

React 的文档推荐使用 JSX 的方式是使用 babel 转换,其实使用 babel 这已经涉及到前端工程化的初步阶段了,因为使用 babel 需要使用 node.js,毕竟你知道了 node.js 后就会知道 npm 就会知道 webpack 就会知道 create-react-app

而对于 Vue 来说,你可以用下面的模板写好一段时间

// template
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<body>
<!-- DOM -->
</body>
<script>
// js
</script>
<style>
/** css **/
</style>

Webpack 启动

真正要上手 React 还是需要使用 WebpackWebpack 主要使用来对项目中模块进行打包,并且它的生态链中还具有热更新和一系列转换的功能,大概是下面这样

IMG

接下来就是 React 项目启动的初步配置

先找个位置并在终端(或者命令行)初始化一个项目

npm init

初始化后会有一些选项,可以直接回车全部忽略,也可以根据自己意向填写

IMG

选择完成之后

这个时候系统会创建一个 package.json 文件

IMG

接下来就是开始配置 Webpack 系列套件

Webpack 系列套件

npm install -D webpack webpack-dev-server webpack-cli

-D, --save-dev 代表打包时该部分依赖不会被打包进去

创建 webapck.config.js,它是 Webpack 的配置文件,文件内容如下

// webpack.config.js
const path = require("path");

module.exports = {
  mode: "development",
  devServer: {
    static: {
    // 配置提供静态资源服务的目录
      directory: path.join(__dirname, "public"),
    },
    // 静态资源服务端口
    // React 预览将在此端口
    port: 8080,
  },
};

配置脚本命令,修改 package.json"script" 字段如下

{
  // ...
  "scripts": {
    "start": "webpack server"
  },
  // ...
}

同时创建 public 文件夹,后期将在这个文件上创建静态资源文件,例如 .html

此时的文件目录结构

├── node_modules
├── public
├── package-lock.json
├── package.json
└── webpack.config.js

React 系列套件

接下来就是 React 系列套件的配置,根据上面的 html 例子分别下载 reactreact-dom

npm install react react-dom

react 大家都清楚,那么 react-dom 这个包是干嘛的呢?简单来说,react-domreact 剥离出的涉及 DOM 操作的部分

react 的核心思想是虚拟 DOMreact 包含了生成虚拟 DOM 的函数 react.createElement,及 Component 类。当我们自己封装组件时,就需要继承 Component 类,才能使用生命周期函数等。而 react-dom 包的核心功能就是把这些虚拟 DOM 渲染到文档中变成实际 DOM

不关注 Vue 的同学可以跳过下面这个段落

与 Vue 编译时的区别

比较熟悉 Vue 的同学可能会有疑问,react-domreact 的编译时吗?因为在 Vue2 中有运行时(runtime)和编译时,在 Vue3 中也是单独剥离出一个包给编译时(@vue/complier-sfc

其实严格来说 react-dom 是负责处理将各自框架的虚拟 DOM 渲染到浏览器中,算是一个运行时的东西,但是 Vue 把它集成在了运行时中而不是和 React 一样独立出来

Vue 的编译时严格意义上来说是将开发自定义的模板编译成虚拟 DOM,比如

<template>
  <div>
    {{ "count:" + count }}
<button @click="handleClick">click + 1</button>
  </div>
</template>
// 模拟虚拟 DOM
// 可以将 h 函数中的参数结合为一个 option 对象
// option 对象就是一个简易的 vnode
// vnode 通常会有更多内置的属性
h("div", [
  "count:" + this.count,
  h(
"button",
{
  on: {
click: this.handleClick,
  },
},
"click + 1"
  ),
]);

react 本身是通过 babel-loader 实现 JSXReact.createElement 的互转的,在下面的 JSX 配置 中有说明

所以结论就是 react-domVue 运行时中将虚拟 DOM 渲染到浏览器真实 DOM 的功能部分,是运行时而不是编译时

解构

下载完 React 系列套件的依赖后,就需要将上面的 html 例子进行解构,创建 src/index.jsApp.jspublic/index.html

将上面的 html 的例子转换成下面的结构

// App.js
import { useState, createElement } from "react";

function App() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount(++count);
  };

  return createElement("div", {
    children: [
      "count:" + count,
      createElement(
        "button",
        {
          key: "uniqueId",
          onClick: handleClick,
        },
        "click + 1"
      ),
    ],
  });
}

export default App;
// index.js
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  React.createElement(React.StrictMode, {
    children: [React.createElement(App, { key: "1" })],
  })
);
<!-- index.html -->
<body>
  <div id="root"></div>
</body>
<script src="./bundle.js"></script>

同时修改 webpack.config.js 添加输入输出

module.exports = {
  // ...
  entry: path.join(__dirname, "./src/index.js"),
  output: {
    publicPath: "",
    filename: "bundle.js",
  },
};

关于 entryoutput 两个字段配置不熟悉的可以参考这篇文章 基于 Webpack 从 0 到 1 启动一个 Vue 项目

此时在终端/命令行运行脚本 npm run start 即可得到文章开头提到的效果

IMG

此时的文件目录结构

├── node_modules
├── public
|  └── index.html
├── src
|  ├── App.js 
|  └── index.js
├── package-lock.json
├── package.json
└── webpack.config.js

JSX 配置

前面说过 React.createElement 可以用 JSX 代替,因为

从本质上讲,JSX 只是为 React.createElement(component, props, ...children) 函数提供的语法糖

JSX 代替 React.createElement 的步骤如下

下载 babel-loader@babel/preset-react

npm instal -D babel-loader @babel/preset-react

在根目录下创建 .babelrc

// .babelrc
{
  "presets": [
    [
      "@babel/preset-react",
      {
        "runtime": "automatic"
      }
    ]
  ]
}

修改 webpack.config.js

const path = require("path");

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /[\.js|\.jsx]$/,
        loader: "babel-loader",
        exclude: /node_modules/,
      },
    ],
  },
};

修改 App.jsindex.js 中关于 React.createElement 的部分

// index.js
// ...
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
// App.js
import { useState } from "react";

function App() {
  // ...

  return (
    <div>
      {"count:" + count}
      <button onClick={handleClick}>click + 1</button>
    </div>
  );
}

export default App;

再次运行脚本 npm run start,可以达到同样预期

写在最后,如果按照文章的操作没有达到预期效果,多半是库的版本不对!

参考资料

  1. react-dom作用 -白日梦想家 – 博客园
  2. 安装 – 将 React 添加到网站 – React 中文文档
© 版权声明
THE END
喜欢就支持一下吧
点赞7 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容