欢迎光临
我们一直在努力

轻量本地响应式清单:基于 Dexie.js 的原子事务与拖拽排序

轻量本地响应式清单:基于 Dexie.js 的原子事务与拖拽排序

封面信息图

在构建本地优先(Local-First)的极简清单工具时,前端工程师经常会遇到两个核心技术痛点:

  • 拖拽重排性能卡顿与数据错乱(Drag & Drop Index Corruption):用户拖拽一个待办项调整顺序时,如果直接用简单的索引重写,并发修改容易导致多个任务拥有相同的序号(Order Collision)。
  • 多操作原子性缺失(Transaction Inconsistency):批量标记完成、清空已归档便签时,如果中途发生异常,容易导致本地 IndexedDB 数据库产生孤儿记录。
  • 借助 Dexie.js 的事务支持(db.transaction)配合浮点数排序算法(Lexorank / Fractional Indexing),我们可以用极简的代码实现 0 网络依赖、0 掉帧、具备强原子性保障的响应式清单数据层。

    +——————————————————————–+
    | 基于 Dexie.js 与浮点排序算法的响应式清单架构 |
    +——————————————————————–+
    | [用户拖拽任务 B 移动到任务 A (order: 1000) 与 任务 C (order: 2000) 之间]|
    | | |
    | v (无需全量重写数组,单次修改计算浮点中点值: (1000 + 2000) / 2 = 1500)|
    | [Dexie 原子读写事务: db.transaction('rw', db.todos, async () => …)]|
    | ├── 1. 更新任务 B: orderIndex = 1500 (单行更新, 耗时 < 1ms) |
    | └── 2. 事务自动提交 -> 触发 useLiveQuery 原子级响应式重新渲染 |
    | | |
    | [全过程仅修改 1 条数据库记录,0 全量数组遍历重排,性能达到 $O(1)$] |
    +——————————————————————–+

    1. 声明强类型本地清单数据库

    import Dexie, { Table } from "dexie";

    export interface TodoItem {
    id: string;
    text: string;
    orderIndex: number; // 浮点排序索引
    completed: boolean;
    createdAt: number;
    completedAt?: number;
    }

    export class TodoDatabase extends Dexie {
    todos!: Table<TodoItem, string>;

    constructor() {
    super("TideTodoDB");
    this.version(1).stores({
    todos: "id, orderIndex, completed, createdAt",
    });
    }
    }

    export const todoDb = new TodoDatabase();

    2. $O(1)$ 浮点插值排序与原子事务实现

    export async function reorderTodoItem(
    draggedId: string,
    prevItem: TodoItem | null,
    nextItem: TodoItem | null
    ) {
    let newOrder = 1000;

    if (!prevItem && nextItem) {
    // 拖到最顶部:取下一项 order 的一半
    newOrder = nextItem.orderIndex / 2;
    } else if (prevItem && !nextItem) {
    // 拖到最底部:取上一项 order + 1000
    newOrder = prevItem.orderIndex + 1000;
    } else if (prevItem && nextItem) {
    // 插入两项中间:取算术平均中点
    newOrder = (prevItem.orderIndex + nextItem.orderIndex) / 2;
    }

    // 执行原子事务单行更新
    await todoDb.transaction("rw", todoDb.todos, async () => {
    await todoDb.todos.update(draggedId, { orderIndex: newOrder });
    });
    }

    3. React useLiveQuery 响应式列表渲染

    import React from "react";
    import { useLiveQuery } from "dexie-react-hooks";
    import { todoDb } from "@/db/todoDb";

    export const ResponsiveTodoList: React.FC = () => {
    // 响应式订阅:永远按 orderIndex 升序返回
    const todos = useLiveQuery(() => todoDb.todos.orderBy("orderIndex").toArray(), []);

    if (!todos) return <div className="text-xs text-stone-400">正在翻开清单本…</div>;

    return (
    <div className="space-y-2">
    {todos.map((item) => (
    <div
    key={item.id}
    className="flex items-center justify-between p-3 bg-[#FAF7F0] border border-[#2D2B2A] rounded-xl shadow-[2px_2px_0px_#2D2B2A]"
    >
    <span className={`text-sm ${item.completed ? "line-through text-stone-400" : "text-[#2D2B2A]"}`}>
    {item.text}
    </span>
    </div>
    ))}
    </div>
    );
    };

    用最高效的数学与事务机制,让本地数据层在任何高频拖拽下都稳如磐石。

    赞(0)
    未经允许不得转载:171主机测评 » 轻量本地响应式清单:基于 Dexie.js 的原子事务与拖拽排序
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址