_React「 Refs 转发 」初探

前端基础 2019-06-25 428 次浏览 次点赞

前言

Refs 转发:将父组件创建的 ref 传递给子组件的某个dom元素(或组件)。让父组件可以直接操作该dom元素(或组件)

一开始使用该技术的时候,分不清 传 自定义 ref prop 和 转发ref 有什么区别,本文稍微探讨下

Demo: 父组件触发子组件的input元素获取焦点

InputChild 为子组件,App 为父组件

const InputChild = (props) => (
  <input></input>
));
class App extends React.Component {
  constructor() {
    super()
    this.icRef = React.createRef();
  }
  render () {
    <InputChild ref={this.icRef}>Click me!</InputChild>;
  }
}

按上面的操作,icRef 拿到的是 InputChild 组件,拿不到 InputChild 下的 input 元素。

比如想在父组件上让 InputChild 的 input 获取焦点,是不行的。

this.icRef.current.focus() //报错

InputChild 创建 ref 绑定 input

在 InputChild 上,利用inputRef = React.createRef() 绑定 input,

然后父组件通过 ref.current.inputRef.current.focus() 获取焦点

class InputChild extends React.Component{
  constructor(){
    super()
    this.inputRef = React.createRef()
  }
  render(){
    return (
      <input ref={this.inputRef}></input>
    )
  } 
}
class App extends React.Component {
  constructor() {
    super()
    this.icRef = React.createRef();
  }
  render () {
    <InputChild ref={this.icRef}>Click me!</InputChild>;
  }
}
this.ref.current.inputRef.current.focus() // input 获取焦点

Refs 转发

函数组件使用

const InputChild = React.forwardRef((props, ref) => (
  <input ref={ref}>
  </input>
));
class App extends React.Component {
  constructor() {
    super()
    this.icRef = React.createRef();
  }
  handleClick = () => {
    this.icRef.current.focus()
  }
  render () {
     <>
      <button onClick={this.handleClick}>Learn React</button>
      <InputChild ref={this.icRef}>Click me!</InputChild>;
     </>
  }
}

点击 button 后,input 可以获取到焦点

Class 组件使用

function refProps(Component) {
  return React.forwardRef((props, ref) => {
    return <Component {...props} forwardedRef={ref} />;
  });
}

@refProps
class InputChild extends React.Component{
  render(){
    const { forwardedRef } = this.props;
    return (
      <input ref={forwardedRef}></input>
    )
  }
}

效果同上,但是这里是 将 ref 绑定到新的prop上

尚未知道还有没有更好的做法

自定义 prop 属性

其实也可以直接定义一个 非ref 的prop,如下

class InputChild extends React.Component{
  render(){
    const { forwardedRef } = this.props;
    return (
      <input ref={forwardedRef}></input>
    )
  }
}
// 父组件使用
<InputChild forwardRef={this.ref} />

注意:函数式组件不能提供ref,否则对ref的访问将会失败,只能用React.forwardRef()传递 ref

效果是一样,但是对于上层使用者(不知道子组件代码的)来说, 第一想法是用 ref 绑定子组件而不是其他额外prop (forwardRef)

高阶组件上使用 refs 转发

需求:我们有一个 LogProps 高阶组件用于记录 props log,但是对 上层用户是不可见的

用户使用 <Child ref={this.ref}/> 。 ref 拿到的应该是 Child 而不是 高阶组件

错误使用

function logProps(WrappedComponent) {
  class LogProps extends React.Component {
    componentDidUpdate(prevProps) {
      console.log('old props:', prevProps);
      console.log('new props:', this.props);
    }

    render() {
      return <WrappedComponent {...this.props} />;
    }
  }

  return LogProps;
}

@logProps
class Child extends React.Component{
  render(){
    <div/>
  }
}
<Child ref={this.ref}>Click me!</Child>;

this.ref 拿到的是 LogProps 组件,ref prop被消化,不会被传递到 Child

正确使用

function logProps(WrappedComponent) {
  class LogProps extends React.Component {
    componentDidUpdate(prevProps) {
      console.log('old props:', prevProps);
      console.log('new props:', this.props);
    }

    render() {
      const {forwardedRef, ...rest} = this.props;

      // Assign the custom prop "forwardedRef" as a ref
      return <WrappedComponent ref={forwardedRef} {...rest} />;
    }
  }

  return React.forwardRef((props, ref) => {
    return <LogProps {...props} forwardedRef={ref} />;
  });
}
@logProps
class Child extends React.Component{
  render(){
    <div/>
  }
}
<Child ref={this.ref}>Click me!</Child>;

this.ref 拿到的是 Child 组件

Child 组件使用高阶组件,并且父组件的 ref 要绑定 Child下的 input元素

在以上的基础上,再转发一次

const InputChild = React.forwardRef((props, ref) => (
  <input ref={ref}>
  </input>
));

// 使用高阶组件对其进行封装
export default logProps(InputChild);
// 父组件中
<Child ref={this.ref}>Click me!</Child>;

父组件 this.ref.current.focus() 即可让 input 获取焦点

参考

  1. 转发 Refs
  2. React 中的转发ref

React.forwardRef 源码解析

...


本文由 GaHingZ 创作,采用 署名-非商业性使用-相同方式共享 3.0,可自由转载、引用,但需署名作者且注明文章出处。

赏个馒头吧