3

我需要访问子组件的位置。据我了解,要访问子属性,我需要使用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>

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

4

3 回答 3

5

要使用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>
  );
});

如果您希望您的逻辑有效(使用相同的 forwarded 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)覆盖了按钮。refuseImperativeHandleText3ref.currentgetLocation

编辑 admiring-darkness-o8bm4

于 2020-03-09T12:08:44.007 回答
1

文档中所示(可能还不够理解),子组件本身应该有不同的 ref,通过 useImperativeHandle 可以定义一个函数映射 forwardedRef 到子 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"))
于 2020-03-09T12:06:56.463 回答
0

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

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()(这基本上是相同的)。

于 2020-03-09T15:17:15.657 回答