Quiet
  • 主页
  • 归档
  • 分类
  • 标签
  • 链接
  • 关于我

bajiu

  • 主页
  • 归档
  • 分类
  • 标签
  • 链接
  • 关于我
Quiet主题
  • React

自定义hooks之拖拽功能

bajiu
前端

2024-11-08 22:13:00

demo地址: 服务器整理中…
代码地址: https://github.com/bajiu/blog_code/tree/master/temp/react-demo

最近想到好久没写Hooks了,之前工作基本都是直接拿现成的白嫖,打算写个Hooks练练手,在react中自定义hooks是一种复用逻辑的方式,它允许我们将组件中可复用的状态和逻辑抽离成独立的函数,通常以 use 开头。如果多个组件需要共享某些功能(如窗口大小、表单处理、API 数据获取等),就可以将这些功能提取为自定义 Hook。

特点

  • 以 use 开头:符合 Hook 的命名规则,确保其使用时能够被 React 检测到。
  • 可以调用其他 Hooks:自定义 Hooks 内部可以使用 React 自带的 Hook(如 useState、useEffect 等)。
  • 逻辑复用:方便在多个组件中复用复杂的逻辑

举个栗子

写个经典拖拽作为栗子吧,vue中基于指令实现的拖拽,而在react中,我们尽量使用自定义hooks来实现,代码大概如下:

import React, {MutableRefObject, useCallback, useEffect, useRef} from "react";
import {IPosition} from "../types/IDrag.ts";


const useDrag = (ref: MutableRefObject<HTMLElement|null> ,onDragChange?: (newPosition: IPosition) => void) => {
    const isDragging = useRef(false);
    const offset = useRef({ x: 0, y: 0 });

    const handleMouseDown = useCallback((event: React.MouseEvent) => {
        console.log('handleMouseDown');
        // 没传入或者没有父级元素时候返回
        if (!ref.current || !ref.current.parentElement) {
            console.error('请传入 ref 或者 ref.current.parentElement');
            return;
        }
        const elementRect = ref.current.getBoundingClientRect();
        offset.current = {
            x: event.clientX - elementRect.left,
            y: event.clientY - elementRect.top,
        };
        isDragging.current = true;
    }, [ref])

    const handleMouseMove = useCallback((event: React.MouseEvent) => {
        if (!isDragging.current  || !ref.current || !ref.current.parentElement) {
            console.error('请传入 ref 或者 ref.current.parentElement');
            return;
        }
        const parentRect = ref.current.parentElement.getBoundingClientRect();

        const newX = event.clientX - parentRect.left - offset.current.x;
        const newY = event.clientY - parentRect.top - offset.current.y;

        const newPosition = { x: newX, y: newY };
        // TODO 这里可以添加配置不能超出父级元素
        ref.current.style.transform = `translate(${newX}px, ${newY}px)`;


        // 如果有外部回调,传递位置信息
        if (onDragChange) {
            onDragChange(newPosition);
        }
    }, [isDragging, offset, ref, onDragChange]);

    const handleMouseUp = useCallback(() => {
        isDragging.current = false;
    }, []);

    useEffect(() => {
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
        // @ts-expect-error
        document.addEventListener('mousemove', handleMouseMove);
        document.addEventListener('mouseup', handleMouseUp);
        return () => {
            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
            // @ts-expect-error
            document.removeEventListener('mousemove', handleMouseMove);
            document.removeEventListener('mouseup', handleMouseUp);

        }
    }, [handleMouseMove, handleMouseUp]);

    return {
        handleMouseDown
    }
}

export default useDrag;

用法的画可以这么用

import useDrag from "../hooks/useDrag.tsx";
import {useRef, useState} from "react";
import {IPosition} from "../types/IDrag.ts";


const Drag: React.FC = () => {
    const draggableRef = useRef(null);
    const [currentPosition, setCurrentPosition] = useState({ x: 0, y: 0 });

    const handleDragChange = (newPosition: IPosition) => {
        console.log('当前位置', newPosition);
        setCurrentPosition(newPosition);
    };
    const {  handleMouseDown } = useDrag(draggableRef, handleDragChange);
    return (
        <div style={{ position: "relative", width: "400px", height: "400px", border: "1px solid #ccc" }}>
            <div
                ref={draggableRef}
                onMouseDown={handleMouseDown}
                style={{
                    width: "100px",
                    height: "100px",
                    backgroundColor: "lightblue",
                    cursor: "grab",
                }}
            >
                拖动我
            </div>
            <div style={{ marginTop: "20px" }}>
                当前相对位置: X: {currentPosition.x}, Y: {currentPosition.y}
            </div>
        </div>
    );

}

export default Drag;

大概也就是这样

react拖拽hooks

其实有很多类似的npm库,比如说 react-draggable 、 react-dnd 这种的成熟库,工作中图快基本都会直接用现成的,虽然白嫖居多,但作为一个程序猿还是需要自己会写的。

上一篇

react实现类似GPT的对话应用

下一篇

React项目结构和JSX工作原理(一)

©2024 By bajiu.