You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 15:01:16