Next.js 14 SSR中router.refresh()无反馈的UX优化问题求助
Next.js 14 Todo应用编辑后router.refresh()无反馈的优化方案
场景说明
我正在用Next.js 14开发Todo应用:
/todos页面是Server Component,负责获取所有待办事项并向下传递数据,加载时会显示Loading.tsx兜底组件。- 编辑待办事项提交后,后端仅返回单个更新后的待办数据,之后调用
router.refresh()刷新页面,但该方法不是异步的,没有状态反馈,导致用户体验很差:点击保存后UI很快恢复可交互,但要过一会儿待办列表才更新,用户完全不知道中间的加载状态。
当前代码片段:
function EditToDoForm(){ const onSubmit = () => { setDisableUi(true) // editNote 是Server Action editNote(props.note.id, inputState.name, inputState.body).then(() => { setDisableUi(false) // -->1: UI恢复可交互,但没有任何加载动画 router.refresh() // -->2: 过一会儿待办列表才更新 - 糟糕的用户体验 }) } // 其他代码... }
核心问题:router.refresh()无状态反馈,无法在等待服务器返回新数据时展示加载动画,目前只能通过页面重载解决,求优化方案。
优化方案
方案1:手动管理全局加载状态,配合路由刷新周期
router.refresh()会触发页面重新渲染,此时Server Component的加载状态会再次激活。你可以在客户端维护一个刷新状态,在调用refresh()后保持UI禁用,直到路由状态完成更新。
实现示例:
'use client' import { useRouter, usePathname } from 'next/navigation' import { useState, useEffect } from 'react' function EditToDoForm(props) { const router = useRouter() const pathname = usePathname() const [disableUi, setDisableUi] = useState(false) const [isRefreshing, setIsRefreshing] = useState(false) // 监听路由路径变化,确认刷新完成后关闭加载状态 useEffect(() => { if (isRefreshing) { setIsRefreshing(false) setDisableUi(false) } }, [pathname, isRefreshing]) const onSubmit = () => { setDisableUi(true) editNote(props.note.id, inputState.name, inputState.body).then(() => { setIsRefreshing(true) router.refresh() // 不再立即关闭disableUi,交给路由监听处理 }) } return ( <form onSubmit={onSubmit}> {/* 表单内容 */} <button type="submit" disabled={disableUi || isRefreshing}> {disableUi || isRefreshing ? '保存中...' : '保存'} </button> </form> ) }
方案2:利用返回的单个待办数据直接更新客户端状态
既然后端已经返回了更新后的待办项,完全可以跳过全页刷新,直接在客户端替换列表中的旧数据,这样体验更流畅。
如果用React内置状态(假设父组件传递了待办列表和更新方法):
function EditToDoForm(props) { const [disableUi, setDisableUi] = useState(false) const onSubmit = () => { setDisableUi(true) editNote(props.note.id, inputState.name, inputState.body) .then(updatedNote => { // 调用父组件的更新方法,替换列表中的旧待办 props.updateTodo(updatedNote) setDisableUi(false) // 可选:关闭编辑弹窗/返回列表页 }) .catch(err => { setDisableUi(false) // 处理错误提示 }) } // 渲染表单... }
如果用React Query(适合复杂缓存场景):
'use client' import { useQueryClient, useMutation } from '@tanstack/react-query' function EditToDoForm(props) { const queryClient = useQueryClient() const [disableUi, setDisableUi] = useState(false) const editMutation = useMutation({ mutationFn: () => editNote(props.note.id, inputState.name, inputState.body), onMutate: () => setDisableUi(true), onSuccess: (updatedNote) => { // 更新缓存中的待办列表 queryClient.setQueryData(['todos'], oldTodos => oldTodos.map(todo => todo.id === updatedNote.id ? updatedNote : todo) ) setDisableUi(false) }, onError: () => { setDisableUi(false) // 错误提示 } }) const onSubmit = () => editMutation.mutate() return ( <form onSubmit={onSubmit}> <button type="submit" disabled={disableUi}> {disableUi ? '保存中...' : '保存'} </button> </form> ) }
方案3:监听Next.js路由事件控制加载状态
Next.js的路由系统提供了事件监听,可以捕获routeChangeStart和routeChangeComplete事件,用来控制局部UI的加载状态。
实现示例:
'use client' import { useRouter } from 'next/navigation' import { useState, useEffect } from 'react' function EditToDoForm(props) { const router = useRouter() const [disableUi, setDisableUi] = useState(false) useEffect(() => { const handleLoadingStart = () => setDisableUi(true) const handleLoadingEnd = () => setDisableUi(false) // 绑定路由事件监听 router.events.on('routeChangeStart', handleLoadingStart) router.events.on('routeChangeComplete', handleLoadingEnd) router.events.on('routeChangeError', handleLoadingEnd) // 组件卸载时移除监听 return () => { router.events.off('routeChangeStart', handleLoadingStart) router.events.off('routeChangeComplete', handleLoadingEnd) router.events.off('routeChangeError', handleLoadingEnd) } }, [router]) const onSubmit = () => { setDisableUi(true) editNote(props.note.id, inputState.name, inputState.body).then(() => { router.refresh() }) } // 渲染表单... }
内容的提问来源于stack exchange,提问作者Alexander Kolarov
相关产品推荐
相关产品推荐

