NextJS横竖屏切换时组件内部状态丢失如何解决
你的判断完全准确,问题本质是横竖屏切换时React卸载重建了整个子组件树,和普通的组件重渲染无关。
React.memo 对这个问题完全无效:memo的作用是跳过同位置、同类型组件的无意义重渲染,但你当前的写法是条件渲染两个完全独立的布局组件:
size.isPortrait? <NavPortrait children={props.children}/> : <NavLandscape children={props.children}/>
React的diff规则判定组件是否可复用的第一优先级是组件类型是否一致,这里两个分支的根组件类型完全不同,React会直接卸载旧分支下的所有子节点(包括你的MyPage组件),再挂载新分支的全新组件实例。MyPage会走完整的首次挂载流程,内部useState定义的状态自然会被重置为初始值false,哪怕给MyPage包多层memo也拦不住组件的卸载流程。
优先推荐:合并布局根组件,保证子节点挂载位置稳定
这是改动最小、长期维护成本最低的方案。
不要把横竖屏布局拆成两个平级的独立组件做条件切换,把两套布局的逻辑收拢到同一个NavWrapper组件内部:
- 组件外层始终保持同一个组件类型,从根源上避免子树被卸载重建
- 横竖屏的布局差异,优先通过CSS(媒体查询、动态className、CSS变量)控制样式实现;如果结构差异实在大,也在同一个NavWrapper内部做条件渲染不同布局区块,只要保证
{children}(即MyPage)的挂载位置、父组件链始终稳定即可。
只要MyPage的挂载路径不随横竖屏切换发生组件类型变化,MyPage的所有内部状态——包括弹窗显隐、表单输入值、滚动位置、临时缓存数据——都会完整保留,切换屏幕方向不会有任何状态丢失。
次选方案:需要跨切换保留的状态上移到稳定层级
如果两套布局的差异大到实在没法合并成同一个根组件,就把需要跨横竖屏切换保留的状态,往上挪到不会随屏幕方向切换被卸载的组件层级中。
你提到的Modal上下文方案是可行的:在NavWrapper的上层、或者应用根节点创建Modal Context Provider,把showModal状态、弹窗打开/关闭的控制方法都放到Provider中管理,MyPage里只需要通过context读取状态、调用控制方法即可。这时候哪怕MyPage因为布局切换被卸载重建,状态保存在上层稳定的组件实例里,切换完成后读取到的还是切换前的值,弹窗不会消失。
这个方案还有额外收益:全局弹窗逻辑统一管控后,后续其他页面调用弹窗不需要重复编写状态逻辑,也方便统一处理弹窗层级、挂载节点、滚动穿透这类通用问题。
临时补漏方案:弹窗通过Portal挂载到全局节点
如果只有弹窗这一个浮层存在状态丢失问题,暂时不想调整现有布局结构,可以给弹窗组件使用React Portal,把弹窗的DOM直接渲染到document.body这类应用组件树外的稳定节点上。
但这个方案只能保住弹窗本身的显隐状态,MyPage内部其他的表单输入值、滚动位置、组件缓存状态还是会在横竖屏切换时被重置,只适合临时救急,不推荐作为长期方案。
不要试图给NavPortrait和NavLandscape设置相同的key值,诱导React复用子节点。这种写法违反React的diff设计逻辑,后续大概率会出现DOM属性串扰、状态错绑到其他组件的诡异问题,排查成本极高。
内容的提问来源于stack exchange,提问作者Ali762

