为了实现低代码平台,我写了一个拖拽库,顺便学习如何从0发布一个开源库

拖拽库github地址:github.com/bpuns/easy-...

拖拽库说明文档:bpuns.github.io/easy-dnd-do...

前言

一年前,我接到一个需求,需要开发一个低代码平台,调研了下市面上适用于React的拖拽库,本人都感觉使用起来过于复杂,并且体积都挺大的,我就想用原生的拖拽Api,但是原生Api在复杂场景下(无限嵌套,嵌套规则复杂),是不可用的,如果想了解具体遇到的问题,可以看 巨详细!帮你踩坑HTML5拖拽api(下) 这篇文章。因此,我就想基于 Drag and Drop api 封装的一套可以应用与复杂场景下的拖拽库

并且这个库最好不要和任何的前端框架绑定,这样方便移植和扩展,因为是自己封装的,因此可以掌握所有实现细节,开发过程中如果遇到了性能瓶颈,也能最快的找到解决方案,而且我也没完整的发布过一个开源库,正好学习下整个过程

拖拽库实现思路

拖拽上下文

页面中可能会有很多可以拖拽的区域,并且拖拽区域之间互不影响,那么就需要为每个拖拽区域创建一个上下文,那么就可以提供一个方法,用于上下文的创建,为了到时候方便扩展,可以传一个配置项作为参数。

ts 复制代码
interface ProviderConfig { } 

/**
 * 创建拖拽作用域下的基本数据
 * @param {*} config 配置项
 * @returns
 */
function createProvider(config?: ProviderConfig): Context;

拖拽类型

在现实生活中,我们之所以知道一个物品可以放到另外一个物品中,比如食物剩余的包装袋需要放到垃圾桶中,电脑需要放到电脑包里。所以抽到代码中,需要给可以拖拽的dom元素添加一个类型,给需要放置的元素设置允许放置的类型有哪些

ts 复制代码
// 创建拖拽对象
const drag = useDrag({
  config: {
    type: '类型'
  }
})

// 创建放置对象
const drop = useDrop({
  config: {
    acceptType: [ '类型' ]
  }
})

canDrop

光有类型不够,还需要给Drop添加一个方法,判断当前是否允许放置,比如当前Drop元素内部可能只能放一个元素

ts 复制代码
const drop = useDrop({
  config: {
    canDrop: () => true | false
  }
})

Drag api设计

Drag Api的事件需要和h5事件保持一致

ts 复制代码
const drag = useDrag({
  config: {
    type: '类型',
    context
  },
  dragStart(){
    console.log('开始拖拽')
  },
  drag(){
    console.log('拖拽中')
  },
  dragEnd(){
    console.log('结束拖拽')
  }
})

Drop api设计

Drop Api的事件需要和h5事件保持一致

ts 复制代码
const drop = useDrop({
  config: {
    context,
    // 判断是否允许拖拽
    canDrop: () => true | false,
    // 使用Set替换数组
    acceptType: new Set([ '类型' ]),
  },
  dragEnter(){
    console.log('拖拽元素进入')
  },
  dragOver(){
    console.log('拖拽元素在当前元素范围内移动')
  },
  dragLeave(){
    console.log('拖拽元素离开')
  },
  drop(){
    console.log('拖拽元素放置')
  }
})

Vue 与 React 支持

因为在Vue和React中都有上下文的概念,那就好办了,提供个组件包裹需要拖拽的元素,用于传递上下文

  • Vue
vue 复制代码
<template>
  <dnd-provider>
    <component />
  </dnd-provider>
</template>

<script setup lang="ts">
import { DndProvider } from 'easy-dnd/vue'
</script>
  • React
tsx 复制代码
import { DndProvider } from 'easy-dnd/react'

function Example1() {
  return (
    <DndProvider>
      <Component />
    </DndProvider>
  )
}

因为拖拽和放置都需要作用在dom上,那么可以Drag与Drop都可以提供个方法,用于dom的绑定

  • Vue
vue 复制代码
<template>
  <div :ref="drag.dragRef" class="a">
    盒子A
  </div>
</template>

<script setup lang="ts">

import { useDrag } from 'easy-dnd/vue'

const drag = useDrag({
  config: {
    type: 'A'
  },
  dragStart: () => {
    console.log('A 开始拖拽')
  },
  dragEnd: () => {
    console.log('A 结束拖拽')
  }
})

</script>
  • React
tsx 复制代码
import { useDrag } from 'easy-dnd/react'

function A() {

  // 创建拖拽实例
  const drag = useDrag(() => ({
    config: {
      type:  'A'
    },
    // 拖拽开始的回调
    dragStart: () => {
      console.log('A 开始拖拽')
    },
    // 拖拽结束的回调
    dragEnd: () => {
      console.log('A 结束拖拽')
    }
  }))

  return (
    <div
      // 与dom绑定
      ref={drag.dragRef}
    >
      盒子A
    </div>
  )
}

编写打包脚本

随便找个打包工具,我这个库使用的是rollup

js 复制代码
import { rollup } from 'rollup'
import typescript from 'rollup-plugin-typescript2'
import { terser } from 'rollup-plugin-terser'

rollup({
    // 入口配置
    input:   entry,
    // 输出配置
    output: {},
    // 插件配置
    plugins: [
      // 代码压缩
      terser({
        ie8:      false,
        compress: true,
        format:   {
          quote_style: 1
        }
      }),
      // typescript支持
      typescript()
    ],
    // 配置要排除哪些文件,
    external: []
  })

详细配置文件可以看这里

使用vuePress编写Doc并发布到GitHub Pages上

一个开源库,一定需要一个说明文档,那么这里我使用的是 VuePress,官方的介绍如下

VuePress 是一个以 Markdown 为中心的静态网站生成器。你可以使用 Markdown在新窗口打开 来书写内容(如文档、博客等),然后 VuePress 会帮助你生成一个静态网站来展示它们

文档编写完成之后,如果你有自己的服务器,可以把静态资源部署到自己的服务器上,像我这样的白嫖党,可以选择发布到 GitHub Pages 上,可以看Github的官方文档来部署你的说明文档

部署完成的效果就是下面这样啦

编写使用demo

打包脚本和文档有了之后,就需要准备demo了,因为有些人是不会看文档的,直接看demo基本上就可以很快的把一个库的使用方法学会,因此我准备了在 纯原生环境下使用,在Vue下使用,在React下使用的代码示例,点击这里 查看

npm发布

我选择再编写了个js脚本,帮我把package.json,README.md文件全部拷贝到打包文件夹下,这样我就不用编写 .npmignore文件了

  • scripts/build.js
js 复制代码
import { join } from 'node:path'
import process from 'node:process'
import { rollup } from 'rollup'
......

async function buildPackage(_package) {

  console.log(`${name}:开始构建`)

  // 构建代码 ......

  console.log(`${name}:构建结束`)

}

; (async function () {
  // 清空原有的输出路径
  await fs.emptyDir(BUILD_PATH)
  // 打包
  await Promise.allSettled(packages.map(buildPackage))
  // 复制需要提交到npm上的配置文件
  copyNpmConfig()
})()

function copyNpmConfig() {
  // 复制package.json
  const desJson = JSON.parse(fs.readFileSync(join(PROJECT_PATH, 'package.json'), 'utf-8'))
  const newPackageJson = {
    name:       desJson.name,
    version:    desJson.version,
    keywords:   desJson.keywords,
    author:     desJson.author,
    repository: desJson.repository
  }
  fs.writeFileSync(join(BUILD_PATH, 'package.json'), JSON.stringify(newPackageJson, null, 2))
  console.log('package.json完成')
  // 复制README.md
  fs.copyFileSync(
    join(PROJECT_PATH, 'README.md'),
    join(BUILD_PATH, 'README.md')
  )
  console.log('复制README.md完成')
}

最后就是调用推送命令发布。当然,如果你实在懒的话,也可以编写node脚本帮你提交

shell 复制代码
$ npm publish

最后

这个拖拽库是真实可用的,并不是整活,它已经用在了我私人项目中,并且已经使用了快1年多的时间,目前已经趋于稳定,欢迎大家使用并提出宝贵意见!!

相关推荐
代码匠心1 天前
AI 自动编程:一句话设计高颜值博客
前端·ai·ai编程·claude
_AaronWong1 天前
Electron 实现仿豆包划词取词功能:从 AI 生成到落地踩坑记
前端·javascript·vue.js
cxxcode1 天前
I/O 多路复用:从浏览器到 Linux 内核
前端
用户5433081441941 天前
AI 时代,前端逆向的门槛已经低到离谱 — 以 Upwork 为例
前端
JarvanMo1 天前
Flutter 版本的 material_ui 已经上架 pub.dev 啦!快来抢先体验吧。
前端
恋猫de小郭1 天前
AI 可以让 WIFI 实现监控室内人体位置和姿态,无需摄像头?
前端·人工智能·ai编程
哀木1 天前
给自己整一个 claude code,解锁编程新姿势
前端
程序员鱼皮1 天前
GitHub 关注突破 2w,我总结了 10 个涨星涨粉技巧!
前端·后端·github
UrbanJazzerati1 天前
Vue3 父子组件通信完全指南
前端·面试
是一碗螺丝粉1 天前
5分钟上手LangChain.js:用DeepSeek给你的App加上AI能力
前端·人工智能·langchain