reactjs - React'in ForwardedRef'i ile RefObject'i arasındaki fark nedir?
Üzerinde çalıştığım bir React kod tabanında, bir RefObject’i parametre olarak alan ve böyle bir kanca ile kullanılacak bir Sağlayıcıya eşlik eden özel bir kanca (hook) var:
export const ScrollUtilsProvider = React.forwardRef<HTMLDivElement, ScrollUtilsProviderProps>(
(props, ref) => {
const scrollUtils = useScrollUtils(ref) // issue happens on this line
return <div ref={ref}><ScrollUtilsContext.Provider value={scrollUtils}>{props.children}</ScrollUtilsContext.Provider></div>
},
)
export const useScrollUtils = <T extends Element>(ref: RefObject<T>) => {
return {
// some cool functions w/ the passed ref
}
}
Aldığım hata mesajı:
Argument of type 'ForwardedRef<HTMLDivElement>' is not assignable to parameter of type 'RefObject<HTMLDivElement>'.
Type 'null' is not assignable to type 'RefObject<HTMLDivElement>'.
Her iki türü inceledikçe gerçekten farklı olduklarını fark ettim:
// from @types/[email
interface RefObject<T> {
readonly current: T | null;
}
type ForwardedRef<T> = ((instance: T | null) => void) | MutableRefObject<T | null> | null;
Sorularım şunlardır:
- Bu türler neden bu kadar farklı?
- Bir ForwardedRef'i RefObject olarak kullanmanın bir yolu var mı?
- Eğer değilse, ForwardedRefs'i bu kadar özel yapan bir şey var mı?
Cevap #1
RefObject
interface RefObject<T> {
readonly current: T | null;
}
RefObject, React.createRef metodunun dönüş türüdür.
Bu yöntem çağrıldığında, tek alanı .current null olarak ayarlanmış bir nesne döner. Daha sonra, bir render ref'i bir bileşene geçirdiğinde, React .current'i bileşene referans olarak ayarlayacaktır. Bu bileşen genellikle bir DOM elemanı (eğer bir HTML elementine geçirilmişse) veya bir sınıf bileşeni örneği (eğer özel bir sınıf bileşenine geçirilmişse) olacaktır.
Dikkat edin ki RefObject, .current'in readonly olması dışında MutableRefObject<T | null> ile çok benzerdir. Bu tür belirtimi, yalnızca .current özelliğinin React tarafından dahili olarak yönetildiğini ve bir React uygulamasındaki kod tarafından değiştirilmemesi gerektiğini belirtmek için yapılmıştır.
DeğiştirilebilirRefNesnesi
interface MutableRefObject<T> {
current: T;
}
MutableRefObject, React.useRef metodunun dönüş türüdür. Dahili olarak, React.useRef bir MutableRefObject oluşturur, bunu fonksiyonel bileşenin durumuna kaydeder ve nesneyi döndürür.
Dikkat edin, bir React bileşeninin durumuna nesneler kaydedildiğinde, onların özelliklerini değiştirmek yeniden render tetiklemeyecektir (çünkü Javascript nesneleri referans tipleridir). Bu durum, örneği olmayan fonksiyonel bileşenlerde sınıf örneği değişkenlerini taklit etmenizi sağlar. Başka bir deyişle, React.useRef'i, bir değişkeni fonksiyonel bir bileşenle ilişkilendirmenin ve bu değişkenin bileşenin renderlarını etkilememesinin bir yolu olarak düşünebilirsiniz.
İşte aynı amaca ulaşmak için örnek değişkenler kullanan sınıf bileşeni ve React.useRef kullanan işlevsel bileşen örneği:
class ClassTimer extends React.Component {
interval: NodeJS.Timer | null = null;
componentDidMount() {
this.interval = setInterval(() => { /* ... */ });
}
componentWillUnmount() {
if (!this.interval) return;
clearInterval(this.interval);
}
/* ... */
}
function FunctionalTimer() {
const intervalRef = React.useRef<NodeJS.Timer>(null);
React.useEffect(() => {
intervalRef.current = setInterval(() => { /* ... */ });
return () => {
if (!intervalRef.current) return;
clearInterval(intervalRef.current);
};
}, []);
/* ... */
}
ForwardedRef
type ForwardedRef<T> =
| ((instance: T | null) => void)
| MutableRefObject<T | null>
| null;
ForwardedRef, React'in React.forwardRef kullanarak fonksiyonel bileşenlere geçirdiği ref türüdür.
Buradaki ana fikir, üst bileşenlerin bir ref'i alt bileşenlere geçirebilmesidir. Örneğin, MyForm, bir ref'i MyTextInput'e iletebilir ve bu sayede öncekisi, sonuncusunun render ettiği HTMLInputElement'in .value'sine erişebilir.
Birlik tipini parçalamak:
MutableRefObject<T | null>- Yönlendirilen referansReact.useRefile oluşturuldu.((instance: T | null) => void)- İletilen referans bir geri arama (callback) referansıdır.null- Hiçbir referans iletilmedi.
Çocuk bileşende ForwardedRef kullanımı
Bir çocuk bileşen bir ForwardedRef aldığında, genellikle bu, ref'i bir üst bileşene açmak içindir. Ancak, bazen çocuk bileşenin ref'i kendisinin kullanması gerekebilir. Bu durumda, yukarıda listelenen her bir ForwardedRef türünü uyumlamak için bir hook kullanabilirsiniz.
İşte bunu başarmaya yardımcı olan bu makaleden (Typescript için uyarlanmış) bir kanca:
function useForwardedRef<T>(ref: React.ForwardedRef<T>) {
const innerRef = React.useRef<T>(null);
React.useEffect(() => {
if (!ref) return;
if (typeof ref === 'function') {
ref(innerRef.current);
} else {
ref.current = innerRef.current;
}
});
return innerRef;
}
Bu hook'un arkasındaki fikir, bileşenin kendi ref'ini oluşturabilmesi ve ebeveyn bir ref iletmiş olsa da olmasa da bunu kullanabilmesidir. Hook, iletilen herhangi bir ref'in .current özelliğinin içtekilerle senkronize kalmasını sağlamaya yardımcı olur.
Bu hook'un dönüş tipi MutableRefObject<T> olup, kod parçacığınızda useScrollUtils için yer alan RefObject<T> argümanı ile uyumlu olmalıdır, örneğin:
const MyComponent = React.forwardRef<HTMLDivElement>(
function MyComponent(_props, ref) {
const innerRef = useForwardedRef(ref);
useScrollUtils(innerRef);
return <div ref={innerRef}></div>;
}
);
