你还在使用MVC吗?~开始使用Django x React开发SPA~

本文教程的完整版本第二版已经发布!
https://note.com/uichiyy/n/nadc6f56b01de
我们根据最新版本进行了重新制作,还添加了新的说明,使得学习更加方便!

最近,使用JS框架的网站越来越多。
尤其是React和Vue等JS框架经常被用于单页应用程序(SPA)的开发,不仅能为用户提供更好的体验,还能给开发人员带来许多优势。

目标读者

    • Web開発経験者

 

    • APIを使ったWebアプリケーションを開発したことがある人

 

    • JavaScriptをそこそこ知っててPythonもそこそこ知ってる人

 

    • Djangoをちょっと知っている

 

    MVCもしくはMTVを使った開発をしたことがある人

由于还有更详细的相关文章,所以如果对本文感到困惑或想深入学习的人,请查看这里。
还在使用MVC吗? 〜通过 React 和 Django 开始现代 Web 开发〜

本文中将使用React作为前端,Django作为后端来进行教程的进展。教程将以ToDo应用为例进行讲解。

SPA是指療養地。

SPA被称为单页面应用程序,是一种有效的提升用户体验的方法。此外,它融合了数据绑定、虚拟DOM和组件的三个特点。

数据绑定

使用原生JavaScript来修改值的话,每次都需要运行指定DOM来修改值的处理函数。
但是,使用JS框架之后,只要定义的变量被更新,界面上的值也会随之改变。

虚拟DOM

JS框架有两种类型的DOM,一种是用于在客户端浏览器中进行渲染的DOM,另一种是存在于服务器和DOM之间的虚拟DOM。
虚拟DOM的作用是获取从服务器上抓取的新的虚拟DOM与当前存在的虚拟DOM之间的差异,并将这些差异反映到DOM中。
因此,DOM的更新只会针对有差异的部分,可以加快页面的渲染速度。

成分

在JS框架中,可以将页面元素分解为称为组件的部件单元。这样一来,就可以重复使用组件,而无需编写相同的代码。

在这个教程中,由于只创建了一页网页,可能无法亲身感受到与用户体验相关的好处,但我认为可以感受到在开发方面的好处。

搭建Django环境

首先,我们将从后端开始进行。

请按照以下顺序执行这些命令。

mkdir todo-backend
cd todo-backend
python3 -m venv env
source env/bin/activate
pip install django djangorestframework django-cors-headers
django-admin startproject project .
django-admin startapp todo
python manage.py migrate
python manage.py createsuperuser
python manage.py runserver

请在环境构建好后访问127.0.0.1:8000。
应该会显示初始页面。

配置Django环境。

我们将在settings.py文件中添加插件和跨域设置。跨域设置是为了防止在浏览器中访问API时出现访问被拒绝的情况。

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

MIDDLEWARE = [
   'corsheaders.middleware.CorsMiddleware',
]

# 許可するオリジン
CORS_ORIGIN_WHITELIST = [
   'http://localhost:3000',
]

顺便在项目目录下的URL配置文件中设置API的路由。

from django.contrib import admin
from django.urls import path, include

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

后端实现

我将在待办事项应用程序中进行实现。

from django.db import models

class Todo(models.Model):
   name = models.CharField(max_length=64, blank=False, null=False)
   checked = models.BooleanField(default=False)

   def __str__(self):
       return self.name

进行迁移。

python manage.py makemigrations
python manage.py migrate
from django.contrib import admin
from .models import Todo

@admin.register(Todo)
class Todo(admin.ModelAdmin):
   pass
from rest_framework import serializers
from .models import Todo

class TodoSerializer(serializers.ModelSerializer):
   class Meta:
       model = Todo
       fields = ('id', 'name', 'checked')
from rest_framework import filters, generics, viewsets
from .models import Todo
from .serializer import TodoSerializer

class ToDoViewSet(viewsets.ModelViewSet):
   queryset = Todo.objects.all()
   serializer_class = TodoSerializer
   filter_fields = ('name',)
from rest_framework import routers
from .views import ToDoViewSet
from django.urls import path, include

router = routers.DefaultRouter()
router.register(r'todo', ToDoViewSet)

urlpatterns = [
   path('', include(router.urls)),
]

完成这些步骤后,请访问http://localhost:8000/admin,并添加一些ToDo事项。

搭建React环境

在React的环境搭建中,使用Create React App来完成。

yarn create react-app todo-frontend
cd todo-frontend
yarn start

当您访问 http://localhost:3000 并成功显示页面时,环境已经成功配置完成。

路由

由于React没有路由功能,因此需要安装一个额外的插件。

yarn add react-router-dom

请在src目录下创建一个Router.jsx文件。

import React from 'react';
import { BrowserRouter, Route } from 'react-router-dom';
import Top from '../components/Top';

const Router = () => {
 return (
   <BrowserRouter>
   </BrowserRouter>
 );
};
export default Router;

将路由加载到App.js中。

import React from 'react';
import Router from './configs/Router';

function App() {
 return (
   <Router />
 );
}

export default App;

界面设计

请使用Material UI设计框架来设计界面。它是作为React插件提供的,所以请使用yarn add进行安装。

yarn add @material-ui/core

这是下面图片的成品图。

Screenshot from 2020-02-16 00-50-08.png

实现API

首先,我們將開始實現API。
由於把它放在單個組件中會降低可讀性,所以我們將把API處理分開到不同的文件中實現。
要實現的API處理包括獲取待辦事項清單、創建待辦事項、勾選待辦事項和刪除待辦事項這四個部分。

请创建一个名为src/common/api的文件夹,并在其中创建一个名为todo.js的文件。


const originUrl = 'http://127.0.0.1:8000';

const getTodoList = (() => {
  const url = new URL('/api/todo/', originUrl);
  return new Promise( (resolve, reject) => {
    fetch(url.href)
    .then( res => res.json() )
    .then( json => resolve(json) )
    .catch( () => reject([]) );
  });
});
export default getTodoList;

export const postCreateTodo = (name) => {
  const url = new URL('/api/todo/', originUrl);
  return new Promise( resolve => {
    fetch(url.href, {
      method: 'POST',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        name: name
      })
    })
    .then( res => res.json() )
    .then( data => resolve(data) );
  });
};

export const patchCheckTodo = ((id, check) => {
  const url = new URL(`/api/todo/${id}/`, originUrl);
  fetch(url.href, {
    method: 'PATCH',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      checked: check
    })
  });
});

export const deleteTodo = ((id) => {
  const url = new URL(`/api/todo/${id}/`, originUrl);
  fetch(url.href, { method: 'DELETE' });
});

组件的实现 de

接下来,我们将实现组件。

import React, { useEffect, useState } from 'react';
import Button from '@material-ui/core/Button';
import Box from '@material-ui/core/Box';
import FormGroup from '@material-ui/core/FormGroup';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Checkbox from '@material-ui/core/Checkbox';
import Container from '@material-ui/core/Container';
import { makeStyles } from '@material-ui/core/styles';
import TextField from '@material-ui/core/TextField';
import getToDoList, { postCreateTodo, patchCheckTodo, deleteTodo } from '../../common/api/todo';

const useStyles = makeStyles(theme => ({
  todoTextField: {
    marginRight: theme.spacing(1)
  }
}));

const Top = () => {
  const classes = useStyles();
  const [todoList, setTodoList] = useState([]);
  const [todo, setTodo] = useState('');

  useEffect(() => {
    (async () => {
      const list = await getToDoList();
      setTodoList(list);
    })();
  }, []);

  const handleCreate = async () => {
    if ( todo === '' || todoList.some( value => todo === value.name ) ) return;
    const createTodoResponse = await postCreateTodo(todo);
    setTodoList(todoList.concat(createTodoResponse));
  };

  const handleSetTodo = (e) => {
    setTodo(e.target.value);
  };

  const handleCheck = (e) => {
    const todoId = e.target.value;
    const checked = e.target.checked;
    const list = todoList.map( (value, index) => {
      if (value.id.toString() === todoId) {
        todoList[index].checked = checked;
      }
      return todoList[index];
    });
    setTodoList(list)
    patchCheckTodo(todoId, checked);
  }

  const handleDelete = (e) => {
    const todoId = e.currentTarget.dataset.id;
    const list = todoList.filter( value => value['id'].toString() !== todoId);
    setTodoList(list);
    deleteTodo(todoId);
  };

  return (
    <Container maxWidth="xs">
      <Box display="flex" justifyContent="space-between" mt={4} mb={4}>
        <TextField className={classes.todoTextField} label="やること" variant="outlined" size="small" onChange={handleSetTodo} />
        <Button variant="contained" color="primary" onClick={handleCreate}>作成</Button>
      </Box>
      <FormGroup>
        {todoList.map((todo, index) => {
          return (
            <Box key={index} display="flex" justifyContent="space-between" mb={1}>
              <FormControlLabel
                control={
                  <Checkbox
                    checked={todo.checked}
                    onChange={handleCheck}
                    value={todo.id}
                    color="primary"
                  />
                }
                label={todo.name}
              />
              <Button variant="contained" color="secondary" data-id={todo.id} onClick={handleDelete}>削除</Button>
            </Box>
          )
        })}
      </FormGroup>
    </Container>
  )
};
export default Top;

最后

我已经制作了一个ToDo应用程序,但仅凭本文的内容仍无法实际使用,因此我打算介绍一下后续将要托管的部分。

如果有错别字或错误,请与我们联系。
由于我们将源代码上传到GitHub上,请随意使用如有需要。

前端 (Front-end)
https://github.com/uichi/todo-frontend

后端
https://github.com/uichi/todo-backend

请提供以下内容的中文本地语言解释:”参考”

React 公式:用于构建用户界面的 JavaScript 库
Material UI 公式:一个在 React 基础上构建的开源 UI 组件库
Django 公式:一个高级的 Python Web 框架,用于快速开发安全可靠的网站
Django REST framework 公式:专为 Django 开发的强大且灵活的 Web API 框架

bannerAds