React+Flask前后端分离开发


一、动机

通过 React(前端框架)和 Flask(后端框架)构建一个初始网站,可以结合它们的优势,前端负责交互界面,后端提供 API 接口。

二、准备环境

2.1 安装必要工具

  • 确保安装了 Node.js 和 npm,用于管理 React 项目。
  • 确保安装了 Python(推荐 3.8 及以上版本)和 pip,用于管理 Flask 项目。

npm是什么?

npm 是 Node Package Manager(节点包管理器)的缩写,它是 JavaScript 运行时环境 Node.js 的默认包管理器。

npm 用于管理和安装 Node.js 应用程序所需的各种代码库和工具,这些代码库和工具被称为“包”或“模块”。

以下是 npm 的一些主要功能:

  1. 依赖管理:npm 允许你为你的项目定义依赖关系,并通过 package.json 文件来管理这些依赖。这使得项目可以轻松地在不同的开发环境中共享和部署。
  2. 包安装:你可以使用 npm 来安装项目所需的包。这些包可以是第三方库,也可以是你自己的代码模块。
  3. 版本控制:npm 支持语义化版本控制,允许你指定依赖的版本范围,以确保兼容性。
  4. 包发布:开发者可以将自己的包发布到 npm 的公共仓库,供其他开发者使用。
  5. 全局安装:npm 允许你全局安装包,这样你就可以在任何地方使用这些工具,而不需要在每个项目中单独安装。
  6. 脚本package.json 文件中的 scripts 字段允许你定义自定义脚本,这些脚本可以通过 npm run <script-name> 来执行。
  7. 工作区:npm 支持工作区,允许你在多个包之间共享配置和依赖。

三、创建 Flask 后端

3.1 创建 Flask 项目

初始化 Flask 项目目录:

mkdir flask-backend
cd flask-backend

3.2 创建虚拟环境并激活

conda 或 venv

3.2.1 anaconda

3.2.2 venv

python -m venv venv
source venv/bin/activate   # Windows: venv\Scripts\activate

3.2.3 安装 Flask

pip install flask

3.2.4 创建 app.py 文件

from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/api/message', methods=['GET'])
def get_message():
    return jsonify({"message": "Hello from Flask!"})

if __name__ == '__main__':
    app.run(debug=True)

3.2.5 启动 Flask 服务器

python app.py

访问 http://127.0.0.1:5000/api/message

四、创建 React 前端

4.1 初始化 React 项目

在另一个目录中创建 React 项目

npx create-react-app react-frontend
cd react-frontend
  • 命令解析:

**npx**是 npm 的一部分,用于运行 npm 包而无需全局安装它们。

**create-react-app**是 React 官方提供的脚手架工具,用于快速搭建 React 项目的初始结构。

**react-frontend**是项目的目录名称,你可以根据需要自定义,例如 my-react-app

报错:

截屏2024-12-09 17.44.29

try:

npm config set legacy-peer-deps true
npm i

参考:https://stackoverflow.com/questions/72596908/could-not-resolve-dependency-error-peer-react16-8-0-17-0-0-from-materia

npm config set legacy-peer-deps true

这条命令用于配置 npm 使用 “legacy peer dependencies” 机制来解决依赖冲突。具体来说:

  1. peer dependencies
    是指某个 npm 包对其他包的版本要求,它并不直接安装这些依赖,而是由使用该包的项目来安装。它用于确保在多个包中共享同一版本的依赖。
  2. “legacy peer dependencies”
    在 npm 7 版本及之后,npm 会强制执行严格的 peer dependencies 规则。若存在不兼容的版本,npm 会拒绝安装。这可能导致一些旧项目的依赖安装失败,特别是一些老旧的 npm 包没有更新以适应这些新规则。
  3. npm config set legacy-peer-deps true
    这条命令设置 npm 配置,指示 npm 在安装时忽略 peer dependencies 的严格检查,允许安装不完全匹配的版本。这对于那些依赖较旧的 npm 包(或者在升级后依赖有冲突的情况)特别有用。

npm i (或 npm install)**

这是用于安装项目依赖的命令,它会按照项目根目录下的 package.json 文件来安装所有列出的依赖。

  • 在执行 npm i 时,npm 会下载并安装依赖到项目的 node_modules 目录。
  • 如果你在之前执行过 npm config set legacy-peer-deps true,npm 会在安装过程中忽略 peer dependencies 的严格检查,允许版本冲突的依赖继续安装。

启动服务并访问测试:

截屏2024-12-10 09.18.39

bug处理:

截屏2024-12-10 09.21.15 截屏2024-12-09 17.53.02

安装缺失的库。

npm install --save-dev web-vitals

重新运行npm start启动前端项目。

4.2 修改 React 项目以调用 Flask API

4.2.1 安装 axios(用于发起 HTTP 请求)

npm install axios

4.2.2 在 src/App.js 中编辑代码

import React, { useState, useEffect } from 'react';
import axios from 'axios';

function App() {
    const [message, setMessage] = useState('');

    useEffect(() => {
        // 调用 Flask API
        axios.get('http://127.0.0.1:5000/api/message')
            .then(response => setMessage(response.data.message))
            .catch(error => console.error("Error fetching data:", error));
    }, []);

    return (
        <div style={{ textAlign: 'center', marginTop: '50px' }}>
            <h1>React + Flask 初始页面</h1>
            <p>{message || "Loading..."}</p>
        </div>
    );
}

export default App;
  • 代码解析:

这段代码是一个简单的 React 组件,它使用 axios 库从一个 Flask API 获取数据。下面是代码的逐行解释:

  1. import React, { useState, useEffect } from ‘react’;
    这行代码从 react 库中导入了 React 对象、useState 钩子和 useEffect 钩子。useState 用于在函数组件中添加状态,useEffect 用于处理副作用(例如,数据获取、订阅或手动更改 React 组件中的 DOM)。

  2. import axios from ‘axios’;
    这行代码导入了 axios,这是一个基于 Promise 的 HTTP 客户端,用于浏览器和 node.js,用于向后端发送请求。

  3. function App() { … }
    定义了一个名为 App 的函数组件。

  4. const [message, setMessage] = useState(‘’);
    使用 useState 钩子创建了一个名为 message 的状态变量和一个名为 setMessage 的函数,用于更新这个状态。初始值设置为空字符串。

  5. useEffect(() => { … }, []);
    useEffect 钩子用于在组件加载后执行副作用。这里的效果是调用 Flask API 获取消息。空数组 [] 作为第二个参数表示这个效果仅在组件首次渲染时运行。

  6. axios.get(‘http://127.0.0.1:5000/api/message‘)
    使用 axios 发送一个 GET 请求到 http://127.0.0.1:5000/api/message 这个 URL。这个 URL 应该是你的 Flask 应用提供的 API 端点。

  7. .then(response => setMessage(response.data.message))
    如果请求成功,.then 方法会处理响应。这里将响应中的 message 数据赋值给状态变量 message。

  8. .catch(error => console.error(“Error fetching data:”, error));
    如果请求失败,.catch 方法会捕获错误,并在控制台打印错误信息。

  9. return ( … );
    这是 React 组件的返回语句,它定义了组件的 JSX 结构,即组件在页面上呈现的内容。

  10. <div style={{ textAlign: 'center', marginTop: '50px' }}> ... </div>
    
    
        + 返回一个 div 元素,其中包含一些内联样式:文本居中和上边距为 50px。
    
    11. ```html
        <h1>React + Flask 初始页面</h1>
    + 在 div 内部,有一个标题元素 h1,显示文本 “React + Flask 初始页面”。
  11. <p>{message || "Loading..."}</p>
    
    
        + 一个段落元素 p 显示 message 状态的值。如果 message 是空的(即正在加载中),则显示 “Loading…”。
          整体来看,这段代码创建了一个 React 组件,该组件在加载时从 Flask API 获取一条消息,并将其显示在页面上。如果消息尚未加载,页面将显示 “Loading…”。
    
    <img src="React+Flask%E5%90%8E%E7%AB%AF%E5%88%86%E7%A6%BB%E5%BC%80%E5%8F%91/%E6%88%AA%E5%B1%8F2024-12-10%2009.49.10.png" alt="截屏2024-12-10 09.49.10" style="zoom:50%;" />
    
    ### 4.2.3 访问测试
    
    ![截屏2024-12-10 09.53.06](React+Flask%E5%90%8E%E7%AB%AF%E5%88%86%E7%A6%BB%E5%BC%80%E5%8F%91/%E6%88%AA%E5%B1%8F2024-12-10%2009.53.06.png)
    
    前端执行成功,但是后端的message数据请求失败。
    
    原因:同源策略限制(React端口3000,Flask端口5000)。
    
    # 五、跨域请求设置
    
    ## 5.1 设置 Flask 支持跨域请求
    
    安装 Flask-CORS:
    
    ```bash
    pip install flask-cors

修改 app.py

from flask_cors import CORS

app = Flask(__name__)
CORS(app)  # 允许跨域请求

5.2 测试验证

前后端联动测试成功

六、部署优化

开发环境:前后端分开运行。

生产环境:可以使用 Flask 提供 React 构建后的静态文件。

6.1 构建 React 项目

npm run build

输出:

npm run build

react-frontend@0.1.0 build
react-scripts build

Creating an optimized production build…
One of your dependencies, babel-preset-react-app, is importing the
“@babel/plugin-proposal-private-property-in-object” package without
declaring it in its dependencies. This is currently working because
“@babel/plugin-proposal-private-property-in-object” is already in your
node_modules folder for unrelated reasons, but it may break at any time.

babel-preset-react-app is part of the create-react-app project, which
is not maintianed anymore. It is thus unlikely that this bug will
ever be fixed. Add “@babel/plugin-proposal-private-property-in-object” to
your devDependencies to work around this error. This will make this message
go away.

Compiled successfully.

File sizes after gzip:

73.28 kB build/static/js/main.7d3412ba.js
2.7 kB build/static/js/488.ee97bdc1.chunk.js
264 B build/static/css/main.e6c13ad2.css

The project was built assuming it is hosted at /.
You can control this with the homepage field in your package.json.

The build folder is ready to be deployed.
You may serve it with a static server:

npm install -g serve
serve -s build

Find out more about deployment here:

https://cra.link/deployment

6.2 设置 Flask 提供静态文件服务

6.2.1 将 build 文件夹复制到 Flask 项目目录

截屏2024-12-10 10.06.17

截屏2024-12-10 10.08.24

6.2.3 设置 Flask 提供静态文件服务

from flask import Flask, jsonify, send_from_directory

from flask_cors import CORS

app = Flask(__name__, static_folder='build')
CORS(app)


@app.route('/')
def serve():
    return send_from_directory(app.static_folder, 'index.html')


@app.route('/api/message', methods=['GET'])
def get_message():
    return jsonify({"message": "Hello from Flask!"})


if __name__ == '__main__':
    app.run(debug=True)

6.2.4 访问测试

启动 Flask 项目后,直接访问根路径 http://127.0.0.1:5000

首页为空,静态资源下载失败。

6.2.5 修改 package.json

"homepage": "."
截屏2024-12-10 10.26.43

重新构建项目:

npm run build

重新测试:

截屏2024-12-10 10.28.54

仍然失败。

6.2.6 问题进一步分析

http://127.0.0.1:5000/build/static/css/main.e6c13ad2.css
截屏2024-12-10 10.31.55

添加build目录能够成功访问。

6.2.7 问题解决

app = Flask(__name__, static_folder='build', static_url_path='')

添加配置:static_url_path=’’

6.3 访问测试

至此,成功完成一个Flask+React的Demo系统。

进阶技能,后续继续加以学习。

目录结构总结

前端:

react-frontend/
├── public/
├── src/
│   ├── App.js
│   ├── index.js
├── package.json

后端:

flask-backend/
├── app.py
├── venv/
├── build/ (React 构建的静态文件)

文章作者: 司晓凯
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 司晓凯 !
  目录