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

相关推荐
码农幻想梦1 小时前
实验九 视图的使用
前端·数据库·oracle
开心工作室_kaic3 小时前
ssm010基于ssm的新能源汽车在线租赁管理系统(论文+源码)_kaic
java·前端·spring boot·后端·汽车
大力水手~4 小时前
css之loading旋转加载
前端·javascript·css
Nguhyb4 小时前
-XSS-
前端·xss
前端郭德纲4 小时前
深入浅出ES6 Promise
前端·javascript·es6
就爱敲代码4 小时前
ES6 运算符的扩展
前端·ecmascript·es6
王哲晓5 小时前
第六章 Vue计算属性之computed
前端·javascript·vue.js
究极无敌暴龙战神X5 小时前
CSS复习2
前端·javascript·css
风清扬_jd5 小时前
Chromium HTML5 新的 Input 类型week对应c++
前端·c++·html5
Ellie陈5 小时前
Java已死,大模型才是未来?
java·开发语言·前端·后端·python