特殊的ref
在 2.6.2 节非受控组件中,已经使用过 ref 来获取表单元素。ref 不仅可以用来获取表单元素,还可以用来获取其他任意 DOM 元素,甚至可以用来获取 React 组件实例。在一些场景下,ref 的使用可以带来便利,例如控制元素的焦点、文本的选择或者和第三方操作 DOM 的库集成。但绝大多数场景下,应该避免使用 ref,因为它破坏了 React 中以 props 为数据传递介质的典型数据流。本节将介绍 ref 常用的使用场景。
在DOM元素上使用ref
在 DOM 元素上使用 ref 是最常见的使用场景。ref 接收一个回调函数作为值,在组件被挂载或卸载时,回调函数会被调用,在组件被挂载时,回调函数会接收当前 DOM 元素作为参数;在组件被卸载时,回调函数会接收 null 作为参数。例如:
class AutoFocusTextInput extends React.Component {
componentDidMount() {
// 通过 ref 让 input 自动获取焦点
this.textInput.focus();
}
render() {
return (
<div>
<input
type="text"
ref={(input) => { this.textInput = input; }}
/>
</div>
)
}
}
AutoFocusTextInput 中为 input 元素定义 ref,在组件挂载后,通过 ref 获取该 input 元素,让 input 自动获取焦点。如果不使用 ref,就难以实现这个功能。
在组件上使用ref
React 组件也可以定义 ref,此时 ref 的回调函数接收的参数是当前组件的实例,这提供了一种在组件外部操作组件的方式。例如,在使用 AutoFocusTextInput 组件的外部组件 Container 中控制 AutoFocusTextInput:
class AutoFocusTextInput extends React.Component {
constructor(props) {
super(props);
this.blur = this.blur.bind(this);
}
componentDidMount() {
// 通过 ref 让 input 自动获取焦点
this.textInput.focus();
}
// 让 input 失去焦点
blur() {
this.textInput.blur();
}
render() {
return (
<div>
<input
type="text"
ref={(input) => {this.textInput = input;}}
/>
</div>
)
}
}
class Container extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
// 通过 ref 调用 AutoFocusTextInput 组件的方法
this.inputInstance.blur();
}
render() {
return (
<div>
<AutoFocusTextInput ref={(input) => {this.inputInstance = input}} />
<button onClick={this.handleClick}>失去焦点</button>
</div>
);
}
}
在 Container 组件中,我们通过 ref 获取到了 AutoFocusTextInput 组件的实例对象,并把它赋值给 Container 的 inputInstance 属性,这样就可以通过 inputInstance 调用 AutoFocusTextInput 中的 blur 方法,让已经处于获取焦点状态的 input 元素失去焦点。
注意,只能为类组件定义 ref 属性,而不能为函数组件定义 ref 属性,例如下面的写法是不起作用的:
function MyFunctionalComponent() {
return <input />;
}
class Parent extends React.Component {
render() {
// ref 不生效
return (
<MyFunctionalComponent ref={(input) => {this.textInput = input; }} />
);
}
}
函数组件虽然不能定义 ref 属性,但这并不影响在函数组件内部使用 ref 来引用其他 DOM 元素或组件,例如下面的例子是可以正常工作的:
function MyFunctionalComponent() {
let textInput = null;
function handleClick() {
textInput.focus();
}
return (
<div>
<input
type="text"
ref={(input) => {textInput = input; }}
/>
<button onClick={handleClick} >获取焦点</button>
</div>
)
}
父组件访问子组件的DOM节点
在一些场景下,我们可能需要在父组件中获取子组件的某个 DOM 元素,例如父组件需要知道这个 DOM 元素的尺寸或位置信息,这时候直接使用 ref 是无法实现的,因为 ref 只能获取子组件的实例对象,而不能获取子组件中的某个 DOM 元素。不过,我们可以采用一种间接的方式获取子组件的 DOM 元素:在子组件的 DOM 元素上定义 ref,ref 的值是父组件传递给子组件的一个回调函数,回调函数可以通过一个自定义的属性传递,例如 inputRef,这样父组件的回调函数中就能获取到这个 DOM 元素。下面的例子中,父组件 Parent 的 inputElement 指向的就是子组件的 input 元素。
function Children(props) {
// 子组件使用父组件传递的 inputRef, 为 input 的 ref 赋值
return (
<div>
<input ref={props.inputRef} />
</div>
);
}
class Parent extends React.Component {
render() {
// 自定义一个属性 inputRef,值是一个函数
return (
<Children inputRef={el => this.inputElement = el} />
);
}
}
从这个例子中还可以发现,即使子组件是函数组件,这种方式同样有效。