通常情况下, 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),并根据个人理解和习惯做了一些结构的调整。