react18中在列表项中如何使用useRef来获取每项的dom对象

在react中获取dom节点都知道用ref,但是在一个列表循环中,这样做是行不通的,需要做进一步的数据处理。

实现效果

需求:点击每张图片,当前图片出现在可视区域。

代码实现

css 复制代码
.box{
  border: 1px solid #000;
  list-style: none;
  padding: 0;
  font-size: 0;
  width: 600px;
  white-space: nowrap;
  overflow: hidden;
}
.box li{
  display: inline-block;
  padding: 10px;
  
}
.active{
  border: #000 1px solid;
}
.img{
  width:80px;
  height:80px;
  display: block;
}
  • 核心代码
js 复制代码
import { useEffect, useState, useRef } from "react";
import { flushSync } from "react-dom";
import "./compref.css";
function CompRef() {
  const [catLists, setCatLists] = useState([]);
  const [index, setIndex] = useState(0);
  const itemsRef = useRef(null);
  useEffect(async () => {
    const getData = await fetch(
      "https://xxxxx"
    );
    const data = await getData.json();
    const lists = data.map((item, index) => ({ id: index, url: item.url }));
    setCatLists(lists);
  }, []);

  function getMap() {
    if (!itemsRef.current) {
      // 首次运行时初始化 Map。
      itemsRef.current = new Map();
    }
    return itemsRef.current;
  }

  return (
    <>
      <div>CompRef</div>
      <ul className="box">
        {catLists.map((item, i) => (
          <li
            key={item.id}
            className={index === i && "active"}
            onClick={() => {
              setIndex(i);
              const map = getMap();
              const node = map.get(index);
              node.scrollIntoView({
                behavior: "smooth",
                block: "nearest",
                inline: "center",
              });
            }}
            ref={(node) => {
              const map = getMap();
              if (node) {
                map.set(item.id, node);
              } else {
                map.delete(item.id);
              }
            }}
          >
            <img className="img" src={item.url} alt="" />
          </li>
        ))}
      </ul>
    </>
  );
}
export default CompRef;

思维扩展

需求:点击下一张按钮,图片出现在可视区域。

  • 实现效果
  • 代码实现
js 复制代码
import { useEffect, useState, useRef } from "react";
import { flushSync } from "react-dom";
import "./compref.css";
function CompRef() {
  const [catLists, setCatLists] = useState([]);
  const [index, setIndex] = useState(0);
  const itemsRef = useRef(null);
  useEffect(async () => {
    const getData = await fetch(
      "https://xxxxxxxxxxxxxxxxxxx"
    );
    const data = await getData.json();
    const lists = data.map((item, index) => ({ id: index, url: item.url }));
    console.log("🚀 ~ useEffect ~ data:", lists);
    setCatLists(lists);
  }, []);

  function getMap() {
    if (!itemsRef.current) {
      // 首次运行时初始化 Map。
      itemsRef.current = new Map();
    }
    return itemsRef.current;
  }

  return (
    <>
      <div>CompRef</div>
      <button
        onClick={() => {
          setIndex(index);
          if (index < catLists.length - 1) setIndex(index + 1);
          else setIndex(0);
          const map = getMap();
          const node = map.get(index);
          console.log("🚀 ~ CompRef ~ node:", node);
          node.scrollIntoView({
            behavior: "smooth",
            block: "nearest",
            inline: "center",
          });
        }}
      >
        click
      </button>
      <ul className="box">
        {catLists.map((item, i) => (
          <li
            key={item.id}
            className={index === i && "active"}
            ref={(node) => {
              const map = getMap();
              if (node) {
                map.set(item.id, node);
              } else {
                map.delete(item.id);
              }
            }}
          >
            <img className="img" src={item.url} alt="" />
          </li>
        ))}
      </ul>
    </>
  );
}
export default CompRef;

可以看出上面的确实现了效果,但是有个小Bug,大家可以去测试下,自己排查优化下。


修复代码

js 复制代码
import { useEffect, useState, useRef } from "react";
import { flushSync } from "react-dom";
import "./compref.css";
function CompRef() {
  const [catLists, setCatLists] = useState([]);
  const [index, setIndex] = useState(0);
  const itemsRef = useRef(null);
  const nowIndex = useRef(0);
  useEffect(async () => {
    const getData = await fetch(
      "https://xxxxxxxxxxxxxxxxxxxxxxx"
    );
    const data = await getData.json();
    const lists = data.map((item, index) => ({ id: index, url: item.url }));
    console.log("🚀 ~ useEffect ~ data:", lists);
    setCatLists(lists);
  }, []);

  function getMap() {
    if (!itemsRef.current) {
      // 首次运行时初始化 Map。
      itemsRef.current = new Map();
    }
    return itemsRef.current;
  }

  function handleClick() {
    flushSync(() => {
      if (index < catLists.length - 1) {
        setIndex((index) => index + 1);
        nowIndex.current = index + 1;
      } else {
        setIndex(0);
        nowIndex.current = 0;
      }
      console.log(index);
      console.log("🚀 ~ CompRef ~ nowIndex:", nowIndex);
    });
    const map = getMap();
    const node = map.get(nowIndex.current);
    node.scrollIntoView({
      behavior: "smooth",
      block: "nearest",
      inline: "center",
    });
  }

  return (
    <>
      <div>CompRef</div>
      <button onClick={handleClick}>click</button>
      <ul className="box">
        {catLists.map((item, i) => (
          <li
            key={item.id}
            className={index === i && "active"}
            ref={(node) => {
              const map = getMap();
              if (node) {
                map.set(item.id, node);
              } else {
                map.delete(item.id);
              }
            }}
          >
            <img className="img" src={item.url} alt="" />
          </li>
        ))}
      </ul>
    </>
  );
}
export default CompRef;
  • 实现效果
    这样,就不会延迟显示一张图片了

总结

react17中

1、在react可调度范围内的setState是异步的,并且多次setState会合并只执行最后一次,进行批量更新。

  • react合成事件
  • 生命周期
  • 事件处理,如onClickonChange等。

2、在react可调度范围外的setState是同步的。

  • 宏任务 setTimeoutsetInterval
  • 微任务 .then
  • 原生 js 绑定事件 document.addEventListener()

3、setState 并非真正的异步,而是通过设置全局变量 isBatchingUpdates 来判断 setState是先存进队列还是直接更新,如果值为true,则执行异步操作,如果为false,则直接更新。
isBatchingUpdates 会在 React 可以控制的地方,则为true,比如React生命周期和合成事件中,而在外部 react 无法控制的地方,比如原生事件,具体就是在 addEventListenersetTimeoutsetInterval.then等事件中,就只能同步。

react18

React18版本引入了自动批处理功能,批处理是指,当 React 在一个单独的渲染事件中批量处理多个状态更新以此实现优化性能。如果没有自动批处理的话,我们仅能够在 React 事件处理程序中批量更新。在 React 18 之前,默认情况下 promise、setTimeout、原生应用的事件处理程序以及任何其他事件中的更新都不会被批量处理;但现在,这些更新内容都会被自动批处理。

相关推荐
格子软件6 小时前
2026年GEO优化系统源码级状态机与多模型调度拆解
java·前端·vue.js·人工智能·vue·geo
HUMHSX7 小时前
Vue 项目启动全流程解析:从入口文件到全局指令注册与页面渲染
前端·javascript·vue.js
有颜有货7 小时前
PMC生产排产的4种算法,一次讲清
java·服务器·前端
小虎牙0077 小时前
Android kotlin图片库Coil源码详解
android·前端
随风一样自由7 小时前
【前端领域】前端开发核心应用场景与落地实践
前端·前端框架
an317428 小时前
弹窗数据流设计的两种高阶架构实践
前端·vue.js·架构
谢尔登8 小时前
【React】 状态管理方案
前端·react.js·前端框架
用户2136610035728 小时前
Vue商品详情与放大镜组件
前端·javascript
半个落月8 小时前
从Tapas小Demo理清localStorage、事件与this
前端·javascript
李明卫杭州8 小时前
Vue2 中 v-model 处理不同数据结构的技巧
前端·javascript·vue.js