熟悉我的同学可能知道,我近期开始尝试用 React Native + Expo 开发移动应用,接触到很多不之前熟悉的东西。当一切顺利的时候就还好,当遇到难以排查的问题时,就需要付出数倍于以往的努力才行。今天就分享近期一段调试 React Native 诡异 bug 的经历。
需求其实很简单,就是个单选组件。我使用 <ScrollView>
做外部容器,里面有若干<Pressable>
,点击哪个就选哪个。现在的问题是:选择之后,会更新 state,造成父组件刷新,继而 <ScrollView>
刷新,然后滚动位置就归零了,感观上就滚动到最上面了。很难受,折腾一晚上没解决……
我在 X 上求助,有同学回复:“就算父组件更新 也不应该滚动的 感觉可以贴个伪代码。”我觉得很有道理。因为虽然状态改变,但是只应该影响某个选项(也就是子组件)渲染,不应该整个 <ScrollView>
都被更新。还有位同学回复,让我直接问 Claude,还给我贴了 AI 的答复,我就……
我觉得 AI 出现给很多人带来一种错觉:我好厉害啊,我太懂了,我有 AI 加持,简直无敌了。我想说 AI 并不“真的”知道答案,它只是组织已知内容,相当于猜。蒙对了就对了,没蒙对它也不负责。对于 React Native 这种历史很长,变化很多的框架,更容易出错,参考价值很低。比如这个答案,就是错的,或者说没有实用意义。我建议大家如果有经验,就积极分享;如果只是从 AI 那里得到了似是而非未经验证的答案,就别到处张贴,即污染环境,又污染我们的语言。
言归正传。我继续调试这个 bug。后来怀疑这个 bug 是新版本 React Native(0.76)导致的,因为开发阶段没发现过这个问题。我怀疑是重新渲染之后,高度计算的时机不对,于是尝试给 ScrollView 加上固定高度,在模拟器上(18.1)好像修复了 bug;但是真机效果不明显。其实还是没解决,只不过模拟器和真机的表现不太一致。
无奈之下,我只好启用“代码挪移”大法,即,把工作正常的代码挪到出问题的位置,或者把出问题的代码挪到正常运行的位置。这下收获不小,范围一下子就缩小了。我发现,作为一系列页面(<Stack>
)里的第 5 个,如果把它挪到前面去,问题就消失;如果把前面的 123 挪到第 5 个,也会出现 bug。
于是我认为一定是第 4 页有问题,然后开始“缩小范围”大法,即,把第四页的组件全部注释掉,看看问题能否解决。如果可以的话,再把前面一半注释掉;再把前面 1/4 注释掉;如是反复。经过数轮缩小范围,我把问题确定到一个动画组件,非常简单,只是使用 react-native-reanimated 制作的简单 SlideInLeft / SlideInRight。
因为组件实在太简单,我无法再缩小范围。于是我尝试通过侦听事件的方式,看能不能在动画结束后换上静态组件,结果还是失败了,大概的表现是:只要在第四页使用动画组件播放了动画,就会影响后面的页面,出现大量意外的 re-rendering,造成各种无法预期的错误。
而且在 debug 过程中,我发现只要用到这个动画组件,都会影响后面的页面,只是有一些问题被我偷懒忽略了。所以其实跟 <ScrollView>
什么的没关系。
我跟老板商量,能否不用动画组件?他不同意,他觉得动画效果在这里很重要。于是我尝试用 React Native 原生的 Animate
实现动画,运气不错,问题解决。
总结一下吧。我们开发的时候,难免会撞鬼。我们当然应该先尝试用合理合法的方式去解决,比如查文档、问 AI。但是如果都无法解决,老办法如“代码挪移”,“缩小范围”也能起到效果。希望我这段经历对大家有所启发。
欢迎吐槽,共同进步