使用Django REST框架构建React.js应用程序
在本教程中,我们将使用React和Django构建一个Todo应用程序。[React]是一个前端JavaScript框架,在为单页应用程序创建用户界面时使用组件。[Django]是一个Python后端网络框架,用于构建可扩展和安全的网站应用程序。
我们将使用Django REST框架(DRF)创建一个应用程序,在用户界面上使用[React],在应用程序的API上使用[Django]。
先决条件
需要有React,Python(Django) 的基本知识,但我将尽可能地把事情简化。
第1步:使用Django的后端
在终端上创建一个新的项目目录,名为django-react-todoApp
mkdir django-react-todoApp
cd django-react-todoApp
我们必须激活一个虚拟环境以便能够安装Django。
让我们创建一个虚拟环境并激活它。
pip install pipenv
pipenv shell
安装Django并创建一个名为backend 的项目。
pipenv install django
django-admin startproject backend
现在我们已经创建了我们的项目,让我们继续创建一个名为todo 的应用程序并将模型迁移到数据库中。
如果一切顺利,你应该看到Django的 "祝贺 "页面。
导航到backend/settings.py ,并将todo 。INSTALLED_APPS
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'todo',
]
我们将继续为Todo项目字段设置我们的模型。
修改todo/models.py ,如下所示。
from django.db import models
class Todo(models.Model):
title = models.CharField(max_length=100)
description = models.TextField()
completed = models.BooleanField(default=False)
def _str_(self):
return self.title
该模型包含。
-
标题。任务是什么。
-
描述。对某项任务给予更多的解释。
-
已完成。如果任务完成了,状态为True,否则仍为False。
Django使用_str_() 来显示要显示的默认属性,在我们的例子中,我们返回title 来显示我们的模型。
让我们运行迁移,将我们的模型添加到数据库模式中。
python manage.py makemigrations
python manage.py migrate
Django自带了一个内置的管理界面。该界面允许管理员和授权用户直接对模型中定义的对象进行操作。
我们可以使用admin.site.register() 功能将模型添加到我们的管理页面。在todo应用的admin.py ,让我们把模型添加到我们的管理页面。
from django.contrib import admin
from .models import Todo
class TodoAdmin(admin.ModelAdmin):
list = ('title', 'description', 'completed')
admin.site.register(Todo, TodoAdmin)
让我们为管理页面创建一个超级用户来登录。
python manage.py createsuperuser
这将提示你输入username,email,password,password(again) 。我们可以使用以下链接打开管理页面http://localhost:8000/admin 。
python manage.py runserver

我们现在可以从管理页面添加和删除项目。很好!

第2步:放入API
安装djangorestframework 和django-cors-headers 。
pipenv install djangorestframework django-cors-headers
将rest_framework 和corsheaders 添加到INSTALLED_APPS 和backend/settings.py 文件中,并修改MIDDLEWARE 。
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'todo',
'corsheaders',
'rest_framework',
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'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',
]
在backend/settings.py 文件中添加这个代码片段。
CORS_ORIGIN_WHITELIST = [
'http://localhost:3000',
]
Django-cors-headers是一个基于HTTP头的,允许服务器向你的Django应用程序指示任何其他来源。跨源资源共享(CORS)。在CORS_ORIGIN_WHITELIST 内,localhost:3000 将作为我们的端口。
现在,让我们创建一个串行器文件。
序列器是一个将Django模型转换为JSON对象的组件,反之亦然。
touch todo/serializers.py
让我们把它添加到serializers.py 文件中。
from rest_framework import serializers
from .models import Todo
class TodoSerializer(serializers.ModelSerializer):
class Meta:
model = Todo
fields = ('id' ,'title', 'description', 'completed')
从rest_framework 包中,我们导入serializers 。我们创建了一个类,TodoSerializer ,它扩展自ModelSerializer 类。然后我们继续指定我们想要返回的模型和字段。
让我们也更新一下todo/views.py 。
from django.shortcuts import render
from .serializers import TodoSerializer
from rest_framework import viewsets
from .models import Todo
class TodoView(viewsets.ModelViewSet):
serializer_class = TodoSerializer
queryset = Todo.objects.all()
在Django中创建网页之前,我们必须定义我们的URL。URL是网页的一个地址,是网页的服务对象。定义URL描述了当模板在浏览器中渲染时,从视图中返回的请求是什么。
在backend/urls.py ,我们定义了API的URL路由。
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))
]
下一个模块是router.urls ,为我们的API提供路由。
router 使我们能够创建后续的操作。
对我们的项目进行CRUD操作是由router 。
-
/todos/- 这个路由从我们的API返回每个项目。 -
todos/id- 返回一个特定的项目和它的 。id
python manage.py runserver

我们已经设置了我们的后端,让我们前进到前台。
第3步:使用React的前台
为了安装React,我们使用以下命令。
-g 代表global,因为我们首先要在全球范围内安装 。create-react-app
npm install -g create-react-app
而在父目录--django-react-todoApp--创建一个React应用程序,frontend 。
create-react-app frontend
要启动服务器。
cd frontend
npm start
现在你应该能看到默认的React应用程序了。
接下来,让我们安装bootstrap 和reactstrap ,来设计用户界面的风格。
npm install bootstrap@4.6.0 reactstrap@8.9.0 --legacy-peer-deps
当我们打开我们的index.js 文件时,它应该类似于下面的代码。
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<App />,
document.getElementById('root')
);
//React generated comments ignored
reportWebVitals();
ReactDOM.render() 将一个React元素渲染到DOM中的给定容器元素中。它需要两个参数。第一个是被渲染的JSX,第二个是在HTML页面上显示容器元素。
将下面的代码替换为src/App.js 。
import React, { Component } from "react"
const todoItems = [
{
id: 1,
title: "Nature walk in the park",
description: "Visit the park with my friends",
completed: true
},
{
id: 2,
title: "Visit",
description: "Got to my aunt's place",
completed: true
},
{
id: 3,
title: "Write",
description: "Do an article about anthropology",
completed: true
},
];
class App extends Component {
constructor(props) {
super(props);
this.state = {todoItems};
};
render() {
return (
<main className="content">
<div className="row">
<div className="col-md-6 col-sm-10 mx-auto p-0">
<div className="card p-3">
<ul className="list-group list-group-flush">
{this.state.todoItems.map(item => (
<div>
<h1>{item.title}</h1>
<span>{item.description}</span>
</div>
))}
</ul>
</div>
</div>
</div>
</main>
)
}
}
export default App;
我们开始渲染一个项目的列表。这个列表将是人工数据,但稍后我们将从我们在前面步骤中创建的API中获取数据。
我们定义一个项目的列表。每个项目都有一个id,title,description, 以及任务是否完成的状态,completed 。
我们引入类的构造函数,在这里我们设置初始状态。在我们的例子中,内部状态是假的项目列表,todoItems 。
我们在我们的JavaScript XML(JSX)中使用内置的JavaScriptmap 功能。
map() 方法创建了一个新的数组,其中填充了对调用数组中每个元素调用所提供函数的结果。我们使用大括号来评估JavaScript表达式。
render() 方法被调用时显示JJSX。render() 方法中的classname 属性使我们能够使用CSS属性。
我们使用箭头函数,因为它们缩短了我们的函数声明。
你的用户界面应该类似于下面的界面。

现在是消耗我们先前创建的API的时候了。
cd backend
python manage.py runserver
我们需要修改frontend/package.json ,加入proxy 。在开发环境中使用proxy ,以方便服务器和用户界面之间的通信,因为后台和用户界面将在不同的端口上运行。
proxy 使我们能够使用Django的localhost来处理我们的API请求。
让我们继续添加它吧。
[...]
"name": "frontend",
"version": "0.1.0",
"private": true,
"proxy": "http://localhost:8000",
"dependencies": {
"@testing-library/jest-dom": "^5.11.9",
"@testing-library/react": "^11.2.3",
"@testing-library/user-event": "^12.6.0",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-scripts": "4.0.1",
"web-vitals": "^0.2.4"
"reactstrap": "^8.8.1",
},
[...]
为了消耗我们的API而不是人工数据,用下面的片段更新frontend/src/App.js 。
import React, { Component } from "react"
class App extends Component {
constructor(props) {
super(props);
this.state = {
viewCompleted: false,
activeItem: {
title: "",
description: "",
completed: false
},
todoList: []
};
}
async componentDidMount() {
try {
const res = await fetch('http://localhost:8000/api/todos/');
const todoList = await res.json();
this.setState({
todoList
});
} catch (e) {
console.log(e);
}
}
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>
</li>
));
};
render() {
return (
<main className="content">
<div className="row">
<div className="col-md-6 col-sm-10 mx-auto p-0">
<div className="card p-3">
<ul className="list-group list-group-flush">
{this.renderItems()}
</ul>
</div>
</div>
</div>
</main>
)
}
}
export default App;
让我们浏览一下每一行代码,以便更好地理解它们的作用。
在我们的构造函数中,我们在我们的state 对象中创建了几个属性。我们将viewCompleted 属性分配为false,因为我们的界面目前只显示来自我们API的标记为未完成的项目。
activeItem 属性包括title,description 并将false 到completed 作为默认状态传递。
然后我们传递一个空数组给我们的todoList ,因为我们要从API中获取我们的数据。
首先,我们把fetch() 包在一个try/catch 块中,以处理任何网络错误。然后我们用await 关键字调用fetch() ,在这里我们传递我们的API端点。
async 实现异步操作,它从我们的函数中返回一个解析值承诺。
我们将定义componentDidMount() 方法作为async 函数的一部分。这使我们能够使用await 关键字来执行每个fetch 。
当一个组件在客户端渲染时,componentDidMount() 函数会被React调用。
componentDidMount() 函数中的setState() 方法,当我们想在应用程序中更新对之前状态的改变时,会被调用。
我们创建一个renderItems() 函数,使用filter 内置的数组功能,来显示我们的todoList 中已完成的项目。filter 函数需要一个函数来评估列表中的每个项目。
我们定义一个变量newItems 来存储我们通过使用map 功能来显示的项目。我们使用一个三元条件运算符来显示一个item description 是否被标记为完成。
三元是一个JavaScript运算符,在表达式的第一部分条件下返回真,在第二部分返回假。
在我们的render() 方法中,我们通过renderItems() 函数显示项目。
从API中消耗的数据应显示如下。

为了处理添加任务和标记任务完成等动作,我们可以创建一个模态组件。
模态使我们能够在我们的应用程序中创建自定义内容,如弹出窗口或对话框。
让我们继续前进,在src 目录中创建一个components 文件夹,然后在其中创建一个名为Modal.js 的文件。
mkdir src/components
cd components
touch 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="title">Title</Label>
<Input
type="text"
name="title"
value={this.state.activeItem.title}
onChange={this.handleChange}
placeholder="Enter Todo Title"
/>
</FormGroup>
<FormGroup>
<Label for="description">Description</Label>
<Input
type="text"
name="description"
value={this.state.activeItem.description}
onChange={this.handleChange}
placeholder="Enter Todo description"
/>
</FormGroup>
<FormGroup check>
<Label for="completed">
<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>
);
}
}
在上面的代码中,我们首先导入React和我们之前安装的reactstrap 中的组件。在构造函数中,我们使用我们先前在App.js 文件中创建的属性。props 关键字将参数作为对象传递给activeItem 组件。
handleChange 方法注意到React组件的状态变化,将事件作为参数,并做一些事情来改变状态。
我们使用destructuring赋值来创建一个复选框,用户可以点击它来标记任务完成。然后我们通过setState() 方法改变我们的状态对象中的activeItem 。
在我们的render() 方法中,我们将toggle 和onSave() 方法传递给props 。当切换时,我们返回Modal组件。
我们在ModalHeader 中添加了toggle 组件,以实现丢弃模态。在ModalBody ,我们添加表单来添加项目标题和项目描述。
在每个FormGroup ,我们指定activeItem 值。我们使用onChange 事件来检测输入值的变化,并返回目标输入的名称和值。
由于最后一个FormGroup'的输入类型是一个复选框,目标值将是checked ,因为我们在handlechange() 方法中指定了它。
在ModalFooter ,我们将使用onSave() 方法创建一个按钮来保存我们的项目。
然后我们可以在App.js 中创建add task 和mark as completed 的功能。
在我们继续之前,让我们安装axios 。它允许我们的应用程序向外部端点发出请求。我们用它来对我们的API进行CRUD操作。
npm install axios@0.21.1
在App.js ,添加下面的代码片段。
import React, { Component } from "react"
import Modal from "./components/Modal";
import axios from "axios";
class App extends Component {
state = {
viewCompleted: false,
activeItem: {
title: "",
description: "",
completed: false
},
todoList: []
};
async componentDidMount() {
try {
const res = await fetch('http://localhost:8000/api/todos/');
const todoList = await res.json();
this.setState({
todoList
});
} catch (e) {
console.log(e);
}
}
toggle = () => {
this.setState({ modal: !this.state.modal });
};
//Responsible for saving the task
handleSubmit = item => {
this.toggle();
if (item.id) {
axios
.put(`http://localhost:8000/api/todos/${item.id}/`, item)
return;
}
axios
.post("http://localhost:8000/api/todos/", item)
};
createItem = () => {
const item = {title: "", description: "", completed: false };
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="my-5 tab-list">
<button
onClick={() => this.displayCompleted(true)}
className={this.state.viewCompleted ? "active" : ""}
>
Complete
</button>
<button
onClick={() => this.displayCompleted(false)}
className={this.state.viewCompleted ? "" : "active"}
>
Incomplete
</button>
</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>
</li>
));
};
render() {
return (
<main className="content">
<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="">
<button onClick={this.createItem} className="btn btn-success">Add Task</button>
</div>
{this.renderTabList()}
<ul className="list-group list-group-flush">
{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;
首先,导入我们之前创建的Modal 和axios 。toggle() 方法在切换时改变了Modal 的状态,如果表达式为真,则返回Modal 中定义的属性,在Modal.js ,否则什么都不会发生。我们在render() 方法中添加这个内容。
handleSubmit() 将我们的项目保存到API中,我们使用axios 来向它发出请求。我们使用PUT ,根据项目ID将项目插入到已经存在的项目列表中。
然后我们创建一个createItem() 方法来添加我们的任务,这个任务是在render() 方法中定义的。
displayCompleted() 方法检查我们先前在状态中创建的viewCompleted 的状态,并返回真或假。
renderTabList() 方法定义了两个按钮Complete 和Incomplete ,如果viewCompleted() 方法返回真,那么这个项目就是Complete 。如果它返回假,那么这个项目就是Incomplete 。我们在前面说过renderItems() 方法是如何工作的。
我们的render() 方法返回renderTabList() 、renderItems() 方法和Add Task 功能,该功能使用createItem() 方法来允许用户添加任务。
在这一点上,你的应用程序应该像下面这样。

第4步:测试
让我们通过运行以下命令来启动我们用Django制作的后端服务器。
cd backend
pipenv shell
python manage.py runserver
让我们通过运行以下命令来启动我们的React应用程序。
npm start
结语
我们已经学会了如何将Django应用程序与作为前端的React整合起来。