React useImperativeHandle и forwardRef установлены, ссылка, похоже, не обновляется
Мне нужно получить доступ к расположению дочернего компонента. Насколько я понимаю, для доступа к дочерним свойствам мне нужно использовать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 ответа
Использовать 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
(попробуйте зарегистрировать его), потому что вы фактически переопределили кнопку ref
с useImperativeHandle
(Проверьте Text3
в песочнице ref.current
ценность имеет getLocation
назначается после монтирования).
Как показано в документации(возможно, недостаточно понятно), у самого дочернего компонента должен быть другой ref, и с помощью useImperativeHandle вы можете определить отображение функции forwardedRef в дочернюю ссылку:
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"))
Я просто хотел добавить этот ответ, чтобы показать, как все может стать проще при удалении бесполезного чрезмерного контроля...
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()
(что в основном то же самое).