HTML拖放场景下调用axios POST请求,parcel打包后报sendNewCard未定义
已实现的基础能力
目前已完成可正常运行的看板卡片拖放逻辑,核心代码位于response.js:
function allowDrop(ev) { ev.preventDefault(); } function drag(ev) { ev.dataTransfer.setData("text", ev.target.id); } const drop = async(ev, el) => { ev.preventDefault(); var data = ev.dataTransfer.getData("text"); el.appendChild(document.getElementById(data)); newSection = el.parentElement.childNodes[0].childNodes[0].textContent; sortItems(el); }
- 拖放逻辑通过PUG渲染生成的HTML内联事件绑定,对应代码为:
ondrop='drop(event, this)' ondragover='allowDrop(event)'
待实现需求
拖放操作完成后,需要同步发送Axios POST请求,将卡片新的所属看板列信息同步到后端,请求结构为:
const res = await axios({ method: 'POST', url: 'http://127.0.0.1:3000/api/1/projects/updateCardPlace', data: { cardID, // 暂未获取该参数,可暂时忽略 newSection } });
开发环境配置
- 打包工具使用Parcel,
package.json中的JS构建脚本配置为:
"build:js": "parcel watch ./public/js/index.js --out-dir --public-url ./public/js --out-file bundle.js"
- 已确认PUG渲染的HTML页面正确引入了构建生成的
bundle.js文件
已尝试方案与报错现象
先后尝试两种自定义请求方法的接入方式:
- 将原本定义在
login.js中的sendNewCard方法导入到入口文件index.js - 直接将
sendNewCard方法写入打包生成的bundle.js
两种方案均触发报错:Uncaught ReferenceError: sendNewCard is not defined
目前已在卡片的
ondrop内联事件中添加对sendNewCard方法的调用,bundle.js中实际打包生成的sendNewCard代码结构如下:
var sendNewCard = /*#__PURE__*/function () { var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee(el) { var res; return regeneratorRuntime.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: if (!(el != 1)) { _context.next = 13; break; } newSection = el.parentElement.childNodes[0].childNodes[0].textContent; _context.prev = 2; _context.next = 5; return (0, _axios.default)({ method: 'POST', url: 'http://127.0.0.1:3000/api/1/projects/updateCardPlace', data: { cardID: cardID, newSection: newSection } }); case 5: res = _context.sent; if (res.data.status === 'Success') { (0, _alerts.showAlert)('success', 'Successfully transferred.'); } else { (0, _alerts.showAlert)('error', 'Unsuccessfully transferred.'); } _context.next = 13; break; case 9: _context.prev = 9; _context.t0 = _context["catch"](2); console.log(_context.t0.message); (0, _alerts.showAlert)('error', 'Unsuccessfully transferred.'); case 13: case "end": return _context.stop(); } } }, _callee, null, [[2, 9]]); })); return function sendNewCard(_x) { return _ref.apply(this, arguments); }; }(); exports.sendNewCard = sendNewCard;
问题根因
Parcel默认按照CommonJS模块化规范打包代码,模块内部定义的sendNewCard变量属于模块作用域,不会自动挂载到浏览器全局window对象上;而HTML内联事件(如ondrop)只能访问全局作用域内的方法,因此会触发未定义报错。
可落地的解决方式
推荐方案:取消内联事件绑定,改用JS动态绑定事件
移除HTML属性中的ondrop='drop(event, this)'写法,为所有可放置的看板列元素添加统一标识类名,在入口JS中通过DOM选择器查找元素并动态绑定事件,全程不需要将方法挂载到全局作用域,完全符合模块化开发规范:// 入口JS中执行一次即可 const dropTargets = document.querySelectorAll('.kanban-drop-zone'); dropTargets.forEach(target => { target.addEventListener('dragover', allowDrop); target.addEventListener('drop', (ev) => drop(ev, target)); })绑定完成后,直接在原有
drop方法内部调用请求逻辑即可,无需跨作用域查找方法。兼容方案:手动将方法挂载到全局对象
如果暂时不想修改现有HTML的内联绑定逻辑,只需要在源文件中sendNewCard定义完成后,手动将方法挂载到window对象上即可。注意不要直接修改打包生成的bundle.js,否则下次Parcel重新构建时改动会被覆盖:// 源文件中添加 window.sendNewCard = sendNewCard;极简方案:直接将请求逻辑集成到原有
drop方法内
原有drop方法已经可以拿到newSection参数,完全不需要单独封装sendNewCard方法,直接把请求逻辑嵌入到drop方法末尾即可,减少不必要的跨作用域引用:const drop = async(ev, el) => { ev.preventDefault(); var data = ev.dataTransfer.getData("text"); const draggedCard = document.getElementById(data); el.appendChild(draggedCard); const newSection = el.parentElement.childNodes[0].childNodes[0].textContent; sortItems(el); // 直接嵌入请求逻辑 try { // 从DOM卡片元素上读取卡片ID,不需要依赖全局变量 const cardID = draggedCard.dataset.cardId; const res = await axios({ method: 'POST', url: 'http://127.0.0.1:3000/api/1/projects/updateCardPlace', data: { cardID, newSection } }); if (res.data.status === 'Success') { showAlert('success', 'Successfully transferred.'); } else { showAlert('error', 'Unsuccessfully transferred.'); } } catch (err) { showAlert('error', 'Unsuccessfully transferred.'); } }
内容的提问来源于stack exchange,提问作者Matt

