你是否想过 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 及其应用场景 。
总结一下这篇文章的知识点就是:
笔者负责了一个开发业绩长图的需求,场景是将一篇比较丰富的海报用 DOM 还原出来供用户预览,再通过类似于“截图”的方式将海报下载成图片。业内截图用的比较多的是 html2canvas 。
附上简单代码。
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>
)
}
简单梳理代码过程如下
领导建议组件中UI代码和逻辑代码分离,这样对团队成员的协同开发和代码的可读性都有好处。UI代码即 jsx 代码,逻辑代码包括 hook 代码和各种回调函数代码,将逻辑代码抽成自定义 hook 代码,第一反应是从上述代码抽解出自定义的下载图片 hook(后称 useDownload hook ),useDownload hook 唯一依赖于 DOM 节点,因此我很自然地将 DOM 节点即 reviewRef.current 当做函数参数传入 useDownload hook 中
/**
* 下载预览区域为图片的业务逻辑钩子
*/
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 元素对象的引用,因此也就无法将元素下载成图片。
原因是什么呢?