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
相关产品推荐
相关产品推荐

