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

不使用useCallback/useMemo等阻止Button重渲染及相关问题

React Todo组件集问题解答

组件功能与渲染效果

该组件集包含App、Button、Todo三个组件,点击任意按钮可通过API获取对应ID的Todo数据并展示。渲染效果为:页面顶部显示"Hello CodeSandbox"标题,下方排列4个按钮(Button 1至Button 4),按钮下方展示Todo的标题和描述内容。


原组件代码

App.jsx

import { useEffect, useState } from 'react'
import './App.css'
import Button from './Button'
import Todo from './Todo'
import axios from 'axios'

export default function App() {
  const arr = [1, 2, 3, 4];
  const [todo, setTodo] = useState({title:"",description:""});
  useEffect(()=>eventFunc(1),[]);

  function eventFunc(id) {
    (async () => {
      const res = await axios.get(`https://sum-server.100xdevs.com/todo?id=${id}`);
      setTodo(res.data.todo);
    })();
  }
  
  return (
  <>
  <h1>Hello CodeSandbox</h1>
      {arr.map((id) => (
        <Button key={id} eventFunc={eventFunc} id={id} />
      ))}

      <Todo todo={todo} />
      </>
  )
}

Button.jsx

export default function Button({ id, eventFunc }) {
    return <button onClick={()=>eventFunc(id)}key={id}> Button {id}</button>;
  }

Todo.jsx

export default function Todo({todo}){
    return (
        <>
        <h1>{todo.title}</h1>
        <h3>{todo.description}</h3>
        </>
        
    )
}

待解决的三个技术问题

  1. 不使用自定义hooks、useCallback()或useMemo(),如何阻止Button组件重渲染?
  2. 是否可通过API调用将todo的初始state值设为第一个todo而非空对象?
  3. 尝试将eventFunc()移至App()函数外部,并使用memo()包裹Button组件的方案失败,出现错误「Uncaught TypeError: Cannot read properties of undefined (reading 'title') at Todo (Todo.jsx:4:19)」,该错误的原因是什么?

失败方案代码

App.jsx

import { useEffect, useState } from 'react'
import './App.css'
import Button from './Button'
import Todo from './Todo'
import axios from 'axios'

function eventFunc(id) {
  (async () => {
    const res = await axios.get(`https://sum-server.100xdevs.com/todo?id=${id}`);
    return res.data.todo;
  })();
}

export default function App() {
  const arr = [1, 2, 3, 4];
  const [todo, setTodo] = useState({title:"",description:""});
  useEffect(()=>setTodo(eventFunc(1)),[]);

  return (
  <>
    <h1>Hello CodeSandbox</h1>
      {arr.map((id) => (
        <Button key={id} eventFunc={eventFunc} id={id} />
      ))}

    <Todo todo={todo} />
  </>
  )
}

Button.jsx

import {memo} from 'react'

export default memo(function Button({ id, eventFunc }) {
    return <button onClick={()=>eventFunc(id)}key={id}> Button {id}</button>;
  })

问题解答

1. 不使用自定义hooks、useCallback()或useMemo(),阻止Button组件重渲染的方法

可以将eventFunc定义在App组件外部,让它接收setTodo作为参数,确保函数引用不会随App组件渲染而变化,再配合memo包裹Button组件:

  • 定义外部eventFunc:
    async function eventFunc(setTodo, id) {
      const res = await axios.get(`https://sum-server.100xdevs.com/todo?id=${id}`);
      setTodo(res.data.todo);
    }
    
  • App组件中传递setTodo给Button:
    {arr.map((id) => (
      <Button key={id} eventFunc={() => eventFunc(setTodo, id)} id={id} />
    ))}
    
  • 用memo包裹Button组件,这样当eventFunc引用稳定时,Button不会因App重渲染而重复渲染。

2. 是否可通过API调用将todo的初始state值设为第一个todo而非空对象?

可以,但不能直接在useState初始值中执行异步请求(useState初始值为同步逻辑),推荐两种实现方式:

  • 方式一:使用异步初始化函数
    const [todo, setTodo] = useState(async () => {
      const res = await axios.get(`https://sum-server.100xdevs.com/todo?id=1`);
      return res.data.todo;
    });
    
    注意:这种方式下初始state是Promise对象,需要在Todo组件中处理加载状态,避免读取属性失败。
  • 方式二:在useEffect中发起初始请求
    优化初始state为null,并在Todo组件中做空值判断:
    // App.jsx
    const [todo, setTodo] = useState(null);
    useEffect(() => {
      async function fetchInitialTodo() {
        const res = await axios.get(`https://sum-server.100xdevs.com/todo?id=1`);
        setTodo(res.data.todo);
      }
      fetchInitialTodo();
    }, []);
    
    // Todo.jsx
    export default function Todo({todo}){
      if (!todo) return <div>加载中...</div>;
      return (
          <>
          <h1>{todo.title}</h1>
          <h3>{todo.description}</h3>
          </>
      )
    }
    

3. 失败方案错误原因分析

错误的核心原因有两点:

  • eventFunc无有效返回值:外部eventFunc内部的自执行异步函数返回了Todo数据,但eventFunc本身没有返回这个值,导致eventFunc(1)调用后返回undefined。在useEffect中执行setTodo(eventFunc(1))会把todo设为undefined,Todo组件读取undefined.title时触发报错。
  • 异步逻辑处理错误:即使修复返回值,eventFunc返回的是Promise对象,直接传给setTodo会将state设为Promise,而非期望的Todo数据,同样会导致读取属性失败。

正确的处理方式是在useEffect中等待异步请求完成后再更新state:

// 修复后的eventFunc
async function eventFunc(id) {
  const res = await axios.get(`https://sum-server.100xdevs.com/todo?id=${id}`);
  return res.data.todo;
}

// App.jsx中的useEffect
useEffect(()=>{
  async function fetchInitial() {
    const initialTodo = await eventFunc(1);
    setTodo(initialTodo);
  }
  fetchInitial();
},[]);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 12:35:01