在常规的 React 数据六中,props
是父组件与子组件交互的唯一方法。如果需要改子元素,你需要用新的props
去重新渲染子元素。 然而,在少数情况下,你需要在常规数据流外强制修改子元素。被修改的子元素可以是React组件实例,或者是一个DOM元素。React团队提供了Refs
办法
# 使用Refs 场景
在如下几种情形中,我们可以引入refs
:
- 处理
focus
、文本选择或者媒体播放时 - 触发强制动画时
- 继承第三方DOM库时
注意:如果可以通过声明式实现,就尽量避免使用refs
。例如一个场景。如果我们想在Dialog
组件中暴露open()
和 close()
方法,则传递isOpen
状态标志即可。也就是说,我们不应该过度的使用refs,通常较高级别的state
更为清晰。
# 在元素本身的DOM元素上添加 Ref
React 支持给任何组件添加特殊的属性。 ref
属性接受回调函数,并且当组件 装载(mounted)
或者 卸载(unmounted)
之后,回调函数会立即执行。
当给HTML元素添加 ref
属性时,ref
回调接受底层的DOM 元素作为参数。例如,下面的代码使用ref
回调来存储DOM节点的引用
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
this.focus = this.focus.bind(this);
}
focus() {
// 通过原生的API,显示地聚焦text输入框
this.textInput.focus();
}
render() {
// 在实例中通过使用`ref`回调函数来存储text输入框的DOM元素引用(例如:this.textInput)
return (
<div>
<input
type="text"
ref={(input) => { this.textInput = input; }} />
<input
type="button"
value="Focus the text input"
onClick={this.focus} />
</div>
)
}
}
React 组件在加载时将DOM元素传入ref
的回调函数,在写在时则会传入 null
, 在 componentDidMount
或 componentDidUpdate
这些生命周期回之前执行 ref
回调
使用ref
回调只是为了在类上设置一个属性,是访问DOM元素常见模式。 首选的方法是在 ref
回调中设置属性。就像上面的例子一样,他也可以写成ref={input => this.textInput = input}
# 为 类(class)组件添加 Ref
当 ref
属性用于类声明的自定义组件时, ref
回到上述收到的参数装载(mounted)
的组件实例. 例如如果我们想包装 CustomTextInput
组件,实现组件在 装载(mounted)
后立即点击的效果:
class AutoFocusTextInput extends React.Component {
componentDidMount() {
this.textInput.focus();
}
render() {
return (
<CustomTextInput
ref={(input) => { this.textInput = input; }} />
);
}
}
需要注意的是,这种方法仅对以类声明的 CustomTextInput
有效,函数式声明的是无效的。
# Refs 与函数式组件
你不能在函数式组件上使用 ref 属性,因为它们没有实例
function MyFunctionalComponent() {
return <input />;
}
class Parent extends React.Component {
render() {
// 这里 *不会* 执行!
return (
<MyFunctionalComponent
ref={(input) => { this.textInput = input; }} />
);
}
}
如果你需要使用 ref
,你需要将组件转化成 类组件,就像需要 生命周期方法 或者 state
一样。然而你可以 在函数式组件内部使用ref
来引用一个 DOM 元素或者 类组件
function CustomTextInput(props) {
// textInput必须在这里声明,所以 ref 回调可以引用它
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 节点
在极少数的情况下,你可能希望从父组件中访问子节点的DOM节点。这种情况不被推崇使用,因为他会破坏组件的封装,丢失子组件独立工作的效果。但偶尔也可用于触发焦点或测量子 DOM 节点的大小或位置。
虽然你可以向子组件添加 ref
,但这不是一个理想的解决方案,因为你只能获取组件实例而不是 DOM 节点。并且,它还在函数式组件上无效。
相反,在这种情况下,我们建议在子节点上暴露一个特殊的属性。子节点将会获得一个函数属性,并将其作为 ref
属性附加到 DOM 节点。这允许父代通过中间件将 ref
回调给子代的 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
将它的ref
回调作为一个特殊的inputRef
传递给 CustomTextInput
,然后 CustomTextInput
通过ref
属性将其传递给 <input>
。最终,Parent
中的 this.inputElement
将被设置为与 CustomTextInput
中的 <input>
元素相对应的 DOM 节点。
请注意,上述示例中的 inputRef
属性没有特殊的含义,它只是一般的组件属性。然而,使用<input>
本身的ref
属性很重要,因为它告诉 React
将 ref
附加到它的 DOM 节点。
即使 CustomTextInput
是一个函数式组件,它也同样有效。这里在父组件定义的是自定义属性,而不是特殊的 ref
因此不会受影响
这种模式的另一个好处是它能作用很深。假如有个 Parent
组件不需要 DOM 节点 A,但是某个渲染 Parent
的组件(我们称之为Grandparent
)需要通过它访问。这时我们可以让 Grandparent
传递 inputRef
给 Parent
组件,然后让Parent
组件将其转发给 CustomTextInput
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}
/>
);
}
}
总而言之,我们建议尽可能不暴露 DOM 节点,但这是一个有用的解决方式。请注意,此方法要求您向子组件添加一些代码,如果你无法完全控制子组件,最后的办法是使用findDOMNode()
,但是不推荐这样做。