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

React 首次渲染时如何按点赞数从高到低初始排序博客?

博客应用初始渲染排序问题解决

我用React 18开发博客应用,首次渲染时需要从后端拉取所有博客并按点赞数从高到低排序,但在useEffect钩子中实现时要么触发依赖警告,要么导致后端API无限调用。

原App.jsx代码

import { useEffect, useRef, useState } from 'react';
import Blog from './components/Blog';
import LoginForm from './components/LoginForm';
import NewBlogForm from './components/NewBlogForm';
import Notification from './components/Notification';
import Togglable from './components/Togglable';
import blogService from './services/blogs';
import loginService from './services/login';

const App = () => {
  const [blogs, setBlogs] = useState([]);
  const blogFormRef = useRef();
  const [notification, setNotification] = useState({
    message: '',
    isError: false,
  });
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [user, setUser] = useState(null);

  useEffect(() => {
    blogService.getAll().then((initialBlogs) => setBlogs(initialBlogs));
    // 想要在首次渲染时排序博客
  }, []);

  useEffect(() => {
    const loggedUserJSON = window.localStorage.getItem('loggedBlogappUser');
    if (loggedUserJSON) {
      const user = JSON.parse(loggedUserJSON);
      setUser(user);
      blogService.setToken(user.token);
    }
  }, []);

  const compareBlogs = (a, b) => {
    if (a.likes < b.likes) return 1;
    if (a.likes > b.likes) return -1;
    return 0;
  };

  // 这个函数用于更新博客时排序,也想用于初始博客排序
  const sortBlogsAsPerMostLikedFirst = (blogs) => {
    const sortedBlogs = blogs.sort(compareBlogs);
    setBlogs(sortedBlogs);
  };

  const removeNotificationAfterFiveSeconds = () => {
    setTimeout(() => {
      setNotification({
        message: '',
        isError: false,
      });
    }, 5000);
  };

  const handleLogin = async (event) => {
    event.preventDefault();

    try {
      const user = await loginService.login({
        username,
        password,
      });

      window.localStorage.setItem('loggedBlogappUser', JSON.stringify(user));

      blogService.setToken(user.token);
      setUser(user);
    } catch (error) {
      setNotification({
        message: error.response.data.error,
        isError: true,
      });
      removeNotificationAfterFiveSeconds();
    }
  };

  const handleLogout = async () => {
    try {
      window.localStorage.removeItem('loggedBlogappUser');
      setUser(null);
      setUsername('');
      setPassword('');
    } catch (exception) {
      console.error(exception.message);
    }
  };

  const addBlog = (blogObject) => {
    blogFormRef.current.toggleVisibility();

    blogService
      .create(blogObject)
      .then((returnedBlog) => {
        setBlogs(blogs.concat(returnedBlog));
        setNotification({
          message: `SUCCESS: Added ${returnedBlog.title} blog`,
          isError: false,
        });
        removeNotificationAfterFiveSeconds();
      })
      .catch((error) => {
        setNotification({
          message: error.response.data.error,
          isError: true,
        });
        removeNotificationAfterFiveSeconds();
      });
  };

  const increaseLikes = (blogToUpdate) => {
    const blog = blogs.find((b) => b.id === blogToUpdate.id);
    const updatedBlog = { ...blog, likes: blog.likes + 1 };

    blogService
      .update(blog.id, updatedBlog)
      .then((returnedBlog) => {
        const blogList = blogs.map((blog) => blog.id !== blogToUpdate.id ? blog : returnedBlog);
        // 这里调用排序函数
        sortBlogsAsPerMostLikedFirst(blogList);
      })
      .catch((error) => {
        setNotification({
          message: `Blog '${blog.title}' was already removed from server`,
          isError: true,
        });
        removeNotificationAfterFiveSeconds();
        const blogList = blogs.filter((b) => b.id !== blogToUpdate.id);
        setBlogs(blogList);
        console.error(error.message);
      });
  };

  const deleteBlog = (blog) => {
    const toDelete = window.confirm(`Delete "${blog.title}" blog?`);
    console.log({ toDelete });

    if (toDelete) {
      blogService
        .deleteBlog(blog)
        .then(() => {
          const blogList = blogs.filter((b) => blog.id !== b.id);
          setBlogs(blogList);
          console.log('Blog deleted');
        })
        .catch((err) => {
          console.log(err.message);
          alert('Sorry! Blog cannot be deleted as it is not created by you.');
        });
    }
  };

  const loginForm = () => {
    return (
      <Togglable buttonLabel="login">
        <LoginForm
          username={username}
          password={password}
          handleUsernameChange={({ target }) => setUsername(target.value)}
          handlePasswordChange={({ target }) => setPassword(target.value)}
          handleSubmit={handleLogin}
        />
      </Togglable>
    );
  };

  const blogForm = () => (
    <Togglable buttonLabel="new blog" ref={blogFormRef}>
      <NewBlogForm createBlog={addBlog} />
    </Togglable>
  );

  return (
    <div>
      <h1>Blogs</h1>

      <Notification notification={notification} />

      {!user && loginForm()}

      {user && (
        <div>
          <p>
            {user.name} logged in{' '}
            <button type="button" onClick={handleLogout}>
              logout
            </button>
          </p>

          {blogForm()}

          <ul style={{ listStyleType: 'none', padding: 0 }}>
            {blogs.map((blog) => (
              <Blog
                key={blog.id}
                blog={blog}
                loggedInUsername={user.username}
                handleLikes={() => increaseLikes(blog)}
                handleDeleteBlog={() => deleteBlog(blog)}
              />
            ))}
          </ul>
        </div>
      )}
    </div>
  );
};

export default App;

遇到的问题

  • 尝试直接在useEffect中调用排序函数:
    useEffect(() => {
      blogService.getAll().then((initialBlogs) => sortBlogsAsPerMostLikedFirst(initialBlogs));
    }, []);
    
    会收到依赖警告:
    React Hook useEffect has a missing dependency: 'sortBlogsAsPerMostLikedFirst'. Either include it or remove the dependency array.
    
  • 若将sortBlogsAsPerMostLikedFirst加入依赖数组,后端API会被无限调用。

解决方案

方法一:将排序逻辑内联到useEffect中

直接在获取博客的回调里完成排序,避免依赖外部函数,同时注意不要原地修改原数组:

useEffect(() => {
  blogService.getAll().then((initialBlogs) => {
    // 复制数组后再排序,防止修改后端返回的原始数据
    const sortedBlogs = [...initialBlogs].sort((a, b) => {
      if (a.likes < b.likes) return 1;
      if (a.likes > b.likes) return -1;
      return 0;
    });
    setBlogs(sortedBlogs);
  });
}, []);

这种写法既满足初始排序需求,又不会触发依赖警告,也不会导致无限渲染。

方法二:用useCallback缓存排序函数

如果需要复用排序函数,用useCallback包裹函数,避免每次组件渲染生成新的函数引用:

// 先引入useCallback
import { useEffect, useRef, useState, useCallback } from 'react';

// ...

// 用useCallback缓存比较函数
const compareBlogs = useCallback((a, b) => {
  if (a.likes < b.likes) return 1;
  if (a.likes > b.likes) return -1;
  return 0;
}, []);

// 用useCallback缓存排序函数,依赖compareBlogs
const sortBlogsAsPerMostLikedFirst = useCallback((blogs) => {
  const sortedBlogs = [...blogs].sort(compareBlogs);
  setBlogs(sortedBlogs);
}, [compareBlogs]);

// 修改useEffect,加入缓存后的函数作为依赖
useEffect(() => {
  blogService.getAll().then((initialBlogs) => sortBlogsAsPerMostLikedFirst(initialBlogs));
}, [sortBlogsAsPerMostLikedFirst]);

这样函数引用不会随组件渲染频繁变化,就不会触发useEffect的无限执行循环。

关键注意点

  • 数组的sort方法是原地修改原数组的,排序前一定要复制一份(比如用[...blogs]或者blogs.slice()),避免意外修改后端返回的原始数据。
  • 之前的无限API调用问题,本质是每次组件渲染都会生成新的sortBlogsAsPerMostLikedFirst函数引用,加入useEffect依赖后,函数变化会触发useEffect重新执行,进而更新状态、触发组件重新渲染,形成循环。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 18:03:13