Refs & DOM
Refs 提供了一种方法来访问在 make
组件函数中创建的 DOM 节点或 React 元素。
在典型的 React 数据流中,props是父组件与子组件交互的唯一方式。要修改一个子组件,你需要使用新的 props 来重新渲染它。但是,在某些情况下,你需要在典型数据流之外强制修改子组件。被修改的子组件可能是一个 React.element
或 Dom.element
。对于这两种情况,React 都提供了解决办法。
像这样定义一个 React.ref
:
REStype 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 的当前属性访问对节点的引用。
RESlet 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
设置焦点:
// 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
属性传递给组件函数:
上面的代码片段将不会被编译,并输出一个错误:"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 节点的引用。
// 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 一样。
// 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 回调函数传递给 CustomTextInput
的 setInputRef
prop,CustomTextInput
将同样的函数作为特别的 ref 属性传递给 <input>
。因此,父组件中的 textInput
ref 会被设置为 CustomTextInput
中的 <input>
的 DOM 节点。