React Ref 为什么是对象

2022-12-08 16:13:50 浏览数 (1)

你是否想过 React 中 ref 的用法是 ref.current 而不是直接通过 ref 获得我们想要的数据,这个包含 current 属性的对象结构是多此一举吗?毕竟它有且仅有 current 这一个属性。const ref = useRef(null); // 声明 ref console.log(ref.current); // 使用 ref 为什么不直接设计成 console.log(ref)先说结论,React Ref 的数据结构设计成 JavaScript Obeject 是为了让数据在其他作用域中也能被正确地读取

在React 函数式组件(FC)中,我们使用 useRef hook 来声明 ref 数据,可能你对 ref 特性或者 useRef hook 并不熟悉,这里有一篇文章深入浅出地介绍了 useRef 及其应用场景 。

总结一下这篇文章的知识点就是:

  • ref 数据和 state 数据不同点在于,ref 更新时组件不会更新(重走一遍函数作用域)
  • 由于 ref 的上述特性,它常常可以用作保存无需响应式更新UI的数据,用的最多的是保存某个 DOM 节点对象的引用

一个截图的例子

笔者负责了一个开发业绩长图的需求,场景是将一篇比较丰富的海报用 DOM 还原出来供用户预览,再通过类似于“截图”的方式将海报下载成图片。业内截图用的比较多的是 html2canvas 。

附上简单代码。

代码语言:javascript复制
const App = () => {
  const reviewRef = useRef(null) // 创建 ref,用于引用 DOM 节点对象

  /**
   * 点击下载按钮后进行简单的保存 DOM 为图片并下载的逻辑
   */
  const onClick = () => {
    reviewRef.current &&
      html2canvas(reviewRef.current, {}).then((canvas) => {
        downloadByB64({
          fileName: "report.jpg",

          b64: `${canvas.toDataURL()}`,
        })
      })
  }

  return (
    <div>
      <button onClick={onClick}>下载图片</button>
      {/* 以下是预览区域 */}
      <article ref={reviewRef}>
        <h1>标题</h1>
        <p>内容</p>
      </article>
    </div>
  )
}

简单梳理代码过程如下

  • App 组件内声明了 ref 数据 reviewRef,声明了回调函数 onClick,App 函数作用域返回 jsx 代码,将 onClick 回调函数设置为 button 元素的 click event handler,当页面中的App组件渲染完毕后,reviewRef 和 article 元素形成一对一的关系,具体表现为 review.ref 为 article 的 DOM 元素引用
  • 当用户点击下载图片 button,onClick 回调函数执行,完成预期的下载操作。

UI和逻辑分离

领导建议组件中UI代码和逻辑代码分离,这样对团队成员的协同开发和代码的可读性都有好处。UI代码即 jsx 代码,逻辑代码包括 hook 代码和各种回调函数代码,将逻辑代码抽成自定义 hook 代码,第一反应是从上述代码抽解出自定义的下载图片 hook(后称 useDownload hook ),useDownload hook 唯一依赖于 DOM 节点,因此我很自然地将 DOM 节点即 reviewRef.current 当做函数参数传入 useDownload hook

代码语言:javascript复制
/**
* 下载预览区域为图片的业务逻辑钩子
*/
const useDownload = (el) => {
  const onClick = () => {
    el &&
      html2canvas(el).then((canvas) => {
        downloadByB64({
          fileName: "report.jpg",
          b64: `${canvas.toDataURL()}`,
        })
      })
  }

  return onClick
}

const App = () => {
  const reviewRef = useRef(null)
  const onClick = useDownload(reviewRef.current)

  return (
    <div>
      <button onClick={onClick}>下载图片</button>
      {/* 以下是预览区域 */}
      <article ref={reviewRef}>
        <h1>标题</h1>
        <p>内容</p>
      </article>
    </div>
  )
}

但是这样写出来代码却并不符合预期,一番 debug 过后,发现在点击下载图片按钮,执行 onClick 回调的过程中,el 的值为一直为 null ,而并非 DOM 元素对象的引用,因此也就无法将元素下载成图片。

原因是什么呢?

0 人点赞