文档 / rescript-react / 转发 Refs
Edit

转发 Refs

Ref 转发是一种将 React.ref 通过组件自动传递给它的子组件的技术。对于应用中的大部分组件来说,这通常是不必要的。然而,它对某些组件非常有用,尤其是可复用组件库中的组件。下面描述了最常见的应用场景。

为什么要转发 Ref ?

考虑一个渲染原生 button DOM 的 FancyButton 组件:

RES
// FancyButton.res @react.component let make = (~children) => { <button className="FancyButton"> children </button> }

React 组件隐藏了实现细节,也包括渲染输出。其他使用 FancyButton 的组件通常不需要获取它内部的 button DOM 元素。这很好,因为它可以防止组件过度依赖彼此的 DOM 结构。

这种封装对于类似 FeedStoryComment 的应用级组件来说是可取的,但是对于类似 FancyButtonMyTextInput 的高度可复用的“叶子“组件来说可能有点不方便。通常我们倾向于像一个常规 button 和 input DOM 一样使用这些组件,访问它们的 DOM 节点来管理焦点,控制选择或动画,可能是不可避免的。

目前有两种策略来通过组件转发引用。在 ReScript 和 React 中我们强烈推荐将 ref 作为 prop 传递,但也有一个专用 API React.forwardRef

下面我们会讨论这两种方法。

通过 Props 转发 Refs

React.ref 能像其他 prop 一样传递。组件会将 ref 转发到正确的 DOM 元素。

不需要学习任何新概念!

下面的例子中,FancyInput 定义了一个 inputRef prop,会被转发到它的 input 元素:

RES
// App.res module FancyInput = { @react.component let make = (~children, ~inputRef: ReactDOM.domRef) => <div> <input type_="text" ref=inputRef /> children </div> } @send external focus: Dom.element => unit = "focus" @react.component let make = () => { let input = React.useRef(Js.Nullable.null) let focusInput = () => input.current ->Js.Nullable.toOption ->Belt.Option.forEach(input => input->focus) let onClick = _ => focusInput() <div> <FancyInput inputRef={ReactDOM.Ref.domRef(input)}> <button onClick> {React.string("Click to focus")} </button> </FancyInput> </div> }

我们使用 ReactDOM.domRef 类型来表示 inputRef。我们可以像将 ref 属性传给 DOM 一样传递 inputRef<input ref={ReactDOM.Ref.domRef(myRef)} />)。

[不推荐] React.forwardRef

注意: 我们不推荐这种方法,因为它可能会在某个时间点消失,而且与前面提到的传递 ref prop 相比,它没有任何明显的优势。

React.forwardRef 提供了一种在自定义组件中“模拟 ref prop”的方法。在组件内部,组件会把传递的 ref 值转发给目标 DOM 元素。

上面例子的 React.forwarRef 版本:

ReScriptJS Output
// App.res

module FancyInput = {
  @react.component
  let make = React.forwardRef((~className=?, ~children, ref_) =>
    <div>
      <input
        type_="text"
        ?className
        ref=?{Js.Nullable.toOption(ref_)->Belt.Option.map(
          ReactDOM.Ref.domRef,
        )}
      />
      children
    </div>
  )
}

@send external focus: Dom.element => unit = "focus"

@react.component
let make = () => {
  let input = React.useRef(Js.Nullable.null)

  let focusInput = () =>
    input.current
    ->Js.Nullable.toOption
    ->Belt.Option.forEach(input => input->focus)

  let onClick = _ => focusInput()

  <div>
    <FancyInput className="fancy" ref=input>
      <button onClick> {React.string("Click to focus")} </button>
    </FancyInput>
  </div>
}

注意: 在我们的 React.forwardRef 函数中,@react.component 装饰器使用与 make 函数相同的方式转换标签参数。

这样,使用 FancyInput 的组件可以获得底层 input DOM 节点的引用,并在必要时访问它,就像在直接使用 input 一样。

组件库维护者须知

当你开始在组件库中使用 ref 转发时,你应该把它当作一个 breaking change,并且发布一个新的 major 版本。 这是因为你的库可能会具有不同的行为(例如分配给哪些 refs,哪些类型会被导出),这可能会破坏依赖于旧行为的应用或其他库。