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

React使用useEffect重渲染时前端博客列表无法正确更新

问题根因

你的猜测完全正确,除此之外代码里还有几个显性bug共同导致了问题:

  • 存在localStorage里的loggedBlogUser是登录瞬间接口返回的静态数据,其中的blog字段只包含登录时刻该用户的所有博客,登录后新增的博客从来没有被更新进这份本地存储的数据里,刷新页面读本地数据自然拿不到新内容
  • 监听user变化的useEffect存在变量名错误:你定义的暂存新博客的状态是数组newBlogs,effect里却写了不存在的单个变量newBlog,逻辑本身就无法正常执行
  • 这个useEffect的依赖数组只加了user,哪怕newBlogs更新也不会触发博客列表重新计算
  • 登录态恢复的逻辑只做了读本地用户数据,没有在页面加载、用户已登录的情况下主动拉取服务端最新的博客列表,完全依赖本地存储的旧快照
  • 登录失败提示的setTimeout参数写错,第二个参数传了空数组而非延迟毫秒数,提示无法正常自动消失
  • 新增博客时直接用前端构造的无id临时对象更新列表,会导致渲染key异常
修复步骤
  • 修复基础语法bug,调整setTimeout参数为正确的延迟时间
  • 调整博客列表加载逻辑:页面检测到本地有登录态、设置完user信息后,主动调用博客接口拉取当前用户的最新全量博客列表,不要直接用user对象里带的旧博客数据
  • 新增博客成功后,除了更新前端状态,同步更新localStorage里存储的用户数据,保证本地快照和最新状态一致
  • 移除冗余的newBlogs状态,避免多状态同步导致的数据不一致问题
  • 新增博客时使用接口返回的带唯一id的完整博客对象更新列表,避免渲染key错误
修正后完整代码
import { useState, useEffect } from 'react'
import Blog from './components/Blog'
import blogService from './services/blogs'
import loginService from './services/login'
import userService from './services/user'

const App = () => {
  const [blogs, setBlogs] = useState([])
  const [user, setUser] = useState(null)
  const [username, setUsername] = useState('')
  const [password, setPassword] = useState('')
  const [errorMsg, setErrorMsg] = useState('')

  const [blogTitle, setBlogTitle] = useState('')
  const [blogAuthor, setBlogAuthor] = useState('')
  const [blogUrl, setBlogUrl] = useState('')

  // 页面加载时检测本地登录态
  useEffect(() => {
    const loggedInUser = window.localStorage.getItem('loggedBlogUser')
    if(loggedInUser){
      const userData = JSON.parse(loggedInUser)
      setUser(userData)
      blogService.setToken(userData.token)
    }
  },[])

  // 用户存在时拉取最新博客列表
  useEffect(() => {
    const fetchLatestBlogs = async () => {
      if(user) {
        // 调用接口拉取当前用户最新全量博客,替换本地旧数据
        // 如果没有单独的用户博客接口,可拉取全量博客后过滤对应当前用户id的内容
        const latestBlogs = await blogService.getUserBlogs(user.id)
        setBlogs(latestBlogs)
      }
    }
    fetchLatestBlogs()
  }, [user])

  // 登录逻辑
  const handleLogin = async (event) => {
    event.preventDefault()
    try {
      const user = await loginService({username, password})
      blogService.setToken(user.token)
      window.localStorage.setItem('loggedBlogUser', JSON.stringify(user))
      setUser(user)
      setUsername('')
      setPassword('')
    }
    catch(error){
      setErrorMsg('用户名或密码错误')
      setTimeout(() => {
        setErrorMsg(null)
      },5000)
    }
  }

  // 登出逻辑
  const handleLogout = () => {
    window.localStorage.removeItem('loggedBlogUser')
    setUser(null)
    setBlogs([])
  }

  // 新增博客逻辑
  const addNewBlog = async (e) => {
    e.preventDefault()
    try {
      const newBlog = {
        title: blogTitle,
        author: blogAuthor,
        url: blogUrl
      }
      // 拿到接口返回的带id的完整博客数据
      const returnedBlog = await blogService.createBlog(newBlog)
      // 更新前端博客列表
      const updatedBlogs = blogs.concat(returnedBlog)
      setBlogs(updatedBlogs)
      // 同步更新localStorage里的用户数据,保证本地快照最新
      const updatedUser = {...user, blog: updatedBlogs}
      window.localStorage.setItem('loggedBlogUser', JSON.stringify(updatedUser))
      setUser(updatedUser)
      // 清空表单
      setBlogTitle('')
      setBlogAuthor('')
      setBlogUrl('')
    }
    catch(error){
      console.log("新增博客失败", error)
    }
  }

  return (
    <div>
      <h2>blogs</h2>
      {errorMsg && <div className="error">{errorMsg}</div>}
      {user == null && <div className="login-form">
      <form onSubmit={handleLogin}>
      <div className="username-container">
        username
        <input type='text' value={username} onChange={(e) => setUsername(e.target.value)} name='username'/>
      </div>
      <div className="password-container">
        password
        <input type='password' value={password} onChange={(e) => setPassword(e.target.value)} name='password'/>
      </div>
      <button type='submit'>Login</button>
      </form>
      </div>}
      {user != null && <div className="notes">
        <p>{user.name} logged in <button onClick={handleLogout}>logout</button></p>
      </div>}
      {user != null && <div className="addBlog-container">
      <b>create new</b>
      <form onSubmit={addNewBlog}>
        <label>Title:</label><input type="text" value={blogTitle} onChange={(e) => setBlogTitle(e.target.value)} name="blog-title"/>
        <label>Author:</label><input type="text" value={blogAuthor} onChange={(e) => setBlogAuthor(e.target.value)} name="blog-author"/>
        <label>Url:</label><input type="text" value={blogUrl} onChange={(e) => setBlogUrl(e.target.value)} name="blog-url"/>
        <button type='submit'>create blog</button>
      </form>
      </div>}
      {blogs.map(blog =>
        <Blog key={blog.id} blog={blog} />
      )}
    </div>
  )
}

export default App

核心原则:涉及用户数据的场景,页面刷新时永远以服务端返回的最新数据为准,localStorage只用来持久化登录凭证和非核心快照数据,不要作为业务数据的唯一可信来源。

内容的提问来源于stack exchange,提问作者Robert Williamson

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.31 11:24:09