将 HTML 元素的 ref 传递给自定义钩子 [英] Passing a ref to an HTML element to a custom hook
问题描述
假设我有一个自定义钩子,我将使用它向 HTML 元素中添加一个单击事件侦听器.
我用 const buttonRef = useRef(null);
创建了 ref,所以第一次渲染时的值为空.ref 值仅在渲染方法的最后一个分配,在我的自定义钩子已经被调用的地方.
因此,在第一次渲染时,我的自定义钩子没有任何可以添加事件侦听器的内容.
我最终不得不在我的自定义钩子第二次运行之前更新组件,并最终将事件侦听器添加到元素.我得到以下行为:
问题:
如何解决这个问题?我真的需要强制更新我的组件才能向自定义钩子发送引用吗?由于我只能在顶层调用钩子和自定义钩子(钩子规则),因此不允许使用以下内容.
useEffect(() => {useMyHook();});
<小时>
App.js
function App() {const buttonRef = useRef(null);const hookValue = useMyHook(buttonRef.current);const [forceUpdate, setForceUpdate] = useState(false);返回 (<div><button onClick={() =>setForceUpdate(prevState => !prevState)}>更新组件按钮><button ref={buttonRef}>更新钩子</button>{"这是钩子返回值:" + hookValue}
);}
useMyHook.js(自定义钩子)
import { useEffect, useState } from "react";函数 useMyHook(element) {const [myHookState, setMyHookState] = useState(0);console.log("useMyhook 内部...");console.log("这是收到的元素:" + element);useEffect(() => {console.log("useMyhook useEffect 里面...");函数 onClick() {setMyHookState(prevState => prevState + 1);}如果(元素!== 空){element.addEventListener("click", onClick);}返回 () =>{console.log("useMyhook useEffect 内部返回...");如果(元素!== 空){element.removeEventListener("click", onClick);}};});返回 myHookState;}导出默认 useMyHook;
解决方案非常简单,您只需要将 ref 传递给自定义钩子而不是 ref.current
因为,ref当 ref 被分配给 DOM 并且自定义钩子中的 useEffect 在第一次渲染后被触发时,.current 在其原始引用处发生变异,因此 buttonRef.current
将引用 DOM 节点
useMyHook.js
import { useEffect, useState } from "react";函数 useMyHook(refEl) {const [myHookState, setMyHookState] = useState(0);console.log("useMyhook 内部...");console.log("这是接收到的元素:", refEl);useEffect(() => {const 元素 = refEl.current;console.log("useMyhook useEffect 里面...");函数 onClick() {console.log("点击");setMyHookState(prevState => prevState + 1);}控制台日志(元素);如果(元素!== 空){element.addEventListener("click", onClick);}返回 () =>{console.log("useMyhook useEffect 内部返回...");如果(元素!== 空){element.removeEventListener("click", onClick);}};}, []);返回 myHookState;}导出默认 useMyHook;
<小时>
index.js
function App() {const buttonRef = useRef(null);const hookValue = useMyHook(buttonRef);const [forceUpdate, setForceUpdate] = useState(false);返回 (<div><button onClick={() =>setForceUpdate(prevState => !prevState)}>更新组件按钮><button ref={buttonRef}>更新钩子</button>{"这是钩子返回值:" + hookValue}
);}
Imagine I have a custom hook that I'll use to add a click event listener into an HTML element.
I create the ref with const buttonRef = useRef(null);
, so the value on first render is null. The ref value is only assigned in the final of the render method, at a point where my custom hook has already been called.
Therefore, on first render, my custom hook doesn't have anything to add an event listener to.
I end up having to update the component before my custom hook can run for the second time and finally add the event listener to the element. And I get the following behavior:
QUESTION:
How to get around this? Do I really need to force update my component in order to send a ref to a custom hook? Since I only can call hooks and custom hooks at top-level (rules of hooks), something like the following it's not allowed.
useEffect(() => {
useMyHook();
});
App.js
function App() {
const buttonRef = useRef(null);
const hookValue = useMyHook(buttonRef.current);
const [forceUpdate, setForceUpdate] = useState(false);
return (
<div>
<button onClick={() => setForceUpdate(prevState => !prevState)}>
Update Component
</button>
<button ref={buttonRef}>Update Hook</button>
{"This is hook returned value: " + hookValue}
</div>
);
}
useMyHook.js (custom hook)
import { useEffect, useState } from "react";
function useMyHook(element) {
const [myHookState, setMyHookState] = useState(0);
console.log("Inside useMyhook...");
console.log("This is the element received: " + element);
useEffect(() => {
console.log("Inside useMyhook useEffect...");
function onClick() {
setMyHookState(prevState => prevState + 1);
}
if (element !== null) {
element.addEventListener("click", onClick);
}
return () => {
console.log("Inside useMyhook useEffect return...");
if (element !== null) {
element.removeEventListener("click", onClick);
}
};
});
return myHookState;
}
export default useMyHook;
The solution is pretty trivial, you just need to pass on the ref to the custom hook instead of ref.current
since, ref.current is mutated at its original reference when the ref is assigned to the DOM and the useEffect in your custom hook is triggered post the first render, so buttonRef.current
will reference the DOM node
useMyHook.js
import { useEffect, useState } from "react";
function useMyHook(refEl) {
const [myHookState, setMyHookState] = useState(0);
console.log("Inside useMyhook...");
console.log("This is the element received: ", refEl);
useEffect(() => {
const element = refEl.current;
console.log("Inside useMyhook useEffect...");
function onClick() {
console.log("click");
setMyHookState(prevState => prevState + 1);
}
console.log(element);
if (element !== null) {
element.addEventListener("click", onClick);
}
return () => {
console.log("Inside useMyhook useEffect return...");
if (element !== null) {
element.removeEventListener("click", onClick);
}
};
}, []);
return myHookState;
}
export default useMyHook;
index.js
function App() {
const buttonRef = useRef(null);
const hookValue = useMyHook(buttonRef);
const [forceUpdate, setForceUpdate] = useState(false);
return (
<div>
<button onClick={() => setForceUpdate(prevState => !prevState)}>
Update Component
</button>
<button ref={buttonRef}>Update Hook</button>
{"This is hook returned value: " + hookValue}
</div>
);
}
这篇关于将 HTML 元素的 ref 传递给自定义钩子的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!