你是否想过 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 元素对象的引用,因此也就无法将元素下载成图片。
原因是什么呢?