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

介绍

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

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

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

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

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

用户与应用程序交互以创建新任务并在完成和未完成状态之间切换的动画 gif。

注意:本教程源代码可在 GitHub 上找到

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

先决条件

要学习本教程,您需要:

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

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

本教程已通过 Python pipv3.9.1 v20.2.4、Django v3.1.6、djangorestframeworkv3.12.2、django-cors-headersv3.7.0、Node v15.8.0、npmv7.5.4、React v17.0.1 和axiosv0.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

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

成功运行的默认 Django 应用程序的屏幕截图。

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

注册todo应用程序

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

backend/settings.py在代码编辑器中打开文件并添加todoINSTALLED_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在代码编辑器中打开文件并添加以下代码行:

待办事项/模型.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

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

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

待办事项/管理.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

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

Django 应用程序管理界面的屏幕截图。

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

显示待办事项的 Django 应用程序管理界面的屏幕截图。

试用此界面后,您可以停止服务器(CONTROL+CCTRL+C)。

第 2 步 – 设置 API

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

安装djangorestframeworkdjango-cors-headers使用 Pipenv:

  • pipenv install djangorestframework django-cors-headers

您需要将rest_framework添加corsheaders到已安装的应用程序列表中。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文件并使用以下代码行更新它:

todo/serializers.py
from rest_framework import serializers
from .models import Todo

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

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

创建视图

您需要TodoViewtodo/views.py文件中创建一个

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

待办事项/视图.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/idTodo使用id主键返回单个项目UPDATEDELETE操作可以在这里进行。

让我们重新启动服务器:

  • python manage.py runserver

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

Todo 项的 API 结果的屏幕截图。

您可以CREATE使用界面创建一个新的 Todo 项目:

用于创建新 Todo 项的 API 工具的屏幕截图。

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

成功创建 Todo 项的 API 响应的屏幕截图。

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

添加1到 URL 以检查带有id“1”的 Todo 项http://localhost:8000/api/todos/1在 Web 浏览器中导航到

用于 DELETE 和 PUT 的 API 工具的屏幕截图。

这样就完成了应用程序后端的构建。

第 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 屏幕:

默认 Create React App 应用程序的登录页面的屏幕截图。

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

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

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

修改时,最新版本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()函数呈现两个跨度,帮助控制显示哪些项目集。单击“已完成”选项卡将显示已完成的任务。单击“未完成”选项卡将显示未完成的任务。

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

当前显示研究和文章任务的应用程序的屏幕截图。

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

首先,componentssrc目录中创建一个文件夹

  • 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接收activeItemtoggle以及onSave当道具:

  1. activeItem 表示要编辑的 Todo 项。
  2. toggle 是用于控制模态状态(即打开或关闭模态)的函数。
  3. onSave 是一个函数,用于保存 Todo 项的编辑值。

接下来,您将把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.

在第二个终端窗口中,确保您在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 请求隧道传送http://localhost:8000到 Django 应用程序将处理它们的位置。没有这个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 请求时都会调用函数。它更新 Todo 列表以显示最近添加的项目列表。

handleSubmit()函数负责创建和更新操作。如果作为参数传递的项目没有id,则它可能尚未创建,因此函数会创建它。

At this point, verify that your backend server is running in your first terminal window:

  • python manage.py runserver

Note: If you closed this terminal window, remember that you will need to navigate to the backend directory and use the virtual Pipenv shell.

And in your second terminal window, ensure that you are in the frontend directory and start your frontend application:

  • npm start

Now when you visit http://localhost:3000 with your web browser, your application will allow you to READ, CREATE, UPDATE, and DELETE tasks.

用户与应用程序交互以创建新项目并在完成和未完成状态之间切换的动画 gif。

This completes the frontend and backend of the Todo application.

Conclusion

In this article, you built a To-Do application using Django and React. You achieved this with the djangorestframework, django-cors-headers, axios, bootstrap, and reactstrap libraries.

如果您想了解更多关于Django的,检查出[我们的Django的专题页面(hhttps:// www.digitalocean.com/community/tags/django)的练习题和编程。

如果您想了解有关 React 的更多信息,请查看我们的How To Code in React.js系列,或查看我们的 React 主题页面以获取练习和编程项目。

觉得文章有用?

点个广告表达一下你的爱意吧 !😁