第30篇窗口缩放后页面重置把布局状态和业务状态分开摘要窗口缩放后页面重置常见于把布局断点、选中项、筛选条件、滚动位置都塞进同一个页面状态。自由窗口、横竖屏或分屏变化时页面重新布局业务状态也被顺手清掉。更稳的做法是把布局状态和业务状态分开窗口变化只更新布局模式课程选中、筛选条件、草稿输入等业务上下文要保留下来。一个平板页面在全屏时左侧列表、右侧详情显示正常拖成窄窗口后切成单列再拖回宽屏用户刚选中的课程丢了筛选条件也回到默认。问题不是断点本身而是断点切换时重新初始化了整页状态。这篇文章解决四个实际问题如何区分窗口布局状态和业务上下文。如何用 LayoutPolicy 统一断点判断。如何在结构切换时保留选中项和筛选条件。如何验证分屏、旋转和长内容路径。先拆出布局状态和业务状态状态示例窗口变化时是否重置布局状态compact / medium / expanded可以重算业务状态selectedCourseId、filterKey不应重置输入状态搜索词、草稿不应因缩放丢失临时视觉状态hover、展开动画可按布局重置窗口变化只能影响排布方式不能代表用户换了业务上下文。用 LayoutPolicy 集中断点exporttypeLayoutModecompact|medium|expandedexportclassLayoutPolicy{resolve(widthVp:number):LayoutMode{if(widthVp600){returncompact}if(widthVp840){returnmedium}returnexpanded}}断点不要散在多个组件里。否则列表、详情、工具栏各自判断宽度缩放时就容易出现结构不同步。业务上下文单独建模exportinterfaceCourseWorkspaceState{selectedCourseId:stringfilterKey:stringkeyword:stringscrollAnchorId:string}exportfunctioncreateDefaultWorkspaceState():CourseWorkspaceState{return{selectedCourseId:,filterKey:all,keyword:,scrollAnchorId:}}这份状态描述用户在做什么。窗口宽度改变时它不应该被重新创建。页面只在尺寸变化时更新布局模式EntryComponentstruct CourseWorkspacePage{StatelayoutMode:LayoutModecompactStateworkspace:CourseWorkspaceStatecreateDefaultWorkspaceState()privatelayoutPolicy:LayoutPolicynewLayoutPolicy()privatehandleWidthChange(widthVp:number):void{constnextthis.layoutPolicy.resolve(widthVp)if(nextthis.layoutMode){return}this.layoutModenext}}handleWidthChange()只更新layoutMode。它不清筛选、不清选中项、不重新请求业务数据。按布局模式切结构BuilderprivateWorkspaceContent(){if(this.layoutModecompact){CourseSingleColumn({workspace:this.workspace,selectCourse:(id:string)this.selectCourse(id)})}else{CourseSplitPane({workspace:this.workspace,selectCourse:(id:string)this.selectCourse(id)})}}结构可以切传入的业务状态不变。这样从单列切回双列时右侧详情还能显示刚才选中的课程。缩放后恢复锚点privateselectCourse(courseId:string):void{this.workspace{...this.workspace,selectedCourseId:courseId,scrollAnchorId:courseId}}窗口变化后如果列表重新排布至少要知道用户刚才关注哪一项。scrollAnchorId可以帮助列表恢复到合理位置。验证清单宽屏选中课程后拖到窄屏选中项仍然存在。窄屏编辑搜索词后拖回宽屏搜索词不丢。分屏宽度来回拖动页面不重复请求无关数据。横竖屏切换后按钮和长标题不挤压。空态、详情态、编辑态都走一次缩放路径。宽窄布局不要共用一套临时组件状态从双栏切单栏时组件树可能变了。如果把业务状态藏在某个子组件里结构切换就会丢。状态应放位置当前选中的课程 id页面或页面级 Store当前筛选条件页面或 Service 查询条件列表项展开动画子组件内部详情页编辑草稿草稿服务或页面级状态判断标准很简单切换布局后用户仍然关心的状态应放在布局结构之外只和视觉展开相关的状态可以跟着组件销毁。用页面级 Store 保住业务上下文exportclassCourseWorkspaceStore{privatestate:CourseWorkspaceStatecreateDefaultWorkspaceState()getState():CourseWorkspaceState{returnthis.state}updateSelectedCourse(courseId:string):CourseWorkspaceState{this.state{...this.state,selectedCourseId:courseId,scrollAnchorId:courseId}returnthis.state}}如果页面结构复杂可以把业务上下文放到页面级 Store。注意这里仍然不是全局长期状态它只是帮助同一页面在布局结构变化时保住上下文。验证时要拖动窗口不只改模拟宽度真实自由窗口变化会连续触发多次尺寸变化。验证时要覆盖慢拖、快速拖、分屏、旋转和后台恢复。只在代码里手动传一个宽度值无法发现连续变化导致的抖动、重复请求和焦点丢失。验收时至少记录三类现象断点切换时是否重新请求业务数据。选中项、搜索词和草稿是否保留。长标题、按钮和列表项是否出现挤压。缩放问题要看连续变化不看单点宽度窗口拖动不是一次事件而是一串连续尺寸变化。页面如果每次宽度变化都重新请求数据、重建列表或清空详情就会在拖动过程中明显抖动。我的做法是让尺寸变化只更新layoutMode并且只有跨过断点时才切结构如果宽度仍在同一个断点范围内就不要触发业务刷新。连续拖动时重点看两件事同一断点范围内不应反复切结构。跨断点时只更新布局不清业务上下文。断点变化后的内容恢复规则场景恢复目标双栏切单栏打开当前选中课程详情单栏切双栏左侧保留列表位置右侧显示当前课程搜索中缩放保留关键词和筛选结果编辑中缩放保留草稿不重新创建编辑页这些规则要在开发前写清楚。否则每个组件都会按自己的理解处理缩放最后就会出现列表恢复了详情没恢复搜索词还在选中项没了。发布前验收不要只看模拟器如果目标设备包含平板、折叠屏或 PC 自由窗口至少要真机或对应模拟器拖动窗口验证一次。检查时重点看三件事布局切换有没有闪白、业务选择有没有丢、长标题和按钮有没有被压扁。视觉不稳定通常比代码报错更早暴露结构问题。验收记录建议保留当前设备或模拟器型号。横竖屏、分屏、自由窗口三组结果。仍需真机确认的布局风险。交付前验收表验收项通过标准断点切换只切布局不清业务状态搜索与筛选缩放后仍保留当前条件详情选中宽窄布局切换后仍指向同一课程长文案标题、按钮、空态不挤压窗口类问题要看连续动作。拖动过程里如果看到闪白、重新加载、选中项丢失就说明布局状态仍然影响了业务状态。线上回归时重点看状态是否被误初始化窗口变化后如果页面重新走了“初始化默认状态”通常会出现三个信号筛选条件回到全部、当前详情页关闭、列表滚动回到顶部。排查时我会先搜索createDefaultWorkspaceState()的调用位置确认它只在页面首次创建或用户明确重置时使用而不是在每次断点变化时使用。rgcreateDefaultWorkspaceState|handleWidthChange|layoutMode|selectedCourseIdentry features common rgaboutToAppear|onAreaChange|onPageShowentry features common这两条命令能把布局切换、页面生命周期和业务状态初始化位置放在一起看。只要发现尺寸变化回调里重新创建业务状态就要先移出去。小结窗口变化只应该改变布局一多适配里窗口变化是布局事件不是业务重置事件。断点判断集中在 LayoutPolicy业务上下文单独保存视图按布局模式切结构。这样自由窗口、分屏和旋转不会把用户正在做的事情清掉。ear|onAreaChange|onPageShow entry features common这两条命令能把布局切换、页面生命周期和业务状态初始化位置放在一起看。只要发现尺寸变化回调里重新创建业务状态就要先移出去。 ## 小结窗口变化只应该改变布局 一多适配里窗口变化是布局事件不是业务重置事件。断点判断集中在 LayoutPolicy业务上下文单独保存视图按布局模式切结构。这样自由窗口、分屏和旋转不会把用户正在做的事情清掉。