1. Create-react-app基础操作
当下以及未来的前端开发的主流一定是组件化和模块化。
- 有助于团队协作开发
- 便于组件的复用:提高开发效率、方便后期维护、减少页面中的冗余代码
1.1 如何划分组件
-
业务组件:针对项目需求封装
-
普通业务组件:复用性低,只是单独拆选出来的一个模块
-
通用业务组件:具备复用性
-
-
功能组件:适用于多个项目「例如:UI 组件库中的组件」
- 通用功能组件
组件化开发必然会带来工程化,即基于 Webpack / Vite / Rollup / Turbopack 等工具实现组件的合并、压缩、打包等。
1.2 安装 create-react-app
我们可以基于 Webpack 自己去搭建一套工程化打包的脚手架,但这样非常的麻烦和繁琐;因此,React 官方为我们提供了一个脚手架,即 create-react-app,便于创建 React 项目。基于该脚手架创建项目,默认就把 Webpack 的打包规则已经处理好了,把一些项目需要的基本文件也都创建好了。
全局安装 create-react-app 脚手架:
npm i create-react-app -g
检查 create-react-app 的版本(是否安装完成·):
create-react-app --version
1.3 创建工程化项目
创建的命令为:
create-react-app 项目名称
项目名称应该仅使用数字、小写字母和下划线
_的组合。
一个 React 项目中,默认会安装:
- react:React框架的核心
- react-dom:React 视图渲染的核心「基于 React 构建 WebApp(HTML 页面)」
- react-scripts:脚手架为了让项目目录看起来干净一点,把 Webpack 打包的规则及相关的插件、预处理器等都隐藏到了 node_modules 目录下,react-scripts 就是脚手架中自己对打包命令的一种封装,基于它打包,会调用 node_modules 中的 Webpack 等进行处理
在项目根目录的 package.json 内容如下:
{
"name": "demo",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4" // 性能检测工具
},
// 打包命令是基于 react-scripts 处理的
"scripts": {
"start": "react-scripts start", // 开发环境:在本地启动 Web 服务器,预览打包内容
"build": "react-scripts build", // 生产环境:打包部署,打包的内容输出到 dist 目录
"test": "react-scripts test", // 单元测试
"eject": "react-scripts eject" // 暴露 Webpack 配置,可以修改默认配置
},
// 对 Webpack 中 ESLint 词法检测的相关配置
// 词法检测
// - 词法错误(不符合标准规范)
// - 不符合标准(代码本身不报错,但不符合 ESLint 的检测规范)
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
// 基于 browserlist 规范设置浏览器的兼容情况
// - postcss-loader + autoprefixer 会给 CSS3 设置相关的前缀
// babel-loader 会把 ES6 编译为 ES5
"browserslist": {
"production": [
">0.2%", // 使用率超过 0.2% 的浏览器
"not dead", // 不考虑 IE
"not op_mini all" // 不考虑欧朋浏览器
],
"development": [ // 不兼容低版本和 IE 浏览器
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
值得一提的是,JSON 文件对格式的要求十分严格,是不允许注释的,上面的注释仅帮助理解,在文件中不可使用。
根目录之下,除了 node_modules 子目录,还有两个非常重要的子目录分别为:
- src:所有后续编写的代码,几乎都放在该目录下「打包的时候,一般只对这个目录下的代码进行处理」
- public:存放页面模版
将 src 目录下的大部分文件删除,仅留下 index.jsx(如果后缀是 .js,改为 .jsx)文件,其内容改为:
import React from 'react';
import ReactDOM from 'react-dom/client';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode> {/* React 的严格语法模式,它和 JS 中的 "use strict" 并不相同 */}
<div>珠峰培训</div>
</React.StrictMode>
);
将 public 目录下的大部分文件删除,仅留下 favicon.ico(项目网站的 logo 图标)和 index.html 文件,并将 index.html 的内容改为:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<!-- 后期 Webpack 打包时,会对这个语法进行编译,其表示 public 这个目录 -->
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>珠峰 React 系统课</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
2. 脚手架的进阶应用
2.1 暴露 Webpack 配置
前面说到,react-scripts 把 Webpack 打包的规则及相关的插件、预处理器等都隐藏到了 node_modules 目录下了。那么,如果我们想要修改 Webpack 的一些默认配置时,该怎么办呢?
这时就需要使用 eject 命令了,即:
npm run eject # 或者 yarn eject
注意:一旦暴露 Webpack 配置,该操作是永久的,就不能还原了。
执行该命令后,会给出提示 Are you sure you want to eject? This action is permanent.,输入 y 后很可能会报错,这是因为我们刚才删除了项目中的一些文件并修改了一些文件的代码,在暴露之前,需要先让我们把代码提交到 git 历史区保留下来,主要是防止暴露后的代码覆盖了我们原来的代码。
这个报错会建立在两个前提下:有 git 仓库、暴露前修改过代码
因此,执行以下命令:
git add -A
git commit -m'init'
然后再次执行:
npm run eject # 或者 yarn eject
这时,会发现根目录下会多了 config 和 scripts 两个文件夹,并且 package.json 中内容会变得非常多(把 Webpack 打包需要的所有模块都放在了依赖项中)。
其中,/config/webpack 下有几个文件值得注意:
- webpack.config.js:脚手架中默认 Webpack 打包的配置
- webpackDevServer.config.js:默认 web pack-dev-server 的配置
- paths.js:打包中用到的路径
scripts 目录中的 build.js 是后期执行相关打包命令的入口文件。
在 package.json 增加的依赖中,有几个模块值得注意:
- babel-preset-react-app:它是对 @babel/preset-env 语法包的重写,目的是让语法包也可以识别 React 的 jsx 语法,实现代码转换
create-react- app 脚手架默认配置是使用的 sass 预编译语言,如果项目中使用的就是 sass,则无需处理;如果使用的是 less 或 stylus,则需要自己处理。
package.json 中的 scripts 也发生了变化,为:
"scripts": {
"start": "node scripts/start.js",
"build": "node scripts/build.js",
"test": "node scripts/test.js"
},
不再使用 react-scripts 封装的插件执行命令了,而是直接基于 node 去执行对应的入口文件。
而且,现在也没有 eject 命令了,因为前面说到了,暴露的过程是不可逆的,无法再隐藏回去了。
package.json 中还增加了 babel 配置项:
// 对 babel-loader 进行额外配置,等价于 babel.config.js
"babel": {
"presets": [
"react-app"
]
}
2.2 常见配置修改
2.2.1 使用 less
前面提到,脚手架默认配置是使用的 sass 预编译语言,如果要使用 less,需要自己进行配置:
npm install less less-loader@8 # 新版本的 less-loader 兼容性不好
npm uninstall sass-loader
然后修改暴露出来的 webpack.config.js 中的配置:
// 修改前
...
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;
...
{
test: sassRegex,
exclude: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
modules: {
mode: 'icss',
},
},
'sass-loader'
),
sideEffects: true,
},
{
test: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
modules: {
mode: 'local',
getLocalIdent: getCSSModuleLocalIdent,
},
},
'sass-loader'
),
},
...
// 修改后
...
const lessRegex = /\.less$/;
const lessModuleRegex = /\.module\.less$/;
...
{
test: lessRegex,
exclude: lessModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
modules: {
mode: 'icss',
},
},
'less-loader'
),
sideEffects: true,
},
{
test: lessModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
modules: {
mode: 'local',
getLocalIdent: getCSSModuleLocalIdent,
},
},
'less-loader'
),
},
...
测试 less 是否生效:在 src 目录下新建文件 index.less,代码如下:
@B: lightblue;
html,
body {
height: 100%;
background-color: @B;
}
然后在 src/index.jsx 文件中添加:
import '@/index.less'
其中,
@代表 src 目录,它是经过路径配置的结果,需要手动进行配置,配置过程如下:
在暴露 Webpack 配置之后,项目中多了 config 文件夹,其内部有一个 paths.js 文件,其中导出的代码中,有一行为:
appSrc: resolveApp('src'),表示
appSrc对应的就是 src 目录。然后修改 webpack.config.js 中的配置(大约在 312 行):
// 修改前 extensions: paths.moduleFileExtensions .map(ext => `.${ext}`) .filter(ext => useTypeScript || !ext.includes('ts')), alias: { 'react-native': 'react-native-web', ... // 修改后 extensions: paths.moduleFileExtensions .map(ext => `.${ext}`) .filter(ext => useTypeScript || !ext.includes('ts')), alias: { '@': paths.appSrc, 'react-native': 'react-native-web', ...以后就都可以使用
@代表 src 目录了。
然后通过 npm start 运行项目,就可以发现页面背景为蓝色了。
2.2.2 修改域名或端口号
默认情况下,启动项目使用的是 localhost:3000,可以在 scripts/start.js 文件中修改:
const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000; // 可修改端口号
const HOST = process.env.HOST || '0.0.0.0'; // 可修改 IP(或域名)
如果想基于修改环境变量的方式来改,需要先安装 cross-env,如下:
npm i cross-env # 或 yarn add cross-env
然后,修改 package.json:
// 修改前
"scripts": {
"start": "node scripts/start.js",
...
},
// 修改后
"scripts": {
"start": "cross-env PORT=8080 node scripts/start.js",
...
},
2.2.3 修改浏览器兼容
如果需要修改浏览器的兼容性,则需要修改 package.json 中 "browserslist" 项的内容。
但是,修改兼容列表实现浏览器兼容时,只能解决两个问题,即:
- 对 postcss-loader 生效,控制 CSS3 的前缀
- 对 babel-loader 生效,控制 ES6 的转换
但还存在一个问题,就是无法处理 ES6 内置 API 的兼容。
为了解决这个问题,你可以安装 @babel/polyfill(其作用是对常见的内置 API 进行重写),然后在入口文件(index.jsx)中引入 import '@babel/polyfill'。
但是,在脚手架中,通常不需要安装它,因为脚手架默认已经安装了 react-app-polyfill,它是对 @babel/polyfill 的重写,仅需要在入口文件(index.jsx)中引入:
// 对 ES6 内置 API 的兼容性处理
import 'react-app-polyfill/ie9'
import 'react-app-polyfill/ie11'
import 'react-app-polyfill/stable'
2.2.4 处理 Proxy 跨域
安装 http-proxy-middleware(实现跨域代理的模块,webpack-dev-server 的跨域代理也是基于它实现的):
npm i http-proxy-middleware # 或 yarn add http-proxy-middleware
在 src 目录中,新建 setupProxy.js,内容为:
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
app.use(
createProxyMiddleware("/jian", {
target: 'https://www.jianshu.com/',
chanageOrigin: true,
ws: true,
pathRewrite: {
"^/jian": ""
}
})
),
app.use(
createProxyMiddleware("/bai", {
target: 'https://www.baidu.com/',
chanageOrigin: true,
ws: true,
pathRewrite: {
"^/bai": ""
}
})
)
}
还有一种方式是在 package.json 中增加配置项
"proxy",但是其后面只能跟一个字符串,不太灵话,因此一般不使用,仅作了解。