React-Native基础(4):组件引用Ref

原创
2017/05/22 16:39
阅读数 3.2K

在典型的React数据流中,父组件与子组件的唯一交互方式就是通过Props。修改子组件,就需要使用新的Prop来重新渲染。但是在有些情况,需要在数据流之外,直接来执行子组件的修改操作。React提供了使用Ref方式来解决直接修改子组件或子DOM元素的问题。

1. 添加Ref到DOM元素

ref是React组件一个特殊属性,使用回调函数作为属性,函数的接收组件的基本DOM元素作为它的参数。

<Component ref={(el) => this.el = el }/>

定义的回调函数会在组件加载和卸载时立即调用。 在组件卸载时函数中元素的值为null。 一个简单的例子

...
class CustomTextInput extends React.Component{
   constructor(props){
       super(props);
       this.focus = this.focus.bind(this);
   }
   
   focus(){
       // 获取文字焦点
       this.textInput.focus();
   }
   
   render(){
       // 使用ref回调函数存储text input 元素的引用
       return (
        <div>
          <input 
            type="text" 
            ref={(input) => { this.textInput = input; }} />
          <input
            type="button"
            value="Focus the text input"
            onClick={this.focus} />
        </div>
       );
   }
}

// es6 箭头函数
ref={input => this.textInput = input}

2. 添加Ref到Class组件

当为组件添加Ref回调函数,回调函数将使用自定义的组件作为它的参数。 例如:

class AutoFocusTextInput extends React.Component{
    constructor(props){
        this.textInput.focus();
    }
    
    render(){
        return(
          <CustomTextInput
            ref={ (input) => {this.textInput = input; }}
            />
        );
    }
}

这个方式只能用于以class方式声明的CustomTextInput

class CustomTextInput extends React.Component {
    // ...
}

4. Ref和函数组件

因为函数式组件没有组件实例,所以不能够通过上面的方式来给函数组件添加ref。

通过两种方式来解决

1. 将函数组件转为Class组件。
2. 在函数组件内部使用`ref`,这种方式同样适应于class组件。
function CustomTextInput(props){
    // 需要声明引用的textInput
    let textInput = null;
    function handleClick(){
        textInput.focus();
    }
    
    return (
      <div>
        <input 
          text="text"
          ref={ (input) => { textInput = input; }}
          />
        <input 
          text="button"
          value="Focus the text input"
          onClick={ handleClick }
          />
      </div>
    );
}

4. 暴露DOM的Ref给父组件

在极少情况下,父组件需要访问子组件。因为破坏了组件的封装性,这种做法并不推荐,但是有时对于触发焦点和测量子DOM节点的位置或大小会有用。如果给子组件添加ref时,也并不是理想解决方案,因为ref回调函数的参数是子组件,而不是DOM节点元素,并且不适用函数式组件。 因此,在这些情况建议暴露子组件的特殊属性,子组件使用任意名称(如:inputRef)的函数类型的Prop,并把这个属性添加大DOM节点元素中,作为ref属性。就可以通过中间组件将父组件的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}
          />
      );
    )
}

上面代码中父组件使用inputRef属性将ref回调函数传递给中间组件CustomTextInputCustomTextInput又将函数传给<input />的ref属性。最终父组件获取到对<input />的引用。这种形式不仅适用于Class组件,同样也适用于函数组件。 这种形式的另一个好处就是:对于深层的DOM节点同样有效。假设父组件不需要DOM节点引用,但是祖父组件想要获取子组件中DOM节点元素的引用,可以通过让祖父组件指定inputRef属性,传递给父组件,父组件再把inputRef转给CustomTextInput

function CustomTextInput(props){
  return (
    <div>
      <input ref={props.inputRef}/>
    </div>
  );
}

class Parent extends React.Component{
    render (
      return (
        <div>
          My input: <CustomTextInput inputRef={props.inputRef} />
        </div>
      );
    )
}


class GrandParent extends React.Component{
  render() {
    return (
      <Parent
        inputRef={el => this.inputElement = el}
      />
    );
  }
}

综合考虑下,不建议暴露DOM节点。 因为破坏了组件的封装,而且需要子节点添加额外的属性。

5. 说明

如果ref以内联函数形式定义,在更新时会被调用两次 - 第一次为函数的参数为null,第二次为DOM元素 因为每次render时,都要创建一个新的函数实例,建议通过组件bind函数来定义ref回调函数。

6. 适应场景

1. 管理输入焦点,文字选择或者媒体播放时;
2. 触发必要的动画时;
3. 与第三方DOM库交互时;

避免使用在可用声明方式的情景下。如:通过定义isOpen的方式,避免暴露Dialogopenclose的方法。

7. 避免过度使用

在使用ref时,一定要经过慎重思考,尤其是拥有State状态的组件。首先考虑使用Props

Refs and the DOM 公众号

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部