React中的Refs与Refs的转发

864 阅读2分钟

Refs 提供了一种可以便捷访问DOM元素和React组件实例的方式。可以通过Refs强制执行一些实例方法。例如:控制input标签的焦点管理、控制视频组件(<video/>标签)的播放管理。

Refs的三种创建方式

  1. String 类型的Refs. (已过时,不建议使用) 在标签处为ref赋值。如下
   <input ref="numberInput"></input>

然后在组件中可以使用this访问:

this.refs.numberInput
  1. 创建Refs变量(在React16.3版本中引进) 在构建组件的时候,通过React.createRef()创建ref变量,然后在附加到实例标签。
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }
  render() {
    return <div ref={this.myRef} />;
  }
}

然后在组件中可以使用如下方式获取

const node = this.myRef.current;
  1. 回调方式创建Refs. React会在组件挂载的时候调用Ref回调函数并传入DOM元素。
import React from "react";
export class CustomInput extends React.Component {
  constructor(props) {
    super();
    this.myInputRef = null;
    this.setMyInputRef = element => {
      this.myInputRef = element;
    };
  }

  render() {
    return (<div>
        <input ref={this.setMyInputRef} />
      </div>
    );
  }
}

Ref回调函数会在更新过程中执行两次,第一次由于组件没有挂载,会传入null。第二次会传入真正的DOM元素。另外Ref回调函数和其他组件事件监听函数例如:onChange、onMouseMove 等一样,最好提前绑定this. 不然会重复创建函数变量。

Refs的转发

在实际开发中经常遇到访问已封装好组件的内部组件(子组件)或DOM元素,例如访问高阶组件的包裹组件。 如果直接在高阶组件上使用ref,引用到的事高阶组件本身的实例,显然这不是我们期待的。不过有两种方式可以解决这个问题。下面通过CustomTextInput 组件自动获焦的实例阐述这两种方法。

  1. Ref回调当做组件属性传入高阶组件
import React from "react";
import { CustomTextInputHOC } from "./CustomTextInputHOC";

class CustomTextInput extends React.Component {
  constructor(props) {
    super();
    this.state = {
      text: "nihao"
    };
    this.inputRef = React.createRef();
  }
  onTextChange = event => {
    event.persist();
    this.setState({
      text: event.target.value
    });
  };
  onFocus = () => {
    this.inputRef.current.focus();
  };
  render() {
    return (
      <div>
        <input ref={this.inputRef} onChange={this.onTextChange} />
        <div>{this.state.text}</div>
      </div>
    );
  }
}
export default CustomTextInputHOC(CustomTextInput);

CustomTextInputHOC组件定义如下:

import React from "react";

export function CustomTextInputHOC(WrapComp) {
  return class extends React.Component {
    render() {
      const { createRefFunc, name } = this.props;
      return <WrapComp ref={createRefFunc} name={name} />;
    }
  };
}

在App里挂载CustomTextInput 组件:

export default function App() {
  let inputRef = null;

  const createRef = ref => {
    inputRef = ref;
  };
  useEffect(() => {
    console.log(inputRef);
    inputRef.onFocus();
  });
  return <CustomTextInput createRefFunc={createRef} />;
}
  1. 通过React.forwardRef劫持ref变量,然后向下传递。

CustomTextInput 组件没有变化和上面实现的一致 CustomTextInputHOC 组件的实现如下:

import React from "react";
export function CustomTextInputHOC(WrapComp) {
  class MyComp extends React.Component {
    render() {
      const { forwardRef, name } = this.props;
      return <WrapComp ref={forwardRef} name={name} />;
    }
  }
  return React.forwardRef((props, ref) => {
    return <MyComp {...props} forwardRef={ref} />;
  });
}

在根节点挂载CustomTextInput组件

export default function App() {
  let inputRef = React.createRef();
  useEffect(()=> {
   inputRef.current.onFocus();
  })
  return <CustomTextInput ref={inputRef} />;
}

参考文献

Refs转发

Refs与DOM