React+TypeScript 任务看板:原生拖拽 API 实战

原文:https://dev.to/codewithsadee/how-to-build-a-task-management-app-with-react-typescript-106o(作者 @codewithsadee)

想不想做一个类似 Trello 或 Jira 的任务看板,能把卡片随意挪动?这篇教程就从零开始手写一个。做完的效果是:点住一张任务卡片,把它拖过屏幕,松手放进一个全新的列里。

最妙的一点是:整个过程不下载任何重量级拖拽库,只使用浏览器原生自带的拖拽能力。

项目结构与配置

先看项目文件,整套配置追求的就是快、省事:

  • 用 Vite 跑本地开发服务器,对应 vite.config.ts 文件。
  • 代码用 TypeScript 编写,相关规则集中在 tsconfig.json 里。
  • 包管理交给 pnpm,它会生成 pnpm-lock.yaml 文件。
  • 为了让界面保持整洁,按钮、输入框、文本域这些 UI 组件统一放进一个 UI 文件夹,另外还有一个 theme-provider.tsx 文件负责管理配色主题。

让任务卡片可拖动

要实现拖拽,先写一个 TaskCard.tsx 组件。想让一个元素可拖动,只需在 HTML 标签上加上 draggable 这个属性,然后通过 onDragStart 事件告诉浏览器“现在拖着的是什么”。

任务卡片的核心代码如下:

// src/components/TaskCard.tsx
export default function TaskCard({ task }) {
  // 按下鼠标开始拖动的那一刻执行
  const handleDragStart = (event) => {
    // 保存任务 ID,让浏览器记住我们手里拿的是哪个任务
    event.dataTransfer.setData("taskId", task.id);
  };

  return (
    <div 
      draggable 
      onDragStart={handleDragStart}
      className="p-4 mb-3 bg-white rounded shadow cursor-grab"
    >
      <p>{task.title}</p>
    </div>
  );
}


在新列里接住任务

任务拖起来了,还得有地方放下。为此再写一个 DropBox.tsx 组件,并把它放进主组件 Column.tsx 里。

一个合格的放置区要挂两个事件:onDragOver 和 onDrop。这里最关键的一个技巧是:悬停时必须阻止浏览器的默认行为(preventDefault),否则浏览器根本不允许你把元素放下来。

放置区的写法如下:

// src/components/DropBox.tsx
export default function DropBox({ onTaskDropped }) {
  // 告诉浏览器:"这里允许放置元素!"
  const handleDragOver = (event) => {
    event.preventDefault(); 
  };

  // 松开鼠标按键时执行
  const handleDrop = (event) => {
    // 取出之前保存的任务 ID
    const droppedTaskId = event.dataTransfer.getData("taskId");
    
    // 把这个 ID 传给应用,让它去更新界面
    onTaskDropped(droppedTaskId);
  };

  return (
    <div 
      onDragOver={handleDragOver}
      onDrop={handleDrop}
      className="w-full min-h-[200px] p-4 bg-gray-100 rounded-lg"
    >
      {/* 任务卡片会显示在这里 */}
    </div>
  );
}


添加新任务

光能拖还不够,还得能录入新的待办事项。为此再做一个 TaskCreateField.tsx 组件,在里面输入你的下一个点子,直接添加到看板上。完整的界面效果和更多细节,可以参考作者的配套视频演示。

原文:https://dev.to/codewithsadee/how-to-build-a-task-management-app-with-react-typescript-106o(作者 @codewithsadee)

发布评论
全部评论(0)