You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React TypeScript拖拽Resize组件报错:string无法赋值给Element类型

React拖拽调整大小组件TypeScript适配方案

问题背景

开发可通过拖拽边角栏调整大小的React组件时,参考无依赖JavaScript实现方案转TypeScript后,遇到两个类型错误:

  • Argument of type 'string' is not assignable to parameter of type 'Element'
  • Argument of type 'null' is not assignable to parameter of type 'Element'

错误原因

  1. useRef初始值错误:原代码用选择器字符串(如"#box")作为useRef初始值,但React的useRef是用来绑定DOM元素的,不能直接存储选择器;若设为null,TypeScript因默认类型推断规则会报错,需显式指定泛型类型。
  2. 未处理null安全:组件挂载前ref.current为null,直接调用DOM方法会触发类型错误。

修复后的完整代码

TypeScript组件代码

import React, { useRef, useEffect } from "react";
import styles from "./Resize.module.css";

// 定义组件Props类型
interface ResizeableComponentsProps {
  children?: React.ReactNode;
}

const ResizeableComponents = ({ children }: ResizeableComponentsProps) => {
  // 给useRef指定泛型,明确绑定HTMLDivElement,初始值设为null
  const ref = useRef<HTMLDivElement>(null);
  const refLeft = useRef<HTMLDivElement>(null);
  const refTop = useRef<HTMLDivElement>(null);
  const refRight = useRef<HTMLDivElement>(null);
  const refBottom = useRef<HTMLDivElement>(null);

  useEffect(() => {
    // 先检查所有ref是否已绑定DOM元素,避免null错误
    const resizeableEle = ref.current;
    const resizerRight = refRight.current;
    const resizerTop = refTop.current;
    const resizerBottom = refBottom.current;
    const resizerLeft = refLeft.current;

    if (!resizeableEle || !resizerRight || !resizerTop || !resizerBottom || !resizerLeft) {
      return;
    }

    const computedStyles = window.getComputedStyle(resizeableEle);
    let width = parseInt(computedStyles.width, 10);
    let height = parseInt(computedStyles.height, 10);
    let x = 0;
    let y = 0;

    resizeableEle.style.top = "150px";
    resizeableEle.style.left = "150px";

    // 右侧拖拽逻辑
    const onMouseMoveRightResize = (event: MouseEvent) => {
      const dx = event.clientX - x;
      x = event.clientX;
      width += dx;
      resizeableEle.style.width = `${width}px`;
    };

    const onMouseUpRightResize = () => {
      document.removeEventListener("mousemove", onMouseMoveRightResize);
    };

    const onMouseDownRightResize = (event: MouseEvent) => {
      x = event.clientX;
      resizeableEle.style.left = computedStyles.left;
      resizeableEle.style.right = "auto";
      document.addEventListener("mousemove", onMouseMoveRightResize);
      document.addEventListener("mouseup", onMouseUpRightResize);
    };

    // 顶部拖拽逻辑
    const onMouseMoveTopResize = (event: MouseEvent) => {
      const dy = event.clientY - y;
      height -= dy;
      y = event.clientY;
      resizeableEle.style.height = `${height}px`;
    };

    const onMouseUpTopResize = () => {
      document.removeEventListener("mousemove", onMouseMoveTopResize);
    };

    const onMouseDownTopResize = (event: MouseEvent) => {
      y = event.clientY;
      const styles = window.getComputedStyle(resizeableEle);
      resizeableEle.style.bottom = styles.bottom;
      resizeableEle.style.top = "auto";
      document.addEventListener("mousemove", onMouseMoveTopResize);
      document.addEventListener("mouseup", onMouseUpTopResize);
    };

    // 底部拖拽逻辑
    const onMouseMoveBottomResize = (event: MouseEvent) => {
      const dy = event.clientY - y;
      height += dy;
      y = event.clientY;
      resizeableEle.style.height = `${height}px`;
    };

    const onMouseUpBottomResize = () => {
      document.removeEventListener("mousemove", onMouseMoveBottomResize);
    };

    const onMouseDownBottomResize = (event: MouseEvent) => {
      y = event.clientY;
      const styles = window.getComputedStyle(resizeableEle);
      resizeableEle.style.top = styles.top;
      resizeableEle.style.bottom = "auto";
      document.addEventListener("mousemove", onMouseMoveBottomResize);
      document.addEventListener("mouseup", onMouseUpBottomResize);
    };

    // 左侧拖拽逻辑
    const onMouseMoveLeftResize = (event: MouseEvent) => {
      const dx = event.clientX - x;
      x = event.clientX;
      width -= dx;
      resizeableEle.style.width = `${width}px`;
    };

    const onMouseUpLeftResize = () => {
      document.removeEventListener("mousemove", onMouseMoveLeftResize);
    };

    const onMouseDownLeftResize = (event: MouseEvent) => {
      x = event.clientX;
      resizeableEle.style.right = computedStyles.right;
      resizeableEle.style.left = "auto";
      document.addEventListener("mousemove", onMouseMoveLeftResize);
      document.addEventListener("mouseup", onMouseUpLeftResize);
    };

    // 绑定拖拽触发事件
    resizerRight.addEventListener("mousedown", onMouseDownRightResize);
    resizerTop.addEventListener("mousedown", onMouseDownTopResize);
    resizerBottom.addEventListener("mousedown", onMouseDownBottomResize);
    resizerLeft.addEventListener("mousedown", onMouseDownLeftResize);

    // 组件卸载时清理所有事件监听,防止内存泄漏
    return () => {
      resizerRight.removeEventListener("mousedown", onMouseDownRightResize);
      resizerTop.removeEventListener("mousedown", onMouseDownTopResize);
      resizerBottom.removeEventListener("mousedown", onMouseDownBottomResize);
      resizerLeft.removeEventListener("mousedown", onMouseDownLeftResize);

      document.removeEventListener("mousemove", onMouseMoveRightResize);
      document.removeEventListener("mouseup", onMouseUpRightResize);
      document.removeEventListener("mousemove", onMouseMoveTopResize);
      document.removeEventListener("mouseup", onMouseUpTopResize);
      document.removeEventListener("mousemove", onMouseMoveBottomResize);
      document.removeEventListener("mouseup", onMouseUpBottomResize);
      document.removeEventListener("mousemove", onMouseMoveLeftResize);
      document.removeEventListener("mouseup", onMouseUpLeftResize);
    };
  }, []);

  return (
    <div className={styles.container}>
      <div ref={ref} className={styles.resizeable}>
        <div ref={refLeft} className={`${styles.resizer} ${styles.resizerl}`}></div>
        <div ref={refTop} className={`${styles.resizer} ${styles.resizert}`}></div>
        <div ref={refRight} className={`${styles.resizer} ${styles.resizerr}`}></div>
        <div ref={refBottom} className={`${styles.resizer} ${styles.resizerb}`}></div>
        {children}
      </div>
    </div>
  );
};

export default ResizeableComponents;

CSS样式文件(Resize.module.css)

.container {
    border-radius: 5px;
    width: 100vw;
    height: 100vh;
    background: #FFBC97;
    position: relative;
}

.resizeable {
    position: absolute;
    border: 2px solid #533535;
    width: 100px;
    height: 100px;
    border-radius: 3px;
    display: flex;
    justify-content: center;
    align-items: center;
    min-width: 15px;
    min-height: 15px;
}

.resizer {
    position: absolute;
    background: black;
}

.resizerr {
    cursor: col-resize;
    height: 100%;
    right: 0;
    top: 0;
    width: 5px;
}

.resizert {
    cursor: row-resize;
    height: 5px;
    left: 0;
    top: 0;
    width: 100%;
}

.resizerb {
    cursor: row-resize;
    height: 5px;
    left: 0;
    bottom: 0;
    width: 100%;
}

.resizerl {
    cursor: col-resize;
    height: 100%;
    left: 0;
    top: 0;
    width: 5px;
}

内容的提问来源于stack exchange,提问作者Kartik Fulara

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 07:01:27