本文为稀土技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!

三种运用办法

React 供给了 Refs,协助咱们访问 DOM 节点或在 render 办法中创立的 React 元素。

React 供给了三种运用 Ref 的办法:

1. String Refs

class App extends React.Component {
    constructor(props) {
        super(props)
    }
    componentDidMount() {
        setTimeout(() => {
             // 2. 经过 this.refs.xxx 获取 DOM 节点
             this.refs.textInput.value = 'new value'
        }, 2000)
    }
    render() {
        // 1. ref 直接传入一个字符
        return (
            <div>
              <input ref="textInput" value='value' />
            </div>
        )
    }
}
root.render(<App />);

2. 回调 Refs

class App extends React.Component {
    constructor(props) {
        super(props)
    }
    componentDidMount() {
        setTimeout(() => {
              // 2. 经过实例特点获取 DOM 节点
              this.textInput.value = 'new value'
        }, 2000)
    }
    render() {
        // 1. ref 传入一个回调函数
        // 该函数中承受 React 组件实例或 DOM 元素作为参数
        // 咱们一般会将其存储到详细的实例特点(this.textInput)
        return (
            <div>
              <input ref={(element) => {
                this.textInput = element;
              }} value='value' />
            </div>
        )
    }
}
root.render(<App />);

3. createRef

class App extends React.Component {
    constructor(props) {
        super(props)
        // 1. 运用 createRef 创立 Refs
        // 并将 Refs 分配给实例特点 textInputRef,以便在整个组件中引证
        this.textInputRef = React.createRef();
    }
    componentDidMount() {
        setTimeout(() => {
            // 3. 经过 Refs 的 current 特点进行引证
            this.textInputRef.current.value = 'new value'
        }, 2000)
    }
    render() {
        // 2. 经过 ref 特点附加到 React 元素
        return (
            <div>
              <input ref={this.textInputRef} value='value' />
            </div>
        )
    }
}

这是最被引荐运用的办法。

两种运用目的

Refs 除了用于获取详细的 DOM 节点外,也能够获取 Class 组件的实例,当获取到实例后,能够调用其中的办法,从而强制履行,比如动画之类的效果。

咱们举一个获取组件实例的比如:

class Input extends React.Component {
    constructor(props) {
        super(props)
        this.textInputRef = React.createRef();
    }
    handleFocus() {
        this.textInputRef.current.focus();
    }
    render() {
        return <input ref={this.textInputRef} value='value' />
    }
}
class App extends React.Component {
    constructor(props) {
        super(props)
        this.inputRef = React.createRef();
    }
    componentDidMount() {
        setTimeout(() => {
                this.inputRef.current.handleFocus()
        }, 2000)
    }
    render() {
        return (
            <div>
              <Input ref={this.inputRef} value='value' />
            </div>
        )
    }
}

在这个比如中,咱们经过 this.inputRef.current 获取到 Input 组件的实例,并调用了实例的 handleFocus 办法,在这个办法中,又经过 Refs 获取到详细的 DOM 元素,履行了 focus 原生办法。

forwardRef

留意在这个比如中,咱们的 Input 组件运用的是类组件,Input 组件能够改为运用函数组件吗?

答案是不能够,咱们不能在函数组件上运用 ref 特点,由于函数组件没有实例。

假如咱们强行运用,React 会报错并提示咱们用 forwardRef:

function Input() {
  return <input value='value' />
}
class App extends React.Component {
    constructor(props) {
        super(props)
        this.inputRef = React.createRef();
    }
    render() {
        return (
            <div>
              <Input ref={this.inputRef} value='value' />
            </div>
        )
    }
}

React 之 Refs 的运用和 forwardRef 的源码解读

可是呢,对于“获取组件实例,调用实例办法”这个需求,即便运用 forwardRef 也做不到,借助 forwardRef 后,咱们也便是跟类组件一样,能够在组件上运用 ref 特点,然后将 ref 绑定到详细的 DOM 元素或许 class 组件上,也便是咱们常说的 Refs 转发。

Refs 转发

有的时分,咱们开发一个组件,这个组件需要对组件运用者供给一个 ref 特点,用于让组件运用者获取详细的 DOM 元素,咱们就需要进行 Refs 转发,咱们一般的做法是:

// 类组件
class Child extends React.Component {
    render() {
        const {inputRef, ...rest} = this.props;
        // 3. 这儿将 props 中的 inputRef 赋给 DOM 元素的 ref
        return <input ref={inputRef} {...rest} placeholder="value" />
    }
}
// 函数组件
function Child(props) {
      const {inputRef, ...rest} = props;
      // 3. 这儿将 props 中的 inputRef 赋给 DOM 元素的 ref
      return <input ref={inputRef} {...rest} placeholder="value" />
}
class Parent extends React.Component {
    constructor(props) {
        super(props)
        // 1. 创立 refs
        this.inputRef = React.createRef();
    }
    componentDidMount() {
        setTimeout(() => {
            // 4. 运用 this.inputRef.current 获取子组件中烘托的 DOM 节点
            this.inputRef.current.value = 'new value'
        }, 2000)
    }
    render() {
        // 2. 由于 ref 特点不能经过 this.props 获取,所以这儿换了一个特点名
        return <Child inputRef={this.inputRef} />
    }
}

React 供给了 forwardRef 这个 API,咱们直接看运用示例:

// 3. 子组件经过 forwardRef 获取 ref,并经过 ref 特点绑定 React 元素
const Child = forwardRef((props, ref) => (
  <input ref={ref} placeholder="value" />
));
class Parent extends React.Component {
    constructor(props) {
        super(props)
        // 1. 创立 refs
        this.inputRef = React.createRef();
    }
    componentDidMount() {
        setTimeout(() => {
            // 4. 运用 this.inputRef.current 获取子组件中烘托的 DOM 节点
            this.inputRef.current.value = 'new value'
        }, 2000)
    }
    render() {
        // 2. 传给子组件的 ref 特点
        return <Child ref={this.inputRef} />
    }
}

尤其是在咱们编写高阶组件的时分,往往要完成 refs 转发。咱们知道,一个高阶组件,会承受一个组件,回来一个包裹后的新组件,从而完成某种功用的增强。

但也正是如此,咱们添加 ref,获取的会是包裹后的新组件的实例,而非被包裹的组件实例,这就可能会导致一些问题。

createRef 源码

现在咱们看下 createRef 的源码,源码的方位在 /packages/react/src/ReactCreateRef.js,代码其实很简单,就只是回来了一个具有 current 特点的目标:

// 简化后
export function createRef() {
  const refObject = {
    current: null,
  };
  return refObject;
}

在烘托的进程中,refObject.current 会被赋予详细的值。

forwardRef 源码

那 forwardRef 源码呢?源码的方位在 /packages/react/src/ReactForwardRef.js,代码也很简单:

// 简化后
const REACT_FORWARD_REF_TYPE = Symbol.for('react.forward_ref');
export function forwardRef(render) {
  const elementType = {
    $$typeof: REACT_FORWARD_REF_TYPE,
    render,
  };
  return elementType;
}

可是要留意这儿的 $$typeof,尽管这儿是 REACT_FORWARD_REF_TYPE,但终究创立的 React 元素的 $$typeof 依然为 REACT_ELEMENT_TYPE

关于 createElement 的源码剖析参阅 《React 之 createElement 源码解读》,咱们这儿简单剖析一下,以 InputComponent 为例:

// 运用 forwardRef
const InputComponent = forwardRef(({value}, ref) => (
  <input ref={ref} className="FancyButton" value={value} />
));
// 依据 forwardRef 的源码,终究回来的目标格局为:
const InputComponent = {
    $$typeof: REACT_FORWARD_REF_TYPE,
    render,
}
// 运用组件
const result = <InputComponent />
// Bable 将其转译为:
const result = React.createElement(InputComponent, null);
// 终究回来的目标为:
const result = {
  $$typeof: REACT_ELEMENT_TYPE,
  type: {
    $$typeof: REACT_FORWARD_REF_TYPE,
    render,
  }
}

咱们尝试着打印一下终究回来的目标,确实也是这样的结构:

React 之 Refs 的运用和 forwardRef 的源码解读

React 系列

  1. React 之 createElement 源码解读
  2. React 之元素与组件的区别

React 系列的预热系列,带我们从源码的视点深入理解 React 的各个 API 和履行进程,全目录不知道多少篇,估计写个 50 篇吧。