【问题标题】:React useImperativeHandle and forwardRef being set, the reference doesn't seem to be updatedReact useImperativeHandle 和 forwardRef 被设置,引用似乎没有更新
【发布时间】:2020-06-21 07:17:52
【问题描述】:

我需要访问子组件的位置。据我了解,要访问子属性,我需要使用 useImperativeHandle 将子 API 添加到其引用中。此外,我需要使用forwardRef 将参考从父母传递给孩子。所以我这样做了:

const Text = React.forwardRef(({ onClick }, ref) => {
  const componentAPI = {};
  componentAPI.getLocation = () => {
    return ref.current.getBoundingClientRect ? ref.current.getBoundingClientRect() : 'nope'
  };
  React.useImperativeHandle(ref, () => componentAPI);
  return (<button onClick={onClick} ref={ref}>Press Me</button>);
});

Text.displayName = "Text";

const App = () => {
  const ref = React.createRef();
  const [value, setValue] = React.useState(null)

  return (<div>
    <Text onClick={() => setValue(ref.current.getLocation())} ref={ref} />
    <div>Value: {JSON.stringify(value)}</div>
    </div>);
};

ReactDOM.render(<App />, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="app"></div>

如您所见,ref 没有 getBoundingClientRect 属性,但如果我这样做,它将按预期工作:

const App = () => {
  const ref = React.createRef();
  const [value, setValue] = React.useState(null)

  return (<div>
      <button ref={ref} onClick={() => setValue(ref.current.getBoundingClientRect()) } ref={ref}>Press Me</button>
      <div>Value: {JSON.stringify(value)}</div>
    </div>);
};

ReactDOM.render(<App />, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="app"></div>

那么我对useImperativeHanedleforwardRef的理解有什么问题呢?

【问题讨论】:

    标签: javascript reactjs react-hooks react-forwardref


    【解决方案1】:

    我只是想添加这个答案,以说明在消除无用的过度控制时事情如何变得更容易......

    const Text = React.forwardRef(({ onClick }, ref) => {
      ref.getLocation = () => ref.current && ref.current.getBoundingClientRect()
      return (<button onClick={onClick} ref={ref}>Press Me</button>);
    });
    
    Text.displayName = "Text";
    
    function App() {
      const ref = { current: null };
      const [value, setValue] = React.useState(null)
      return (<div>
        <Text onClick={() => setValue(ref.getLocation())} ref={ref} />
        <div>Value: {JSON.stringify(value)}</div>
      </div>);
    }
    
    
    ReactDOM.render(<App />, document.querySelector("#app"))
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
    <div id="app"></div>

    在上面的代码中,我们只是使用了 forwardRef 并将子 API 附加到它的 ref 上,最终看起来很自然,并且非常人性化。

    唯一会阻止你使用它的是 React.createRef 调用Object.preventExtension()(感谢你让我的生活变得更艰难......),所以黑客是使用{ current: null }而不是Object.createRef() (基本相同)。

    【讨论】:

      【解决方案2】:

      要使用useImperativeHandle,您需要像这样使用另一个ref 实例:

      const Text = React.forwardRef(({ onClick }, ref) => {
        const buttonRef = React.useRef();
      
        React.useImperativeHandle(
          ref,
          () => ({
            getLocation: () => buttonRef.current.getBoundingClientRect()
          }),
          [buttonRef]
        );
      
        return (
          <button onClick={onClick} ref={buttonRef}>
            Press Me
          </button>
        );
      });
      

      如果您希望您的逻辑有效(使用相同的转发ref),这将起作用:

      const Text = React.forwardRef(({ onClick }, ref) => {
        React.useEffect(() => {
          ref.current.getLocation = ref.current.getBoundingClientRect;
        }, [ref]);
      
        return (
          <button onClick={onClick} ref={ref}>
            Press Me
          </button>
        );
      });
      

      为什么你的例子不起作用?

      因为ref.current.getBoundingClientRect 在将其分配到useImperativeHandle 时不可用(尝试记录它),因为您实际上用useImperativeHandle 覆盖了按钮的ref(在沙盒中检查Text3ref.current 值在挂载后分配了getLocation)。

      【讨论】:

      • 非常感谢!你能解释一下为什么吗?我看不出实际发生了什么阻止我的解决方案发挥作用。
      • 在您的第二个示例中,如果 ref.current 值得到更新,我不会冒丢失 getLocation 函数的风险吗?
      • 我更新了答案,是的,这是一个风险(你可以处理),它只是你的逻辑如何工作的一个例子,在实践中从未见过这样的代码
      • 但是buttonRef.current.getBoundingClientRect 也不可用,是吗?这是我看不出区别的地方...
      • 我错了,我来修正答案
      【解决方案3】:

      docs所示(可能理解不够),子组件本身应该有不同的ref,通过useImperativeHandle可以定义一个函数映射forwardedRef到child ref:

      import React from 'react'
      import ReactDOM from 'react-dom'
      const Text = React.forwardRef(({ onClick }, ref) => {
        const buttonRef = React.useRef() // create a new ref for button
        const componentAPI = {};
        componentAPI.getLocation = () => {
          return buttonRef.current.getBoundingClientRect ? buttonRef.current.getBoundingClientRect() : 'nope' // use buttonRef here
        };
        React.useImperativeHandle(ref, () => componentAPI); // this maps ref to buttonRef now
        return (<button onClick={onClick} ref={buttonRef}>Press Me</button>); // set buttonRef
      });
      
      Text.displayName = "Text";
      
      const App = () => {
        const ref = React.useRef();
        const [value, setValue] = React.useState(null)
      
        return (<div>
          <Text onClick={() => setValue(ref.current.getLocation())} ref={ref} />
          <div>Value: {JSON.stringify(value)}</div>
          </div>);
      };
      
      ReactDOM.render(<App />, document.querySelector("#app"))
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-09-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-03-27
        相关资源
        最近更新 更多