Реагируют на useImperativeHandle и forwardRef, ссылка не обновляется - PullRequest
1 голос
/ 09 марта 2020

Мне нужно получить доступ к расположению дочернего компонента. Насколько я понимаю, для доступа к дочерним свойствам мне нужно использовать 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>

Как видите, ссылка не имеет свойства 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>

Так что не так с моим пониманием useImperativeHanedle и forwardRef?

Ответы [ 3 ]

1 голос
/ 09 марта 2020

Чтобы использовать 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>
  );
});

Если вы хотите, чтобы ваш логин c был действительным (используя то же самое перенаправлено 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 (попробуйте зарегистрировать его), потому что вы фактически переопределили ref кнопки с помощью useImperativeHandle (отметьте Text3 в песочнице, значение ref.current имеет getLocation, назначенное после монтирования).

Edit admiring-darkness-o8bm4

1 голос
/ 09 марта 2020

Как показано в документах (возможно, недостаточно понятном), сам дочерний компонент должен иметь другой 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"))
0 голосов
/ 09 марта 2020

Я просто хотел добавить этот ответ, чтобы показать, как можно упростить работу при удалении ненужного сверхуправления ...

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.preventExtension() (что в основном то же самое).

...