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

简介: 之前写了了篇文章介绍了如何基于 Webpack 从 0 到 1 启动一个 Vue 项目,那么就很有必要介绍一下 Vue 的竞品 React 是如何基于 Webpack 从 0 到 1 启动 下面是这个

之前写了了篇文章介绍了如何基于 Webpack 从 0 到 1 启动一个 Vue 项目,那么就很有必要介绍一下 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() {
    let [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() {
  let [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

如果强行使用 JSXrun 都做不到,直接报错

ERROR in ./src/index.js 7:2
Module parse failed: Unexpected token (7:2)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
| const root = ReactDOM.createRoot(document.getElementById("root"));
| root.render(
>   <React.StrictMode>
|     <App />
|   </React.StrictMode>

替换 createElement

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,可以达到同样预期

以下几个问题可以关注一下

注意问题

@babel/preset-react

React 预设(preset)因为 Babel 的版本问题,在 Babel 7 中预设被换成了 @babel/preset-react 而不是 babel-preset-react

通常 Babel X 是指 @babel/core 的版本,比如当前的最新版本 @babel/core@7.20.5,虽然文章例子中并没有使用直接使用 @babel/corebabel-loader v8/v9 对应 Babel v7

This README is for babel-loader v8/v9 with Babel v7 If you are using legacy Babel v6, see the  7.x branch docs

可以参考这个讨论 Got error: Plugin/Preset files are not allowed to export objects, only functions

automatic 配置

你可能注意到了,在 .babelrc@babel/preset-react 有选项配置

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

首先这个 runtime 选项是用于配置需不需要自动导入 ReactJSX 转换函数,因为 @babel/preset-react"rumtime" 默认值 classic 默认不导入,只是将 JSX 转换成渲染函数版本大概如下

return /*#__PURE__*/React.createElement("div", null, "count:" + count, /*#__PURE__*/React.createElement("button", {
    onClick: handleClick
  }, "click + 1"));

而且会报错

Uncaught ReferenceError: React is not defined

解决方法

  1. 修改 rumtime 的值
  2. 或者主动导入 React,比如
import React from "react";
import { useState } from "react";

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

参考资料

  1. react-dom作用 -白日梦想家 - 博客园
  2. 安装 - 将 React 添加到网站 - React 中文文档
  3. babel-loader - npm
  4. "You may need an appropriate loader to handle this file type" with Webpack and Babel - stackoverflow
  5. Got error: Plugin/Preset files are not allowed to export objects, only functions - stackoverflow
相关文章
|
2月前
|
前端开发 JavaScript
React项目路由懒加载lazy、Suspense,使第一次打开项目页面变快
本文介绍了在React项目中实现路由懒加载的方法,使用React提供的`lazy`和`Suspense`来优化项目首次加载的速度。通过将路由组件改为懒加载的方式,可以显著减少初始包的大小,从而加快首次加载速度。文章还展示了如何使用`Suspense`组件包裹`Switch`来实现懒加载过程中的fallback效果,并提供了使用前后的加载时间对比,说明了懒加载对性能的提升作用。
171 2
React项目路由懒加载lazy、Suspense,使第一次打开项目页面变快
|
2月前
|
前端开发 JavaScript Java
SpringBoot项目部署打包好的React、Vue项目刷新报错404
本文讨论了在SpringBoot项目中部署React或Vue打包好的前端项目时,刷新页面导致404错误的问题,并提供了两种解决方案:一是在SpringBoot启动类中配置错误页面重定向到index.html,二是将前端路由改为hash模式以避免刷新问题。
229 1
|
20天前
|
前端开发 JavaScript
手敲Webpack 5:React + TypeScript项目脚手架搭建实践
手敲Webpack 5:React + TypeScript项目脚手架搭建实践
|
2月前
|
移动开发 前端开发
react项目配合diff实现文件对比差异功能
在React项目中,可以使用`diff`库实现文件内容对比差异功能。首先安装`diff`库,然后在组件中引入并使用`Diff.diffChars`或`Diff.diffLines`方法比较文本差异。通过循环遍历`diff`结果,可以生成不同样式的HTML元素来高亮显示文本差异。
113 1
react项目配合diff实现文件对比差异功能
|
2月前
|
前端开发 算法 JavaScript
React项目input输入框输入自动失去焦点
本文讨论了在React项目中如何处理input输入框自动失去焦点的问题,特别是在移动端开发中。文章提供了一个使用React Native的TouchableWithoutFeedback组件来监听点击事件,并在事件处理函数中通过调用Keyboard.dismiss()方法使输入框失去焦点的示例代码。这种方法可以确保在用户点击页面其他区域时,键盘能够收起,输入框失去焦点。
89 1
React项目input输入框输入自动失去焦点
|
2月前
|
JavaScript 前端开发 应用服务中间件
本地运行打包好的React、Vue项目
本文讨论了如何本地运行打包好的React和Vue项目,并解决了使用React-Router时Tomcat部署刷新页面导致404的问题,提出了将请求转回index.html的解决方案。
28 1
本地运行打包好的React、Vue项目
|
1月前
|
前端开发 JavaScript 应用服务中间件
linux安装nginx和前端部署vue项目(实际测试react项目也可以)
本文是一篇详细的教程,介绍了如何在Linux系统上安装和配置nginx,以及如何将打包好的前端项目(如Vue或React)上传和部署到服务器上,包括了常见的错误处理方法。
265 0
linux安装nginx和前端部署vue项目(实际测试react项目也可以)
|
2月前
|
资源调度 JavaScript 前端开发
使用vite+react+ts+Ant Design开发后台管理项目(二)
使用vite+react+ts+Ant Design开发后台管理项目(二)
|
3月前
|
JSON 前端开发 JavaScript
使用vite搭建一个React项目!真香!
【8月更文挑战第13天】使用vite搭建一个React项目!真香!
421 3
使用vite搭建一个React项目!真香!
|
2月前
|
缓存 前端开发 JavaScript
在react项目中实现按钮权限createContext && useContext
文章介绍了在React项目中如何使用`createContext`和`useContext`来实现按钮级别的权限控制。
63 0