Skip to content

forwardRef ​

在 React 的生态系统中,forwardRef 是一个强大的工具,尤其是当你需要在组件之间传递 ref 时。在本文中,我们将深入探讨 forwardRef 的各种用法。

什么是 forwardRef? ​

forwardRef 是一个函数,它接收一个渲染函数作为参数。这个渲染函数接收 props 和 ref 作为参数,并返回一个 React 节点。

js
React.forwardRef((props, ref) => {
  return <div ref={ref} />
})

为什么要使用 forwardRef? ​

在 React 中,ref 是一个特殊的属性,它可以用来引用组件或 DOM 元素。当你在组件中使用 ref 时,你可以通过 ref.current 来访问组件或 DOM 元素。

用法 ​

1. 传递 ref 给 DOM 元素 ​

forwardRef 的主要功能是将引用 (ref) 从父组件转发到子组件。这对于访问子组件的 DOM 元素非常有用。

demo 代码
jsx
import { forwardRef, useRef } from 'react'

const FancyButton = forwardRef((props, ref) => (
  <Button {...props} ref={ref}>
    {props.children}
  </Button>
))

function App() {
  const ref = useRef()
  return (
    <Card title="案例 demo">
      <FancyButton
        ref={ref}
        onClick={() => {
          // 更改按钮字体的随机颜色
          ref.current.style.color = `#${Math.floor(
            Math.random() * 16777215,
          ).toString(16)}`
          // 更改按钮背景的随机颜色
          ref.current.style.background = `#${Math.floor(
            Math.random() * 16777215,
          ).toString(16)}`
        }}
      >
        Click me!
      </FancyButton>
    </Card>
  )
}

export default App

在这个例子中,FancyButton 组件使用 forwardRef 来接收一个 ref 并将其传递给内部的 Button 组件。由于 ref 已经被转发到 Button 组件,我们可以直接通过 ref.current 访问到 DOM 元素。在 App 组件中,当按钮被点击时,我们使用 ref.current 来更改按钮的字体颜色和背景色。

2. 在高阶组件中使用 forwardRef ​

forwardRef 也可以在高阶组件中使用。在这种情况下,ref 参数不会作为 props 传递给组件,而是作为第二个参数传递给渲染函数。

demo 代码
jsx
import React, { forwardRef } from 'react'

// 高阶组件,为组件添加一个边框
function withBorder(WrappedComponent) {
  const WithBorder = forwardRef((props, ref) => {
    // TODO:先执行
    return (
      <div style={{ border: '2px solid blue' }}>
        <WrappedComponent ref={ref} {...props} />
      </div>
    )
  })

  return WithBorder
}

// 原始组件
const SimpleButton = forwardRef((props, ref) => {
  // TODO:后执行
  return <button ref={ref} style={{ width: '100%' }} {...props} />
})

// 使用高阶组件包裹原始组件
const ButtonWithBorder = withBorder(SimpleButton)

function App() {
  const ref = React.useRef()

  return (
    <Card title="案例 demo">
      <ButtonWithBorder ref={ref} />
      <Button
        onClick={() => {
          // 随机文本
          ref.current.innerText = Math.random().toString(36).slice(-8)
        }}
        style={{ marginTop: 16 }}
      >
        Change Text
      </Button>
    </Card>
  )
}

export default App

在这个例子中,我们创建了一个 withBorder 高阶组件,它为传入的组件添加了一个蓝色的边框。在 App 组件中,我们创建了一个 ref 并将其传递给 ButtonWithBorder。然后,我们可以使用这个 ref 来访问 SimpleButton 的 DOM 元素,并调用其 innerText 更改按钮的文本。

3. 与 useImperativeHandle 配合使用 ​

forwardRef 还可以与 useImperativeHandle 配合使用。useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。

demo 代码
jsx
import { forwardRef, useImperativeHandle, useRef } from 'react'

const ForwardedInput = forwardRef((props, ref) => {
  const inputRef = useRef()

  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus()
    },
  }))

  return <Input ref={inputRef} {...props} />
})

function App() {
  const inputRef = useRef()

  return (
    <Card title="案例 demo">
      <ForwardedInput ref={inputRef} placeholder="点击按钮试试!" />
      <Button
        onClick={() => inputRef.current.focus()}
        type="primary"
        style={{ marginTop: 16 }}
      >
        Focus Input
      </Button>
    </Card>
  )
}

export default App

总结 ​

forwardRef 提供了一种在 React 组件中传递 ref 的强大方法。通过上述示例,我们可以看到它在各种场景中的应用,从基本的函数组件到高阶组件,再到与其他 React Hooks 的结合使用。掌握 forwardRef 可以帮助你更好地管理组件间的交互和引用,从而提高应用的灵活性和可维护性。