Refs and the DOM

在常规的React数据流中,props是父组件与子组件交互的唯一方式。为了修改子元素,你需要用新的props去重新渲染子元素。然而,在少数情况下,你需要在常规数据流外强制修改子元素。被修改的子元素可以是React组件实例,也可以是DOM元素。在这种情况下,React提供了解决办法。

何时使用Refs

下面有一些恰当地使用refs的场景:

  • 处理focus、文本选择或者媒体播放
  • 触发强制动画
  • 集成第三方DOM库

如果可以通过声明式实现,就尽量避免使用refs。

例如,相比于在Dialog组件中暴露open()close()方法,最好传递isOpen属性。

为DOM元素添加Ref

React支持给任何组件添加特殊属性。ref属性接受回调函数,当组件安装(mounted)或者卸载(unmounted)之后,回调函数会立即执行。

当给HTML元素添加ref属性时,回调函数接受底层的DOM元素作为参数。例如,下面的代码使用ref函数来存储DOM节点的引用。

  1. class CustomTextInput extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.focus = this.focus.bind(this);
  5. }
  6. focus() {
  7. // 通过使用原生API,显式地聚焦text输入框
  8. this.textInput.focus();
  9. }
  10. render() {
  11. // 在实例中通过使用`ref`回调函数来存储text输入框的DOM元素引用(例如:this.textInput)
  12. return (
  13. <div>
  14. <input
  15. type="text"
  16. ref={(input) => { this.textInput = input; }} />
  17. <input
  18. type="button"
  19. value="Focus the text input"
  20. onClick={this.focus}
  21. />
  22. </div>
  23. );
  24. }
  25. }

React将会在组件安装(mount)时,用DOM元素作为参数回调ref函数,在组件卸载(unmounts)时,使用null作为参数回调函数。

对class设置ref回调函数是访问DOM元素的一种常见方法。首选的方式就是像上面的代码一样,对ref设置回调函数。还有更简洁的方式:ref={input => this.textInput = input}

为类(Class)组件添加Ref

为用类(class)声明的自定义组件设置ref属性时,ref回调函数收到的参数是安装(mounted)的组件实例。例如,如果我们想包装CustomTextInput组件,实现组件在mounted后立即点击的效果:

  1. class AutoFocusTextInput extends React.Component {
  2. componentDidMount() {
  3. this.textInput.focus();
  4. }
  5. render() {
  6. return (
  7. <CustomTextInput
  8. ref={(input) => { this.textInput = input; }} />
  9. );
  10. }
  11. }

需要注意的是,这种方法仅对以类(class)声明的CustomTextInput有效。

  1. class CustomTextInput extends React.Component {
  2. // ...
  3. }

Refs与函数式Components

你不能在函数式组件上使用ref因为函数式组件不会创建实例:

  1. function MyFunctionalComponent() {
  2. return <input />;
  3. }
  4. class Parent extends React.Component {
  5. render() {
  6. // 无效!
  7. return (
  8. <MyFunctionalComponent
  9. ref={(input) => { this.textInput = input; }} />
  10. );
  11. }
  12. }

如果你需要使用ref,你需要将组件转化成class组件,就像需要生命周期函数或者state那样。

然而你可以在函数式组件内部使用ref来引用一个DOM元素或者class组件。

  1. function CustomTextInput(props) {
  2. // textInput must be declared here so the ref callback can refer to it
  3. let textInput = null;
  4. function handleClick() {
  5. textInput.focus();
  6. }
  7. return (
  8. <div>
  9. <input
  10. type="text"
  11. ref={(input) => { textInput = input; }} />
  12. <input
  13. type="button"
  14. value="Focus the text input"
  15. onClick={handleClick}
  16. />
  17. </div>
  18. );
  19. }

不要滥用Refs

在你应用中,你可能会倾向于使用ref使得”事件发生”。如果这种情况下,花一点时间仔细考虑一下在组件层次结构中哪些地方需要state。通常情况下,应该在层次结构中较高级别中拥有state。有关这方面的实例参阅提升state

旧版API: String类型的Refs

如果你之前使用过React,你可能了解过之前的API:string类型的ref属性。类似于textInput,可以通过this.refs.textInput访问DOM节点。我们不建议使用,因为string类型的ref存在问题。已经过时了,可能会在未来的版本是移除。如果你目前还在使用this.refs.textInput这种方式访问refs,我们建议用回调函数的方式代替。

注意

如果ref以内联函数的方式定义,在update期间会被调用两次,第一次参数是null,之后参数是DOM元素。这是因为在每次渲染中都会创建一个新的函数实例。因此,React需要清理旧的ref并且设置新的。通过将ref的回调函数定义成类的绑定函数的方式可以避免上述问题,但是在大多数例子中这都不是很重要。