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

React中useEffect首次加载后不再调用Firebase读取API的问题

问题根源

你当前的问题是:组件首次渲染能正常从Firebase获取数据,但执行更新、删除操作后不会自动重新渲染,必须刷新页面才能看到变化;如果把todos加入useEffect依赖,虽然能实现自动更新,但会触发大量不必要的API调用。

这是因为你用getDocs做单次数据拉取,Firebase数据变更后不会主动通知前端;而把todos加进依赖会导致每次setTodos都触发重新拉取,形成无限循环。

最优解决方案:使用Firebase实时监听(onSnapshot)

Firestore提供onSnapshot方法,能建立和数据库的实时连接,当集合内数据新增、修改、删除时,自动触发回调更新本地状态,既不用手动重复拉取数据,也能保证组件实时渲染最新内容。

核心代码修改

替换原来的useEffect部分为以下代码:

// 实时监听todo集合的变化
useEffect(() => {
  // 订阅集合变化,返回取消订阅的函数
  const unsubscribe = onSnapshot(collection(db, "react-todo"), (snapshot) => {
    // 把快照数据转换成数组
    const todoArray = snapshot.docs.map(doc => ({ ...doc.data(), id: doc.id }));
    setTodos(todoArray);
  });

  // 组件卸载时取消订阅,防止内存泄漏
  return () => unsubscribe();
}, []);

其他细节优化

  1. 替换列表key为todo.id:用index做key会导致列表项顺序变化时渲染异常,改用Firebase生成的唯一id更可靠
{todos.map((todo) => (
  <Todo
    key={todo.id} // 替换原来的index
    todo={todo}
    toggleComplete={toggleComplete}
    deleteTodo={deleteTodo}
  />
))}
  1. 修正checkbox的checked属性:checked应该是布尔值,不用转成字符串
<input
  onChange={() => toggleComplete(todo)}
  type="checkbox"
  checked={todo.complete} // 直接用布尔值
/>
  1. 简化事件处理逻辑:e.preventDefault()不需要传参数,用onChange替代onChangeCapture更符合常规用法
// createTodo函数修正
const createTodo = async (e) => {
  e.preventDefault();
  if (input === "") {
    alert("请输入待办内容");
    return;
  }
  await addDoc(collection(db, "react-todo"), {
    text: input,
    complete: false,
  });
  setInput("");
};

// input的onChange修正
<input
  type="text"
  placeholder="Add Todo"
  className={style.input}
  value={input}
  onChange={(e) => setInput(e.target.value)}
/>
完整修改后的代码

App.jsx

function App() {
  const [todos, setTodos] = useState([]);
  const [input, setInput] = useState("");

  // Create todo
  const createTodo = async (e) => {
    e.preventDefault();
    if (input === "") {
      alert("请输入待办内容");
      return;
    }
    await addDoc(collection(db, "react-todo"), {
      text: input,
      complete: false,
    });
    setInput("");
  };

  // 实时监听todo集合变化
  useEffect(() => {
    const unsubscribe = onSnapshot(collection(db, "react-todo"), (snapshot) => {
      const todoArray = snapshot.docs.map(doc => ({ ...doc.data(), id: doc.id }));
      setTodos(todoArray);
    });
    return () => unsubscribe();
  }, []);

  // update todo in firebase
  const toggleComplete = async (todo) => {
    await updateDoc(doc(db, "react-todo", todo.id), {
      complete: !todo.complete,
    });
  };

  // delete todo
  const deleteTodo = async (todo) => {
    await deleteDoc(doc(db, "react-todo", todo.id));
  };

  return (
    <div className={style.bg}>
      <div className={style.container}>
        <h3 className={style.heading}>Todo App</h3>
        <form onSubmit={createTodo} className={style.form}>
          <input
            type="text"
            placeholder="Add Todo"
            className={style.input}
            value={input}
            onChange={(e) => setInput(e.target.value)}
          />
          <button className={style.button}>
            <AiOutlinePlus size={30} />
          </button>
        </form>
        <ul>
          {todos.map((todo) => (
            <Todo
              key={todo.id}
              todo={todo}
              toggleComplete={toggleComplete}
              deleteTodo={deleteTodo}
            />
          ))}
        </ul>
        <p className={style.count}>{`你有 ${todos.length} 条待办事项`}</p>
      </div>
    </div>
  );
}

Todo.jsx

function Todo({ todo, toggleComplete, deleteTodo }) {
  return (
    <li className={todo.complete ? style.liComplete : style.li}>
      <div className={style.row}>
        <input
          onChange={() => toggleComplete(todo)}
          type="checkbox"
          checked={todo.complete}
        />
        <p
          onClick={() => toggleComplete(todo)}
          className={todo.complete ? style.textComplete : style.text}
        >
          {todo.text}
        </p>
      </div>
      <button onClick={() => deleteTodo(todo)}>{<FaRegTrashAlt />}</button>
    </li>
  );
}
原理说明

onSnapshot会在组件挂载时建立与Firestore的实时连接,当集合数据发生任何变化,立即触发回调更新本地todos状态,从而触发组件重新渲染。返回的unsubscribe函数用于在组件卸载时关闭监听,避免内存泄漏。这种方式既不需要依赖todos,也不会产生多余API调用,完美实现数据实时同步。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 23:54:52