文档 / rescript-react / Refs & DOM
Edit

Refs & DOM

Refs 提供了一种方法来访问在 make 组件函数中创建的 DOM 节点或 React 元素。

在典型的 React 数据流中,props是父组件与子组件交互的唯一方式。要修改一个子组件,你需要使用新的 props 来重新渲染它。但是,在某些情况下,你需要在典型数据流之外强制修改子组件。被修改的子组件可能是一个 React.elementDom.element。对于这两种情况,React 都提供了解决办法。

像这样定义一个 React.ref

RES
type t<'value> = { mutable current: 'value }

注意不要将 React.ref 和用于添加可变性的语言特性ref类型弄混了。

何时使用 Refs

Refs 的应用场景:

  • 管理不应该触发重新渲染的状态。

  • 管理焦点、文本选择、媒体播放。

  • 触发命令式的动画。

  • 与第三方 DOM 库集成。

避免在能够使用声明式的方式完成的事情上使用 refs

创建 Refs

React ref 被表示为 React.ref('value) 类型,一个管理 'value 类型的可变值的容器。你能使用 React.useRef hook 来创建 ref:

RES
@react.component let make = () => { let clicks = React.useRef(0); let onClick = (_) => { clicks.current = clicks.current + 1; }; <div onClick> {Belt.Int.toString(clicks.current)->React.string} </div> }

上面的例子定义了一个 React.ref(int) 类型的绑定 clicks。注意,改变 clicks.current 的值不会触发任何组件重渲染。

访问 Refs

当渲染过程中将 ref 传递给元素后,就能通过 ref 的当前属性访问对节点的引用。

RES
let value = myRef.current

ref 的值取决于节点的类型:

  • 当在 HTML 元素上使用 ref 属性时,通过 ReactDOM.Ref.domRef 接收基础 DOM 元素作为其当前属性(类型为 React.ref<Js.Nullable.t<Dom.element>>

  • 在与 JS 互操作的情况下,当在自定义类组件(基于 JS 类)上使用 ref 属性时,ref 对象将接收组件的 mounted 实例(本文档中未讨论)。

  • 你不能在组件函数上使用 ref 属性因为它们没有实例(Rescript 不会暴露 JS 类)。

下面是一些例子:

为 DOM 元素添加 Ref

这段代码使用 React.ref 储存 input DOM 节点的引用,用于在按钮被点击时给 text 的 input 设置焦点:

ReScriptJS Output
// CustomTextInput.res

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

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

  let focusInput = () =>
    switch textInput.current->Js.Nullable.toOption {
    | Some(dom) => dom->focus
    | None => ()
    }

  let onClick = _ => focusInput()

  <div>
    <input type_="text" ref={ReactDOM.Ref.domRef(textInput)} />
    <input type_="button" value="Focus the text input" onClick />
  </div>
}

解释一下上面的代码发生了什么:

  • 我们将 textInput ref 实例化为 Js.Nullable.null

  • 我们使用 ReactDOM.Ref.domRef(textInput)<input> 元素内注册 textInput 的 ref

  • focusInput 函数中,我们首先需要验证 ref 是否设置为 DOM 元素,然后使用 focus binding 来设置焦点。

React 将在组件 mount 时给 current 赋值 DOM 元素,并在组件 unmount 时将其分配回 null。

Refs & 组件函数

在 React 中,你不能ref 属性传递给组件函数:

ReScriptJS Output
module MyComp = {
  @react.component
  let make = (~ref) => <input />
}

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

  // This will **not** work
  <MyComp ref={ReactDOM.Ref.domRef(textInput)} />
}

上面的代码片段将不会被编译,并输出一个错误:"Ref cannot be passed as a normal prop. Please use forwardRef API instead."

正如错误消息所说,如果你需要允许其他人对你的组件函数使用 ref ,你可以使用Ref转发(可能与 useImperativeHandle 一起使用)。

向父组件暴露 DOM Refs

在极少数情况下,你可能想要从父组件访问子组件的 DOM 节点。通常不建议这样做,因为它破坏了组件封装,但是偶尔可以用于触发焦点,或是用于获取子 DOM 节点的大小或位置。

我们建议使用Ref转发Ref 转发允许组件将子组件的 Ref 暴露给自己。你能在 Ref 转发的文档中找到将子组件的 DOM 节点暴露给父组件的例子。

回调 Refs

React 还支持另一种设置 refs 的方法,称为 “回调 refs”(React.Ref.callbackDomRef),它能让你更精细地控制 refs 的设置和解除。

不同于传递由 React.useRef() 创建的 ref 值,你会传递一个回调函数。这个函数接收 Dom.element 作为参数,以使这个 Dom.element 能被存储并在其他地方访问。

注意:通常我们会使用 React.Ref.domRef() 传递一个 ref 值,但是对于回调 refs,我们使用 React.Ref.callbackDomRef()

下面的例子描述了一个通用的范例:使用 ref 回调函数,在实例的属性中存储对 DOM 节点的引用。

ReScriptJS Output
// CustomTextInput.res

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

@react.component
let make = () => {
  let textInput = React.useRef(Js.Nullable.null)
  let setTextInputRef = element => {
    textInput.current = element;
  }

  let focusTextInput = _ => {
    textInput.current
    ->Js.Nullable.toOption
    ->Belt.Option.forEach(input => input->focus)
  }

  <div>
    <input type_="text" ref={ReactDOM.Ref.callbackDomRef(setTextInputRef)} />
    <input
      type_="button" value="Focus the text input" onClick={focusTextInput}
    />
  </div>
}

React 会在组件 mount 时使用 DOM 元素调用 ref 回调函数,并在 unmount 时使用 null 调用它。

你能在组件间传递回调 refs,就像传递 React.useRef() 创建的对象 refs 一样。

ReScriptJS Output
// Parent.res

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

module CustomTextInput = {
  @react.component
  let make = (~setInputRef) => {
    <div>
      <input type_="text" ref={ReactDOM.Ref.callbackDomRef(setInputRef)} />
    </div>
  }
}

@react.component
let make = () => {
  let textInput = React.useRef(Js.Nullable.null)
  let setInputRef = element => { textInput.current = element}

  <CustomTextInput setInputRef/>
}

在上面的例子中,Parent 将它的 ref 回调函数传递给 CustomTextInputsetInputRef prop,CustomTextInput 将同样的函数作为特别的 ref 属性传递给 <input>。因此,父组件中的 textInput ref 会被设置为 CustomTextInput 中的 <input> 的 DOM 节点。