通常情况下, React 使用 props 和子组件进行交互,如果需要修改子组件,则通过更新子组件的 props 来重新 render 子组件,但是在有些情况下,可能想要在 props 的数据流之外修改 props ,对于这种情况, React 提供了 refs 来允许不通过 props 的数据流对子组件修改。
Refs 应用场合
refs 的几个应用场合:
- 管理元素焦点、文本选择或者媒体播放等等;
 - 触发命令式动画
 - 与第三方的类库整合
 
refs 是直接操作 DOM 元素的,因此在使用之前,最好考虑一下是否真的必须需要使用 refs ,是否可以使用 state 来代替使用 refs , react 的文档建议避免在可声明的对象上使用 refs , 比如对于 Dialog 组件,通过向其传递 isOpen 属性来控制其显示隐藏,而不是调用其 open() 或者 close() 方法。
使用 Refs 属性
React 允许为组件或者 HTML 元素添加一个特殊的 ref 属性,ref 属性是一个回调函数,这个回调函数在组件或者 HTML 元素被挂载或者移除挂载后自动执行,当这个回调函数执行的时候,会把这个组件或者 HTML 的实际的 DOM 元素做为参数给它,如以下示例:
class CustomTextInput extends React.Component {  | 
React 在 input 实例化的时候调用 ref 函数,并把该 input 元素作为参数传递进去,当这个元素卸载的时候,也会调用这个函数,此时传递的参数是 null。
在元素上使用 ref 属性通常是为了在某些场合下操作这个 DOM 元素,如本例使用 ref={input => this.textInput = input} 保存了这个 input 的 DOM 元素的引用,然后在点击按钮的时候使其获得焦点。
在类组件上添加 Refs 引用
当 ref 引用被用在一个声明为类的组件上, 此时传递给 ref 回调函数的参数是这个组件的实例,如下示例,对组件 CustomTextUnput 进行了一层包裹,试该组件加载完成后的自动获得焦点:
class AutoFocusTextInput extends React.Component {  | 
在上述示例中,ref 指向的是子组件的示例对象,通过这个引用,可以访问子组件的 focus 方法的。注意,只有当 CustomTextUnput 被声明为一个类组件的时候才能正常工作:
class CustomTextInput extends React.Component {  | 
在函数式组件上不能使用 Refs
函数式组件由于没有示例,因此不能对其使用 ref ,如果需要使用 ref 引用,则必须先将其转换为类组件:
function MyFunctionalComponent() {  | 
虽然函数式组件上不能使用 ref 但是,可以在函数式组件内部使用,前提是使用在类组件或者 DOM 元素:
function CustomTextInput(props) {  | 
给父组件暴露 DOM 的Refs
在很少的一些情况下,可能需要从父组件访问子组件的 DOM 元素,这种方式不推荐,因为破坏了组件的封装,但是在一些如触发子组件 DOM 的焦点,计算子组件的大小或位置场合下会需要。
根据前两节,直接给子组件增加 ref 是没有用的,这种方式只能获得自组件实例的引用而不是子组件,因此并无法操作子组件的 DOM 元素,而且这对函数式组件不起作用。相反的,这种情况下可以给子组件传递一个 prop ,通过这个 prop 给子组件传递 ref 的回调函数,然后在子组件内把这个 ref 绑定在自己的 DOM 元素上,通过这种方式,使父组件获得自足件的 DOM 元素引用,并且可以通过中间组件传递,而且,对于函数式组件也可以使用,具体看如下示例:
function CustomTextInput(props) {  | 
在上面的示例中, Parent 组件通过 inputRef prop 传递自己的 ref 回调函数给 CustomTextInput , CustomTextInput 使用 Parent 组件传递过来的 inputRef 为 input 元素绑定 ref,这样 Parent 组件就保留了 CustomTextInput 组件的 input 元素的引用,就可以对其进行一些必要的操作,比如使其获得焦点。
而且注意到,CustomTextInput 实际是个函数式组件,这种方式也不会被 ref 只能绑定到类组件和 DOM 元素所限制,因为 ref 被通过 props 传递给它的。
这种方式的另一个有点是可以支持很深的组件层级,比如,如果中间的组件不需要这个引用,可以直接将其传递给其子组件处理:
function CustomTextInput(props) {  | 
如上述示例,中间组件 Parent 收到其父组件 Grandparent 传递的 inputRef , 直接将其丢给下一级组件 CustomTextInput 处理。
注意到,这种方式下需要修改子组件的代码,如果在某些情况子组件是不受控制的,无法修改子组件的代码的时候,则还有一种方式是使用 ReactDOM.findDOMNode(component) ,这种方式不鼓励使用,只有在别无选择的时候可以考虑。
旧版 API:字符串 Refs
之前的版本使用字符串的 this.refs.input ,这种方式存在一些问题,仅作保留兼容,未来可能被一处,不要使用。
注意事项
如果 ref 回调被定义为一个内联函数,在组件更新的时候它会被调用两次,第一次是传入 null 调用,第二次传入 DOM 元素调用。这是因为每一次重新渲染的时候,传递给 ref 的回调函数都是新创建的函数实例,因此 React 需要清除旧的 ref 然后重新设置。大多数情况下是无关紧要的,通过传入一个绑定在类上的函数来可以避免这个问题。
参考
 本文主要参考 React 的官方文档(Refs and the DOM),并根据个人理解和习惯做了一些结构的调整。