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、原生应用的事件处理程序以及任何其他事件中的更新都不会被批量处理;但现在,这些更新内容都会被自动批处理。

相关推荐
轻口味35 分钟前
【每日学点鸿蒙知识】AVCodec、SmartPerf工具、web组件加载、监听键盘的显示隐藏、Asset Store Kit
前端·华为·harmonyos
alikami38 分钟前
【若依】用 post 请求传 json 格式的数据下载文件
前端·javascript·json
wakangda1 小时前
React Native 集成原生Android功能
javascript·react native·react.js
吃杠碰小鸡1 小时前
lodash常用函数
前端·javascript
emoji1111111 小时前
前端对页面数据进行缓存
开发语言·前端·javascript
泰伦闲鱼1 小时前
nestjs:GET REQUEST 缓存问题
服务器·前端·缓存·node.js·nestjs
m0_748250031 小时前
Web 第一次作业 初探html 使用VSCode工具开发
前端·html
一个处女座的程序猿O(∩_∩)O2 小时前
vue3 如何使用 mounted
前端·javascript·vue.js
m0_748235952 小时前
web复习(三)
前端
AiFlutter2 小时前
Flutter-底部分享弹窗(showModalBottomSheet)
java·前端·flutter