导航


HTML

CSS

JavaScript

浏览器 & 网络

版本管理

框架

构建工具

TypeScript

性能优化

软实力

算法

UI、组件库

Node

业务技能

针对性攻坚

AI


Refs

通过 React.createRef() 创建 Ref

24Ref/App_1input.jsx

class MyInput extends React.Component {
  constructor(props) {
    super(props);
    this.inputRef = React.createRef();
  }
  state = {
    inputValue: ''
  }
  inputFocus() {
    const oInput = this.inputRef.current;
    oInput.focus()
    this.setState({
      inputValue: ''
    })
  }
  changeInputValue(e) {
    this.setState({
      inputValue: e.target.value
    })
  }
  render() {
    return (
      <div>
        <input type="text"
          ref={ this.inputRef }
          value={ this.state.inputValue }
          onChange={ this.changeInputValue.bind(this) }
        />
        <button onClick={ this.inputFocus.bind(this) }>清空并聚焦</button>
        <br />
        <p>{ this.state.inputValue }</p>
      </div>
    )
  }
}
class App extends React.Component {
  render() {
    return (
      <MyInput />
    )
  }
}
ReactDOM.render(<App />, document.getElementById('app'));

将组件自身传递给外界(回调形式)

通过 props.onRef

App_5modal_dialog.jsx 01

class Modal extends React.Component {
  constructor(props) {
    super(props);
    this.modalRef = React.createRef();
    // 把自身传递给外界
    if (props.onRef) {
      props.onRef(this)
    }
  }
  open() {
    this.modalRef.current.style.display = 'block';
  }
  close() {
    this.modalRef.current.style.display = 'none';
  }
  render() {
    return (
      <div
        ref={ this.modalRef }
        style={{
          width: '300px',
          height: '300px',
          border: '1px solid #000',
          display: 'none'
        }}

      >
        <h1>modal dialog</h1>
        <p>This is a modal dialog</p>
      </div>
    )
  }
}
class App extends React.Component {
  modalOpen(status) {
    switch (status) {
      case 'open':
        this.modal.open();
        break;
      case 'close':
        this.modal.close();
        break;
      default:
        break;
    }
  }
  render() {
    return (
      <div>
				{ /* render 执行到下面代码时,把 instance 组件自身赋值给了 App 组件实例的 modal 属性 } */ }
        <Modal onRef={ instance => this.modal = instance } />
        <div>
          <button onClick={ this.modalOpen.bind(this, 'open') }>Open</button>
          <button onClick={ this.modalOpen.bind(this, 'close') }>Close</button>
        </div>
      </div>
    )
  }
}
ReactDOM.render(<App />, document.getElementById('app'));

或者

class Demo extends Component {
	inputRef = null
	render() {
		return (
			<input ref={ c => this.inputRef = c } />
		)
	}
}

当然除此以外,我们还可以利用 状态提升 而不是 refs 来将 modal 打开与否放进 App 中:

App_5modal_dialog.jsx 02

class Modal extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
      <div
        style={{
          width: '300px',
          height: '300px',
          border: '1px solid #000',
          display: this.props.isOpen ? 'block' : 'none'
        }}

      >
        <h1>modal dialog</h1>
        <p>This is a modal dialog</p>
      </div>
    )
  }
}
class App extends React.Component {
  state = {
    isOpen: false
  }
  modalOpen(status) {
    this.setState({
      isOpen: status === 'open'
    })
  }
  render() {
    return (
      <div>
        <Modal isOpen={ this.state.isOpen } />
        <div>
          <button onClick={ this.modalOpen.bind(this, 'open') }>Open</button>
          <button onClick={ this.modalOpen.bind(this, 'close') }>Close</button>
        </div>
      </div>
    )
  }
}
ReactDOM.render(<App />, document.getElementById('app'));

React.createRef

App_6ref.jsx 01

class Test extends React.Component {
  constructor(props) {
    super(props);
    this.divRef = React.createRef();
    console.log('Test constructor', this.divRef); // log日志未把对象展开时为null,展开后未 Ref 对象(因为此刻 log 的瞬间,ref 是没值的,componentDidMount 时才有值)
  }
  componentDidMount() {
    console.log('Test componentDidMount', this.divRef); // 更新,有值
  }
  componentDidUpdate() {
    console.log('Test componentDidUpdate', this.divRef); // 更新,有值
  }
  render() {
    return (
      // 1. 绑定到元素上,ref 的 current 是元素
      <div ref={ this.divRef }>
        { this.props.children }
      </div>
    )
  }
}

Ref 不同的使用方式

  1. ref 绑定在 html 元素上时,current 是真实 DOM 节点
  2. ref 绑定在 class 组件上时, current 是组件实例
  3. ref 绑定在 函数组件 上时,函数组件是没有实例的,是直接执行的,返回的是一个 React 元素,所以附加不到组件上,是没有值的(但可以用 hooks 方案解决)

App_6ref.jsx 02

function Test2() {
  // 3. 函数组件中,通过 hooks 使用 ref
  const divRef = React.useRef(null);
  React.useEffect(() => {
    console.log('函数组件', divRef);
  }, []);
  return (
    <div ref={ divRef }>Hello, Function ref</div>
  )
}
class App extends React.Component {
  state = {
    text: 'This is a Test component'
  }
  constructor(props) {
    super(props);
    this.testRef = React.createRef();
  }
  componentDidMount() {
    console.log('App componentDidMount', this.testRef);
    setTimeout(() => {
      this.setState({
        text: 'change text'
      })
    }, 1000);
  }
  render() {
    return (
      <div>
        {/* 2. 绑定到类组件上,ref 的 current 是组件实例 */}
        <Test ref={ this.testRef }>This is a Test component</Test>
        <Test2 />
      </div>
    )
  }
}
ReactDOM.render(
  <App />,
  document.getElementById('app')

Refs 转发机制(不推荐使用,但是是救命稻草)

App_7ref转发机制.jsx

使用条件:React 16.3 及其以上版本

当 ref 赋值给一个 React 组件时,ref 的 current 是组件实例,此刻拿不到组件内部的 DOM 元素,如果需要拿到,就可以利用 Refs 的转发机制

React 16.3 及其以上版本 可以使用 Refs 转发,将 ref 自动的通过组件传递给子组件。

// class MyInput extends React.Component {
//   render() {
//     return (
//       <input type="text" placeholder="请填写..." />
//     )
//   }
// }

// 3. 通过 forwardRef 向 input 转发 ref 属性
//      React.forwardRef((props, ref) => { return React元素 })

const MyInput = React.forwardRef((props, ref) => {
  // 5. ref 只能用 forwardRef 定义的组件接收
  return <input type="text" placeholder={ props.placeholder } ref={ ref } />
});
class App extends React.Component {
  constructor(props) {
    super(props)
    // 1. 创建 ref 对象
    this.myInputRef = React.createRef();
  }
  componentDidMount() {
    // 4. myInputRef.current 指向了 input DOM
    console.log(this.myInputRef);
  }
  inputOperate() {
    const oInput = this.myInputRef.current;
    oInput.value = ''
    oInput.focus()
  }
  render() {
    return (
      <div>
        {/* 2. 给组件赋值 ref */}
        <MyInput ref={ this.myInputRef } placeholder="请填写..." />
        <button onClick={ this.inputOperate.bind(this) }>Click</button>
      </div>
    )
  }
}
ReactDOM.render(<App />, document.getElementById('app'));

在高阶组件中的应用

App_8ref在高阶组件中的应用.jsx

class MyInput extends React.Component {
  render() {
    return (
      <input type="text" placeholder={ this.props.placeholder } />
    )
  }
}
function InputHOC(WrapperComponent) {
  class Input extends React.Component {
    render() {
      // 容器组件内部获取 ref 属性
      const { forwardRef, ...props } = this.props

      // 将 forwardRef 传递给参数组件
      return <WrapperComponent ref={ forwardRef } { ...props } />
    }
  }
  // 向子组件传递 ref
  function forwardRef(props, ref) {
    return <Input { ...props } forwardRef={ ref } />
  }
  forwardRef.displayName = 'Input - ' + WrapperComponent.name
  return React.forwardRef(forwardRef)
}

const MyInputHOC = InputHOC(MyInput);

class App extends React.Component {
  constructor(props) {
    super(props)
    this.inputRef = React.createRef()
  }
  componentDidMount() {
    console.log(this.inputRef)
  }
  render() {
    return (
      // 用 ref 接收我们的转发的 ref
      <MyInputHOC ref={ this.inputRef } placeholder="请填写..." />
    )
  }
}
ReactDOM.render(<App />, document.getElementById('app'))

React16.2 及其以下 ref 传递方式

  1. props 的方式
  2. 回调1: 本组件想要用本组件内部 DOM 元素的回调写法
  3. 回调2: 父组件想用子组件 DOM 元素的回调写法

App_9ref旧版本转发.jsx

// React16.2 及其以下 ref 传递方式

// 1. props
// class MyInput extends React.Component {
//   render() {
//     return (
//       <input type="text" ref={ this.props.inputRef } />
//     )
//   }
// }
// class App extends React.Component {
//   constructor(props) {
//     super(props)
//     this.inputRef = React.createRef()
//   }
//   componentDidMount() {
//     console.log(this.inputRef); // current 是 input DOM
//   }
//   render() {
//     return (
//       <MyInput inputRef={ this.inputRef } />
//     )
//   }
// }
// ReactDOM.render(<App />, document.getElementById('app'))

// 2. 回调1: 本组件想用的回调写法
// class MyInput extends React.Component {
//   constructor(props) {
//     super(props);
//     this.MyInput = null;
//   }
//   setMyInput(el) { // 回调中的 el 就是 input 节点
//     this.MyInput = el;
//   }
//   focusInput() {
//     this.MyInput.value = ''
//     this.MyInput.focus()
//   }
//   render() {
//     return (
//       <>
//         <input type="text" ref={ this.setMyInput.bind(this) } />
//         <button onClick={ this.focusInput.bind(this) }>Click</button>
//       </>
//     )
//   }
// }
// class App extends React.Component {
//   render() {
//     return (
//       <MyInput />
//     )
//   }
// }
// ReactDOM.render(<App />, document.getElementById('app'))

// 3. 回调2: 父组件想用的回调写法
class MyInput extends React.Component {
  render() {
    return (
      <>
        <input type="text" ref={ this.props.inputRef } />
      </>
    )
  }
}
class App extends React.Component {
  constructor(props) {
    super(props)
    this.oInput = null
  }
  componentDidMount() {
    console.log(this.oInput)
  }
  render() {
    return (
      <MyInput inputRef={ el => this.oInput = el } />
    )
  }
}
ReactDOM.render(<App />, document.getElementById('app'))

废弃的获取 ref 方案

Untitled