介绍
在本教程中,您将使用django创建一个英雄构建器应用程序并进行反应。英雄构建器将允许用户构建角色并在诸如强度,敏捷和智能之类的属性上分发点。
Django是用Python编写的高级开源网络框架,可鼓励快速开发和清洁,务实的设计。它于2005年发行,遵循“电池”的理念,为开发人员提供了一套丰富的工具和公用事业,包括一个ORM(对象相关的映射),管理员界面和表单处理,以命名很少。 Django强调了组件的干燥(不要重复自己)原理和可重复使用性,从而使开发人员能够使用更少的代码构建可靠,可扩展和可维护的Web应用程序。
React通常称为React.js,是Facebook开发的开源JavaScript库,用于构建用户界面或UI组件。它在2013年推出,允许开发人员使用基于组件的体系结构构建复杂的交互式UI。 React中的每个组件都会管理其自己的状态并将其渲染到DOM,从而更容易开发应用程序的模块化件。多年来,React已成为现代网络开发的基础工具,为当今许多最受欢迎的网站和应用程序提供了动力。
在此应用程序中,Django框架将服务于前端,而React将接管需要动态交互的页面的特定部分。
先决条件
要构建此应用程序,您需要完成以下内容:
- 安装node.js和npm
- 安装python3
设置Django项目
在本节中,您将设置Django项目并验证所有先决条件是否就位。
首先使用 django-admin 工具来创建一个新的django项目。
django-admin startproject hero_builder
现在在 hero_builder 目录中创建一个新应用程序。
cd hero_builder
python3 manage.py startapp hero
运行SQLite开发数据库的初始迁移。
python3 manage.py migrate
启动开发服务器,以验证到目前为止的所有功能。
python3 manage.py runserver
您现在应该能够访问 http://localhost:8000 。 Django将为占位符页面提供。
注册英雄申请
您现在应该注册以较早的步骤创建的英雄应用程序。打开 hero_builder/settings.py 在编辑器中的文件,然后添加英雄 app installed_apps list。
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"hero",
]
将更改保存到 settings.py 文件。
定义字符模型
英雄应用程序将具有一个字符模型,可以存储跨属性的点的分布。 Open HERIO/MODEL.PY 并添加以下内容。
from django.db import models
class Character(models.Model):
strength = models.PositiveIntegerField(default=20)
dexterity = models.PositiveIntegerField(default=20)
health = models.PositiveIntegerField(default=20)
intelligence = models.PositiveIntegerField(default=20)
charisma = models.PositiveIntegerField(default=20)
name = models.CharField(max_length=200)
def __str__(self):
return self.name
现在您将添加并运行创建字符模型所需的迁移。
python manage.py makemigrations hero
运行迁移:
python manage.py migrate hero
设置django表格和视图
英雄应用程序视图将使用简单的Django表单类来验证字符数据。打开英雄/forms.py 并添加以下内容。
from django import forms
class CharacterForm(forms.Form):
strength = forms.IntegerField(min_value=0, initial=20)
dexterity = forms.IntegerField(min_value=0, initial=20)
health = forms.IntegerField(min_value=0, initial=20)
intelligence = forms.IntegerField(min_value=0, initial=20)
charisma = forms.IntegerField(min_value=0, initial=20)
def clean(self):
cleaned_data = super().clean()
total = (
cleaned_data.get("strength", 0)
+ cleaned_data.get("dexterity", 0)
+ cleaned_data.get("health", 0)
+ cleaned_data.get("intelligence", 0)
+ cleaned_data.get("charisma", 0)
)
if total > 100:
raise forms.ValidationError("The total attributes must not exceed 100.")
return cleaned_data
表单类将验证每个属性是一个正数,总数不超过100点。
接下来,您将使用此表单类作为字符视图的一部分,浏览器将在其中提出表单发布请求以创建字符。打开英雄/views.py 文件并添加以下内容。
from django.shortcuts import render, redirect, get_object_or_404
from .models import Character
from .forms import CharacterForm
def home(request):
form = CharacterForm()
return render(request, "hero/home.html", {"form": form})
def character(request, character_id=None):
if request.method == "POST":
form = CharacterForm(request.POST)
if form.is_valid():
character = Character(
strength=form.cleaned_data["strength"],
dexterity=form.cleaned_data["dexterity"],
health=form.cleaned_data["health"],
intelligence=form.cleaned_data["intelligence"],
charisma=form.cleaned_data["charisma"],
)
character.save()
return redirect("character_with_id", character_id=character.pk)
else:
character = get_object_or_404(Character, id=character_id)
return render(request, "hero/character.html", {"character": character})
主页视图呈现角色创建页面。字符页将在get请求期间在只读表中渲染创建字符的属性,并在邮政请求期间创建一个新字符。
定义Django模板
现在,您将定义英雄应用中使用的Django模板。
base.html 模板将充当基础,以便所有常见的脚本和样式导入发生在一个地方。
开放英雄/模板/英雄/base.html 并添加以下内容。
<!DOCTYPE html>
<html lang="en">
{% load static %}
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Hero Builder</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
{% block extra_js %}{% endblock extra_js %}
{% block extra_css %}{% endblock extra_css %}
</head>
<body>
{% if messages %}
{% for message in messages %}
<div class="alert alert-danger alert-bottom alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% endif %}
<nav class="navbar navbar-expand-lg">
<div class="container-fluid">
Hero Builder
</div>
</nav>
<div>
{% block content %}{% endblock content %}
</div>
</body>
</html>
此模板加载了React和Reactdom库的缩小生产源代码。稍后,当您将要在接下来的几个步骤中构建的React Web组件时,这将很重要。
现在您将编写 home.html 模板,其中包含代表英雄编辑器的反应组件。
{% extends "hero/base.html" %}
{% load static %}
{% block extra_js %}
<script src="{% static 'hero/js/main.js' %}"></script>
{% endblock %}
{% block extra_css %}
<link href="{% static 'hero/css/main.css' %}" rel="stylesheet">
{% endblock %}
{% block content %}
<div class="d-flex justify-content-center align-items-center vh-100">
<div class="w-75 p-4 mb-4 border">
<hero-attribute-editor></hero-attribute-editor>
<form method="POST" action="{% url 'character' %}">
{% csrf_token %}
{{ form.strength.as_hidden }}
{{ form.dexterity.as_hidden }}
{{ form.health.as_hidden }}
{{ form.intelligence.as_hidden }}
{{ form.charisma.as_hidden }}
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</div>
{% endblock %}
如果您现在要查看浏览器中呈现的此页面,则 hero-attribute-editor 标签将无法使用。您需要在接下来的几个步骤中使用React创建此Web组件。 main.js 和 main.css 文件将包含英雄编辑器标签的React构建输出。
最后,您将创建 cartem.html 模板。此模板只允许用户查看他们创建的英雄的属性。 OPEN 英雄/模板/英雄/角色。
{% extends "hero/base.html" %}
{% load static %}
{% block content %}
<div class="d-flex justify-content-center align-items-center vh-100">
<div class="w-75 p-4 mb-4 border">
<table class="table">
<thead>
<tr>
<th>Attribute</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td>Strength</td>
<td>{{ character.strength }}</td>
</tr>
<tr>
<td>Dexterity</td>
<td>{{ character.dexterity }}</td>
</tr>
<tr>
<td>Health</td>
<td>{{ character.health }}</td>
</tr>
<tr>
<td>Intelligence</td>
<td>{{ character.intelligence }}</td>
</tr>
<tr>
<td>Charisma</td>
<td>{{ character.charisma }}</td>
</tr>
</tbody>
</table>
</div>
</div>
{% endblock %}
设置React项目
在本节中,您将设置React项目并创建一个用于Django模板中的Web组件。
首先创建您将用于包含项目的目录。
mkdir hero-attribute-editor
现在创建一个 package.json 列出项目依赖项并构建命令。
{
"name": "hero-attribute-editor",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "webpack"
},
"author": "",
"license": "ISC",
"dependencies": {
"@r2wc/react-to-web-component": "^2.0.2",
"rc-slider": "^10.2.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@babel/core": "^7.22.10",
"@babel/preset-env": "^7.22.10",
"@babel/preset-react": "^7.22.5",
"babel-loader": "^9.1.3",
"css-loader": "^6.8.1",
"style-loader": "^3.3.3",
"webpack-cli": "^5.1.4"
}
}
这里最重要的依赖性是 r2wc/react to-web-component 库。该库充当反应组件与Web Component API之间的桥梁。例如,在Web组件介质中工作时,React Prop的概念需要特殊处理。
接下来创建 index.js 是 package.json 文件中定义的入口点。 index.js 文件应包含以下内容。
import React from 'react';
import r2wc from '@r2wc/react-to-web-component';
import AttributeEditor from './components/AttributeEditor';
const wcAttributeEditor = r2wc(AttributeEditor, { props: {} });
customElements.define("hero-attribute-editor", wcAttributeEditor);
r2wc 库将 attributeEditor 组件包装并创建Web组件兼容对象。然后,您使用自定义功能向浏览器注册该Web组件。 customElements函数是Web组件API的一部分。
下一个文件使用React实现了 AttributeEditor 组件。该组件呈现一系列代表每个字符属性的滑块。每个属性最多可以分配给它100点,但是只有100点可用。当使用所有点时,分配给属性的任何其他点将自动从其他属性中扣除。
该组件将在每个属性更改上发布自定义事件。稍后,我们将在Django模板中看到这些事件是如何截获的。
将以下内容放在组件/attributeeditor.jsx 文件中。
import React, { useState } from "react";
import Slider from "rc-slider";
import "rc-slider/assets/index.css";
import "./AttributeEditor.css";
const TOTAL_POINTS = 100;
function AttributeEditor() {
const [attributes, setAttributes] = useState({
Strength: 20,
Dexterity: 20,
Health: 20,
Intelligence: 20,
Charisma: 20,
});
const remainingPoints =
TOTAL_POINTS - Object.values(attributes).reduce((a, b) => a + b, 0);
const handleSliderChange = (attribute, value) => {
setAttributes((prev) => {
const newAttributes = { ...prev, [attribute]: value };
let totalUsed = Object.values(newAttributes).reduce((a, b) => a + b, 0);
if (totalUsed > TOTAL_POINTS) {
let excess = totalUsed - TOTAL_POINTS;
let attributesToAdjust = Object.keys(newAttributes).filter(
(key) => key !== attribute
);
while (excess > 0) {
for (let key of attributesToAdjust) {
if (newAttributes[key] > 0 && excess > 0) {
newAttributes[key] -= 1;
excess -= 1;
}
}
}
}
window.dispatchEvent(
new CustomEvent("heroAttributesChange", {
detail: newAttributes,
bubbles: true,
composed: true,
})
);
return newAttributes;
});
};
return (
<div>
<div className="total">Total Points Left: {remainingPoints}</div>
{Object.entries(attributes).map(([attribute, value]) => (
<div className="attribute" key={attribute}>
<label htmlFor={attribute}>{attribute}</label>
<Slider
value={value}
min={0}
max={100}
onChange={(val) => handleSliderChange(attribute, val)}
/>
</div>
))}
</div>
);
}
export default AttributeEditor;
attributeEditor 包含一些对CSS类的引用。在组件/attributeeditor.css 上创建一个新文件,然后插入以下内容。
.attribute {
margin: 65px 0;
}
.attribute label {
display: block;
margin-bottom: 10px;
}
.total {
font-weight: bold;
text-align: center;
margin-bottom: 30px;
}
babel需要一个名为 .babelrc 的配置文件,其中您将定义预设反应是已加载的预设之一。 React预设加载了能够将JSX转换为JavaScript的最终构建输出的插件集合。
{
"presets": [
"@babel/preset-env",
["@babel/preset-react", {"runtime": "automatic"}]
]
}
最后,您将创建一个 webpack.config.js 文件来指示WebPack如何构建项目。
配置将 index.js 定义为构建入门点,并设置了WebPack来编译其找到的任何JSX或CSS文件。配置还从编译的输出中剥离了React运行时,因为Django模板将使用脚本标签从CDN下载React Runtime。
const path = require('path');
module.exports = {
entry: './index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index.js',
libraryTarget: 'umd'
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: 'babel-loader'
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
},
resolve: {
extensions: ['.js', '.jsx']
},
externals: {
'react': 'React',
'react-dom': 'ReactDOM'
}
};
您现在应该能够构建React项目。您可以使用 npm 命令。
npm run build
这应该导致 dist 目录,其中包含 index.js 构建文件。整个React组件及其CSS被编译到此文件中。
现在,您应该将此输出文件复制到适当的Django静态目录中。
cp dist/index.js ../hero_builder/hero/static/hero/js/main.js
启动Django dev服务器,如果它尚未运行,并再次访问 http://localhost:8000 再次查看应用程序。
在Django模板中拦截自定义事件
难题的最后一部分是从 Hero-attribute-editor 中收集数据,并将其发布到Django。
django对Web组件一无所知,因此您需要构造隐藏表单以发布数据。每当收到自定义事件时
打开 hero_builder/hero/hero/segplates/hero/home.html 模板并使用以下内容进行更新。
{% extends "hero/base.html" %}
{% load static %}
{% block extra_js %}
<script src="{% static 'hero/js/main.js' %}"></script>
{% endblock %}
{% block extra_css %}
<link href="{% static 'hero/css/main.css' %}" rel="stylesheet">
{% endblock %}
{% block content %}
<div class="d-flex justify-content-center align-items-center vh-100">
<div class="w-75 p-4 mb-4 border">
<hero-attribute-editor></hero-attribute-editor>
<form method="POST" action="{% url 'character' %}">
{% csrf_token %}
{{ form.strength.as_hidden }}
{{ form.dexterity.as_hidden }}
{{ form.health.as_hidden }}
{{ form.intelligence.as_hidden }}
{{ form.charisma.as_hidden }}
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</div>
<script>
const attributes = [
"Strength",
"Dexterity",
"Health",
"Intelligence",
"Charisma"
];
window.addEventListener("heroAttributesChange", (e) => {
for (const attribute of attributes) {
const lowercasedAttribute = attribute.toLowerCase();
const value = e.detail[attribute];
const inputElement = document.getElementById(`id_${lowercasedAttribute}`);
if (inputElement) {
inputElement.value = value;
}
}
});
</script>
{% endblock %}
模板中的最终脚本标签将从Web组件中收听 hereatributeschange 事件,并更新隐藏的表单。在表单提交中,Django将处理表格,因为它将以任何其他形式。
GitHub存储库
您可以找到Django application on GitHub here的代码。英雄属性编辑器组件can also be found on GitHub的代码。
结论
您现在应该有一个工作示例,即在Django应用程序中包括基于React的Web组件。这种方法仍然使用Django的模板渲染,但可以在需求需要更多互动性的情况下进行逐步增强。