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