【问题标题】:Why usePrevious returns undefined on first render - react hooks?为什么 usePrevious 在第一次渲染时返回 undefined - 反应钩子?
【发布时间】:2021-09-03 07:51:14
【问题描述】:

我想将前一个值存储在一个变量中并在一个函数中使用它。假设如果当前值为 9,则 之前的值应该为 8少一个) .问题是console.log(prevServings) 在第一次渲染时返回 undefined 并在第二次渲染时显示之前的值,但是 当前值和之前的值之间的差异是 2 而不是 1。我的理解是当前值在第一次渲染时不可用,因此先前的值未定义,但我不知道如何修复它。任何帮助,将不胜感激。提前致谢。

const Child = ({originalData}) =>{
  //clone original data object with useState
  const [copyData, setCopyDta] = useState({});
  //clone the copied data 
  let duplicate = {...copyRecipe};
  
  
  //store previous servings value into a variable
  const usePrevious = (servings) => {
    const ref = useRef();
    useEffect(() => {
      ref.current = servings;
    }, [servings]);

    return ref.current;
  };
  const prevServings = usePrevious(duplicate.servings);
  
  //increase the number of servings on click
  const incrementHandle = () => {
    duplicate.servings = `${parseInt(duplicate.servings) + 1}`;
    //this return undefined on the first render
    console.log(prevServings);
    setCopyRecipe(duplicate);
  }

  return(
    <p>{copyData.servings}</p>
    <Increment onClick={incrementHandle}/>
  )
}

【问题讨论】:

    标签: reactjs api react-hooks use-ref


    【解决方案1】:

    它返回undefined,因为useEffect() 至少在第一次渲染之后才会触发。您可能想改为这样做:

    const usePrevious = (servings) => {
      const ref = useRef(servings);
      useEffect(() => {
        ref.current = servings;
      }, [servings])
      return ref.current;
    }
    

    不过,这确实让人觉得很难推理。我可能会建议使用减速器或常规状态。如果您不希望组件对特定值的更改做出“反应”,则 ref 很有用,但每次 ref 更改时,您都会触发状态更新。

    【讨论】:

    • 当我将默认值设置为传入的值时,我使用时它总是返回当前值而不是之前的值。
    【解决方案2】:

    问题

    糟糕,不完全是重复的。这里的问题是,由于您已经在 inside 中声明了组件,因此每个渲染周期都会重新创建它,从而有效地抵消了任何缓存工作。

    解决方案

    usePrevious 挂钩移出组件主体,使其成为稳定的引用。您可能还想删除 useEffect 的依赖项,以便在每个渲染周期缓存该值。

    //store previous servings value into a variable
    const usePrevious = (value) => {
      const ref = useRef();
      useEffect(() => {
        ref.current = value;
      });
      return ref.current;
    };
    
    const Child = ({originalData}) =>{
      //clone original data object with useState
      const [copyData, setCopyDta] = useState({});
      //clone the copied data 
      let duplicate = { ...copyData };
      
      const prevServings = usePrevious(duplicate.servings);
      
      //increase the number of servings on click
      const incrementHandle = () => {
        duplicate.servings = `${parseInt(duplicate.servings) + 1}`;
        setCopyDta(duplicate);
      }
    
      return(
        <p>{copyData.servings}</p>
        <Increment onClick={incrementHandle}/>
      )
    }
    

    仅供参考

    仅供参考,let duplicate = { ...copyRecipe }; 只是一个浅拷贝,而不是对象的克隆,所有嵌套属性仍将引用回原始对象中的对象。也许这就是您所需要的,但只是想指出这不是对象的真正克隆。

    QnA

    问题是 console.log(prevServings) 在 第一次渲染并在第二次渲染时显示之前的值

    我认为这应该是预期的行为,因为在初始渲染中,没有以前的渲染可以从中缓存值。

    但当前值与先前值之间的差异是 2 1 个。

    关于“off-by-2”问题,据我所知,每次渲染都会缓存duplicate 的未更新浅表副本,当点击incrementHandle 时,您会记录prevServings然后将触发重新渲染的更新排入队列。您记录的值和状态更新结果(即&lt;p&gt;{copyData.servings}&lt;/p&gt;)相隔两个 渲染周期。如果您在渲染周期的同一点比较这两个值,您会发现它们相差 1。

    useEffect(() => {
      console.log({ prevServings, current: copyData.servings })
    })
    

    日志输出:

    {prevServings: undefined, current: 0}
    {prevServings: 0, current: "1"}
    {prevServings: "1", current: "2"}
    {prevServings: "2", current: "3"}
    {prevServings: "3", current: "4"}
    

    【讨论】:

    • 感谢您的详细解答。我按照您的建议创建了一个名为 usePrevious 的单独组件并将其导入回 Child 组件。但是第一次点击它仍然返回未定义,我需要它是一个数字。
    • 另外,感谢您提供的额外信息,我在网上查找并找到了一些方法来深度克隆像 {const cloneFood = JSON.parse(JSON.stringify(food));}
    • @Simon 如果没有之前的渲染,那么之前的值是多少?当你说你需要它是一个数字时,你指的是类型,还是你只需要一个定义的值?你能解释一下你需要在初始渲染上定义的先前值的用例吗?
    • 我正在开发一个食谱网站,所以我希望根据份量调整配料量。我在想一个等式:newIngredientAmount = IngredientAmount/prevServings * currentServings。所以默认情况下,preServings 必须是配方的默认值。例如:默认值是 8,当你点击按钮时它变成 9,所以 prevServings 是 8,currentServings 是 9。我希望这对你更有意义。
    猜你喜欢
    • 2020-03-25
    • 2021-11-22
    • 1970-01-01
    • 2020-08-16
    • 2021-02-27
    • 2021-04-22
    • 1970-01-01
    • 2022-01-20
    • 2020-08-03
    相关资源
    最近更新 更多