正在设置 React useImperativeHandle 和 forwardRef,引用似乎没有更新
React useImperativeHandle and forwardRef being set, the reference doesn't seem to be updated
我需要访问子组件的位置。据我了解,要访问子属性,我需要使用 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>
所以我对useImperativeHanedle
和forwardRef
的理解有什么问题?
如docs所示(可能不够理解),子组件本身应该有不同的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"))
要使用 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
(检查 Text3
在沙箱中,ref.current
值在挂载后分配了 getLocation
。
我只是想添加这个答案来展示在删除无用的过度控制时事情如何变得更容易...
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()
(感谢你让我的生活更艰难......),所以 hack 是使用 { current: null }
而不是 Object.createRef()
(基本相同)。
我需要访问子组件的位置。据我了解,要访问子属性,我需要使用 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>
所以我对useImperativeHanedle
和forwardRef
的理解有什么问题?
如docs所示(可能不够理解),子组件本身应该有不同的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"))
要使用 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
(检查 Text3
在沙箱中,ref.current
值在挂载后分配了 getLocation
。
我只是想添加这个答案来展示在删除无用的过度控制时事情如何变得更容易...
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()
(感谢你让我的生活更艰难......),所以 hack 是使用 { current: null }
而不是 Object.createRef()
(基本相同)。