简体   繁体   English

在 React.forwardRef 中使用 ref.current

[英]using ref.current in React.forwardRef

Codesandbox here 代码沙盒在这里

I am trying to use a ref from a parent component to listen to certain ref events in the child component where the ref is attached to the child component using React.forwardRef .我正在尝试使用来自父组件的 ref 来监听子组件中的某些 ref 事件,其中 ref 使用React.forwardRef附加到子组件。 However, I am getting a linting complaint in my child component when I reference ref.current , stating:但是,当我引用ref.current时,我在子组件中收到了一个 linting 投诉,说明:

Property 'current' does not exist on type 'Ref'. 'Ref' 类型上不存在属性 'current'。 Property 'current' does not exist on type '(instance: HTMLDivElement) => void'类型“(实例:HTMLDivElement)=> void”上不存在属性“当前”

How am I supposed to reference a ref in a React.forwardRef component?我应该如何在React.forwardRef组件中引用 ref? Thanks.谢谢。

index.tsx:索引.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);

Refs are not necessarily objects with a current property. Refs 不一定是具有current属性的对象。 They can also be functions.它们也可以是函数。 So the type error is pointing out that you might be passed one of the latter.因此,类型错误指出您可能会通过后者之一。 You'll need to write your code so that it can work with both variations.您需要编写代码,以便它可以与两种变体一起使用。

This can be a bit tricky, but it's doable.这可能有点棘手,但它是可行的。 Our effect can't piggy back on the function that was passed in, since that function could be doing literally anything, and wasn't written with our useEffect in mind.我们的效果不能搭载传入的 function,因为 function 可以做任何事情,并且没有考虑到我们的 useEffect 编写。 So we'll need to create our own ref, which i'll call myRef.所以我们需要创建自己的 ref,我称之为 myRef。

At this point there are now two refs: the one passed in, and the local one we made.此时,现在有两个 refs:传入的一个,以及我们创建的本地一个。 To populate both of them, we'll need to use the function form of refs ourselves, and in that function we can assign the div element to both refs:要填充它们,我们需要自己使用 function 形式的 refs,在 function 中,我们可以将 div 元素分配给两个 refs:

const Component = React.forwardRef<HTMLDivElement>((props, ref) => {
  const myRef = useRef<HTMLDivElement>(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 as MutableRefObject<HTMLDivElement>).current = node;
      }
    }}>Hello World</div>
  );
});

The reason i needed to do a type assertion of (ref as MutableRefObject<HTMLDivElement>) is that the ref object we're passed in is of type RefObject, instead of MutableRefObject.我需要进行类型断言(ref as MutableRefObject<HTMLDivElement>)的原因是我们传入的 ref object 是 RefObject 类型,而不是 MutableRefObject。 As a result, .current is readonly.因此, .current是只读的。 In reality, .current does get changed at runtime, but it's usually React that does so.实际上, .current确实会在运行时更改,但通常是 React 这样做的。 The readonly type makes sense for the normal code people write when developing a react app, but yours is not a normal case. readonly 类型对于人们在开发 React 应用程序时编写的普通代码是有意义的,但您的情况并非如此。

elaborating on @Nicholas answer:详细说明@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;
      }
    });
  };
};

We can also do something like this.我们也可以这样做。 First create a utility like this:首先创建一个这样的实用程序:

function useCombinedRefs(...refs) {
  const targetRef = React.useRef()

  React.useEffect(() => {
    refs.forEach(ref => {
      if (!ref) return
      if (typeof ref === 'function') ref(targetRef.current)
      else ref.current = targetRef.current
    })
  }, [refs])

  return targetRef
}

And use it like so:并像这样使用它:

const CustomInput = React.forwardRef((props, ref) => {
    const innerRef = React.useRef(null)
    const combinedRef = useCombinedRefs(ref, innerRef)

    return (
      <input ref={combinedRef} />
    )
})

Source and more info: Reusing the ref from forwardRef with React hooks来源和更多信息: 使用 React 钩子重用来自forwardRefref

I searched around, since this is a good candidate for another hook such as useForwardRef .我四处搜索,因为这是另一个钩子(例如useForwardRef )的不错选择。 Here's the proposal, https://github.com/facebook/react/issues/24722这是建议, https://github.com/facebook/react/issues/24722

I also tried it myself, works perfectly for this purpose.我自己也尝试过,非常适合这个目的。

const InputField = React.forwardRef<HTMLInputElement, InputFieldProps>(
  (props, ref) => {
   const inputRef = useForwardRef<HTMLInputElement>(ref);
   const onLabelClick = () => {
    inputRef.current?.focus();
  };

  return <input ref={inputRef} />
 );

Of course, this is essentially the same code in the initial answer, but written as a 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;
};

Note: the name of the hook is debatable, it could be named as useCopyRef , useBorrowRef whatever.注意:钩子的名称是有争议的,它可以命名为useCopyRefuseBorrowRef等等。 Here for simplicity, because it was created for the purpose of forwardRef , we named it as useForwardRef , but actually it has nothing to do with forward.这里为了简单起见,因为它是为forwardRef目的而创建的,所以我们将其命名为useForwardRef ,但实际上它与 forward 无关。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM