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

React中使用useEffect、axios和setState时,无法重置书籍状态为空数组的问题

React中使用useEffect、axios和setState时,无法重置书籍状态为空数组的问题

嘿,我来帮你捋清楚这个问题~ 其实核心原因是React的状态更新是异步的,而且useEffect的执行时机是在组件渲染完成之后,你看到的日志只是组件第一次渲染的结果,咱们一步步拆解:

问题到底出在哪?

当你清空搜索框时,整个流程是这样的:

  1. 触发setSearch(""),组件开始第一次重新渲染
  2. 这时候search已经是空字符串了,但books还是之前请求回来的旧数组(因为useEffect里的setBooks([])还没执行)
  3. 同步代码里的filteredBooks会因为search === ""变成空数组,所以你看到的日志里filteredBooks是空,但books还是旧数据
  4. 组件渲染完成后,useEffect才会执行,里面的setBooks([])才会触发状态更新,这时候组件会进行第二次重新渲染
  5. 第二次渲染时,books已经是空数组了,但你可能没注意到这次的日志(浏览器控制台会打印两次,但你可能只看了第一次)

你的filteredBooks做法虽然能让页面显示正确,但其实是绕开了状态更新的异步特性,不是最优雅的解法。

优化方案:去掉冗余变量,利用状态逻辑+解决竞态问题

首先,咱们不需要额外的filteredBooks,直接用books状态结合search来判断渲染内容就行。另外还要处理请求竞态的问题——比如你快速输入“react”又马上清空,之前的搜索请求还没完成,这时候它的响应会覆盖掉你设置的空数组,导致books又变回旧数据。

可以用AbortController来取消未完成的请求,修改后的代码如下:

import React, { useState, useEffect } from 'react';
import Book from './components/Book';
import axios from 'axios'

const App = () => {
  const [search, setSearch] = useState("")
  const [books, setBooks] = useState([])

  useEffect(() => {
    // 创建AbortController实例,用于取消请求
    const abortController = new AbortController();
    
    if (search === "") {
      setBooks([])
      return
    }

    async function fetchData() {
      try {
        const response = await axios({
          url: `https://www.googleapis.com/books/v1/volumes?q=${search}&key=${process.env.REACT_APP_API_GOOGLE_FAKE}`,
          signal: abortController.signal // 绑定信号
        })
        setBooks(response.data.items)
      } catch (exception) {
        // 忽略取消请求的错误
        if (!axios.isCancel(exception)) {
          console.log(exception)
        }
      }
    }

    fetchData()

    // 组件卸载或search变化时,取消未完成的请求
    return () => abortController.abort();
  }, [search])

  const handleSearch = (event) => {
    setSearch(event.target.value)
  }

  console.log(search, 'search...');
  console.log(books, 'books...');

  return (
    <div className="App">
      <header className="App-header">
        <nav>
          <ul>
            <li>Your menu</li>
          </ul>
        </nav>
      </header>
      <section>
        <article>
          <div className="input-group searchListBooks">
            <div className="form-outline">
              <input onChange={handleSearch} type="search" id="form1" className="form-control" value={search} />
              <label className="form-label" htmlFor="form1">Search</label>
            </div>
          </div>
        </article>
        <article>
          <header>
            <h2>Books</h2>
          </header>
          <ul className="listBook">
            {
              search.length === 0
                ? <p>Write some text</p>
                : books.length === 0
                  ? <p>Loading...</p> {/* 可以加个加载提示 */}
                  : books.map(book => (
                    <Book {...book} key={book.id} />
                  ))
            }
          </ul>
        </article>
      </section>
      <aside>
        <h2>About section</h2>
        <p>Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.</p>
      </aside>
      <footer>
        <p>Copyright 2009 Your name</p>
      </footer>
    </div>
  );
}

export default App;

关键优化点说明:

  1. 去掉了filteredBooks:直接用books状态判断,当search为空时显示提示,search不为空但books为空时可以显示加载状态,更符合用户体验
  2. 添加了请求取消逻辑:用AbortController在search变化或组件卸载时取消未完成的请求,彻底避免竞态问题,保证清空搜索框时books一定会被重置为空数组
  3. 简化了useEffect的依赖数组:原来的依赖数组末尾多了个逗号,虽然不影响,但去掉更规范

现在你再清空搜索框,就能看到books在第二次渲染时变成空数组了,而且页面也能正确显示提示内容~

备注:内容来源于stack exchange,提问作者titoih

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.23 08:47:58