移动端页面做得越深入,越会发现它不是缩小尺寸的 PC 页面。屏幕空间有限,用户使用触摸完成操作,页面内容往往又依赖异步请求:筛选面板、吸顶导航、下拉加载、详情弹层和错误重试经常同时存在。
最近在用 React 整理这类移动端页面时,我越来越倾向于先拆状态,再开始拆组件。因为页面是否容易维护,通常不取决于 JSX 写得多漂亮,而取决于一个交互发生以后,哪些地方应该变化。
页面状态可以先分成三类
一个包含筛选和信息流的移动页面,大致有三种状态:
业务数据状态:列表数据、分页信息、筛选项、详情内容界面展示状态:筛选层是否展开、当前 Tab、骨架屏和空状态交互过程状态:请求是否进行中、是否还有下一页、是否正在提交
如果所有状态都集中到一个大对象里,列表翻页、切换筛选和打开弹层会互相影响。更清晰的方式是让每一类状态拥有明确用途:
class FeedPage extends React.Component { state = { list: [], query: { category: "all", sort: "hot" }, page: 1, hasMore: true, loading: false, filterVisible: false, activeItem: null };}
list 与 query 决定数据展示,filterVisible 和 activeItem 决定局部界面,loading 与 hasMore 控制请求节奏。这种划分并不复杂,但能够减少交互叠加之后的混乱。
筛选变化需要重置列表上下文
移动端列表最常见的问题之一,是筛选条件变化以后仍然沿用旧分页位置。用户已经从“热门”切到“最新”,页面却在新条件下请求第二页,或者新旧列表内容混在一起。
筛选确认时应当显式重置列表状态:
handleFilterConfirm = query => { this.setState( { query, page: 1, list: [], hasMore: true, filterVisible: false }, () => this.loadList() );};
这里并不是仅修改查询参数,而是开启了一次新的列表会话。将这个边界写清楚,后面加入排序、地区或价格条件时,也不会不断补特殊判断。
触底加载要避免重复请求
移动端滚动很快,触底回调可能在短时间内多次触发。如果不保护请求状态,相同页数据可能重复插入列表。
loadMore = () => { const { loading, hasMore, page } = this.state; if (loading || !hasMore) return; this.loadList(page + 1);};loadList = nextPage => { this.setState({ loading: true }); fetchFeed({ ...this.state.query, page: nextPage }) .then(result => { this.setState(prevState => ({ list: nextPage === 1 ? result.items : prevState.list.concat(result.items), page: nextPage, hasMore: result.hasMore })); }) .finally(() => { this.setState({ loading: false }); });};
加载锁、页码和是否结束这三个信息需要放在同一个流程中维护,否则页面可能表现为底部 loading 不消失,或者重复出现相同内容。
弹层与滚动需要一起处理
筛选面板或图片预览出现时,底层列表如果仍能滚动,会让用户关闭弹层后回到意料之外的位置。移动端 Web 页面需要处理背景滚动锁定:
function lockPageScroll() { document.body.style.overflow = "hidden";}function unlockPageScroll() { document.body.style.overflow = "";}
实际场景中还要考虑 iOS WebView 的滚动容器和弹层内部滚动区域。重要的是,弹层不能只被看作一个显示状态,它会影响页面的触摸和滚动上下文。
组件复用应围绕稳定交互
移动页面里适合抽出的组件,通常不是最小的一段 DOM,而是边界比较稳定的交互模块:
FilterPanel:维护筛选选择过程,只在确认时向页面返回查询条件。FeedList:接收列表数据、加载状态和触底事件,不负责构造请求参数。ImagePreview:负责预览手势与关闭动作,不修改列表数据。EmptyState:区分首次无内容与筛选后无结果。
页面组件负责组织请求和状态关系,局部组件负责交互细节。这样业务新增条件时,修改范围更可控。
移动端页面最终是在管理反馈节奏
React 帮助我们把界面描述成状态的结果,但移动端体验是否可靠,仍然取决于对交互节奏的处理:点击之后有没有即时反馈,请求变慢时是否保留上下文,弹层打开以后页面是否稳定,返回列表时用户是否还能找到刚才的位置。
把数据、展示与交互过程拆开以后,页面并不会少掉复杂度,但复杂度有了归属。这是 React 在移动端业务中最有价值的地方。