在不使用 useCallback()、useMemo() 或任何自定义钩子的情况下防止重新渲染

问题描述 投票:0回答:1

在下面的代码中,我有一个 Button 组件和一个 Todo 组件,渲染时如下所示:

单击任何按钮都会使用 API 调用提供相应的待办事项。

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:如何在不使用自定义钩子、useCallback() 或 useMemo() 的情况下防止 Button 组件重新渲染?

问题2:有没有可能的方法将初始状态值设置为第一个todo(使用api调用)而不是空的todo对象?

我尝试了以下解决方案:将 eventFunc() 保留在 App() 函数的范围之外,然后将按钮组件包装在 memo() 函数内。但它不起作用并给了我错误:“Uncaught TypeError:无法读取 Todo (Todo.jsx:4:19) 处未定义的属性(读取“标题”)
问题3:为什么我会收到此错误?

失败解决方案:

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} />
  </>
  )
}

按钮.jsx:

import {memo} from 'react'

export default memo(function Button({ id, eventFunc }) {
    return <button onClick= {()=>eventFunc(id)}key={id}> Button {id}</button>;
  })
javascript reactjs react-native react-hooks frontend
1个回答
0
投票

问题1:如何在不使用自定义钩子、useCallback() 或 useMemo() 的情况下防止 Button 组件重新渲染?

React.memo 是其中的一部分,但它依赖于不改变的 props。

eventFunc
在每次渲染时都会重新创建,这会破坏记忆。典型的解决方案是将
eventFunc
包裹在
useCallback
中。我不知道你为什么排除它作为一个选项;如果您提供更多有关为什么受到该限制的详细信息,也许我可以想出一些办法。

问题 2:是否有任何可能的方法将初始状态值设置为第一个待办事项(使用 api 调用)而不是空的待办事项对象?

某些组件必须在加载数据之前进行渲染,然后在加载后重新渲染。但它不需要是 this 组件。您可以将代码分成两个组件,其中一个组件负责加载数据,另一个组件仅在数据存在时才渲染。

const Loader = () => {
  const [data, setData] = useState(null)
  const [loading, setLoading] = useState(true);
  useEffect(() => {
    // load the data, then update the states
  }, []);

  if (loading) {
    return null; // or some placeholder
  } else {
    return <App data={data} />
  }
}

const App = ({ data }) = >{
  const [todo, setTodo] = useState(data);

  // ...
}

问题3:为什么我会收到此错误?

因为 eventFunc 返回

undefined
。这反过来意味着
setTodo(eventFunc(1))
将状态设置为
undefined

eventFunc
当前正在创建一个匿名异步函数,并立即调用它。匿名函数运行直到
await
,然后返回一个promise给
eventFunc
eventFunc
忽略这个promise并完成运行。它没有显式的return语句,所以它隐式返回undefined。一段时间后,axios.get 完成并且异步函数恢复,但这不会追溯改变
eventFunc
返回
undefined
的事实。

相反,让 eventFunc 成为异步函数本身:

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

然后等待它返回的承诺:

useEffect(()=>{
  const load = async () => {
    const todo = await eventFunc(1);
    setTodo(todo);
  }
  load();
},[]);
© www.soinside.com 2019 - 2024. All rights reserved.