• react 中 ref 管理列表


    背景

    最近在看 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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    显然这段代码肯定是不可行的,都跑不起来,因为 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>
        </>
      )
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

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

    ref={(node) => {
    	const map = getMap()
    	node ? map.set(cat.id, node) : map.delete(cat.id)
    }}
    
    • 1
    • 2
    • 3
    • 4

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

    先有 Dom 再滚动

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

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

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

    在这里插入图片描述

    flushSync

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

    flushSync(() => {
      setTodos([ ...todos, newTodo]);
    });
    listRef.current.lastChild.scrollIntoView();
    
    • 1
    • 2
    • 3
    • 4
  • 相关阅读:
    算法进修Day-33
    算法项目(1)—— LSTM+CNN+四种注意力对比的股票预测
    创建自定义日志筛选器
    计算机毕业设计-基于SSM的网上书店管理系统
    安防监控视频汇聚平台EasyCVR视频广场搜索异常,报错“通道未开启”的问题排查与解决
    Python机器学习从0到1:从十余行代码看机器学习的套路(附零基础学习资料)
    微软AI文生图新突破,用于图像生成的多LoRA组合训练模型Multi-LoRA-Composition
    清除浏览器js缓存
    UDP和TCP两大协议的区别,让你快速高效掌握
    【软件与系统安全笔记】六、恶意代码的机理及其防护
  • 原文地址:https://blog.csdn.net/qq_42501092/article/details/127884467