第一个React程序

确保有合适的环境

确保设备上已经安装了Node.js和npm。你可以在终端运行以下命令来检查它们是否已安装:

1
2
node -v
npm -v

如果还没有安装Node.js,请前往Node.js官网下载并安装。

创建React应用

打开终端,运行以下命令来创建一个新的React项目:

1
npx create-react-app my-react-app

这里,my-react-app是你的项目名称,你可以将其更换为你喜欢的名称。npx是一个npm 5.2+附带的包运行工具,它可以运行Registry中的包而不需要全局安装它们。

创建成功后进入项目目录my-react-app下,项目默认结构如下:

1
2
3
4
5
6
├── README.md
├── node_modules
├── package-lock.json
├── package.json
├── public
└── src

此时可以通过以下命令启动本地开发服务器:

1
2
3
npm run start

yarn start // 官方文档推荐使用yarn对react项目进行包管理及项目脚本的执行

这将自动打开你的默认浏览器并加载 http://localhost:3000。当你修改项目文件时,应用将会热重载,你可以实时看到更改。

构建生产环境应用

当你准备好将应用发布到生产环境时,你可以使用以下命令构建你的应用:

1
yarn build

这将创建一个 build 目录,里面包含了用于生产环境的文件,这些文件已经过优化,可以部署到你的服务器上。

自定义配置Webpack

默认package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
{
"name": "my-react-app",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@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", // react封装了一套默认的构建配置,包括 Webpack、Babel、ESLint 等
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start", // 启动开发服务器
"build": "react-scripts build", // 构建生产应用
"test": "react-scripts test", // 单元测试
"eject": "react-scripts eject" // 暴露项目所有的配置和构建依赖
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

默认的package.json看起来十分简洁,因为react帮我们将项目一些必须配置封装进react-scripts中,实际项目会有各种定制化需求、性能优化等,此时可以通过 yarn eject 暴露项目所有的配置和构建依赖,执行 yarn eject 后,所有的配置文件和构建依赖都会移到你的项目中。但这是一个单向操作,一旦 “eject”,就不能回去了。所以在此之前,需要使用git等工具保存一下代码版本

暴露配置后目录结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
├── README.md                      # 项目说明文件,一般包含项目信息、构建过程和使用说明
├── config # 存放与项目构建相关的配置文件
│ ├── env.js # 环境变量相关的配置
│ ├── getHttpsConfig.js # HTTPS本地开发服务器相关配置
│ ├── jest # Jest测试框架的配置文件
│ │ ├── babelTransform.js # Jest中Babel转换配置
│ │ ├── cssTransform.js # Jest中CSS转换配置
│ │ └── fileTransform.js # Jest中文件转换配置(如图片等)
│ ├── modules.js # 用于配置Node模块
│ ├── paths.js # 项目路径配置,例如源代码、构建输出目录等
│ ├── webpack # 包含与Webpack相关的配置和工具
│ │ └── persistentCache
│ │ └── createEnvironmentHash.js # 创建环境敏感的缓存散列
│ ├── webpack.config.js # Webpack的主要配置文件
│ └── webpackDevServer.config.js # Webpack开发服务器配置
├── package-lock.json # 指定了项目依赖的确切版本,保证项目依赖一致性
├── package.json # 项目的依赖、脚本和配置信息
├── public # 公共文件,不会被Webpack处理
│ ├── favicon.ico # 网站图标
│ └── index.html # 主HTML文件,React挂载点
├── scripts # 包含构建和开发环境运行的Node脚本
│ ├── build.js # 用于生产环境构建应用的脚本
│ ├── start.js # 用于启动开发服务器的脚本
│ └── test.js # 用于运行测试的脚本
└── src # 源代码目录
└── index.js # 应用的入口文件

配置目录别名和开发环境Proxy代理

目录别名

打开config/webpack.config.js,找到resolve下的alias属性:

1
2
3
4
5
resolve: {
alias: {
'@': path.resolve(__dirname, paths.appSrc),// 指定@为src目录
...configs
}
配置开发环境Proxy代理

查看config/webpackDevServer.config.js

1
2
3
4
5
6
7
8
9
10
11
onBeforeSetupMiddleware(devServer) {
// Keep `evalSourceMapMiddleware`
// middlewares before `redirectServedPath` otherwise will not have any effect
// This lets us fetch source contents from webpack for the error overlay
devServer.app.use(evalSourceMapMiddleware(devServer));
// 有代理的情况下,导入代理配置
if (fs.existsSync(paths.proxySetup)) {
// This registers user provided middleware for proxy reasons
require(paths.proxySetup)(devServer.app);
}
},

查看config/paths.js

1
2
3
4
module.exports = {
proxySetup: resolveApp('src/setupProxy.js'),
...configs
};

可以看到代理配置默认指定在src目录下,建议跟着项目需要的路径创建配置文件

配置src/setupProxy.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/*
* @Description:
* @Author: xiuji
* @Date: 2023-11-23 11:02:31
* @LastEditTime: 2023-11-23 13:43:36
* @LastEditors: Do not edit
*/
const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function (app) {
app.use(
'/api',
createProxyMiddleware({
target: 'http://localhost:3001',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
})
);
// 添加多个代理
// app.use(
// '/api2',
// createProxyMiddleware({
// target: 'http://localhost:3002',
// changeOrigin: true,
// pathRewrite: {
// '^/api2': ''
// }
// })
}

这里是在Node.js环境下使用中间件http-proxy-middleware,用于创建代理,以便在开发环境中将特定的 API 请求转发到不同的后端服务。

react eject后ESLint报错Using babel-preset-react-app requires NODE_ENV or BABEL_ENV

这个错误可以通过更改package.json中的eslintConfig部分,屏蔽babel-preset-react-app,再开启babel-preset-react-app/prod可以解决这个问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
],
"parserOptions": {
"babelOptions": {
"presets": [
[
"babel-preset-react-app",
false
],
"babel-preset-react-app/prod"
]
}
}
}

最后

比较一下React和Vue

React 和 Vue 是目前前端领域非常流行的两个JavaScript库(React)和框架(Vue),它们各自采用了不同的架构模式:React 更接近于 MVC(Model-View-Controller)的变种,而 Vue 被认为遵循了 MVVM(Model-View-ViewModel)模式。下面分别描述它们的特点:

React(接近于 MVC)

  1. Model(模型):在 React 中,状态(state)和属性(props)通常代表了模型。React 组件的状态可以被看作是模型,是数据的来源。

  2. View(视图):React 的组件结构代表了视图层。它使用 JSX(一种JavaScript的扩展语法)来描述 UI,允许开发者以一种将标记与JavaScript逻辑相结合的方式来定义视图。

  3. Controller(控制器):React 本身不明确区分控制器,但组件的生命周期方法和事件处理函数充当了控制器的角色,负责处理用户输入并更新模型(状态)。

  4. 单向数据流:React 强调单向数据流,即数据有一个明确定义的流向。状态(或props)流入视图,视图通过事件流向控制器逻辑,控制器逻辑更新状态,并触发视图重新渲染。

  5. 组件化:React的核心是可复用的组件,每个组件负责渲染一部分用户界面,并管理该部分的状态。

Vue(遵循 MVVM)

  1. Model(模型):在 Vue 中,数据对象(data)代表模型,是应用状态的声明式定义。

  2. View(视图):模板(template)定义了如何将状态渲染到UI上,Vue的模板语法提供了声明式的将DOM绑定到底层数据的方法。

  3. ViewModel(视图模型):Vue 实例扮演 ViewModel 的角色,它是一个 Vue 特有概念。ViewModel 是 View 和 Model 之间的桥梁,负责监听数据的变化并自动更新DOM,实现数据的双向绑定。

  4. 双向数据绑定:Vue 通过v-model等指令提供了数据的双向绑定,使得数据的更新可以自动映射到界面,同时界面的变化也可以即时反馈到数据。

  5. 响应式系统:Vue 的响应式系统可以自动追踪依赖关系,并在数据改变时执行更新,极大地简化了更新逻辑。

综上所述,React 和 Vue 的主要区别在于数据绑定和组件更新机制上。React 鼓励单向数据流和显示地通过状态变更来更新组件,而 Vue 的双向数据绑定和自动的依赖跟踪则让状态管理更加简单。此外,React 倾向于JavaScript中的函数式编程,Vue则更偏向于声明式的模板和配置。

React和Vue中的单向数据流概念

React 和 Vue 都倡导使用单向数据流,但它们在实施这一概念的细节上有所不同。单向数据流是指在应用中数据有一个明确的流动方向,这样可以更容易地理解和追踪状态的变化,以及更容易地维护和调试代码。

React 中的单向数据流

在 React 中,单向数据流体现在父组件到子组件的数据传递和状态管理上。父组件通过 props 向子组件传递数据,子组件无法直接修改接收到的 props。当子组件需要更新状态时,它会调用从父组件传递下来的回调函数,然后父组件更新自身的状态,这个更新会以 props 的形式再次传递到子组件,从而引起子组件的重新渲染。

这样,数据在组件之间的流动是单向的,状态的更新遵循一个清晰的路径,即:

1
State -> Render -> Child Components -> Callbacks -> State

Vue 中的单向数据流

Vue 也采用了单向数据流的概念,尽管它提供了双向数据绑定的便利(通过v-model指令)。在 Vue 中,父组件通过 props 向子组件传递数据,和 React 类似,子组件不应直接修改这些 props。如果子组件需要改变通过 props 接收到的值,它应该基于这个 prop 的值创建一个局部变量或者发出一个事件通知父组件进行修改。

在 Vue 中,如果子组件需要通知父组件改变状态,它会发射(emit)一个事件,然后父组件在捕获这个事件后更新数据。这样的流程也保证了数据的单向流动:

1
Props -> Child Components -> Emit Event -> Parent Data

总结

虽然 React 和 Vue 在实现上有所不同,React 更强调显式的状态管理和更新机制(函数式组件、Hooks、状态提升等),而 Vue 提供了更多的抽象和便利性(响应式系统、双向绑定),但两者共同遵循的单向数据流原则为开发者提供了一种清晰且可预测的方式来管理应用中的状态。在任何复杂的应用中,遵循单向数据流都能帮助开发者更好地追踪状态的变化,减少不可预期的错误。