我该如何在React中使用forwardRef()?

51

我目前在我的React应用程序中收到以下错误:

函数组件不能给出refs。尝试访问此引用将失败。你是想使用React.forwardRef()吗?

如何使用forwardRef()来修复这个问题?

我的代码如下:

const Services: FunctionComponent = (): ReactElement => {
  const servicesRef = useRef(null);

  return (
    <Layout>
      <ServicesList ref={servicesRef} />
    </Layout>
  );
};
export default Services;



const ServicesList: React.FunctionComponent = ({ children }: Props) => {
  return (
    <section className="my-24 md:my-32">
      {children && children}
    </section>
  );
};

export default ServicesList;

答案取决于您想如何使用ref。请参阅下面的详细答案,了解使用forwardRef的不同方式。 - deckele
2个回答

96
forwardRef API(与useImperativeHandle钩子配合使用)允许你自定义如何以及在哪里放置你的refs在你的自定义组件中。此外,forwardRef是将ref传递给你的自定义函数组件的唯一方法。
首先,理解refs在类组件、函数组件和常规DOM元素上的工作方式不同,这很重要。
文档中可以看到:
引用值因节点类型的不同而异:
1. 当在HTML元素上使用ref属性时,使用React.createRef()在构造函数中创建的ref会将底层DOM元素作为其当前属性。 2. 当在自定义类组件上使用ref属性时,ref对象将获得组件的已挂载实例作为其当前属性。 3. 不能在函数组件上使用ref属性,因为它们没有实例。
以下是在不同元素类型上使用refs的示例:

1. 在DOM元素上使用ref会给你一个对DOM节点本身的引用:

function AutoFocusInput() {
  const inputRef = useRef(null);
  // This effect runs only once after the component mounts (like componentDidMount)
  useEffect(() => {
    // refs on regular DOM elements (e.g. the "input" tag) have access to the DOM node
    inputRef.current.focus();
  }, []);
  return <input ref={inputRef} />
}

2. 通过在类组件上使用ref,我们可以访问该实例及其所有方法和字段:

class Child extends Component {
  state = {color: "red"}
  toggleColor = () => this.setState({color: this.state.color === "red" ? "blue" : "red"})
  render() {
    return <div style={{backgroundColor: this.state.color}}>yo</div>
  }
}

class Parent extends Component {
  childRef = createRef();
  handleButtonClicked = () => {
    // refs on class components: hold the class component instance, 
    // allowing us to call its methods!
    this.childRef.current.toggleColor();
  }
  render() {
    return (
      <div>
        <button onClick={this.handleButtonClicked}>toggle color!</button>
        <Child ref={childRef} />
      </div>
    );
  }
}

3. 现在,最终回答你的问题。Refs不能被传递给函数组件,因为它们没有实例!

唯一的方法是使用forwardRef来将ref传递给函数组件。当使用forwardRef时,你可以简单地将ref传递给DOM元素,这样父组件就可以像示例1中那样访问它,或者你可以使用useImperativeHandle钩子创建一个具有字段和方法的对象,这类似于示例2

3.1 将ref简单地传递给DOM元素:

// Only when using forwardRef, the function component receives two arguments, 
// props and ref (Normally the component only gets the props argument).
const RedInput = forwardRef((props, ref) => {
  // passing the ref to a DOM element, 
  // so that the parent has a reference to the DOM node
  return <input style={{color: "red"}} {...props} ref={ref} />
});

function AutoFocusInput() {
  const inputRef = useRef(null);
  // This effect runs only once after the component mounts (like componentDidMount)
  useEffect(() => {
    // ref on function component is forwarded to a regular DOM element, 
    // so now the parent has access to the DOM node including its focus method.
    // Note that the ref usage is the same as a regular 
    // DOM element, like in example 1!
    inputRef.current.focus();
  }, []);
  return <RedInput ref={inputRef} />
}

3.2 将父引用附加到自定义对象:

如果您想像处理类组件实例那样将函数或字段附加到引用,请使用`useImperativeHandle`钩子:

const Child = forwardRef((props, ref) => {
  const [color, setColor] = useState("red");
  // To customize the value that the parent will get in their ref.current: 
  // pass the ref object to useImperativeHandle as the first argument. 
  // Then, whatever will be returned from the callback in the second argument, 
  // will be the value of ref.current. 
  // Here I return an object with the toggleColor method on it, for the parent to use:
  useImperativeHandle(ref, () => ({
    toggleColor: () => setColor(prevColor => prevColor === "red" ? "blue" : "red")
  }));
  return <div style={{backgroundColor: color}}>yo</div>;
});


class Parent extends Component {
  childRef = createRef();
  handleButtonClicked = () => {
    // Ref passed to a function component wrapped in forwardRef.
    // Note that nothing has changed for this Parent component
    // compared with the class component in example 2!
    this.childRef.current.toggleColor();
  }
  render() {
    return (
      <div>
        <button onClick={this.handleButtonClicked}>toggle color!</button>
        <Child ref={childRef} />
      </div>
    );
  }
}

1
什么是“自定义组件”? - duhaime
2
@duhaime 这是为了区分自己编写的组件和内置的JSX组件之间的区别(每个小写标记,如div,ul等)。内置组件使用ref的方式不同,ref是对DOM节点的引用。 - deckele
你如何导出 RedInput,以便可以在其他组件中再次导入它? - xotix
1
@xotix 我会像导出其他组件一样导出RedInputexport const RedInput = forwardRef((props, ref) => {...} 或者使用默认导出: const RedInput = forwardRef((props, ref) => {...}; export default RedInput; - deckele

3

React文档讲得很清楚..

https://reactjs.org/docs/forwarding-refs.html

使用React.forwardRef将组件包装起来,并将ref设置为目标DOM元素。

 const FancyButton = React.forwardRef((props,ref)=>(
      <button ref={ref} className="FancyButton">
         {props.children}
      </button>)
   );

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接