好好好好好好,这里是萌新一位,由于最近一直在ant design开源社区贡献代码,突发奇想,想把antd的一些有意思的代码和米娜桑分享一下,于是就有了这么一篇文章
好的话不多说,现在开始今天的源码分析
首先呢,接触Ant Design Typography.Paragraph这个组件的契机是由于一个issue: github.com/ant-design/...
好的好的,现在有同学说链接打不开,没事应该是GitHub崩溃了,GitHub好好优化一下
我们知道啊,Ant Design的Typography.Paragraph呢它的字数超出我们设定的行数后,会进行省略,而且他的省略是真的把text截取了
那么这个issue所提现的问题是这样的,当我们手动resize浏览器窗口的高度到刚刚出现滚动条时,滚动条会一直闪烁,并且不会停止除非我们重新拉高,大家可以模仿这个issue试一下,这里是沙盒复现的链接,codesandbox.io/s/s7um3s?fi...
奇怪捏,这到底是为什么呢,(;′⌒`)
我们先来看看大佬的分析
豆酱大佬明确指出,咱们新时代的干部,要脚踏实地,心系人民,啊不不不不重新来啊
豆酱大佬明确指出,由于省略是由js计算得来的,但是滚动条出现时,触发了Typography.Paragraph的重新计算,计算后呢发现不用折行,滚动条消失了,又触发了Typography.Paragraph的重新计算,重新计算的过程中,发现又需要要折行,于是滚动条再次出现,又触发了Typography.Paragraph的重新计算,如此反复,循环不断
整理下情绪,我们回到正题,根据豆酱大佬的明确指示,我们可以得出结论,问题出在了Typography.Paragraph的计算过程中,那我们就要知道Typography.Paragraph的计算原理是什么
我们先看看antd源码的目录结构,找到component目录,再找到Paragraph,然后发现它引入的是Base文件
当我们满怀期待的点开base目录, woc!!!?计算逻辑呢?
但是,我们在最下面可以看到onResize的字样出现和包装了一个ellipsis组件
好好好好,到这里还没找到计算逻辑是吧,我们再点进去ellipsis看看是什么东西,一样我们直接找到下面return 的jsx
好的,这里又要经过一段摸索,才能知道真正渲染的dom是这个mergedChildren,就不多啰嗦了,我们直接去看这个mergedChildren是什么东东
这是一个依赖于walkingState和midlen的useMemo,那我们去看看这三个变量都是干嘛的
首先我们找到midlen是怎么变化的,发现这段代码
好好好,我们直接去找setCutLength这个函数用在了哪就行了,我们发现这两个useIsomorphicLayoutEffect中用了它,这个useIsomorphicLayoutEffect是antd自己封装的函数,我们这次就先不讲
到这里我们就能看出来ellipsis的计算逻辑了,首先第一个useIsomorphicLayoutEffect在每次width变化时,将[startLen, midLen, endLen]重置,让他们取0、完整字符长度的一边和完整字符的长度,并setWalkingState(PREPARE),下面的第二个useIsomorphicLayoutEffect依赖于walkingState,当其变化时,它会开始计算,首先一个逻辑,必须要有每一行的高度,不然没法算,直接不管
再走到里面真正的第一个逻辑,如果walkingState是PREPARE,也就是上面的重置,他会计算一个过渡高度midHeight,取的是midRowRef的高度,这个东西是什么呢,其实就是我们一开始看到的下面的这个东东
这个东东呢其实是一个不可见的dom,它的宽度、样式和内容都和真实要真正显示给用户的dom一致,可以理解为,当ellipsis重新计算的时候,不会直接改变展示给用户的dom,而是先创建一个不可见的dom,在这个dom中模拟要显示的内容,然后计算出适合的字符长度,再真正改变展示给用户的dom的字符长度,这样就不会导致用户看到计算过程
当walkingstate是PREPARE时,这个midRowRef是完整字符的内容,那么midHeight其实就是当字符串完整时dom的高度,而这个maxHeight就是我们用户设置的行数乘于单行的高度,也就是用户允许的最高内容高度,如果这个完整字符串的高度小于或等于用户允许的最高内容高度说明就不用省略了,就不用计算了,否则就setWalkingState(WALKING),开始我们下面的计算
好的我们现在可以看到,这其实是一个二分法查找最适合的字符长度,依然是拿到当字符串完整时dom的高度和用户允许的最高内容高度,先记录上一个首尾长度,也就是这个哈
经过了第一个useIsomorphicLayoutEffect,它发生了改变不再是[0,0,0],我们假设它是[0,250,500],500是完整字符串的长度
现在我们走第一个逻辑,else if (midHeight <= maxHeight) ,如果现在过渡的高度小于用户允许的高度,那么说明字符串的长度还是小了,我们可以让开始长度等于过渡长度,也就是[250,250,500],然后下一个过度长度等于首尾长度除于一半,也就是[250,375,500],然后375作为过渡字符长度设置到之前提到的不可见的计算dom上,然后这个dom渲染以后再来计算我们假设字符串太多了,换行了,现在去走另一个逻辑
也就是else { ,过渡高度大于了用户许可的高度,就是midHeight >maxHeight,这个时候呢就将结尾长度设为过渡长度,然后下一个过度长度等于首尾长度除于一半,也就是[250,313(四舍五入),375],再去计算,如此循环往复,直到首尾长度的差值为一时,说明此时已经计算完了,达到了最合适的长度,将他们俩取相等,走下面else的逻辑,setWalkingState(DONE_WITH_ELLIPSIS);
这样就算计算完了,真实设置到dom上,那么问题来了,根据这个逻辑,我们设置的字符串长度应该都是符合用户行数不会折行的呀,根据这个沙盒实例codesandbox.io/s/s7um3s?fi...
这个示例中,ellipsi出现了四行的情况,这应该是不可能的呀,另外这里我们也可以知道width的变化可以触发ellipsis 的重新计算,而滚动条的出现改变了width
回归问题,到底是什么让ellipsi出现了四行的情况,我们去看真实渲染到dom上的mergedChildren,是怎么计算的,
好了我们可以发现,这里return了两种节点
一种是根据过渡长度裁剪出来的节点 return children(sliceNodes(nodeList, midLen)
而另一种则是完整的节点return children(nodeList, false)
而这个完整的节点的返回逻辑是,当walkingstate不等于DONE_WITH_ELLIPSIS,也就是我们上面提到的省略计算完成,也就是说,当没有计算完成时ellipsis默认返回完整的节点,是这个节点造成的四行,因此我们只要思考,计算过程应该返回什么节点就好啦
最后的思路是,我们直接使用上一次ellipsis计算出的真实dom的字符长度来返回节点就好啦,因为上一次的计算结果一定是满足小于用户设置的最高高度,所以是安全可靠的,最后的解决方式大家可以参考我的pr: github.com/ant-design/...
当然肯定也有其它的方式解决,欢迎大家一起技术分享、讨论