这个系列是将 ahooks 里面的所有 hook 源码都进行解读,通过解读 ahooks 的源码来熟悉自定义 hook 的写法,提高自己写自定义 hook 的能力,希望能够对大家有所帮助。
往期回顾
今天开始进入 UI 部分的 hooks,这部分的每个 hook 都感觉挺有用的,如果遇到对应场景,可以节省不少精力。
UI
useDrop & useDrag
一对帮助你处理在拖拽中进行数据转移的 hooks
type getDragPropsFn = (
data: any,
) => {
draggable: 'true';
key: string;
onDragStart: (e: React.DragEvent) => void;
onDragEnd: (e: React.DragEvent) => void;
};
interface IConfig {
onDragStart?: (data: any, e: React.DragEvent) => void;
onDragEnd?: (data: any, e: React.DragEvent) => void;
}
const useDrag = (config?: IConfig): getDragPropsFn => {
/// 方法每次重新渲染都会重新定义
const getProps = (data: any) => {
return {
key: JSON.stringify(data),
draggable: 'true' as const,/// 必须设置为 true 才能拖拽,html 属性设置为文本 "true"
/// react 事件处理程序绑定方式,和 Dom 0 级事件绑定是不同的。https://react.docschina.org/docs/handling-events.html
/// https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Building_blocks/Events#%E8%A1%8C%E5%86%85%E4%BA%8B%E4%BB%B6%E5%A4%84%E7%90%86%E5%99%A8_-_%E8%AF%B7%E5%8B%BF%E4%BD%BF%E7%94%A8
onDragStart: (e: React.DragEvent) => {
if (config && config.onDragStart) {
config.onDragStart(data, e);
}
/// 通过自定义类型传递数据 https://www.w3.org/TR/2011/WD-html5-20110113/dnd.html#drag-data-store-mode
e.dataTransfer.setData('custom', JSON.stringify(data));
},
onDragEnd: (e: React.DragEvent) => {
if (config && config.onDragEnd) {
config.onDragEnd(data, e);
}
},
};
};
return getProps;
};
export default useDrag;
import { useMemo, useState, useRef, useCallback } from 'react';
export interface DropAreaState {
isHovering: boolean;
}
export interface DropProps {
onDragOver: React.DragEventHandler;
onDragEnter: React.DragEventHandler;
onDragLeave: React.DragEventHandler;
onDrop: React.DragEventHandler;
onPaste: React.ClipboardEventHandler;
}
export interface DropAreaOptions {
onFiles?: (files: File[], event?: React.DragEvent) => void;
onUri?: (url: string, event?: React.DragEvent) => void;
onDom?: (content: any, event?: React.DragEvent) => void;
onText?: (text: string, event?: React.ClipboardEvent) => void;
}
const getProps = (
callback: (dataTransfer: DataTransfer, event: React.DragEvent | React.ClipboardEvent) => void,
setIsHovering: (over: boolean) => void,
): DropProps => ({
onDragOver: (event: React.DragEvent) => {
/// 在 dragenter (en-US) 或 dragover (en-US) 事件中调用 preventDefault() 方法将表明在该位置允许放置 。
/// 否则无法触发 drop 事件
/// https://developer.mozilla.org/zh-CN/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations#droptargets
event.preventDefault();
},
onDragEnter: (event: React.DragEvent) => {
event.preventDefault();
setIsHovering(true);
},
onDragLeave: () => {
setIsHovering(false);
},
onDrop: (event: React.DragEvent) => {
/// 禁止默认行为,拖拽默认行为可能会打开新标签页等不希望产生的行为
event.preventDefault();
/// react 17 之前版本需要这行代码,防止 callback 中是异步操作
/// https://zh-hans.reactjs.org/docs/legacy-event-pooling.html#gatsby-focus-wrapper
/// 17版本该代码已经废弃 https://zh-hans.reactjs.org/docs/events.html#overview
/// 该代码仍然可以运行但是不会有任何效果产生 https://zh-hans.reactjs.org/blog/2020/08/10/react-v17-rc.html#no-event-pooling
event.persist();
setIsHovering(false);
callback(event.dataTransfer, event);
},
onPaste: (event: React.ClipboardEvent) => {
event.persist();
callback(event.clipboardData, event);/// 粘贴操作也触发 drop 的回调
},
});
const useDrop = (options: DropAreaOptions = {}): [DropProps, DropAreaState] => {
const optionsRef = useRef(options);
optionsRef.current = options;
const [isHovering, setIsHovering] = useState<boolean>(false);
const callback = useCallback(
(dataTransfer: DataTransfer, event: React.DragEvent | React.ClipboardEvent) => {
const uri = dataTransfer.getData('text/uri-list'); /// 被拖拽的 a 标签的URL
const dom = dataTransfer.getData('custom'); /// 这个自定义类型是 useDrag hook 定义的类型
/// 使用了 custom 类型的情况,一般是使用了 useDrag
if (dom && optionsRef.current.onDom) {
let data = dom;
try {
data = JSON.parse(dom);
} catch (e) {
data = dom;
}
optionsRef.current.onDom(data, event as React.DragEvent);
return;
}
/// 拖拽的是个链接,不是说文本内容是个链接,而是拖拽的是个 a 标签
if (uri && optionsRef.current.onUri) {
optionsRef.current.onUri(uri, event as React.DragEvent);
return;
}
/// 如果操作中包含文件,则会有 files 字段 https://developer.mozilla.org/zh-CN/docs/Web/API/DataTransfer/files
if (dataTransfer.files && dataTransfer.files.length && optionsRef.current.onFiles) {
optionsRef.current.onFiles(Array.from(dataTransfer.files), event as React.DragEvent);
return;
}
/// 其他情况下的文本操作,页面直接拖拽文本、粘贴文本 type: "text/plain"
if (dataTransfer.items && dataTransfer.items.length && optionsRef.current.onText) {
dataTransfer.items[0].getAsString((text) => {
optionsRef.current.onText!(text, event as React.ClipboardEvent);
});
}
},
[],
);
const props: DropProps = useMemo(() => getProps(callback, setIsHovering), [
callback,
setIsHovering,
]);
return [props, { isHovering }];
};
export default useDrop;
以上内容由于本人水平问题难免有误,欢迎大家进行讨论反馈。