一个坑
因为书写 React 养成的习惯,我们通常在需要渲染一组元素时会使用 React.Fragment 将这一组元素包裹起来来避免新增一个元素从而带来额外的消耗,这在做 web 开发时算是一个最佳实践。
const fragment = <><div>1</div><div>2</div></>;
return someLogic ? fragment : null;
但是在 taro 上,和这个元素同级的元素都会被重渲染,而这种重渲染在某些场景下会导致很奇怪的问题。
function Comp() {
// ...logic
return (
<View>
<ScrollView {...props}/>
{showSomeItem && <SomeItem />}
</View>
)
}
// 每次 showSomeItem 改变为 false, 会触发整个同级元素的重渲染
Why
我们之前的文章中提到过,Taro 使用 taro-runtime 来抹平不同框架(react/vue)和不同端平台(微信小程序/飞书小程序)间的差异,这种差异的抹平大概分为如下步骤:
暂时无法在文档外展示此内容
直观一些我们可以通过小程序调试工具中的 AppData 来看 Taro 渲染页面的黑魔法:
在页面中已经不存在任何和页面数据绑定的data,取而代之的是整个页面节点的树结构,Taro 通过内部的框架处理插件将框架对 dom 的变动转至对其 Taro runtime 的变动,最后再通过 Taro runtime内部的节点 update 机制变成对 AppData 的变动,这才完成了对页面的渲染,相较于传统原生小程序开发中改变 data 刷新整个小程序页面,这种变化颗粒度更细,通过控制对应 dom 节点的 data (root.cn.1.3 = xxxx) 变化来确保只更新发生变化的节点。
一切看起来很美好,但是为什么会发生同级元素的重渲染呢?这涉及到 taro runtime 对删除节点的控制原理:
当节点新增、修改时,都可以通过节点的 Path 精确定位。
// 假设 Taro dom 同级节点原为 [a, b, c, d]
// 新增
this.setData({
cn[4]: e
})
// 最终得到 [a, b, c, d, e]
// 修改
this.setData({
cn[2]: f
});
// 最终得到 [a, b, f, d, e]
但是删除却有些不一样,因为小程序没有提供一种 setData 的方式,让 [a, b, c, d] 直接变成 [a, b, d]。
所以为了删除同级元素,Taro 内部做的处理是直接重写整个数组,也就是说如果要删除一个元素,和这个元素同级的元素都会被重渲染。
this.setData({
cn: `[a, b, d]`
})
// 最终得到 [a, b, d]
现在回头去看,就可以很清楚的了解为什么我们在使用 boolean && 的时候要注意重渲染问题,因为每次布尔值被置为 false,就会导致一次同级元素的重渲染(因为同级元素的dom data被重新更新了),这有可能会导致同级的 swiper 回到初始位置或者其他更多的初始渲染问题。
解决方案
这是一个官方已经明确的 bug,所以官方在文档中已经给出了临时的解决方案:删除楼层节点需要谨慎处理。
只需要在需要做删除的节点外层再包裹一个标签,将删除处理元素和同级元素区分开则可以很好的避开这个问题。