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

如何在useEffect内设置state时避免useRef被重置?

问题描述

实现了一个仅支持水平拖拽的滑块组件,拖动滑块时会修改表单对应的selectedPlanDetails状态,但每次设置该状态都会触发父页面重渲染,导致滑块组件内部的useRef被重置,拖拽功能异常。

解决方案

核心问题是**DraggableComponent定义在父组件内部**,父组件每次重渲染时都会重新创建这个组件,导致其内部的ref、state全部被重置。同时频繁更新position状态也会加剧重渲染问题,以下是具体修复步骤:

1. 将滑块组件移到父组件外部

把DraggableComponent从FinancialLandingPage内部提取出来,避免父组件重渲染时重新定义组件,保证其内部状态和ref的稳定性。

2. 通过props传递依赖项

将父组件的setSelectedPlanDetails、翻译函数t、base_t等需要的参数通过props传入子组件,确保子组件能正常更新父组件状态并获取翻译文本。

3. 优化拖拽状态管理(可选)

用useRef存储拖拽过程中的临时状态(如pressed、position),避免频繁更新state触发重渲染,仅在需要更新表单状态时才修改父组件的selectedPlanDetails。

4. 用React.memo优化组件

给DraggableComponent包裹React.memo,减少不必要的重渲染。

修改后的完整代码
import { useState, useEffect, useRef, useCallback, memo } from 'react';
import { useTranslation } from 'react-i18next';

// 提取滑块组件到外部
const DraggableComponent = memo(({ setSelectedPlanDetails, t, base_t }) => {
    const pressedRef = useRef(false);
    const positionRef = useRef({ x: 0, y: 0 });
    const [currentPlan, setCurrentPlan] = useState(1);
    const ref = useRef();

    // 监听position变化更新DOM和表单状态
    useEffect(() => {
        const updatePlan = () => {
            const x = positionRef.current.x;
            if (x >= 0 && x < 147) {
                setCurrentPlan(1);
                setSelectedPlanDetails(SUBSCRIPTION_PLANS.newbie.name);
            } else if (x >= 147 && x < 294) {
                setCurrentPlan(2);
                setSelectedPlanDetails(SUBSCRIPTION_PLANS.enthusiast.name);
            } else if (x >= 294 && x < 441) {
                setCurrentPlan(3);
                setSelectedPlanDetails(SUBSCRIPTION_PLANS.trader.name);
            } else if (x >= 441 && x <= 600) {
                setCurrentPlan(4);
                setSelectedPlanDetails(SUBSCRIPTION_PLANS.traderplus.name);
            }
        };

        // 更新DOM位置
        if (ref.current) {
            ref.current.style.transform = `translate(${positionRef.current.x}px, 0)`;
        }
        updatePlan();
    }, [setSelectedPlanDetails]);

    // 鼠标移动事件,用ref存储状态避免依赖更新
    const onMouseMove = useCallback((event) => {
        if (!pressedRef.current) return;
        
        let newX = positionRef.current.x + event.movementX;
        // 限制拖拽范围
        newX = Math.max(0, Math.min(newX, 600));
        
        positionRef.current.x = newX;
        // 手动触发DOM更新和表单状态检查
        if (ref.current) {
            ref.current.style.transform = `translate(${newX}px, 0)`;
        }
        // 仅当计划切换时更新state,减少重渲染
        const currentPlanIndex = Math.floor(newX / 147) + 1;
        if (currentPlanIndex !== currentPlan) {
            setCurrentPlan(currentPlanIndex);
            const planKeys = ['newbie', 'enthusiast', 'trader', 'traderplus'];
            setSelectedPlanDetails(SUBSCRIPTION_PLANS[planKeys[currentPlanIndex - 1]].name);
        }
    }, [currentPlan, setSelectedPlanDetails]);

    const handleMouseDown = () => {
        pressedRef.current = true;
    };

    const handleMouseUp = () => {
        pressedRef.current = false;
    };

    return (
        <div
            ref={ref}
            onMouseMove={onMouseMove}
            onMouseDown={handleMouseDown}
            onMouseUp={handleMouseUp}
            onMouseLeave={handleMouseUp} // 增加鼠标离开时释放状态
        >
            <div className="slider-bar-item">
                <Heading level="5">{t(base_t + "page_five.slider").replace("[num]", currentPlan)}</Heading>
            </div>
        </div>
    );
});

const FinancialLandingPage = () => {
    const { t } = useTranslation();
    const [selectedPlanDetails, setSelectedPlanDetails] = useState(SUBSCRIPTION_PLANS.newbie.name);

    return (
        <>
            <div className="slider">
                <div className="slider-bar">
                    <DraggableComponent 
                        setSelectedPlanDetails={setSelectedPlanDetails} 
                        t={t} 
                        base_t={base_t} // 假设base_t是外部定义的常量
                    />
                </div>
            </div>

            <div className="form-container">
                <div className="header">
                    <Heading align="center" level="3">
                        {t(base_t + "page_five.form.title")}
                    </Heading>
                    <Heading align="center" level="1">
                        {t(base_t + `page_five.form.${selectedPlanDetails}.price`)}
                    </Heading>
                    <Heading align="center" level="6">
                        {t(base_t + "page_five.form.price_description")}
                    </Heading>
                </div>
                <div className="form">
                    <div className="form-item">
                        <CryptoIcon name="checkmark" />
                        <Text size={16}>
                            {t(base_t + `page_five.form.${selectedPlanDetails}.item_one`)}
                        </Text>
                    </div>
                    <div className="form-item">
                        <CryptoIcon name="checkmark" />
                        <Text size={16}>
                            {t(base_t + `page_five.form.${selectedPlanDetails}.item_two`)}
                        </Text>
                    </div>
                    <div className="form-item">
                        <CryptoIcon name="checkmark" />
                        <Text size={16}>
                            {__(base_t + `page_five.form.${selectedPlanDetails}.item_three`)}
                        </Text>
                    </div>
                    <Button className="button">
                        {t(base_t + "page_five.form.button")}
                        <Icon name="chevron_right" />
                    </Button>
                </div>
            </div>
        </>
    );
};

export default FinancialLandingPage;
关键说明
  • 组件提取后,内部的ref和state不会因父组件重渲染而重置,保证拖拽过程的连续性。
  • 用useRef存储pressed和position,避免频繁更新state导致的重渲染,提升拖拽流畅度。
  • React.memo会对比props的变化,仅在必要时才重渲染滑块组件。
  • 增加onMouseLeave事件,避免鼠标移出滑块区域后仍保持拖拽状态。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 13:25:17