React学习:Refs

在常规的 React 数据流中,props 是父组件与子组件交互的唯一方式。要修改子元素,你需要用新的 props 去重新渲染子元素。然而,在少数情况下,你需要在常规数据流外(冲出 React 虚拟 DOM 的限制)强制修改子元素(被修改的子元素可以是 React 组件实例,或者是一个 DOM 元素),在这种情况下,React 提供了Refs来解决。

简单说就是:
在从 render 方法中返回 UI 结构之后,你可能想冲出 React 虚拟 DOM 的限制,在 render 返回的组件实例上调用某些方法,此时用Refs就好。


一、 何时使用 Refs

下面有一些正好使用 refs 的场景:

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

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

注意:不要过度使用 Refs。应用中可以用state更新组件,就不要用Refs.


二、Refs的调用时机

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

例:

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    this.state = {text:''};  
    this.handler = this.handler.bind(this);
  }
  handler(e){
    this.setState({text:e.target.value});
  }

  render() {
    return (
      <div>
        <input
          type="text"
          value={this.state.text}
          onChange={this.handler}
          ref={() => { alert(this.state.text)}} />
      </div>
    );
  }
}
ReactDOM.render(
    <CustomTextInput/>,
    document.getElementById('root')
);

执行过程:
(1)首先页面什么都没有,只有一个alert框,框的内容还是空的。
说明:ref在componentDidMount前会调用一次。
这里写图片描述
(2)点击确定后出现text框。
说明ref调用完后进行了渲染。
这里写图片描述
(3)当在text框里输入个’s’后,’s’并没有直接渲染出来,而是先执行了ref回调函数,alert弹出输入的值’s’。
说明:ref在componentDidUpdate前会调用一次。因为输入s后会使组件再次更新,在组件更新前首先会进行卸载(unmounted)。
这里写图片描述
(4)点击alert的确定后,再次弹出了一样的alert
说明:ref在componentDidMount前会调用一次。
这里写图片描述
(5)再次点击alert的确定后,界面才渲染出’s’.
说明ref调用完后进行了再次渲染。
这里写图片描述


三、Refs的应用

1、在 DOM 元素上添加 Ref

当给 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();
    console.log(this.textInput);//<input type='text'/>
  }

  render() {
    // 用`ref`回调函数来存储 输入框的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>
    );
  }
}
ReactDOM.render(
    <CustomTextInput/>,
    document.getElementById('root')
);

React 组件在加载时将 DOM 元素传入 ref 的回调函数的形参里在卸载时则会传入 null。在componentDidMount 或 componentDidUpdate 这些生命周期回调之前执行 ref 回调。

执行结果:
这里写图片描述
点击button后,控制台输出this.textInput的值,即ref参数的值:
< input type=’text’/>


2、为 类(Class) 组件添加 Ref

当给 类组件 添加 ref 属性时, ref 回调函数收到的参数是装载(mounted)的组件实例。

例:

class CustomTextInput extends React.Component {
  render() {
    return (
      <div></div>
    );
  }
}

class AutoFocusTextInput extends React.Component {
  componentDidMount() {
    console.log('AutoFocusTextInput',this.textInput);
  }
  render() {
    return (
      <CustomTextInput
        ref={(input) => { this.textInput = input; }} />
    );
  }
}

ReactDOM.render(
    <AutoFocusTextInput/>,
    document.getElementById('root')
);

输出:组件的实例
这里写图片描述


3、Refs 与 函数式组件

你不能在函数式组件上使用 ref 属性,因为它们没有实例:

function MyFunctionalComponent() {
  return <input />;
}

class Parent extends React.Component {
  render() {
    // 这里 *不会* 执行!
    return (
      <MyFunctionalComponent
        ref={(input) => { this.textInput = input; }} />
    );
  }
}

如果你需要使用 ref ,你需要将组件转化成 类(class)组件,就像需要 生命周期方法 或者 state 一样。

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

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 节点。并且,它还在函数式组件上无效。

其实,我们可以在子组件的DOM节点上添加 ref 属性,然后让父组件通过props传递一个回调函数 给子组件的DOM节点,就OK了.

例:

function CustomTextInput(props) {
  return (
    <div>
      <input ref={props.inputRef} />
    </div>
  );
  //此时<input ref={props.inputRef} />相当于
  //<input ref={el => this.inputElement = el} />
}

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 属性没有特殊的含义,它只是一般的组件属性。

这种模式的另一个好处是它能作用很深。假如有个 Parent 组件不需要 DOM 节点 A,但是某个渲染 Parent 的组件(我们称之为 Grandparent)需要通过它访问。这时我们可以让 Grandparent 传递 inputRef 给 Parent 组件,然后让 Parent 组件将其转发给 CustomTextInput:

function CustomTextInput(props) {
  return (
    <div>
      <input ref={props.inputRef} />
    </div>
  );
  //此时<input ref={props.inputRef} />相当于
  //<input ref={el => this.inputElement = el} />
}

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}
      />
    );
  }
}

上面的例子中,Grandparent 首先指定了 ref 回调函数。它通过一个常规的 inputRef 属性被传递到 Parent,Parent 也同样把它传递给了 CustomTextInput。最后 CustomTextInput 读取了 inputRef 属性并将传递的函数作为 ref 属性附加到 < input>。最终,Grandparent 中的 this.inputElement 被设置为 CustomTextInput 的 input 对应的 DOM 节点。

总而言之,我们建议尽可能不暴露 DOM 节点,但这是一个有用的解决方式。请注意,此方法要求您向子组件添加一些代码,如果你无法完全控制子组件,最后的办法是使用 findDOMNode(),但是不推荐这样做。

猜你喜欢

转载自blog.csdn.net/b954960630/article/details/80197173