react 中 ref 管理列表

本文介绍了在React中如何管理列表中多个元素的ref,以便利用scrollIntoView实现滚动定位。通过使用ref回调函数,创建一个映射或列表来保存每个元素的引用。同时,文章提到了在状态更新后同步滚动的问题,指出在更新state后立即滚动可能因DOM未实际更新而出现问题。为解决这个问题,可以使用flushSync强制同步更新DOM,确保滚动操作在正确的时机执行。
摘要由CSDN通过智能技术生成

背景

最近在看 react 新的官方文档 的时候,看到这么一个标题,How to manage a list of refs using a ref callback,就是一个图片的列表,类似这样
在这里插入图片描述
然后点击按钮的时候,通过 scrollIntoView 这个 api 来让他滚动,要使用这个 api 我们就要通过 ref 拿到这个 dom 元素,但是如果是一个列表的话,我们要怎么去做这个事情呢?难道循环用 useRef 申明一堆 ref 引用吗?显然不合理。

问题

文档里头还给了一段代码

<ul>
  {items.map((item) => {
    // Doesn't work!
    const ref = useRef(null);
    return <li ref={ref} />;
  })}
</ul>

显然这段代码肯定是不可行的,都跑不起来,因为 hooks 他只能在组件的最顶层,是不可以放在判断语句或者循环语句里头的

解决方法 ref callback

ref callback 就是给 ref 传一个函数,react 在设置 ref 的值的时候,会自动去调用这个函数,然后我们就可以通过这个函数去维护一个数组或者一个 map,从而实现我们需要多个 ref 的需求。听着好像挺复杂的,上代码吧

const RefListDemo = () => {
  const itemsRef = useRef(null)

  function getMap() {
    if (!itemsRef.current) {
      itemsRef.current = new Map()
    }
    return itemsRef.current
  }

  function scrollToId(itemId) {
    const map = getMap()
    const node = map.get(itemId)
    node.scrollIntoView({
      behavior: 'smooth',
      block: 'nearest',
      inline: 'center',
    })
  }

  return (
    <>
      <nav>
        <button onClick={() => scrollToId(0)}>Tom</button>
        <button onClick={() => scrollToId(5)}>Maru</button>
        <button onClick={() => scrollToId(9)}>Jellylorum</button>
      </nav>
      <div>
        <ul
          style={{
            listStyle: 'none',
            display: 'flex',
            width: 300,
            overflow: 'scroll',
            flexWrap: 'nowrap',
          }}
        >
          {catList?.map((cat) => (
            <li
              key={cat.id}
              style={{ marginLeft: 12 }}
              // 用这样的方式,就可以拿到每个节点,并进行维护了
              ref={(node) => {
                const map = getMap()
                node ? map.set(cat.id, node) : map.delete(cat.id)
              }}
            >
              <img src={cat.imageUrl} alt={'Cat #' + cat.id} />
            </li>
          ))}
        </ul>
      </div>
    </>
  )
}

上面代码的关键就在这里,我们可能平时传递 ref 的时候,都是传递一个具体的引用,好像都比较少用到这种传递函数的方式,官方是使用 map 维护,用列表也是一样的逻辑。

ref={(node) => {
	const map = getMap()
	node ? map.set(cat.id, node) : map.delete(cat.id)
}}

代码示例我放到这里啦。如果有兴趣的话,可以来这里试一下

先有 Dom 再滚动

在文档里头还有说到另一个问题,像这样在输入框输入,然后插入到队列里头,然后还是用 scrollIntoView 这个 api 滚动到新插入的结点的位置,但是这就可能会有一个问题,我们 setTodos 之后就执行了 scrollIntoView

但是我们都知道在 react 里头,更新 state 都是在队列里头更新的,并不是每次 setState 的时候就会立即更新, 所以更新上去的 dom 可能还没插入进去,我们就执行了 scrollIntoView,他不是一个一定会出现的问题,但是如果在实现类似的需求的时候,我们就得意识到这是个潜在的问题,

setTodos([ ...todos, newTodo]);
listRef.current.lastChild.scrollIntoView();

在这里插入图片描述

flushSync

react-dom 里头提供了一个 api,可以让我们实现同步更新 dom, 就是 flushSync,在 setTodos 的时候,用 flushSync 就能避免这样的问题发生

flushSync(() => {
  setTodos([ ...todos, newTodo]);
});
listRef.current.lastChild.scrollIntoView();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值