在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合成事件
 - 生命周期
 - 事件处理,如
onClick、onChange等。 
2、在react可调度范围外的setState是同步的。
- 宏任务 
setTimeout、setInterval - 微任务 
.then - 原生 js 绑定事件 
document.addEventListener() 
3、setState 并非真正的异步,而是通过设置全局变量 isBatchingUpdates 来判断 setState是先存进队列还是直接更新,如果值为true,则执行异步操作,如果为false,则直接更新。
 isBatchingUpdates 会在 React 可以控制的地方,则为true,比如React生命周期和合成事件中,而在外部 react 无法控制的地方,比如原生事件,具体就是在 addEventListener、setTimeout、setInterval 、.then等事件中,就只能同步。
react18
React18版本引入了自动批处理功能,批处理是指,当 React 在一个单独的渲染事件中批量处理多个状态更新以此实现优化性能。如果没有自动批处理的话,我们仅能够在 React 事件处理程序中批量更新。在 React 18 之前,默认情况下 promise、setTimeout、原生应用的事件处理程序以及任何其他事件中的更新都不会被批量处理;但现在,这些更新内容都会被自动批处理。