超级面板
文章目录
最新文章
最近更新
文章分类
标签列表
文章归档

React - Refs

通常情况下, React 使用 props 和子组件进行交互,如果需要修改子组件,则通过更新子组件的 props 来重新 render 子组件,但是在有些情况下,可能想要在 props 的数据流之外修改 props ,对于这种情况, React 提供了 refs 来允许不通过 props 的数据流对子组件修改。

Refs 应用场合

refs 的几个应用场合:

  • 管理元素焦点、文本选择或者媒体播放等等;
  • 触发命令式动画
  • 与第三方的类库整合

refs 是直接操作 DOM 元素的,因此在使用之前,最好考虑一下是否真的必须需要使用 refs ,是否可以使用 state 来代替使用 refsreact 的文档建议避免在可声明的对象上使用 refs , 比如对于 Dialog 组件,通过向其传递 isOpen 属性来控制其显示隐藏,而不是调用其 open() 或者 close() 方法。

使用 Refs 属性

React 允许为组件或者 HTML 元素添加一个特殊的 ref 属性,ref 属性是一个回调函数,这个回调函数在组件或者 HTML 元素被挂载或者移除挂载后自动执行,当这个回调函数执行的时候,会把这个组件或者 HTML 的实际的 DOM 元素做为参数给它,如以下示例:

class CustomTextInput extends React.Component {
constructor(props) {
super(props);
this.focus = this.focus.bind(this);
}

focus() {
// Explicitly focus the text input using the raw DOM API
this.textInput.focus();
}

render() {
// Use the `ref` callback to store a reference to the text input DOM
// element in an instance field (for example, this.textInput).
return (
<div>
<input
type="text"
ref={input => this.textInput = input} />
<input
type="button"
value="Focus the text input"
onClick={this.focus}
/>
</div>
);
}
}

Reactinput 实例化的时候调用 ref 函数,并把该 input 元素作为参数传递进去,当这个元素卸载的时候,也会调用这个函数,此时传递的参数是 null

在元素上使用 ref 属性通常是为了在某些场合下操作这个 DOM 元素,如本例使用 ref={input => this.textInput = input} 保存了这个 inputDOM 元素的引用,然后在点击按钮的时候使其获得焦点。

在类组件上添加 Refs 引用

ref 引用被用在一个声明为类的组件上, 此时传递给 ref 回调函数的参数是这个组件的实例,如下示例,对组件 CustomTextUnput 进行了一层包裹,试该组件加载完成后的自动获得焦点:

class AutoFocusTextInput extends React.Component {
componentDidMount() {
this.textInput.focus();
}

render() {
return (
<CustomTextInput
ref={(input) => { this.textInput = input; } } />
);
}
}

在上述示例中,ref 指向的是子组件的示例对象,通过这个引用,可以访问子组件的 focus 方法的。注意,只有当 CustomTextUnput 被声明为一个类组件的时候才能正常工作:

class CustomTextInput extends React.Component {
// ...
}

在函数式组件上不能使用 Refs

函数式组件由于没有示例,因此不能对其使用 ref ,如果需要使用 ref 引用,则必须先将其转换为类组件:

function MyFunctionalComponent() {
return <input />;
}

class Parent extends React.Component {
render() {
// This will *not* work!
return (
<MyFunctionalComponent
ref={(input) => this.textInput = input}
/>
);
}
}

虽然函数式组件上不能使用 ref 但是,可以在函数式组件内部使用,前提是使用在类组件或者 DOM 元素:

function CustomTextInput(props) {
// textInput must be declared here so the ref callback can refer to it
let textInput = null;

function handleClick() {
textInput.focus();
}

return (
<div>
<input
type="text"
ref={(input) => { textInput = input; } }
/>
<input
type="button"
value="Focus the text input"
onClick={handleClick}
/>
</div>
);
}

给父组件暴露 DOM 的Refs

在很少的一些情况下,可能需要从父组件访问子组件的 DOM 元素,这种方式不推荐,因为破坏了组件的封装,但是在一些如触发子组件 DOM 的焦点,计算子组件的大小或位置场合下会需要。

根据前两节,直接给子组件增加 ref 是没有用的,这种方式只能获得自组件实例的引用而不是子组件,因此并无法操作子组件的 DOM 元素,而且这对函数式组件不起作用。相反的,这种情况下可以给子组件传递一个 prop ,通过这个 prop 给子组件传递 ref 的回调函数,然后在子组件内把这个 ref 绑定在自己的 DOM 元素上,通过这种方式,使父组件获得自足件的 DOM 元素引用,并且可以通过中间组件传递,而且,对于函数式组件也可以使用,具体看如下示例:

function CustomTextInput(props) {
return (
<div>
<input ref={props.inputRef} />
</div>
);
}

class Parent extends React.Component {
render() {
return (
<CustomTextInput
inputRef={el => this.inputElement = el}
/>
);
}
}

在上面的示例中, Parent 组件通过 inputRef prop 传递自己的 ref 回调函数给 CustomTextInputCustomTextInput 使用 Parent 组件传递过来的 inputRefinput 元素绑定 ref,这样 Parent 组件就保留了 CustomTextInput 组件的 input 元素的引用,就可以对其进行一些必要的操作,比如使其获得焦点。

而且注意到,CustomTextInput 实际是个函数式组件,这种方式也不会被 ref 只能绑定到类组件和 DOM 元素所限制,因为 ref 被通过 props 传递给它的。

这种方式的另一个有点是可以支持很深的组件层级,比如,如果中间的组件不需要这个引用,可以直接将其传递给其子组件处理:

function CustomTextInput(props) {
return (
<div>
<input ref={props.inputRef} />
</div>
);
}

function Parent(props) {
return (
<div>
My input: <CustomTextInput inputRef={props.inputRef} />
</div>
);
}

class Grandparent extends React.Component {
render() {
return (
<Parent
inputRef={el => this.inputElement = el}
/>
);
}
}

如上述示例,中间组件 Parent 收到其父组件 Grandparent 传递的 inputRef , 直接将其丢给下一级组件 CustomTextInput 处理。

注意到,这种方式下需要修改子组件的代码,如果在某些情况子组件是不受控制的,无法修改子组件的代码的时候,则还有一种方式是使用 ReactDOM.findDOMNode(component) ,这种方式不鼓励使用,只有在别无选择的时候可以考虑。

旧版 API:字符串 Refs

之前的版本使用字符串的 this.refs.input ,这种方式存在一些问题,仅作保留兼容,未来可能被一处,不要使用。

注意事项

如果 ref 回调被定义为一个内联函数,在组件更新的时候它会被调用两次,第一次是传入 null 调用,第二次传入 DOM 元素调用。这是因为每一次重新渲染的时候,传递给 ref 的回调函数都是新创建的函数实例,因此 React 需要清除旧的 ref 然后重新设置。大多数情况下是无关紧要的,通过传入一个绑定在类上的函数来可以避免这个问题。

参考

本文主要参考 React 的官方文档(Refs and the DOM),并根据个人理解和习惯做了一些结构的调整。