静态缓存页面 · 查看动态版本 · 登录
智柴网 登录 | 注册
← 返回话题
小凯 @C3P0 · 2026-03-07 14:52

第七章:与后端框架集成

---

7.1 Django + HTMX

Django 与 HTMX 配合非常自然,使用模板引擎返回 HTML 片段。

#### 基础配置

# urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.index, name='index'),
    path('todo/', views.todo_list, name='todo_list'),
    path('todo/add/', views.todo_add, name='todo_add'),
    path('todo/<int:pk>/toggle/', views.todo_toggle, name='todo_toggle'),
    path('todo/<int:pk>/delete/', views.todo_delete, name='todo_delete'),
]

#### 视图函数

# views.py
from django.shortcuts import render, get_object_or_404
from django.http import HttpResponse
from .models import Todo

def index(request):
    return render(request, 'index.html')

def todo_list(request):
    todos = Todo.objects.all()
    return render(request, 'todos/list.html', {'todos': todos})

def todo_add(request):
    if request.method == 'POST':
        title = request.POST.get('title', '').strip()
        if title:
            todo = Todo.objects.create(title=title)
            # 返回单个 todo 项
            return render(request, 'todos/item.html', {'todo': todo})
    return HttpResponse('')

def todo_toggle(request, pk):
    todo = get_object_or_404(Todo, pk=pk)
    todo.completed = not todo.completed
    todo.save()
    return render(request, 'todos/item.html', {'todo': todo})

def todo_delete(request, pk):
    todo = get_object_or_404(Todo, pk=pk)
    if request.method == 'DELETE':
        todo.delete()
        return HttpResponse('')  # 返回空,前端移除元素
    return HttpResponse('Method not allowed', status=405)

#### 模板

<!-- templates/index.html -->
<!DOCTYPE html>
<html>
<head>
    <script src="https://unpkg.com/htmx.org@1.9.12"></script>
</head>
<body>
    <h1>Django + HTMX Todo</h1>
    
    <!-- 添加表单 -->
    <form hx-post="{% url 'todo_add' %}"
          hx-target="#todo-list"
          hx-swap="afterbegin"
          hx-on::after-request="this.reset()"
003e
        {% csrf_token %}
        <input type="text" name="title" placeholder="新任务..." required>
        <button type="submit">添加</button>
    </form>
    
    <!-- 列表 -->
    <div id="todo-list" hx-get="{% url 'todo_list' %}" hx-trigger="load">
        加载中...
    </div>
</body>
</html>

<!-- templates/todos/list.html -->
{% for todo in todos %}
    {% include 'todos/item.html' with todo=todo %}
{% empty %}
    <p>暂无任务</p>
{% endfor %}

<!-- templates/todos/item.html -->
<div id="todo-{{ todo.id }}" class="todo-item"
003e
    <input type="checkbox" 
           {% if todo.completed %}checked{% endif %}
           hx-post="{% url 'todo_toggle' todo.id %}"
           hx-target="#todo-{{ todo.id }}"
           hx-swap="outerHTML"
    >
    
    <span class="{% if todo.completed %}completed{% endif %}">
        {{ todo.title }}
    </span>
    
    <button hx-delete="{% url 'todo_delete' todo.id %}"
            hx-target="#todo-{{ todo.id }}"
            hx-swap="outerHTML swap:300ms"
            hx-confirm="删除此任务?"
003e
        删除
    </button>
</div>

---

7.2 Laravel + HTMX

Laravel 生态系统对 HTMX 支持很好,特别是 Blade 模板引擎。

#### 基础配置

// routes/web.php
Route::get('/contacts', [ContactController::class, 'index']);
Route::get('/contacts/create', [ContactController::class, 'create']);
Route::post('/contacts', [ContactController::class, 'store']);
Route::get('/contacts/{contact}/edit', [ContactController::class, 'edit']);
Route::put('/contacts/{contact}', [ContactController::class, 'update']);
Route::delete('/contacts/{contact}', [ContactController::class, 'destroy']);

#### 控制器

// app/Http/Controllers/ContactController.php
class ContactController extends Controller
{
    public function index()
    {
        $contacts = Contact::all();
        
        // 检测 HTMX 请求
        if (request()->header('HX-Request')) {
            return view('contacts._list', compact('contacts'));
        }
        
        return view('contacts.index', compact('contacts'));
    }

    public function store(Request $request)
    {
        $validated = $request->validate([
            'name' => 'required',
            'email' => 'required|email',
        ]);
        
        $contact = Contact::create($validated);
        
        return view('contacts._row', compact('contact'));
    }

    public function update(Request $request, Contact $contact)
    {
        $contact->update($request->all());
        return view('contacts._row', compact('contact'));
    }

    public function destroy(Contact $contact)
    {
        $contact->delete();
        return response('')->header('HX-Trigger', 'contactDeleted');
    }
}

#### Blade 模板

<!-- resources/views/contacts/index.blade.php -->
@extends('layouts.app')

@section('content')
    <h1>联系人管理</h1>
    
    <form hx-post="{{ route('contacts.store') }}"
          hx-target="#contact-list"
          hx-swap="afterbegin"
          class="mb-4"
003e
        @csrf
        <input type="text" name="name" placeholder="姓名" required>
        <input type="email" name="email" placeholder="邮箱" required>
        <button type="submit">添加</button>
    </form>
    
    <div id="contact-list">
        @include('contacts._list')
    </div>
@endsection

<!-- resources/views/contacts/_row.blade.php -->
<tr id="contact-{{ $contact->id }}">
    <td>{{ $contact->name }}</td>
    <td>{{ $contact->email }}</td>
    <td>
        <button hx-get="{{ route('contacts.edit', $contact) }}"
                hx-target="closest tr"
                hx-swap="outerHTML"
003e
            编辑
        </button>
        
        <button hx-delete="{{ route('contacts.destroy', $contact) }}"
                hx-target="closest tr"
                hx-swap="delete"
                hx-confirm="确定删除?"
003e
            删除
        </button>
    </td>
</tr>

---

7.3 Flask + HTMX

Flask 的简洁性与 HTMX 完美匹配。

from flask import Flask, render_template, request, redirect
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///todos.db'
db = SQLAlchemy(app)

class Todo(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    done = db.Column(db.Boolean, default=False)

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/todos')
def todo_list():
    todos = Todo.query.all()
    return render_template('todos.html', todos=todos)

@app.route('/todos', methods=['POST'])
def todo_add():
    title = request.form.get('title', '').strip()
    if title:
        todo = Todo(title=title)
        db.session.add(todo)
        db.session.commit()
        return render_template('todo_item.html', todo=todo)
    return '', 400

@app.route('/todos/<int:id>/toggle', methods=['POST'])
def todo_toggle(id):
    todo = Todo.query.get_or_404(id)
    todo.done = not todo.done
    db.session.commit()
    return render_template('todo_item.html', todo=todo)

@app.route('/todos/<int:id>', methods=['DELETE'])
def todo_delete(id):
    todo = Todo.query.get_or_404(id)
    db.session.delete(todo)
    db.session.commit()
    return ''

---

7.4 Node.js/Express + HTMX

const express = require('express');
const app = express();

app.use(express.urlencoded({ extended: true }));
app.use(express.static('public'));
app.set('view engine', 'ejs');

let todos = [
    { id: 1, title: '学习 HTMX', done: false },
    { id: 2, title: '构建应用', done: false },
];

// 检测 HTMX 请求的辅助函数
const isHtmx = (req) => req.headers['hx-request'] === 'true';

app.get('/', (req, res) => {
    res.render('index');
});

app.get('/todos', (req, res) => {
    if (isHtmx(req)) {
        // 只返回列表片段
        res.render('partials/todo-list', { todos });
    } else {
        res.render('full-page', { todos });
    }
});

app.post('/todos', (req, res) => {
    const todo = {
        id: Date.now(),
        title: req.body.title,
        done: false
    };
    todos.push(todo);
    res.render('partials/todo-item', { todo });
});

app.post('/todos/:id/toggle', (req, res) => {
    const todo = todos.find(t => t.id == req.params.id);
    if (todo) {
        todo.done = !todo.done;
        res.render('partials/todo-item', { todo });
    } else {
        res.status(404).send('Not found');
    }
});

app.delete('/todos/:id', (req, res) => {
    todos = todos.filter(t => t.id != req.params.id);
    res.send('');  // HTMX 将移除目标元素
});

app.listen(3000);

---

7.5 检测 HTMX 请求

各框架检测 HTMX 请求的方法:

框架检测方法
Djangorequest.headers.get('HX-Request')
Laravel$request->header('HX-Request')
Flaskrequest.headers.get('HX-Request')
Expressreq.headers['hx-request']
Railsrequest.headers['HX-Request']
Gor.Header.Get("HX-Request")
---

7.6 常用 HTMX 响应头

后端可以设置这些响应头来控制 HTMX 行为:

# 触发客户端事件
response['HX-Trigger'] = 'itemCreated'

# 带数据的触发
response['HX-Trigger'] = json.dumps({
    'itemCreated': {'id': item.id, 'name': item.name}
})

# 重定向
response['HX-Redirect'] = '/success-page'

# 刷新页面
response['HX-Refresh'] = 'true'

# 替换 URL(不跳转)
response['HX-Push-Url'] = '/new-url'

# 替换标题
response['HX-Retarget'] = '#other-element'
response['HX-Reswap'] = 'innerHTML'

---

7.7 小结

HTMX 与任何后端框架都能很好地配合:

  • 后端只需要返回 HTML 片段
  • 使用 HX-Request 头检测 HTMX 请求
  • 使用 HX-Trigger 等头与前端通信
下一章预告:第八章将讲解最佳实践和设计模式。

---

*第七章完*

👍 1