reactjs - 在 React.forwardRef 中使用 ref.current
Codesandbox 在這裡
我正在嘗試使用父組件的 ref 來監聽附加在子組件上的 ref 的某些事件,該 ref 是使用 React.forwardRef 附加到子組件上的。然而,當我在子組件中引用 ref.current 時,出現了 lint 警告,內容如下:
屬性 'current' 在類型 'Ref' 上不存在。屬性 'current' 在類型 '(instance: HTMLDivElement) => void' 上不存在。
我應該如何在一個 React.forwardRef 元件中引用一個 ref?謝謝。
index.tsx:
import * as React from "react";
import ReactDOM from "react-dom";
const Component = React.forwardRef<HTMLDivElement>((props, ref) => {
React.useEffect(() => {
const node = ref.current;
const listen = (): void => console.log("foo");
if (node) {
node.addEventListener("mouseover", listen);
}
return () => {
node.removeEventListener("mouseover", listen);
};
}, [ref]);
return <div ref={ref}>Hello World</div>;
});
export default Component;
const App: React.FC = () => {
const sampleRef = React.useRef<HTMLDivElement>(null);
return <Component ref={sampleRef} />;
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
答案#1
Refs 不一定是具有 current 屬性的物件。它們也可以是函數。因此,類型錯誤指出你可能會被傳遞後者之一。你需要編寫你的程式碼,使其能夠處理這兩種變化。
這可能有點棘手,但還是可行的。我們的 effect 不能依附在傳入的函數上,因為那個函數可能做任何事情,而且在設計時並沒有考慮到我們的 useEffect。所以我們需要創建我們自己的 ref,我會稱它為 myRef。
此時現在有兩個 ref:一個是傳入的,另一個是我們創建的本地 ref。要同時填充它們,我們需要自己使用 ref 的函數形式,在該函數中我們可以將 div 元素分配給這兩個 ref:
const Component = React.forwardRef<HTMLDivElement>((props, ref) => {
const myRef = useRef<HTMLDivElement | null>(null);
React.useEffect(() => {
const node = myRef.current;
const listen = (): void => console.log("foo");
if (node) {
node.addEventListener("mouseover", listen);
return () => {
node.removeEventListener("mouseover", listen);
};
}
}, [ref]);
return (
<div ref={(node) => {
myRef.current = node;
if (typeof ref === 'function') {
ref(node);
} else if (ref) {
ref.current = node;
}
}}>Hello World</div>
);
});
答案#2
這個討論串中的所有回答都做得過頭了。你只需要使用 useImperativeHandle 鉤子來合併 refs 就可以了。
type Props = { id: string };
export const Component = forwardRef<HTMLInputElement, Props>((props, forwardedRef) => {
const ref = useRef<HTMLInputElement>(null);
useImperativeHandle(forwardedRef, () => ref.current as HTMLInputElement);
return <input ref={ref} />;
});
Component.displayName = 'Component';
答案#3
我四處搜索過,因為這是一個適合使用另一個 hook 的好候選,例如 useForwardRef。這是提案,https://github.com/facebook/react/issues/24722
我自己也試了,對這個用途效果非常好。
const InputField = React.forwardRef<HTMLInputElement, InputFieldProps>(
(props, ref) => {
const inputRef = useForwardRef<HTMLInputElement>(ref);
const onLabelClick = () => {
inputRef.current?.focus();
};
return <input ref={inputRef} />
);
當然,這本質上是最初答案中的相同程式碼,只是寫成一個 hook。
const useForwardRef = <T,>(
ref: ForwardedRef<T>,
initialValue: any = null
) => {
const targetRef = useRef<T>(initialValue);
useEffect(() => {
if (!ref) return;
if (typeof ref === 'function') {
ref(targetRef.current);
} else {
ref.current = targetRef.current;
}
}, [ref]);
return targetRef;
};
注意:這個 hook 的名稱是有爭議的,它可以被命名為 useCopyRef、useBorrowRef 等等。這裡為了簡單起見,因為它是為了 forwardRef 的目的而創建的,我們將它命名為 useForwardRef,但實際上它與 forward 沒有任何關係。
答案#4
詳細說明 @Nicholas 的回答:
import React, { MutableRefObject, Ref, useEffect } from "react";
import { TextInput } from "react-native";
type CustomTextInputProps = {};
export const CustomTextInput = React.forwardRef<
TextInput,
CustomTextInputProps
>((props, ref) => {
const localRef = React.useRef<TextInput | null>(null);
useEffect(() => {
// using the local ref
localRef.current?.focus();
}, []);
return <TextInput {...props} ref={assignRefs(localRef, ref)} />;
});
const assignRefs = <T extends unknown>(...refs: Ref<T | null>[]) => {
return (node: T | null) => {
refs.forEach((r) => {
if (typeof r === "function") {
r(node);
} else if (r) {
(r as MutableRefObject<T | null>).current = node;
}
});
};
};
