Hook的useState初始值在记住的回调中永远不变

时间:2020-05-18 20:10:01

标签: javascript reactjs react-hooks

在这个简单的示例中,我使用 useMemo 记住Child组件,并将 callback 返回给父函数,以添加数据以响应钩子数组,该钩子数组最初设置为空

我的问题如下:为什么钩子 data 永不更改,并将其初始状态保留在来自记忆的Child组件的回调函数中。但是,在 useEffect 方法中检查 data 钩子或在渲染中使用该钩子时-它始终处于最新状态。

更多的是一个普遍的问题,幕后发生了什么,以及最好的方法是什么,例如,如果 data 始终具有初始状态,则从已记忆的子组件中检查回调函数中的重复值

我知道 useReducer ,可以在添加它们之前使用 hooks setter方法处理数据,但是也许还有其他方法吗?

父组件:

import React, {useEffect, useState} from 'react';
import Child from "./Child";

function App() {

const [data, setData] = useState([]);

useEffect(()=>{
    console.log("UseEffect", data)
},[data]);

return (
<div>
  <Child callback={(val)=>{
      console.log("Always returns initial state", data);  // <----------Why?
      setData(old=>{
          console.log("Return previous state", old);
            return [...old, val]
      })
  }} />

   Length: {data.length /*Always gets updated*/}
</div>
);
}

export default App;

子组件:在实际情况下,它是一张地图,我只想渲染一次,而不会引起重新渲染。

import React, {useMemo} from "react"

export default function Child({callback}) {
return useMemo(()=>{
    return <button onClick={()=>callback(1)}>
        Press!
    </button>
},[])
}

2 个答案:

答案 0 :(得分:2)

正如您所说,您的Child组件已已存储,这意味着会在其依赖项之一发生更改时进行更新。 因此,在第一次渲染时,使用传递给Child道具的函数创建callback组件,此时data[]。如果您单击按钮,则data会正确更新,传递给callback的{​​{1}} prop的函数也会正确更新,但是由于您没有为Child设置任何依赖关系,您的useMemo组件将不更新,并且仍会返回其已记录的版本,并收到其收到的第一个回调属性,该属性确实总是记录Child的初始值:{{1} }。

因此,您所需要做的就是将data添加到[]的依赖项列表中:

callback

这样,当useMemo属性更改时,您的export function Child({ callback }) { return useMemo(() => { return <button onClick={() => callback(1)}>Press!</button>; }, [callback]); } 组件还将更新其callback事件处理程序。

此外,我建议使用eslint-plugin-react npm软件包,该软件包会立即警告您有关React挂钩中缺少依赖项的信息,并且更一般地是有关代码中不良做法的信息。

答案 1 :(得分:2)

有没有一种方法可以返回新的回调引用,而无需在useMemo方法中添加依赖项

// returns *(always the same)* function that will forward the call to the latest passed `callback`
function useFunction(callback) {
  const ref = React.useRef();
  ref.current = callback;

  return React.useCallback(function() {
    const callback = ref.current;
    if (typeof callback === "function") {
      return callback.apply(this, arguments);
    }
  }, []);
}

然后:

export function Child({ callback }) {
  const handler = useFunction(callback);

  return useMemo(() => {
    return <button onClick={() => handler(1)}>Press!</button>;
  }, []);
}

function App() {
  const [data, setData] = useState([]);

  const callback = useFunction((val) => {
    console.log("Always returns the latest state", data);
    setData([...data, val]);
  });

  return (
    <div>
      <Child callback={callback} />

     Length: {data.length /*Always gets updated*/}
    </div>
  );
}

function Child({ callback }) {
  return useMemo(() => {
    return <button onClick={() => callback(1)}>
      Press!
      </button>
  }, [])
}