Refs 和 DOM

383 阅读3分钟

本篇文章是对文档的自我学习,都已经完全掌握的同学也已不必去看。

Refs

Refs 提供了我们能够访问DOM节点的能力,或者在 render 方法中创建的 React 元素。

我们知道 React 是单向数据流,props 是父组件与子组件进行交互的唯一方式。如果想要修改一个子组件,你需要用props来重新渲染他。

但是在某些情况下,你需要在 React 数据流之外强制修改子组件。被修改的组件可能是 React 组件的实例,也可能是一个DOM元素。

何时使用 Refs

下面几种情况适合使用refs:

  • 管理焦点,文本选择或者媒体播放
  • 触发强制动画
  • 集成第三方 DOM 库

避免使用 refs 来使用子组件中的方法,比如:子组件中的方法通过refs方式被父组件调用。

创建 refs

Refs是使用 React.createRef() 创建的,并通过 ref 属性附加到React元素。

在构造组件时,通常将Refs分配给实例属性,以便可以在整个组件中引用他们。

class MyComponent extends React.Component {
  constructor(props) {
    super(props)
    this.myRef = React.createRef()
  }
  render() {
    return <div ref={this.myRef} />
  }
}

访问 refs

当 ref 被传递给 render中的元素时,对该节点的引用可以在ref的current属性中访问。

const node = this.myRef.current;

ref的值根据节点的类型而有所不同:

  • 当ref属性用于HTML元素时,构造函数中使用React.createRef()创建的ref接收底层DOM元素作为其current属性。
  • 当ref属性用于定义class组件时,ref对象接收组件的挂载实例作为其current属性。
  • 你不能在函数组件上使用ref属性,因为他们没有实例。

下面的🌰将说明这些差异:

为DOM元素添加ref

以下代码使用ref去存储DOM节点的引用:

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    // 创建一个 ref 来存储 textInput 的 DOM 元素
    this.textInput = React.createRef();    
    this.focusTextInput = this.focusTextInput.bind(this);
  }

  focusTextInput() {
    // 直接使用原生 API 使 text 输入框获得焦点
    // 注意:我们通过 "current" 来访问 DOM 节点
    this.textInput.current.focus();  
  }

  render() {
    // 告诉 React 我们想把 <input> ref 关联到
    // 构造器里创建的 `textInput` 上
    return (
      <div>
        <input
          type="text"
          ref={this.textInput} />        
        <input
          type="button"
          value="Focus the text input"
          onClick={this.focusTextInput} />
      </div>
    )
  }
}

image.png

上面的代码,当onClick事件被触发时会使得第一个input获取焦点,事件中通过ref触发获取焦点。

React会在组件挂载时给current属性传入DOM元素,并在组件卸载时传入null的值。Ref会在componentDidMount或compoonentDidUpdate触发之前进行更新。

为class组件添加ref

如果我们想包装上面的CustomTextInput组件,来模拟他挂载之后立即被点击的操作,我们可以使用ref来获取这个自定义的input组件,并手动调用他的focusTextInput方法:

class AutoFocusTextInput extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef()
  }

  componentDidMount() {
    this.textInput.current.focusTextInput()
  }

  render() {
    return (
      <CustomTextInput ref={this.textInput} />   
    )
  }
}

只有在CustomTextInput为class组件时才能够这样使用ref,接下来看看函数组件如何使用。

Refs 与函数组件

默认情况下,你不能在函数组件上使用 ref 属性,因为没有实例:

function MyFunctionComponent() {  
    return <input />
}

class Parent extends React.Component {
  constructor(props) {
    super(props)
    this.textInput = React.createRef()
  }
  render() {
    // This will *not* work!
    return (
      <MyFunctionComponent ref={this.textInput} />    
    )
  }
}

如果要在函数组件中使用 ref,你可以使用 forwardRef。

function CustomTextInput(props) {
  // 这里必须声明 textInput,这样 ref 才可以引用它  
  const textInput = useRef(null)
  function handleClick() {
    textInput.current.focus()
  }
  return (
    <div>
      <input
        type="text"
        ref={textInput} />      
      <input
        type="button"
        value="Focus the text input"
        onClick={handleClick} />
    </div>
  )
}

useRef 在之后讲到Hooks在进行详解。

回调 Refs

我们只来看看函数组件中如何进行回调的。

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

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

在上面的例子中,Parent 把它的 refs 回调函数当作 inputRef props 传递给了 CustomTextInput,而且 CustomTextInput 把相同的函数作为特殊的 ref 属性传递给了 <input>

结果是,在 Parent 中的 this.inputElement 会被设置为与 CustomTextInput 中的 input 元素相对应的 DOM 节点。

总结

  • refs 如何创建,并且如何访问的
  • refs 能够通过回调的方式设置