【问题标题】:(React Hooks) Ref method not updating state value(React Hooks) Ref 方法不更新状态值
【发布时间】:2021-07-27 12:39:06
【问题描述】:

所以基本上我有一个去抖输入组件,它公开了 2 个 ref 方法,一个用于清除输入的值,一个用于将其设置为字符串。

问题是,使用父组件的 ref 方法不起作用。

代码:

import React, { ChangeEvent, forwardRef, useImperativeHandle, useState } from 'react';
import { TextInput } from 'components/common';
import { useDebounce } from 'utilities/CustomHooks';
import Logger from 'utilities/Logger';

export type DebouncedInputRef = {
  clearValue: () => void;
  setValue: (value: string) => void;
};

export const DebouncedInput = forwardRef(
  (
    { onChange, ...textInputProps }: ComponentProps.DebouncedInputProps,
    ref,
  ) => {
    const [textInputValue, setTextInputValue] = useState('');
    const debouncedOnChange = useDebounce((newValue: string) => {
      onChange && onChange(newValue);
    }, 1000);

    useImperativeHandle(
      ref,
      (): DebouncedInputRef => {
        return {
          clearValue: () => {
            setTextInputValue('');
          },
          setValue: (newValue: string) => {
            Logger.debug('DebouncedInput', 'setValue fired with', newValue);
            setTextInputValue(newValue);
          },
        };
      },
    );

    return (
      <div>
        {Logger.debug('DebouncedInput', 'in render value', textInputValue)}
        <TextInput
          {...textInputProps}
          value={textInputValue}
          onChange={(e: ChangeEvent<HTMLInputElement>) => {
            setTextInputValue(e.target.value);
            debouncedOnChange(e.target.value);
          }}
        />
      </div>
    );
  },
);

调用ref方法的代码如下:

const ListProducts = () => {
  const debouncedInputRef = useRef<DebouncedInputRef>();
  
  useEffect(() => {
    debouncedInputRef?.current && debouncedInputRef.current.setValue('Test');
  }, []);

  return (
    <DebouncedInput
      ref={debouncedInputRef}
    />
  );
};

setValue 中的 Logger.debug 打印来自父组件的传入值。

render 中的 Logger.debug 也运行了两次,表示在调用 setTextInputValue 后立即重新渲染。

但是,渲染过程中状态变量的值和之前一样。

基本上,setValue 运行,但是状态变量没有更新,我也不知道为什么。

非常欢迎任何指点。


更新:

好的,所以我开始工作了。 基本上,我的 ListProducts 组件有一些额外的细节:

const ListProducts = () => {
  const [loading, setLoading] = useState(false);
  const debouncedInputRef = useRef<DebouncedInputRef>();
  
  const mockApiCall = () => {
    setLoading(true);
    // call API and then
    setLoading(false);
  };

  useEffect(() => {
    debouncedInputRef?.current && debouncedInputRef.current.setValue('Test');
    mockApiCall();
  }, []);

  if (loading) {
    return <div>Spinner here</div>;
  }
  return (
    <DebouncedInput
      ref={debouncedInputRef}
    />
  );
};

我认为问题在于,ref 捕获了初始 DebouncedInput,然后调用了 API,该 API 返回了微调器并从 DOM 中删除了 Debounced Input。

后来API做完,又渲染了一遍,但我猜是不同的DOM元素?

我不知道为什么会这样,但确实如此。我很高兴知道究竟是什么导致了这个问题。

这是一个code sandbox example,其中包含有效和无效的示例。

如果有人能详细说明无效示例中的问题究竟是什么,我将不胜感激:)

【问题讨论】:

  • 请添加一个codesandbox可重现的例子
  • 我搞定了。我会将问题作为答案发布,但这里是代码框,适用于工作和不工作的示例:link
  • 虽然,我仍然不确定“不工作示例”中的问题是什么。任何解释都会非常有帮助:)
  • 你应该只更新你的帖子,而不是在自我发布的答案中提问
  • 它不工作的原因是由于渲染时间,在您的“工作示例”中,您实际上在每个渲染器上重新安装了组件,您错过了一个渲染器来更新您的参考。它与这个问题有关:stackoverflow.com/questions/60476155/…

标签: javascript reactjs typescript react-hooks


【解决方案1】:

首先让我们从小调整开始:

useImperativeHandle 应该与 dep 数组一起使用,在这种情况下它是空的:

useImperativeHandle(...,[]);

useEffect 应该始终有一个 SINGLE 责任,根据您的逻辑,您只想在 mount 上设置 ref 值,添加另一个 useEffect(在这个特定示例中没有任何影响)

// API CALl
useEffect(callback1, []);
// Ref set on mount
useEffect(callback2, []);

然后,从理论上讲,在每次状态更改时,React 都会比较 React 子节点树以确定是否需要更新 UI(请参阅 Reconciliation)。

这是两个不同的 React 节点:

// #1
<div>
  {renderContent()}
</div>

// #2
<div>
  <DebouncedInput ref={myCompRef} />
  {loading && <span>Loading right now</span>}
</div>

从 #1 开始,函数在每次渲染时调用renderContent(),因此您实际上在每次渲染时重新安装节点。

为什么您的代码不起作用?因为您在父 MOUNT 上调用了一些逻辑:

useEffect(() => {
  myCompRef.current?.setValue("Value set from ref method");
}, [])

如果 ref 挂载了,它工作,如果没有,没有函数调用。

但在 #1 树的上下文中,您在下一次渲染时立即将其卸载,因此您重置了内部 value 状态。

在 #2 的上下文中,它是同一个 React 节点,因此 React 只是更新了它。

【讨论】:

  • 非常感谢您的详细解释和调整。我将在我的代码中使用这些调整。此外,Reconciliation 文档是一本非常有趣的读物,也非常感谢。那和你的解释帮助我完全理解了这个问题,以及将来如何避免它。每天我都会学到新东西;今天,非常感谢你:)
猜你喜欢
  • 1970-01-01
  • 2021-11-13
  • 1970-01-01
  • 2023-03-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-08
  • 2022-12-12
相关资源
最近更新 更多