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

如何将工具模块函数绑定到React类上下文以在组件回调中使用?

问题描述

我有一个可自行调整尺寸的ResizableComponent组件,它会渲染用于处理点击/触摸事件的SizingHandle组件。ResizableComponent将onResizeStart回调传递给SizingHandle来处理调整尺寸逻辑,这个回调里用到了utilities模块的isMouseEvent和isTouchEvent函数。我在ResizableComponent里用箭头函数确保回调在当前上下文执行,但工具模块里的函数却显示未定义。

如何让工具模块中的函数在回调中可用?onResizeStart在SizingHandle中执行时是已定义的,但isMouseEvent和isTouchEvent未定义。我知道可以通过props传递这些函数,但这显得繁琐。我希望能在多个组件中复用这些通用逻辑的工具函数,同时避免props臃肿。

我尝试过把this.isMouseEvent绑定为isMouseEvent.bind(this),但这完全没用。


相关代码

ResizableComponent.tsx

import React from 'react'
import { isMouseEvent, isTouchEvent } from './utilities'

export default class ResizeableComponent extends React.PureComponent<IResizeableComponentProps, State> {

    constructor(props: IResizeableComponentProps) {
        super(props);
        ...etc.
    }


    onResizeStart = (event: React.MouseEvent<HTMLElement> | React.TouchEvent<HTMLElement>, handle: THandle) => {
        let clientX = 0;
        let clientY = 0;

        if (event.nativeEvent && isMouseEvent(event.nativeEvent)) { //isMouseEvent is not defined when executed in SizingHandle!
            clientX = event.nativeEvent.clientX;
            clientY = event.nativeEvent.clientY;
        } else if (event.nativeEvent && isTouchEvent(event.nativeEvent)) { //isTouchEvent is not defined when executed in SizingHandle!
            clientX = (event.nativeEvent as TouchEvent).touches[0].clientX;
            clientY = (event.nativeEvent as TouchEvent).touches[0].clientY;
        }

        ...more resizing logic.
    }

    renderResizer() {
        const handles : TSizingHandles = { top: true, bottom: true }
    
        const resizers = Object.keys(handles).map(handle => {
            if(handles[handle]){
                return(
                    <SizingHandle 
                        key={handle}
                        handle={handle as THandle}
                        onResizeStart={this.onResizeStart}
                    />
                )
            }
        })
    
        return (
            <div>
                {resizers}
            </div>
        );
    }

    render(){
        const Wrapper = 'div';

        return(
            <Wrapper ref={this.ref} style={style}>
                {this.props.children}
                {this.renderResizer()}
            </Wrapper>
        );
    }
}

Utilities.tsx

export const isTouchEvent = (event: MouseEvent | TouchEvent): event is TouchEvent => {
    return Boolean((event as TouchEvent).touches && (event as TouchEvent).touches.length);
};

export const isMouseEvent = (event: MouseEvent | TouchEvent): event is MouseEvent => {
    return Boolean(
        ((event as MouseEvent).clientX || (event as MouseEvent).clientX === 0) &&
        ((event as MouseEvent).clientY || (event as MouseEvent).clientY === 0),
    );
};

SizingHandle.tsx

export type OnResizeStartCallback = (
    e: React.MouseEvent<HTMLDivElement> | React.TouchEvent<HTMLDivElement>,
    handle: THandle,
  ) => void;

export interface ISizingHandleProps {
    handle: THandle;
    onResizeStart: OnResizeStartCallback;
    children? : React.ReactNode;
}

export default class SizingHandle extends React.PureComponent<ISizingHandleProps> {
    onMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
        this.props.onResizeStart(e, this.props.handle);
    };

    onTouchStart = (e: React.TouchEvent<HTMLDivElement>) => {
        this.props.onResizeStart(e, this.props.handle);
    };

    render(){
        return (
            <div
                style={{
                    position: 'absolute',
                    userSelect: 'none',
                    ...styles[this.props.handle]
                }}
                onMouseDown={this.onMouseDown}
                onTouchStart={this.onTouchStart}
            >
                {this.props.children}
            </div>
        );
    }
}

解决方案

1. 排查模块导入路径

首先确认ResizableComponent.tsx中导入工具函数的路径./utilities是否准确:

  • 若utilities.tsx和ResizableComponent.tsx在同一目录,路径无误;
  • 若不在同一目录,调整为正确的相对路径(如../utilities)或项目配置的绝对路径。

2. 修复回调的作用域绑定问题

箭头函数本身不会导致作用域丢失,但如果项目涉及SSR或序列化传递回调,可能会出现模块引用丢失的情况。可以改用类方法手动绑定上下文:

// 在constructor中绑定上下文
constructor(props: IResizeableComponentProps) {
    super(props);
    this.onResizeStart = this.onResizeStart.bind(this);
}

// 定义类方法(替换原箭头函数)
onResizeStart(event: React.MouseEvent<HTMLElement> | React.TouchEvent<HTMLElement>, handle: THandle) {
    let clientX = 0;
    let clientY = 0;

    if (event.nativeEvent && isMouseEvent(event.nativeEvent)) {
        clientX = event.nativeEvent.clientX;
        clientY = event.nativeEvent.clientY;
    } else if (event.nativeEvent && isTouchEvent(event.nativeEvent)) {
        clientX = (event.nativeEvent as TouchEvent).touches[0].clientX;
        clientY = (event.nativeEvent as TouchEvent).touches[0].clientY;
    }

    ...more resizing logic.
}

如果允许重构为函数组件,用useCallback确保回调稳定且保留模块引用:

import React, { useCallback } from 'react'
import { isMouseEvent, isTouchEvent } from './utilities'

const ResizableComponent = (props: IResizeableComponentProps) => {
    const onResizeStart = useCallback((event: React.MouseEvent<HTMLElement> | React.TouchEvent<HTMLElement>, handle: THandle) => {
        let clientX = 0;
        let clientY = 0;

        if (event.nativeEvent && isMouseEvent(event.nativeEvent)) {
            clientX = event.nativeEvent.clientX;
            clientY = event.nativeEvent.clientY;
        } else if (event.nativeEvent && isTouchEvent(event.nativeEvent)) {
            clientX = (event.nativeEvent as TouchEvent).touches[0].clientX;
            clientY = (event.nativeEvent as TouchEvent).touches[0].clientY;
        }

        ...more resizing logic.
    }, []);

    // 保留原渲染逻辑
}

3. 优化工具函数的参数类型

直接让工具函数接收React事件,避免依赖nativeEvent,减少类型转换问题:

// 修改utilities.tsx
import type { ReactMouseEvent, ReactTouchEvent } from 'react';

export const isMouseEvent = (event: ReactMouseEvent<any> | ReactTouchEvent<any>): event is ReactMouseEvent<any> => {
    return 'clientX' in event && typeof event.clientX === 'number';
};

export const isTouchEvent = (event: ReactMouseEvent<any> | ReactTouchEvent<any>): event is ReactTouchEvent<any> => {
    return 'touches' in event && event.touches.length > 0;
};

// 在onResizeStart中使用
if (isMouseEvent(event)) {
    clientX = event.clientX;
    clientY = event.clientY;
} else if (isTouchEvent(event)) {
    clientX = event.touches[0].clientX;
    clientY = event.touches[0].clientY;
}

4. 检查打包工具配置

如果使用Webpack/Vite等打包工具,确认:

  • 没有错误配置alias导致路径解析错误;
  • Tree Shaking没有误将工具函数标记为未使用代码剔除。

内容的提问来源于stack exchange,提问作者Zeus Krakus

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 14:35:34