遇到的问题
想在useEffect中获取到外层元素的高度值,但获取到的总是比真实值要小
useEffect(() => {
console.log(contentRef.current.clientHeight)
},[mdHtml,contentLoading])
{ contentLoading ? <div className="content__loading"><Spin /></div> : <div className="content__body markdown-body" onClick={handleContentClick} ref={contentRef} dangerouslySetInnerHTML={{ __html: mdHtml }} />}
猜想是因为mdHtml中有很多图片,此时图片没有完全加载完成,于是加了promise进行测试
const imgLoadFinish = () => {
const promiseAry = [];
const imgs = [...contentRef.current.querySelectorAll('img')];
imgs.forEach((img, i) => {
promiseAry[i] = new Promise((resolve) => {
img.onload = () => {
resolve(img);
};
});
});
Promise.all(promiseAry).then(() => {
createWaterMark();
});
};
useEffect(() => {
if (mdHtml && !contentLoading) {
imgLoadFinish();
}
}, [mdHtml, contentLoading]);
当图片加载完成后,在createWaterMark中可以获取到正确的高度
问题解决了,但一个疑问是,React官网中写到,useEffect是在布局和绘制之后执行的:
这个时候图片还没有加载完成吗?那么浏览器渲染一个页面的顺序是什么?带着疑问找了相关文章,总结如下
浏览器渲染页面过程
构建Dom Tree
后端返回html页面,浏览器构建DOM Tree,(从数据Json-> token -> 节点 -> DOM Tree)在构建过程中,如果有图片 css等资源,会发起请求,但不会阻塞页面继续渲染
构建Css Tree
加载css,并构建Css Tree
构建Render Tree(布局)
将css和dom合并,构建Render Tree,此时并不会渲染到页面上,只是通过Render Tree来确定页面布局(即元素在页面的什么位置,元素大小是多少)有两种常见的状况会引起重新Render
1)增删元素(包括插入后续请求到的图片资源)
2)改变盒模型
绘制
Render Tree构建并开始布局后,就可以将Render Tree(即元素颜色,字体等)绘制到页面上,这个时候的更改只会引起页面的部分改变
相关资源:
developer.mozilla.org/en-US/docs/… developer.mozilla.org/zh-CN/docs/…
小结:之所以这样进行,是因为浏览器是单线程运行的,在同一时间,最好不要阻塞主线程的运行。
回到之前的问题,在浏览器完成布局和绘制之后,对于图片等资源的请求也可能没有完成,所以此时获取不到元素真实的高度
总结:
- React的渲染机制是按照浏览器的渲染流程来的,浏览器渲染一个页面时,会先构建DOM树,如果遇到图片等资源不会阻塞,会继续构建
- 因为浏览器是单线程运行的,所以对于一些数据的获取,最好异步或后续执行,所以需要componentDidMount/useEffect生命周期函数。
后续:
上述解决方式会引发其他问题,比如某一张图片加载失败后,整个页面就没有办法显示,并且页面初始化会比较慢
解决办法是,将图片的初始化状态设置为false,同时去获取父元素高度。在useEffect中监听图片状态,当图片加载完成后,再次获取图片高度