简介
在本教程中,您将使用Django和React构建一个To-Do应用程序。
Reaction是一个用于开发SPA(单页面应用程序)的JavaScript框架。它拥有Solid documentation]和一个充满活力的生态系统。
Django是一个Python Web框架,简化了Web开发中的常见实践。Django是可靠的,也有一个由稳定的库组成的充满活力的生态系统,支持共同的开发需求。
对于该应用程序,React充当前端或客户端框架,处理用户界面并通过对Django后端的请求获取和设置数据,Django后端是使用Django rest框架(DRF)构建的API。
At the end of this tutorial, you will have a fully working application:
之间切换的动画gif
<$>[备注] 注意: 本教程的源代码可在GitHub.上找到 <$>
<$>[信息] 如果您想要部署本教程中的应用程序,您可以使用DigitalOcean App Platform.]直接从GitHub Repo进行部署 <$>
该应用程序将允许用户创建任务并将其标记为已完成或未完成。
前提条件
要遵循本教程,您需要:
1.安装和设置PYTHON 3的本地编程环境 2.安装Node.js并创建本地开发Environment
本教程通过了Python v3.9.1、pi
v20.2.4、Django v3.1.6、djangorest Framework
v3.12.2、django-cors-headers
v3.7.0、Node v15.8.0、npm
v7.5.4、Reaction v17.0.1和axios
v0.21.0的验证。
第一步-设置后台
在本节中,您将创建一个新的项目目录并安装Django。
打开一个新的终端窗口,运行以下命令创建一个新的项目目录:
1mkdir django-todo-react
接下来,导航到目录:
1cd django-todo-react
现在使用piap
安装Pipenv:
1pip install pipenv
<$>[备注]
注意: 根据您的安装情况,您可能需要使用pip3
而不是bin
。
<$>
并激活新的虚拟环境:
1pipenv shell
使用Pipenv安装Django:
1pipenv install django
然后创建一个名为backend
的新工程:
1django-admin startproject backend
接下来,导航到新创建的后端目录:
1cd backend
启动一个名为todo
的新应用:
1python manage.py startapp todo
运行迁移:
1python manage.py migrate
并启动服务器:
1python manage.py runserver
在您的Web浏览器中导航到http://localhost:8000
:
此时,您将看到Django应用程序的一个实例成功运行。完成后,您可以停止服务器(CONTROL+C
或CTRL+C
)。
注册todo
应用
现在您已经完成了后台的设置,您可以开始将todo
应用注册为已安装的应用程序,以便Django可以识别它。
在代码编辑器中打开Backend/settings.py
文件,将todo
添加到INSTALLED_APPS
中:
1[label backend/settings.py]
2# Application definition
3
4INSTALLED_APPS = [
5 'django.contrib.admin',
6 'django.contrib.auth',
7 'django.contrib.contenttypes',
8 'django.contrib.sessions',
9 'django.contrib.messages',
10 'django.contrib.staticfiles',
11 'todo',
12]
然后,保存您的更改。
定义Todo
模型
让我们创建一个模型来定义Todo
项应该如何存储在数据库中。
在代码编辑器中打开todo/mods.py
文件,并添加以下代码行:
1[label todo/models.py]
2from django.db import models
3
4# Create your models here.
5
6class Todo(models.Model):
7 title = models.CharField(max_length=120)
8 description = models.TextField()
9 completed = models.BooleanField(default=False)
10
11 def _str_(self):
12 return self.title
上面的代码片段描述了TODO模型的三个属性:
标题
描述
已完成
Complete‘属性是任务的状态。任务可以随时完成,也可以随时不完成。由于您已经创建了
Todo`模型,因此需要创建迁移文件:
1python manage.py makemigrations todo
并将更改应用到数据库:
1python manage.py migrate todo
您可以使用Django默认提供的管理界面来测试CRUD操作是否在您创建的Todo
模型上工作。
使用代码编辑器打开todo/admin.py
文件,并添加以下代码行:
1[label todo/admin.py]
2from django.contrib import admin
3from .models import Todo
4
5class TodoAdmin(admin.ModelAdmin):
6 list_display = ('title', 'description', 'completed')
7
8# Register your models here.
9
10admin.site.register(Todo, TodoAdmin)
然后,保存您的更改。
您需要创建一个超级用户
帐户才能访问管理界面。在您的终端中运行以下命令:
1python manage.py createsuperuser
系统将提示您输入超级用户的用户名、电子邮件和密码。确保输入您可以记住的详细信息,因为您将需要它们登录到管理仪表板。
再次启动服务器:
1python manage.py runserver
在您的Web浏览器中导航到http://localhost:8000/admin
。并使用先前创建的用户名和密码登录:
的管理界面屏幕截图
您可以使用该界面创建、编辑、删除Todo
项:
的Django应用程序管理界面的屏幕截图
体验该界面后,您可以停止服务器(CONTROL+C
或CTRL+C
)。
第二步-设置接口
在本节中,您将使用Django REST框架创建一个API。
使用Pipenv安装djangorest Framework
和django-cors-headers
:
1pipenv install djangorestframework django-cors-headers
您需要在已安装的应用列表中添加rest_Framework
和corsheaders
。在代码编辑器中打开Backend/settings.py
文件,更新INSTALLED_APPS
和MIDDLEWARE
部分:
1[label backend/settings.py]
2# Application definition
3
4INSTALLED_APPS = [
5 'django.contrib.admin',
6 'django.contrib.auth',
7 'django.contrib.contenttypes',
8 'django.contrib.sessions',
9 'django.contrib.messages',
10 'django.contrib.staticfiles',
11 'corsheaders',
12 'rest_framework',
13 'todo',
14]
15
16MIDDLEWARE = [
17 'django.middleware.security.SecurityMiddleware',
18 'django.contrib.sessions.middleware.SessionMiddleware',
19 'django.middleware.common.CommonMiddleware',
20 'django.middleware.csrf.CsrfViewMiddleware',
21 'django.contrib.auth.middleware.AuthenticationMiddleware',
22 'django.contrib.messages.middleware.MessageMiddleware',
23 'django.middleware.clickjacking.XFrameOptionsMiddleware',
24 'corsheaders.middleware.CorsMiddleware',
25]
然后,将以下几行代码添加到backend/settings.py
文件的底部:
1[label backend/settings.py]
2CORS_ORIGIN_WHITELIST = [
3 'http://localhost:3000'
4]
django-cors-headers
是一个Python库,它将防止您通常会因为CORS规则而出现的错误。在CORS_ORIGIN_WHITELIST
代码中,您将localhost:3000
列入白名单,因为您希望应用程序的前端(将在该端口上服务)与API交互。
创建序列化程序
您将需要序列化程序将模型实例转换为JSON,以便前端可以处理接收到的数据。
使用代码编辑器创建一个todo/Serializers.py
文件。打开Serializers.py
文件,并使用以下代码行更新该文件:
1[label todo/serializers.py]
2from rest_framework import serializers
3from .models import Todo
4
5class TodoSerializer(serializers.ModelSerializer):
6 class Meta:
7 model = Todo
8 fields = ('id', 'title', 'description', 'completed')
此代码指定要使用的模型和要转换为JSON的字段。
创建视图
您需要在todo/views.py
文件中创建一个TodoView
类。
使用代码编辑器打开todo/views.py
文件,并添加以下代码行:
1[label todo/views.py]
2from django.shortcuts import render
3from rest_framework import viewsets
4from .serializers import TodoSerializer
5from .models import Todo
6
7# Create your views here.
8
9class TodoView(viewsets.ModelViewSet):
10 serializer_class = TodoSerializer
11 queryset = Todo.objects.all()
默认情况下,viewsets
基类提供CRUD操作的实现。该代码指定了Serializer_Class
和queryset
。
使用代码编辑器打开Backend/urls.py
文件,并将其内容替换为以下代码行:
1[label backend/urls.py]
2from django.contrib import admin
3from django.urls import path, include
4from rest_framework import routers
5from todo import views
6
7router = routers.DefaultRouter()
8router.register(r'todos', views.TodoView, 'todo')
9
10urlpatterns = [
11 path('admin/', admin.site.urls),
12 path('api/', include(router.urls)),
13]
此代码指定API的URL路径。这是完成API构建的最后一步。
您现在可以在Todo模型上执行CRUD操作。router类允许您进行以下查询:
/todos/
-返回所有Todo
项的列表。这里可以进行CREATE
和READ
操作。/todos/id
-使用id
主键返回单个Todo
项。这里可以进行UPDATE
和DELETE
操作。
让我们重新启动服务器:
1python manage.py runserver
在Web浏览器中导航到http://localhost:8000/api/todos
:
!【Todo项目API结果截图】(https://assets.digitalocean.com/articles/build-a-to-do-application-using-django-and-react/n9m3vnno99gawpa12xbi.png)
您可以使用接口`CREATE‘新的TODO项:
接口工具截图
如果TODO项创建成功,您将看到一个成功的响应:
您还可以使用id
主键对特定的Todo
项进行DELETE
和UPDATE
操作。使用地址结构/api/todos/{id}
,并提供id
。
将1
添加到URL中,以检查id
为1
的Todo项目。在Web浏览器中导航到http://localhost:8000/api/todos/1
:
接口工具截图
这就完成了应用程序后端的构建。
Step 3 -设置前端
现在您已经完成了应用程序的后端,您可以创建前端,并让它通过您创建的接口与后端通信。
首先,打开一个新的终端窗口并导航到django-todo-react
项目目录。
为了设置前端,本教程将依赖于Create Reaction App。有几种方法可以使用Create-Reaction-app
。一种方法是使用npx
运行包并创建工程:
1npx create-react-app frontend
您可以通过阅读如何使用创建React应用程序设置React项目了解更多有关此方法的信息。
工程创建完成后,您可以切换到新创建的Frontend
目录:
1cd frontend
然后,启动应用程序:
1npm start
您的Web浏览器将打开http://localhost:3000
,您将看到默认的Create React App屏幕:
的登录页面屏幕截图
接下来,安装bootstrap
和reactstrap
以提供用户界面工具。
1npm install [email protected] [email protected] --legacy-peer-deps
<$>[备注]
注意: 根据您的Reaction、Bootstrap和Reactstrap版本不同,可能会遇到Unable to Resolve Tree
错误。
在进行修订时,popper.js
的最新版本已被弃用,并将与Reaction 17+发生冲突。这是一个[已知的传统],安装时可以使用--issue](https://github.com/reactstrap/reactstrap/issues/2045)-Peer-dess
选项。
<$>
在代码编辑器中打开index.js
,添加bootstrap.min.css
:
1[label frontend/src/index.js]
2import React from 'react';
3import ReactDOM from 'react-dom';
4import 'bootstrap/dist/css/bootstrap.css';
5import './index.css';
6import App from './App';
7import reportWebVitals from './reportWebVitals';
8
9ReactDOM.render(
10 <React.StrictMode>
11 <App />
12 </React.StrictMode>,
13 document.getElementById('root')
14);
15
16// If you want to start measuring performance in your app, pass a function
17// to log results (for example: reportWebVitals(console.log))
18// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
19reportWebVitals();
如果您在这一步遇到困难,您可以参考官方文档添加bootstrap
.
在代码编辑器中打开App.js
并添加以下代码行:
1[label frontend/src/App.js]
2import React, { Component } from "react";
3
4const todoItems = [
5 {
6 id: 1,
7 title: "Go to Market",
8 description: "Buy ingredients to prepare dinner",
9 completed: true,
10 },
11 {
12 id: 2,
13 title: "Study",
14 description: "Read Algebra and History textbook for the upcoming test",
15 completed: false,
16 },
17 {
18 id: 3,
19 title: "Sammy's books",
20 description: "Go to library to return Sammy's books",
21 completed: true,
22 },
23 {
24 id: 4,
25 title: "Article",
26 description: "Write article on how to use Django with React",
27 completed: false,
28 },
29];
30
31class App extends Component {
32 constructor(props) {
33 super(props);
34 this.state = {
35 viewCompleted: false,
36 todoList: todoItems,
37 };
38 }
39
40 displayCompleted = (status) => {
41 if (status) {
42 return this.setState({ viewCompleted: true });
43 }
44
45 return this.setState({ viewCompleted: false });
46 };
47
48 renderTabList = () => {
49 return (
50 <div className="nav nav-tabs">
51 <span
52 className={this.state.viewCompleted ? "nav-link active" : "nav-link"}
53 onClick={() => this.displayCompleted(true)}
54 >
55 Complete
56 </span>
57 <span
58 className={this.state.viewCompleted ? "nav-link" : "nav-link active"}
59 onClick={() => this.displayCompleted(false)}
60 >
61 Incomplete
62 </span>
63 </div>
64 );
65 };
66
67 renderItems = () => {
68 const { viewCompleted } = this.state;
69 const newItems = this.state.todoList.filter(
70 (item) => item.completed == viewCompleted
71 );
72
73 return newItems.map((item) => (
74 <li
75 key={item.id}
76 className="list-group-item d-flex justify-content-between align-items-center"
77 >
78 <span
79 className={`todo-title mr-2 ${
80 this.state.viewCompleted ? "completed-todo" : ""
81 }`}
82 title={item.description}
83 >
84 {item.title}
85 </span>
86 <span>
87 <button
88 className="btn btn-secondary mr-2"
89 >
90 Edit
91 </button>
92 <button
93 className="btn btn-danger"
94 >
95 Delete
96 </button>
97 </span>
98 </li>
99 ));
100 };
101
102 render() {
103 return (
104 <main className="container">
105 <h1 className="text-white text-uppercase text-center my-4">Todo app</h1>
106 <div className="row">
107 <div className="col-md-6 col-sm-10 mx-auto p-0">
108 <div className="card p-3">
109 <div className="mb-4">
110 <button
111 className="btn btn-primary"
112 >
113 Add task
114 </button>
115 </div>
116 {this.renderTabList()}
117 <ul className="list-group list-group-flush border-top-0">
118 {this.renderItems()}
119 </ul>
120 </div>
121 </div>
122 </div>
123 </main>
124 );
125 }
126}
127
128export default App;
此代码包括四个项目的一些硬编码值。在从后端获取项目之前,这些将是临时值。
renderTabList()
函数呈现两个跨度,这有助于控制显示哪组项目。点击已完成 选项卡,将显示已完成的任务。点击** 未完成** 选项卡,将显示未完成的任务。
保存您的更改并在Web浏览器中观察应用程序:
要处理添加和编辑任务等操作,您需要创建一个模式组件。
首先,在src
目录下创建一个Components
文件夹:
1mkdir src/components
然后,创建一个modal.js
文件,并使用代码编辑器打开它。添加以下代码行:
1[label frontend/src/components/Modal.js]
2import React, { Component } from "react";
3import {
4 Button,
5 Modal,
6 ModalHeader,
7 ModalBody,
8 ModalFooter,
9 Form,
10 FormGroup,
11 Input,
12 Label,
13} from "reactstrap";
14
15export default class CustomModal extends Component {
16 constructor(props) {
17 super(props);
18 this.state = {
19 activeItem: this.props.activeItem,
20 };
21 }
22
23 handleChange = (e) => {
24 let { name, value } = e.target;
25
26 if (e.target.type === "checkbox") {
27 value = e.target.checked;
28 }
29
30 const activeItem = { ...this.state.activeItem, [name]: value };
31
32 this.setState({ activeItem });
33 };
34
35 render() {
36 const { toggle, onSave } = this.props;
37
38 return (
39 <Modal isOpen={true} toggle={toggle}>
40 <ModalHeader toggle={toggle}>Todo Item</ModalHeader>
41 <ModalBody>
42 <Form>
43 <FormGroup>
44 <Label for="todo-title">Title</Label>
45 <Input
46 type="text"
47 id="todo-title"
48 name="title"
49 value={this.state.activeItem.title}
50 onChange={this.handleChange}
51 placeholder="Enter Todo Title"
52 />
53 </FormGroup>
54 <FormGroup>
55 <Label for="todo-description">Description</Label>
56 <Input
57 type="text"
58 id="todo-description"
59 name="description"
60 value={this.state.activeItem.description}
61 onChange={this.handleChange}
62 placeholder="Enter Todo description"
63 />
64 </FormGroup>
65 <FormGroup check>
66 <Label check>
67 <Input
68 type="checkbox"
69 name="completed"
70 checked={this.state.activeItem.completed}
71 onChange={this.handleChange}
72 />
73 Completed
74 </Label>
75 </FormGroup>
76 </Form>
77 </ModalBody>
78 <ModalFooter>
79 <Button
80 color="success"
81 onClick={() => onSave(this.state.activeItem)}
82 >
83 Save
84 </Button>
85 </ModalFooter>
86 </Modal>
87 );
88 }
89}
这段代码创建了一个CustomModal
类,它嵌套了从reactstrap
库派生的Modal组件。
此代码还在表单中定义了三个域:
标题
描述
已完成
这些字段与我们在后端中定义为TODO模型属性的字段相同。
CustomModal
接受activeItem
、toggle
、onSave
作为道具:
1.activeItem
表示需要编辑的ToDo项。
2.toggle
是用于控制模式的状态(即打开或关闭模式)的函数。
3.onSave
是用来保存ToDo项编辑后的值的函数。
接下来,将CustomModal
组件导入到App.js
文件中。
使用代码编辑器重新访问src/App.js
文件,并使用以下代码行替换整个内容:
1[label frontend/src/App.js]
2import React, { Component } from "react";
3import Modal from "./components/Modal";
4
5const todoItems = [
6 {
7 id: 1,
8 title: "Go to Market",
9 description: "Buy ingredients to prepare dinner",
10 completed: true,
11 },
12 {
13 id: 2,
14 title: "Study",
15 description: "Read Algebra and History textbook for the upcoming test",
16 completed: false,
17 },
18 {
19 id: 3,
20 title: "Sammy's books",
21 description: "Go to library to return Sammy's books",
22 completed: true,
23 },
24 {
25 id: 4,
26 title: "Article",
27 description: "Write article on how to use Django with React",
28 completed: false,
29 },
30];
31
32class App extends Component {
33 constructor(props) {
34 super(props);
35 this.state = {
36 viewCompleted: false,
37 todoList: todoItems,
38 modal: false,
39 activeItem: {
40 title: "",
41 description: "",
42 completed: false,
43 },
44 };
45 }
46
47 toggle = () => {
48 this.setState({ modal: !this.state.modal });
49 };
50
51 handleSubmit = (item) => {
52 this.toggle();
53
54 alert("save" + JSON.stringify(item));
55 };
56
57 handleDelete = (item) => {
58 alert("delete" + JSON.stringify(item));
59 };
60
61 createItem = () => {
62 const item = { title: "", description: "", completed: false };
63
64 this.setState({ activeItem: item, modal: !this.state.modal });
65 };
66
67 editItem = (item) => {
68 this.setState({ activeItem: item, modal: !this.state.modal });
69 };
70
71 displayCompleted = (status) => {
72 if (status) {
73 return this.setState({ viewCompleted: true });
74 }
75
76 return this.setState({ viewCompleted: false });
77 };
78
79 renderTabList = () => {
80 return (
81 <div className="nav nav-tabs">
82 <span
83 className={this.state.viewCompleted ? "nav-link active" : "nav-link"}
84 onClick={() => this.displayCompleted(true)}
85 >
86 Complete
87 </span>
88 <span
89 className={this.state.viewCompleted ? "nav-link" : "nav-link active"}
90 onClick={() => this.displayCompleted(false)}
91 >
92 Incomplete
93 </span>
94 </div>
95 );
96 };
97
98 renderItems = () => {
99 const { viewCompleted } = this.state;
100 const newItems = this.state.todoList.filter(
101 (item) => item.completed === viewCompleted
102 );
103
104 return newItems.map((item) => (
105 <li
106 key={item.id}
107 className="list-group-item d-flex justify-content-between align-items-center"
108 >
109 <span
110 className={`todo-title mr-2 ${
111 this.state.viewCompleted ? "completed-todo" : ""
112 }`}
113 title={item.description}
114 >
115 {item.title}
116 </span>
117 <span>
118 <button
119 className="btn btn-secondary mr-2"
120 onClick={() => this.editItem(item)}
121 >
122 Edit
123 </button>
124 <button
125 className="btn btn-danger"
126 onClick={() => this.handleDelete(item)}
127 >
128 Delete
129 </button>
130 </span>
131 </li>
132 ));
133 };
134
135 render() {
136 return (
137 <main className="container">
138 <h1 className="text-white text-uppercase text-center my-4">Todo app</h1>
139 <div className="row">
140 <div className="col-md-6 col-sm-10 mx-auto p-0">
141 <div className="card p-3">
142 <div className="mb-4">
143 <button
144 className="btn btn-primary"
145 onClick={this.createItem}
146 >
147 Add task
148 </button>
149 </div>
150 {this.renderTabList()}
151 <ul className="list-group list-group-flush border-top-0">
152 {this.renderItems()}
153 </ul>
154 </div>
155 </div>
156 </div>
157 {this.state.modal ? (
158 <Modal
159 activeItem={this.state.activeItem}
160 toggle={this.toggle}
161 onSave={this.handleSubmit}
162 />
163 ) : null}
164 </main>
165 );
166 }
167}
168
169export default App;
保存您的更改并在Web浏览器中观察应用程序:
阅读代数和历史教科书
如果您尝试编辑并保存Todo
项目,您将收到一个警告,显示Todo
项目的对象。单击【保存】或【删除】,将对待办事项
项执行相应的操作。
<$>[备注]
注意:: 根据您的Reaction和Reactstrap版本,您可能会遇到控制台错误。在修订时,已经在严格模式树中检测到Warning:遗留上下文API。
和Warning:findDOMNode在StrictMode.
中是knownissues.。
<$>
现在,您将修改该应用程序,使其与您在上一节中构建的Django API交互。重新访问第一个终端窗口,并确保服务器正在运行。如果它未运行,请使用以下命令:
1python manage.py runserver
<$>[备注]
注意: 如果您关闭了该终端窗口,请记住您需要导航到backend
目录并使用虚拟的Pipenv外壳。
<$>
要向后端服务器上的API端点发出请求,需要安装一个名为axios
的JavaScript库。
在第二个终端窗口中,确保您在Frontend
目录中,并安装axios
:
1npm install [email protected]
然后在您的代码编辑器中打开Fronend/Package.json
文件,添加一个proxy
:
1[label frontend/package.json]
2[...]
3 "name": "frontend",
4 "version": "0.1.0",
5 "private": true,
6 "proxy": "http://localhost:8000",
7 "dependencies": {
8 "axios": "^0.18.0",
9 "bootstrap": "^4.1.3",
10 "react": "^16.5.2",
11 "react-dom": "^16.5.2",
12 "react-scripts": "2.0.5",
13 "reactstrap": "^6.5.0"
14 },
15[...]
该代理将帮助将API请求隧道传输到http://localhost:8000
,Django应用程序将在那里处理这些请求。如果没有此proxy
,则需要指定完整路径:
1axios.get("http://localhost:8000/api/todos/")
通过proxy
,可以提供相对路径:
1axios.get("/api/todos/")
<$>[注] 注意: 您可能需要重新启动开发服务器,代理才能注册到应用程序。 <$>
重新访问frontend/src/App.js
文件,并使用代码编辑器打开它。在此步骤中,您将删除硬编码的todoItems
,并使用来自后端服务器的请求的数据。删除提交
和删除删除
打开App.js
文件,并将其替换为以下最终版本:
1[label frontend/src/App.js]
2import React, { Component } from "react";
3import Modal from "./components/Modal";
4import axios from "axios";
5
6class App extends Component {
7 constructor(props) {
8 super(props);
9 this.state = {
10 viewCompleted: false,
11 todoList: [],
12 modal: false,
13 activeItem: {
14 title: "",
15 description: "",
16 completed: false,
17 },
18 };
19 }
20
21 componentDidMount() {
22 this.refreshList();
23 }
24
25 refreshList = () => {
26 axios
27 .get("/api/todos/")
28 .then((res) => this.setState({ todoList: res.data }))
29 .catch((err) => console.log(err));
30 };
31
32 toggle = () => {
33 this.setState({ modal: !this.state.modal });
34 };
35
36 handleSubmit = (item) => {
37 this.toggle();
38
39 if (item.id) {
40 axios
41 .put(`/api/todos/${item.id}/`, item)
42 .then((res) => this.refreshList());
43 return;
44 }
45 axios
46 .post("/api/todos/", item)
47 .then((res) => this.refreshList());
48 };
49
50 handleDelete = (item) => {
51 axios
52 .delete(`/api/todos/${item.id}/`)
53 .then((res) => this.refreshList());
54 };
55
56 createItem = () => {
57 const item = { title: "", description: "", completed: false };
58
59 this.setState({ activeItem: item, modal: !this.state.modal });
60 };
61
62 editItem = (item) => {
63 this.setState({ activeItem: item, modal: !this.state.modal });
64 };
65
66 displayCompleted = (status) => {
67 if (status) {
68 return this.setState({ viewCompleted: true });
69 }
70
71 return this.setState({ viewCompleted: false });
72 };
73
74 renderTabList = () => {
75 return (
76 <div className="nav nav-tabs">
77 <span
78 onClick={() => this.displayCompleted(true)}
79 className={this.state.viewCompleted ? "nav-link active" : "nav-link"}
80 >
81 Complete
82 </span>
83 <span
84 onClick={() => this.displayCompleted(false)}
85 className={this.state.viewCompleted ? "nav-link" : "nav-link active"}
86 >
87 Incomplete
88 </span>
89 </div>
90 );
91 };
92
93 renderItems = () => {
94 const { viewCompleted } = this.state;
95 const newItems = this.state.todoList.filter(
96 (item) => item.completed === viewCompleted
97 );
98
99 return newItems.map((item) => (
100 <li
101 key={item.id}
102 className="list-group-item d-flex justify-content-between align-items-center"
103 >
104 <span
105 className={`todo-title mr-2 ${
106 this.state.viewCompleted ? "completed-todo" : ""
107 }`}
108 title={item.description}
109 >
110 {item.title}
111 </span>
112 <span>
113 <button
114 className="btn btn-secondary mr-2"
115 onClick={() => this.editItem(item)}
116 >
117 Edit
118 </button>
119 <button
120 className="btn btn-danger"
121 onClick={() => this.handleDelete(item)}
122 >
123 Delete
124 </button>
125 </span>
126 </li>
127 ));
128 };
129
130 render() {
131 return (
132 <main className="container">
133 <h1 className="text-white text-uppercase text-center my-4">Todo app</h1>
134 <div className="row">
135 <div className="col-md-6 col-sm-10 mx-auto p-0">
136 <div className="card p-3">
137 <div className="mb-4">
138 <button
139 className="btn btn-primary"
140 onClick={this.createItem}
141 >
142 Add task
143 </button>
144 </div>
145 {this.renderTabList()}
146 <ul className="list-group list-group-flush border-top-0">
147 {this.renderItems()}
148 </ul>
149 </div>
150 </div>
151 </div>
152 {this.state.modal ? (
153 <Modal
154 activeItem={this.state.activeItem}
155 toggle={this.toggle}
156 onSave={this.handleSubmit}
157 />
158 ) : null}
159 </main>
160 );
161 }
162}
163
164export default App;
renhList()
函数是可重用的,每次API请求完成时都会调用该函数。它会更新待办事项列表,以显示最近添加的项目列表。
handleSubmit()
函数负责创建和更新操作。如果作为参数传递的项没有id
,那么它可能还没有被创建,所以函数会创建它。
此时,验证您的后端服务器是否在第一个终端窗口中运行:
1python manage.py runserver
<$>[备注]
注意: 如果您关闭了该终端窗口,请记住您需要导航到backend
目录并使用虚拟的Pipenv外壳。
<$>
在您的第二个终端窗口中,确保您在Frontend
目录中并启动您的前端应用程序:
1npm start
现在,当您使用Web浏览器访问http://localhost:3000
时,您的应用程序将允许您READ
、CREATE
、UPDATE
和DELETE
任务。
之间切换的动画gif
这就完成了TODO应用程序的前端和后端。
结论
在本文中,您使用Django和Reaction构建了一个待办应用程序。您可以通过djangorest Framework
、django-cors-headers
、axios
、bootstrap
和reactstRap
库实现这一点。
如果你想了解更多关于Django的知识,请查看我们的Django主题页面获取练习和编程项目。
如果您想了解有关Reaction的更多信息,请查看我们的如何在React.js中编程]系列文章,或查看我们的Reaction主题页面以获取练习和编程项目。