讲师中心 微信公众号

工具推荐

视频效率加速
updream

一键生成高质量推广成片

立刻MV
立刻MV

低成本歌曲 MV、视频生成

Seko
Seko

AI短剧一键生成成片

Laper
Laper

剧本策划到成片协作

React中高效管理与滚动多个Ref:告别冗余useRef与switch

冬雪同学_6671

冬雪同学_6671

发布时间:2025-11-17 12:34:30

|

704人浏览过

|

来源于php中文网

原创

React中高效管理与滚动多个Ref:告别冗余useRef与switch

本教程将探讨在react应用中如何优化对多个dom元素的引用管理。针对传统上使用多个useref和switch语句处理动态元素交互的低效问题,我们将介绍一种基于useref结合createref数组的解决方案,实现更简洁、可扩展且易于维护的ref管理与元素滚动逻辑。

在React开发中,我们经常需要直接操作DOM元素,例如滚动到特定位置、聚焦输入框或测量元素尺寸。React提供了useRef Hook来创建可变的Ref对象,使其能够持久地引用DOM节点或React组件实例。然而,当需要管理大量动态生成的、且需要单独交互的DOM元素时,传统的Ref管理方式可能会变得冗长且难以维护。

冗余的Ref管理模式及其局限性

考虑以下场景:您有一个列表,需要根据某个索引值滚动到列表中的特定项。一种直观但效率不高的方法是为每个可能的列表项声明一个独立的useRef,并结合switch语句来根据索引触发相应的滚动操作。

const ref0 = useRef();
const ref1 = useRef();
const ref2 = useRef();
const ref3 = useRef();
const ref4 = useRef();

// ... 其他业务逻辑

switch(index) {
  case 0:
    ref0?.current?.scrollIntoView();
    break;
  case 1:
    ref1?.current?.scrollIntoView();
    break;
  case 2:
    ref2?.current?.scrollIntoView();
    break;
  case 3:
    ref3?.current?.scrollIntoView();
    break;
  case 4:
    ref4?.current?.scrollIntoView();
    break;
  default:
    break;
}

这种模式的缺点显而易见:

  1. 代码冗余: 随着元素数量的增加,需要声明的useRef变量和switch语句中的case分支也会线性增加,导致代码量膨胀。
  2. 可维护性差: 修改或添加元素时,需要同时修改Ref声明和switch逻辑,容易出错。
  3. 扩展性差: 如果列表项是动态生成的,且数量不固定,这种硬编码的方式将无法适应。

优化方案:使用Ref数组进行动态管理

为了解决上述问题,我们可以利用useRef来存储一个Ref对象的数组。useRef Hook本身可以存储任何可变值,并且该值在组件的整个生命周期中保持不变。我们可以让useRef存储一个由createRef创建的Ref对象组成的数组,从而实现对多个动态元素的集中管理。

实现步骤

  1. 初始化Ref数组: 使用useRef创建一个Ref对象,其current属性将用来存储一个Ref数组。然后,通过Array.from结合map方法,为预期的每个元素动态生成一个createRef()实例,并赋值给refs.current。

    import { createRef, useEffect, useRef } from 'react';
    
    export default function Sample() {
        const refs = useRef([]);
    
        // 确保Ref数组在组件挂载时被初始化,并在元素数量变化时更新。
        // 对于固定数量的元素,可以在初次渲染时一次性创建。
        // 如果元素数量动态变化,则需要更精细的控制,例如在useEffect中根据依赖项更新refs.current。
        // 这里的逻辑确保在组件渲染时,如果refs.current的长度不匹配,则重新初始化或填充Ref对象。
        // `refs.current[i] || createRef()` 确保重用现有Ref,只为新增元素创建新Ref。
        if (refs.current.length !== 10) { // 假设需要管理10个元素
            refs.current = Array.from({ length: 10 }).map((_, i) => refs.current[i] || createRef());
        }
        // ...
    }

    重要提示: createRef()每次调用都会返回一个新的Ref对象。因此,将refs.current = Array.from(...).map(() => createRef()); 直接放在组件函数体内会导致每次渲染都重新创建Ref对象,这通常不是我们期望的。上述示例中的if (refs.current.length !== 10) 结合 refs.current[i] || createRef() 是一种更健壮的模式,它会重用已存在的Ref对象,只为新增的元素创建新的Ref。

    Reactivation Specialist · 流失召回
    Reactivation Specialist · 流失召回

    适用于用户提出以下需求时:“构建赢回活动(win-back campaign)”、“重新激活已流失订阅者(re-engage lapsed subscribers)”、“执行重新授权 / 重新同意(re-permission / re-consent)清理”或“清理无效联系人列表(sunset my dead list)”;输出一个闭环式再激活方案——包括:流失用户群组(lapsed-cohort)定义、分阶段优惠阶梯(staged offer ladder)、重新授权(re-consent / re-p

    下载
  2. 绑定Ref到元素: 在渲染列表时,通过map方法遍历数据,并将数组中对应的Ref绑定到每个DOM元素上。

    return (
        <>
            {Array.from({ length: 10 }).map((ele, index) => (
                <div ref={refs.current[index]} key={index} className='scrollElement'>
                    Item {index}
                </div>
            ))}
        </>
    );
  3. 访问和操作特定Ref: 任何需要操作DOM的逻辑(例如滚动、聚焦等)都可以在事件监听器、useEffect Hook的回调函数中执行。通过索引直接从refs.current数组中获取对应的Ref对象。

    useEffect(() => {
        const indexOfRefIWantToScroll = 5; // 假设我们要滚动到第5个元素
        // 安全地访问Ref的current属性并执行DOM方法
        refs.current[indexOfRefIWantToScroll]?.current?.scrollIntoView({
            behavior: 'smooth', // 平滑滚动
            block: 'start'      // 滚动到元素的顶部
        });
    }, []); // 空依赖数组确保只在组件挂载时运行一次

完整示例代码

import React, { createRef, useEffect, useRef } from 'react';

export default function Sample() {
    // useRef来存储Ref对象的数组
    const refs = useRef([]);
    const numberOfElements = 10; // 假设有10个元素需要管理Ref

    // 确保Ref数组在组件挂载时被初始化,并在元素数量变化时更新
    // 这里的逻辑确保在组件渲染时,如果refs.current的长度不匹配,则重新初始化或填充Ref对象。
    // `refs.current[i] || createRef()` 确保重用现有Ref,只为新增元素创建新Ref。
    if (refs.current.length !== numberOfElements) {
        refs.current = Array.from({ length: numberOfElements }).map((_, i) => refs.current[i] || createRef());
    }

    // 在组件挂载后执行DOM操作
    useEffect(() => {
        const indexOfRefIWantToScroll = 5; // 假设我们要滚动到第6个元素(索引为5)
        // 安全地访问Ref的current属性并执行DOM方法
        if (refs.current[indexOfRefIWantToScroll] && refs.current[indexOfRefIWantToScroll].current) {
            refs.current[indexOfRefIWantToScroll].current.scrollIntoView({
                behavior: 'smooth', // 平滑滚动
                block: 'start' // 滚动到元素的顶部
            });
        }
    }, []); // 空依赖数组确保只在组件挂载时运行一次

    return (
        <div style={{ height: '400px', overflowY: 'scroll', border: '1px solid #ccc', padding: '10px' }}>
            <h2>滚动示例</h2>
            {Array.from({ length: numberOfElements }).map((_, index) => (
                <div
                    ref={refs.current[index]} // 绑定对应的Ref
                    key={index}
                    className='scrollElement'
                    style={{
                        height: '80px',
                        lineHeight: '80px',
                        textAlign: 'center',
                        backgroundColor: index % 2 === 0 ? '#f0f0f0' : '#e0e0e0',
                        marginBottom: '8px',
                        borderRadius: '4px',
                        boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
                    }}
                >
                    这是第 {index + 1} 个可滚动元素
                </div>
            ))}
        </div>
    );
}

注意事项

  1. useRef与createRef的区别:

    • useRef:在函数组件中用于创建Ref对象。它返回一个在组件整个生命周期内保持不变的Ref对象。其current属性是可变的,可以用来存储任何值(包括DOM节点、React组件实例或由createRef创建的Ref对象数组)。
    • createRef:在类组件中创建Ref对象,但在函数组件中也可以独立使用。每次调用createRef()都会返回一个新的Ref对象。在上述方案中,我们利用它为数组中的每个元素生成一个独立的Ref实例。
  2. Ref数组的生命周期: 当元素的数量是动态的或会发生变化时,需要更谨慎地管理refs.current数组的更新。例如,可以在useEffect中根据元素列表的依赖项来重新构建或更新refs.current,确保新元素有新的Ref,旧元素保留其Ref。

  3. 可选链操作符: 在访问Ref的current属性时,使用可选链操作符?.(如refs.current[index]?.current?.scrollIntoView())是一个好习惯,可以防止在Ref尚未绑定到DOM元素时引发错误。

  4. callback ref作为替代方案: 对于完全动态的列表,另一种常见且强大的模式是使用callback ref。它允许您在Ref被绑定或解除绑定时执行回调函数,从而更灵活地管理Ref数组。例如:

    import React, { useCallback, useRef } from 'react';
    
    export default function DynamicList() {
        const itemRefs = useRef({}); // 使用对象存储Ref,键为id或index
    
        // 当Ref被绑定或解除绑定时调用
        const setItemRef = useCallback((element, index) => {
            if (element) {
                itemRefs.current[index] = element; // 绑定时存储元素
            } else {
                delete itemRefs.current[index]; // 解除绑定时移除
            }
        }, []); // 依赖项为空数组,确保callback ref函数只创建一次
    
        // 假设items是一个动态数组
        const items = Array.from({ length: 5 }).map((_, i) => ({ id: i, content: `动态项 ${i + 1}` }));
    
        useEffect(() => {
            // 假设需要滚动到第3项
            const targetIndex

热门AI工具

更多
讯飞智文

一款面向学习和办公场景的AI文档创作工具,可辅助生成PPT与Word文档,提高资料整理和内容制作效率。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

Flowith
Flowith Hot

Flowith是一款基于画布和智能体工作流的 AI 协作工作空间。

花生AI
花生AI Hot

一款AI视频创作工具,主要用于B站推出的AI视频创作工具,适合需要提升相关任务效率的用户。

二狗PPT
二狗PPT Hot

一款AI演示文稿工具,主要用于专为中式职场打造的AI PPT生成工具,适合需要提升相关任务效率的用户。

Atoms
Atoms Hot

Atoms是一款AI智能体工具,第一支自动构建真实业务的 AI 团队。

蛙蛙写作

一款AI论文写作工具,主要用于超级AI智能写作助手,适合需要提升相关任务效率的用户。

相关专题

更多
switch语句用法
switch语句用法

switch语句用法:1、Switch语句只能用于整数类型,枚举类型和String类型,不能用于浮点数类型和布尔类型;2、每个case语句后面必须跟着一个break语句,以防止执行其他case的代码块,没有break语句,将会继续执行下一个case的代码块;3、可以在一个case语句中匹配多个值,使用逗号分隔;4、Switch语句中的default代码块是可选的等等。

1071

2023.09.21

Java switch的用法
Java switch的用法

Java中的switch语句用于根据不同的条件执行不同的代码块。想了解更多switch的相关内容,可以阅读本专题下面的文章。

1457

2024.03.13

length函数用法
length函数用法

length函数用于返回指定字符串的字符数或字节数。可以用于计算字符串的长度,以便在查询和处理字符串数据时进行操作和判断。 需要注意的是length函数计算的是字符串的字符数,而不是字节数。对于多字节字符集,一个字符可能由多个字节组成。因此,length函数在计算字符串长度时会将多字节字符作为一个字符来计算。更多关于length函数的用法,大家可以阅读本专题下面的文章。

1987

2023.09.19

golang map内存释放
golang map内存释放

本专题整合了golang map内存相关教程,阅读专题下面的文章了解更多相关内容。

730

2025.09.05

golang map相关教程
golang map相关教程

本专题整合了golang map相关教程,阅读专题下面的文章了解更多详细内容。

343

2025.11.16

golang map原理
golang map原理

本专题整合了golang map相关内容,阅读专题下面的文章了解更多详细内容。

553

2025.11.17

java判断map相关教程
java判断map相关教程

本专题整合了java判断map相关教程,阅读专题下面的文章了解更多详细内容。

323

2025.11.27

DOM是什么意思
DOM是什么意思

dom的英文全称是documentobjectmodel,表示文件对象模型,是w3c组织推荐的处理可扩展置标语言的标准编程接口;dom是html文档的内存中对象表示,它提供了使用javascript与网页交互的方式。想了解更多的相关内容,可以阅读本专题下面的文章。

6541

2024.08.14

Go-Zero API开发使用教程
Go-Zero API开发使用教程

本专题聚焦Go-Zero的API开发,拆解.api描述文件的语法结构、路由与分组定义、参数绑定与校验规则,说明统一响应体、JWT鉴权、跨域与接口文档生成的落地方式,并给出网关聚合与接口测试的实践建议。

0

2026.11.12

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 12.5万人学习

国外Web开发全栈课程全集
国外Web开发全栈课程全集

共12课时 | 1.4万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn