如何使用Django和React构建待办事项应用程序

来自菜鸟教程
跳转至:导航、​搜索

介绍

在本教程中,您将使用 Django 和 React 构建一个待办事项应用程序。

React 是一个用于开发 SPA(单页应用程序)的 JavaScript 框架。 它有 可靠的文档 和围绕它的充满活力的生态系统。

Django 是一个 Python Web 框架,它简化了 Web 开发中的常见做法。 Django 是可靠的,并且还拥有一个充满活力的稳定库生态系统,支持常见的开发需求。

对于此应用程序,React 充当前端或客户端框架,处理用户界面并通过对 Django 后端的请求获取和设置数据,后者是使用 Django REST 框架 (DRF) 构建的 API。

在本教程结束时,您将拥有一个完整的应用程序:

注意:本教程的源代码在GitHub上可用。


警告: 本教程中提供的代码用于教育目的,不用于生产用途。


此应用程序将允许用户创建任务并将其标记为完成或未完成。

先决条件

要学习本教程,您需要:

  1. 为 Python 3 安装和设置本地编程环境
  2. 安装 Node.js 并创建本地开发环境

本教程已使用 Python v3.9.1、pip v20.2.4、Django v3.1.6、djangorestframework v3.12.2、django-cors-headers v3.7.0、Node v15.8.0、 npm v7.5.4、React v17.0.1 和 axios v0.21.0。

第 1 步 — 设置后端

在本节中,您将创建一个新的项目目录并安装 Django。

打开一个新的终端窗口并运行以下命令来创建一个新的项目目录:

mkdir django-todo-react

接下来,导航到目录:

cd django-todo-react

现在使用 pip 安装 Pipenv:

pip install pipenv

注意: 根据您的安装,您可能需要使用 pip3 而不是 pip


并激活一个新的虚拟环境:

pipenv shell

使用 Pipenv 安装 Django:

pipenv install django

然后创建一个名为 backend 的新项目:

django-admin startproject backend

接下来,导航到新创建的后端目录:

cd backend

启动一个名为 todo 的新应用程序:

python manage.py startapp todo

运行迁移:

python manage.py migrate

并启动服务器:

python manage.py runserver

在 Web 浏览器中导航到 http://localhost:8000

此时,您将看到一个 Django 应用程序的实例成功运行。 完成后,您可以停止服务器(CONTROL+CCTRL+C)。

注册 todo 应用程序

现在您已经完成了后端的设置,您可以开始将 todo 应用程序注册为已安装的应用程序,以便 Django 可以识别它。

在代码编辑器中打开 backend/settings.py 文件并将 todo 添加到 INSTALLED_APPS

后端/settings.py

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'todo',
]

然后,保存您的更改。

定义 Todo 模型

让我们创建一个模型来定义 Todo 项目应如何存储在数据库中。

在代码编辑器中打开 todo/models.py 文件并添加以下代码行:

待办事项/models.py

from django.db import models

# Create your models here.

class Todo(models.Model):
    title = models.CharField(max_length=120)
    description = models.TextField()
    completed = models.BooleanField(default=False)

    def _str_(self):
        return self.title

上面的代码片段描述了 Todo 模型的三个属性:

  • title
  • description
  • completed

completed 属性是任务的状态。 任务将在任何时候完成或未完成。 因为你已经创建了一个 Todo 模型,你需要创建一个迁移文件:

python manage.py makemigrations todo

并将更改应用到数据库:

python manage.py migrate todo

您可以使用 Django 默认提供的管理界面进行测试,以查看 CRUD 操作是否适用于您创建的 Todo 模型。

使用代码编辑器打开 todo/admin.py 文件并添加以下代码行:

待办事项/admin.py

from django.contrib import admin
from .models import Todo

class TodoAdmin(admin.ModelAdmin):
    list_display = ('title', 'description', 'completed')

# Register your models here.

admin.site.register(Todo, TodoAdmin)

然后,保存您的更改。

您需要创建一个“超级用户”帐户才能访问管理界面。 在终端中运行以下命令:

python manage.py createsuperuser

系统将提示您输入超级用户的用户名、电子邮件和密码。 请务必输入您能记住的详细信息,因为您将需要它们来登录管理仪表板。

再次启动服务器:

python manage.py runserver

在 Web 浏览器中导航到 http://localhost:8000/admin。 并使用之前创建的用户名和密码登录:

您可以使用此界面创建、编辑和删除 Todo 项目:

试用此接口后,可以停止服务器(CONTROL+CCTRL+C)。

第 2 步 — 设置 API

在本节中,您将使用 Django REST 框架创建一个 API。

使用 Pipenv 安装 djangorestframeworkdjango-cors-headers

pipenv install djangorestframework django-cors-headers

您需要将 rest_frameworkcorsheaders 添加到已安装的应用程序列表中。 在代码编辑器中打开 backend/settings.py 文件并更新 INSTALLED_APPSMIDDLEWARE 部分:

后端/settings.py

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'corsheaders',
    'rest_framework',
    'todo',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'corsheaders.middleware.CorsMiddleware',
]

然后,将这些代码行添加到 backend/settings.py 文件的底部:

后端/settings.py

CORS_ORIGIN_WHITELIST = [
     'http://localhost:3000'
]

django-cors-headers 是一个 Python 库,可防止您通常会因 CORS 规则而出现的错误。 在 CORS_ORIGIN_WHITELIST 代码中,您将 localhost:3000 列入白名单,因为您希望应用程序的前端(将在该端口上提供服务)与 API 交互。

创建 serializers

您将需要序列化程序将模型实例转换为 JSON,以便前端可以处理接收到的数据。

使用您的代码编辑器创建一个 todo/serializers.py 文件。 打开 serializers.py 文件并使用以下代码行更新它:

待办事项/序列化程序.py

from rest_framework import serializers
from .models import Todo

class TodoSerializer(serializers.ModelSerializer):
    class Meta:
        model = Todo
        fields = ('id', 'title', 'description', 'completed')

此代码指定要使用的模型以及要转换为 JSON 的字段。

创建视图

您需要在 todo/views.py 文件中创建一个 TodoView 类。

使用代码编辑器打开 todo/views.py 文件并添加以下代码行:

待办事项/views.py

from django.shortcuts import render
from rest_framework import viewsets
from .serializers import TodoSerializer
from .models import Todo

# Create your views here.

class TodoView(viewsets.ModelViewSet):
    serializer_class = TodoSerializer
    queryset = Todo.objects.all()

viewsets 基类默认提供 CRUD 操作的实现。 此代码指定 serializer_classqueryset

使用您的代码编辑器打开 backend/urls.py 文件,并将内容替换为以下代码行:

后端/urls.py

from django.contrib import admin
from django.urls import path, include
from rest_framework import routers
from todo import views

router = routers.DefaultRouter()
router.register(r'todos', views.TodoView, 'todo')

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include(router.urls)),
]

此代码指定 API 的 URL 路径。 这是完成 API 构建的最后一步。

您现在可以在 Todo 模型上执行 CRUD 操作。 路由器类允许您进行以下查询:

  • /todos/ - 返回所有 Todo 项目的列表。 可以在此处执行 CREATEREAD 操作。
  • /todos/id - 使用 id 主键返回单个 Todo 项目。 可以在此处执行 UPDATEDELETE 操作。

让我们重新启动服务器:

python manage.py runserver

在 Web 浏览器中导航到 http://localhost:8000/api/todos

您可以使用界面 CREATE 一个新的待办事项:

如果 Todo 项创建成功,您将看到成功的响应:

您还可以使用 id 主键对特定 Todo 项目执行 DELETEUPDATE 操作。 使用地址结构 /api/todos/{id} 并提供 id

1 添加到 URL 以检查 id 为“1”的待办事项。 在 Web 浏览器中导航到 http://localhost:8000/api/todos/1

这样就完成了应用后端的搭建。

第 3 步 — 设置前端

现在您已经完成了应用程序的后端,您可以创建前端并让它通过您创建的接口与后端通信。

首先,打开一个新的终端窗口并导航到 django-todo-react 项目目录。

要设置前端,本教程将依赖 Create React App。 有几种方法可以使用 create-react-app。 一种方法是使用 npx 运行包并创建项目:

npx create-react-app frontend

您可以通过阅读 如何使用 Create React App 设置 React 项目来了解有关此方法的更多信息。

工程创建完成后,可以切换到新建的frontend目录:

cd frontend

然后,启动应用程序:

npm start

您的 Web 浏览器将打开 http://localhost:3000,您将看到默认的 Create React App 屏幕:

接下来,安装 bootstrapreactstrap 以提供用户界面工具。

npm install bootstrap@4.6.0 reactstrap@8.9.0 --legacy-peer-deps

注意: 根据您的 React、Bootstrap 和 Reactstrap 版本,您可能会遇到 unable to resolve dependency tree 错误。

在修订时,最新版本的 popper.js 已被弃用,将与 React 17+ 冲突。 这是 已知问题 ,安装时可以使用 --legacy-peer-deps 选项。


在代码编辑器中打开 index.js 并添加 bootstrap.min.css

前端/src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.css';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

如果这一步有困难,可以查阅官方文档添加bootstrap

在代码编辑器中打开 App.js 并添加以下代码行:

前端/src/App.js

import React, { Component } from "react";

const todoItems = [
  {
    id: 1,
    title: "Go to Market",
    description: "Buy ingredients to prepare dinner",
    completed: true,
  },
  {
    id: 2,
    title: "Study",
    description: "Read Algebra and History textbook for the upcoming test",
    completed: false,
  },
  {
    id: 3,
    title: "Sammy's books",
    description: "Go to library to return Sammy's books",
    completed: true,
  },
  {
    id: 4,
    title: "Article",
    description: "Write article on how to use Django with React",
    completed: false,
  },
];

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      viewCompleted: false,
      todoList: todoItems,
    };
  }

  displayCompleted = (status) => {
    if (status) {
      return this.setState({ viewCompleted: true });
    }

    return this.setState({ viewCompleted: false });
  };

  renderTabList = () => {
    return (
      <div className="nav nav-tabs">
        <span
          className={this.state.viewCompleted ? "nav-link active" : "nav-link"}
          onClick={() => this.displayCompleted(true)}
        >
          Complete
        </span>
        <span
          className={this.state.viewCompleted ? "nav-link" : "nav-link active"}
          onClick={() => this.displayCompleted(false)}
        >
          Incomplete
        </span>
      </div>
    );
  };

  renderItems = () => {
    const { viewCompleted } = this.state;
    const newItems = this.state.todoList.filter(
      (item) => item.completed == viewCompleted
    );

    return newItems.map((item) => (
      <li
        key={item.id}
        className="list-group-item d-flex justify-content-between align-items-center"
      >
        <span
          className={`todo-title mr-2 ${
            this.state.viewCompleted ? "completed-todo" : ""
          }`}
          title={item.description}
        >
          {item.title}
        </span>
        <span>
          <button
            className="btn btn-secondary mr-2"
          >
            Edit
          </button>
          <button
            className="btn btn-danger"
          >
            Delete
          </button>
        </span>
      </li>
    ));
  };

  render() {
    return (
      <main className="container">
        <h1 className="text-white text-uppercase text-center my-4">Todo app</h1>
        <div className="row">
          <div className="col-md-6 col-sm-10 mx-auto p-0">
            <div className="card p-3">
              <div className="mb-4">
                <button
                  className="btn btn-primary"
                >
                  Add task
                </button>
              </div>
              {this.renderTabList()}
              <ul className="list-group list-group-flush border-top-0">
                {this.renderItems()}
              </ul>
            </div>
          </div>
        </div>
      </main>
    );
  }
}

export default App;

此代码包括四个项目的一些硬编码值。 这些将是临时值,直到从后端获取项目。

renderTabList() 函数呈现两个跨度,帮助控制显示哪一组项目。 点击 Completed 选项卡将显示已完成的任务。 单击 Incomplete 选项卡将显示未完成的任务。

保存更改并在 Web 浏览器中观察应用程序:

要处理诸如添加和编辑任务之类的操作,您需要创建一个模态组件。

首先,在src目录下创建一个components文件夹:

mkdir src/components

然后,创建一个 Modal.js 文件并使用您的代码编辑器打开它。 添加以下代码行:

前端/src/components/Modal.js

import React, { Component } from "react";
import {
  Button,
  Modal,
  ModalHeader,
  ModalBody,
  ModalFooter,
  Form,
  FormGroup,
  Input,
  Label,
} from "reactstrap";

export default class CustomModal extends Component {
  constructor(props) {
    super(props);
    this.state = {
      activeItem: this.props.activeItem,
    };
  }

  handleChange = (e) => {
    let { name, value } = e.target;

    if (e.target.type === "checkbox") {
      value = e.target.checked;
    }

    const activeItem = { ...this.state.activeItem, [name]: value };

    this.setState({ activeItem });
  };

  render() {
    const { toggle, onSave } = this.props;

    return (
      <Modal isOpen={true} toggle={toggle}>
        <ModalHeader toggle={toggle}>Todo Item</ModalHeader>
        <ModalBody>
          <Form>
            <FormGroup>
              <Label for="todo-title">Title</Label>
              <Input
                type="text"
                id="todo-title"
                name="title"
                value={this.state.activeItem.title}
                onChange={this.handleChange}
                placeholder="Enter Todo Title"
              />
            </FormGroup>
            <FormGroup>
              <Label for="todo-description">Description</Label>
              <Input
                type="text"
                id="todo-description"
                name="description"
                value={this.state.activeItem.description}
                onChange={this.handleChange}
                placeholder="Enter Todo description"
              />
            </FormGroup>
            <FormGroup check>
              <Label check>
                <Input
                  type="checkbox"
                  name="completed"
                  checked={this.state.activeItem.completed}
                  onChange={this.handleChange}
                />
                Completed
              </Label>
            </FormGroup>
          </Form>
        </ModalBody>
        <ModalFooter>
          <Button
            color="success"
            onClick={() => onSave(this.state.activeItem)}
          >
            Save
          </Button>
        </ModalFooter>
      </Modal>
    );
  }
}

这段代码创建了一个 CustomModal 类,它嵌套了从 reactstrap 库派生的 Modal 组件。

这段代码还在表单中定义了三个字段:

  • title
  • description
  • completed

这些字段与我们在后端的 Todo 模型上定义为属性的字段相同。

CustomModal 接收 activeItemtoggleonSave 作为道具:

  1. activeItem 表示要编辑的 Todo 项。
  2. toggle是用来控制Modal的状态(即打开或关闭modal)的函数。
  3. onSave 是调用来保存待办事项的编辑值的函数。

接下来,您将 CustomModal 组件导入到 App.js 文件中。

使用您的代码编辑器重新访问 src/App.js 文件,并将整个内容替换为以下代码行:

前端/src/App.js

import React, { Component } from "react";
import Modal from "./components/Modal";

const todoItems = [
  {
    id: 1,
    title: "Go to Market",
    description: "Buy ingredients to prepare dinner",
    completed: true,
  },
  {
    id: 2,
    title: "Study",
    description: "Read Algebra and History textbook for the upcoming test",
    completed: false,
  },
  {
    id: 3,
    title: "Sammy's books",
    description: "Go to library to return Sammy's books",
    completed: true,
  },
  {
    id: 4,
    title: "Article",
    description: "Write article on how to use Django with React",
    completed: false,
  },
];

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      viewCompleted: false,
      todoList: todoItems,
      modal: false,
      activeItem: {
        title: "",
        description: "",
        completed: false,
      },
    };
  }

  toggle = () => {
    this.setState({ modal: !this.state.modal });
  };

  handleSubmit = (item) => {
    this.toggle();

    alert("save" + JSON.stringify(item));
  };

  handleDelete = (item) => {
    alert("delete" + JSON.stringify(item));
  };

  createItem = () => {
    const item = { title: "", description: "", completed: false };

    this.setState({ activeItem: item, modal: !this.state.modal });
  };

  editItem = (item) => {
    this.setState({ activeItem: item, modal: !this.state.modal });
  };

  displayCompleted = (status) => {
    if (status) {
      return this.setState({ viewCompleted: true });
    }

    return this.setState({ viewCompleted: false });
  };

  renderTabList = () => {
    return (
      <div className="nav nav-tabs">
        <span
          className={this.state.viewCompleted ? "nav-link active" : "nav-link"}
          onClick={() => this.displayCompleted(true)}
        >
          Complete
        </span>
        <span
          className={this.state.viewCompleted ? "nav-link" : "nav-link active"}
          onClick={() => this.displayCompleted(false)}
        >
          Incomplete
        </span>
      </div>
    );
  };

  renderItems = () => {
    const { viewCompleted } = this.state;
    const newItems = this.state.todoList.filter(
      (item) => item.completed === viewCompleted
    );

    return newItems.map((item) => (
      <li
        key={item.id}
        className="list-group-item d-flex justify-content-between align-items-center"
      >
        <span
          className={`todo-title mr-2 ${
            this.state.viewCompleted ? "completed-todo" : ""
          }`}
          title={item.description}
        >
          {item.title}
        </span>
        <span>
          <button
            className="btn btn-secondary mr-2"
            onClick={() => this.editItem(item)}
          >
            Edit
          </button>
          <button
            className="btn btn-danger"
            onClick={() => this.handleDelete(item)}
          >
            Delete
          </button>
        </span>
      </li>
    ));
  };

  render() {
    return (
      <main className="container">
        <h1 className="text-white text-uppercase text-center my-4">Todo app</h1>
        <div className="row">
          <div className="col-md-6 col-sm-10 mx-auto p-0">
            <div className="card p-3">
              <div className="mb-4">
                <button
                  className="btn btn-primary"
                  onClick={this.createItem}
                >
                  Add task
                </button>
              </div>
              {this.renderTabList()}
              <ul className="list-group list-group-flush border-top-0">
                {this.renderItems()}
              </ul>
            </div>
          </div>
        </div>
        {this.state.modal ? (
          <Modal
            activeItem={this.state.activeItem}
            toggle={this.toggle}
            onSave={this.handleSubmit}
          />
        ) : null}
      </main>
    );
  }
}

export default App;

保存更改并在 Web 浏览器中观察应用程序:

如果您尝试编辑和保存 Todo 项目,您将收到显示 Todo 项目对象的警报。 单击保存删除将对Todo项执行相应的操作。

注意:: 根据您的 React 和 Reactstrap 版本,您可能会遇到控制台错误。 在修订时,Warning: Legacy context API has been detected within a strict-mode tree.Warning: findDOMNode is deprecated in StrictMode.已知 问题


现在,您将修改应用程序,使其与您在上一节中构建的 Django API 交互。 重新访问第一个终端窗口并确保服务器正在运行。 如果它没有运行,请使用以下命令:

python manage.py runserver

注意: 如果您关闭了此终端窗口,请记住您需要导航到 backend 目录并使用虚拟 Pipenv shell。


要向后端服务器上的 API 端点发出请求,您将安装一个名为 axios 的 JavaScript 库。

在第二个终端窗口中,确保您位于 frontend 目录并安装 axios

npm install axios@0.21.1

然后在代码编辑器中打开 frontend/package.json 文件并添加 proxy

前端/package.json

[...]
  "name": "frontend",
  "version": "0.1.0",
  "private": true,
  "proxy": "http://localhost:8000",
  "dependencies": {
    "axios": "^0.18.0",
    "bootstrap": "^4.1.3",
    "react": "^16.5.2",
    "react-dom": "^16.5.2",
    "react-scripts": "2.0.5",
    "reactstrap": "^6.5.0"
  },
[...]

代理将帮助将 API 请求通过隧道传送到 Django 应用程序将处理它们的 http://localhost:8000。 如果没有这个 proxy,您将需要指定完整路径:

axios.get("http://localhost:8000/api/todos/")

使用 proxy,您可以提供相对路径:

axios.get("/api/todos/")

注意: 您可能需要重新启动开发服务器才能让代理注册到应用程序。


重新访问 frontend/src/App.js 文件并使用代码编辑器打开它。 在此步骤中,您将删除硬编码的 todoItems 并使用来自对后端服务器的请求的数据。 handleSubmithandleDelete

打开 App.js 文件并用这个最终版本替换它:

前端/src/App.js

import React, { Component } from "react";
import Modal from "./components/Modal";
import axios from "axios";

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      viewCompleted: false,
      todoList: [],
      modal: false,
      activeItem: {
        title: "",
        description: "",
        completed: false,
      },
    };
  }

  componentDidMount() {
    this.refreshList();
  }

  refreshList = () => {
    axios
      .get("/api/todos/")
      .then((res) => this.setState({ todoList: res.data }))
      .catch((err) => console.log(err));
  };

  toggle = () => {
    this.setState({ modal: !this.state.modal });
  };

  handleSubmit = (item) => {
    this.toggle();

    if (item.id) {
      axios
        .put(`/api/todos/${item.id}/`, item)
        .then((res) => this.refreshList());
      return;
    }
    axios
      .post("/api/todos/", item)
      .then((res) => this.refreshList());
  };

  handleDelete = (item) => {
    axios
      .delete(`/api/todos/${item.id}/`)
      .then((res) => this.refreshList());
  };

  createItem = () => {
    const item = { title: "", description: "", completed: false };

    this.setState({ activeItem: item, modal: !this.state.modal });
  };

  editItem = (item) => {
    this.setState({ activeItem: item, modal: !this.state.modal });
  };

  displayCompleted = (status) => {
    if (status) {
      return this.setState({ viewCompleted: true });
    }

    return this.setState({ viewCompleted: false });
  };

  renderTabList = () => {
    return (
      <div className="nav nav-tabs">
        <span
          onClick={() => this.displayCompleted(true)}
          className={this.state.viewCompleted ? "nav-link active" : "nav-link"}
        >
          Complete
        </span>
        <span
          onClick={() => this.displayCompleted(false)}
          className={this.state.viewCompleted ? "nav-link" : "nav-link active"}
        >
          Incomplete
        </span>
      </div>
    );
  };

  renderItems = () => {
    const { viewCompleted } = this.state;
    const newItems = this.state.todoList.filter(
      (item) => item.completed === viewCompleted
    );

    return newItems.map((item) => (
      <li
        key={item.id}
        className="list-group-item d-flex justify-content-between align-items-center"
      >
        <span
          className={`todo-title mr-2 ${
            this.state.viewCompleted ? "completed-todo" : ""
          }`}
          title={item.description}
        >
          {item.title}
        </span>
        <span>
          <button
            className="btn btn-secondary mr-2"
            onClick={() => this.editItem(item)}
          >
            Edit
          </button>
          <button
            className="btn btn-danger"
            onClick={() => this.handleDelete(item)}
          >
            Delete
          </button>
        </span>
      </li>
    ));
  };

  render() {
    return (
      <main className="container">
        <h1 className="text-white text-uppercase text-center my-4">Todo app</h1>
        <div className="row">
          <div className="col-md-6 col-sm-10 mx-auto p-0">
            <div className="card p-3">
              <div className="mb-4">
                <button
                  className="btn btn-primary"
                  onClick={this.createItem}
                >
                  Add task
                </button>
              </div>
              {this.renderTabList()}
              <ul className="list-group list-group-flush border-top-0">
                {this.renderItems()}
              </ul>
            </div>
          </div>
        </div>
        {this.state.modal ? (
          <Modal
            activeItem={this.state.activeItem}
            toggle={this.toggle}
            onSave={this.handleSubmit}
          />
        ) : null}
      </main>
    );
  }
}

export default App;

refreshList() 函数是可重复使用的,每次完成 API 请求时都会调用该函数。 它更新待办事项列表以显示最新添加的项目列表。

handleSubmit() 函数负责创建和更新操作。 如果作为参数传递的项目没有 id,那么它可能还没有被创建,所以函数创建它。

此时,验证您的后端服务器是否正在您的第一个终端窗口中运行:

python manage.py runserver

注意: 如果您关闭了此终端窗口,请记住您需要导航到 backend 目录并使用虚拟 Pipenv shell。


在您的第二个终端窗口中,确保您位于 frontend 目录中并启动您的前端应用程序:

npm start

现在,当您使用 Web 浏览器访问 http://localhost:3000 时,您的应用程序将允许您执行 READCREATEUPDATEDELETE 任务.

这样就完成了 Todo 应用程序的前端和后端。

结论

在本文中,您使用 Django 和 React 构建了一个待办事项应用程序。 您通过 djangorestframeworkdjango-cors-headersaxiosbootstrapreactstrap 库实现了这一点。

如果您想了解有关 Django 的更多信息,请查看 我们的 Django 主题页面 以获取练习和编程项目。

如果您想了解有关 React 的更多信息,请查看我们的 如何在 React.js 中编码,或查看 我们的 React 主题页面 以了解练习和编程项目。