React 中 ref 的常用方式

3,436 阅读4分钟

img.jpeg

1.refs 是什么?

  • Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素;
  • 官方说明:在典型的 React 数据流中,props 是父组件与子组件交互的唯一方式。要修改一个子组件,你需要使用新的 props 来重新渲染它。但是,在某些情况下,你需要在典型数据流之外强制修改子组件。被修改的子组件可能是一个 React 组件的实例,也可能是一个 DOM 元素。对于这两种情况,React 都提供了解决办法。
  • 通俗来说,提供了一个父组件用来访问子组件中数据或者方法的桥梁

2.refs 实战

2.1 class组件中使用refs (父子组件都是class)

获取DOM节点

import { Button } from 'antd';
export default class RefClass extends Component {
  constructor(props){
    super(props)
    this.myRef = createRef()
    this.changeInput = this.changeInput.bind(this);
  }

  changeInput(){
    console.log('this.myRef',this.myRef.current)
    console.log('this.myRef',this.myRef.current.value)
  }
  render() {
    return (
      <div >
        RefClass
        <input ref={this.myRef} />
        <Button type="primary" onClick={this.changeInput}>
          Submit
        </Button>
      </div>
    )
  }
}

父组件访问子组件中的数据和方法

父组件

import Child from './Child'
import { Button } from 'antd';
export default class RefClass extends Component {
  constructor(props){
    super(props)
    this.myRef = createRef()
    this.changeInput = this.changeInput.bind(this);
    this.childRef = createRef()
  }

  changeInput(){
    console.log(this.childRef.current.state.name)
    // 调用子组件的方法
    this.childRef.current.updateState(this.myRef.current.value)
  }
  render() {
    return (
      <div >
        RefClass
        <input ref={this.myRef} />
        <Child ref={this.childRef} />
        <Button type="primary" onClick={this.changeInput}>
          Submit
        </Button>
      </div>
    )
  }
}

子组件

import React, { Component } from 'react'
export default class Child extends Component {
  constructor(props){
    super(props)
    this.state={
      name:'我是子组件'
    }
  }

  updateState(msg){
    this.setState({
      name:msg
    })
  }
  render() {
    return (
      <div>
        {this.state.name}
      </div>
    )
  }
}

2.2 hooks中年中的refs的使用(父子组件都是hooks)

获取DOM 元素

import { Button } from 'antd';

const RefHooks = () => {
  const childRef = useRef(null)
  const  changeInput = ()=>{
    console.log('changeInput',childRef.current.value)
  }

  return (
    <div>
      <div>RefHooks</div>
      <input ref={childRef} />
      <Button type="primary" onClick={changeInput}>
        Submit
      </Button>
    </div>
  )
}

export default RefHooks

父组件访问子组件的方法或数据

注:引入 useImperativeHandle 把使用 ref 父组件访问子组件的数据与方法暴露出来 且 useImperativeHandle 应当与 forwardRef 一起使用 父组件

import { Button } from 'antd';
import Child from './Child'

const RefHooks = () => {
  const inputRef = useRef(null)
  const childRef = useRef(null)

  const  changeInput = ()=>{
    console.log('changeInput',inputRef.current.value)
    childRef.current.changText('我是父组件修改的子组件')
  }

  return (
    <div>
      <div>RefHooks</div>
      <input ref={inputRef} />
      <Child ref={childRef}/>
      <Button type="primary" onClick={changeInput}>
        Submit
      </Button>
    </div>
  )
}

export default RefHooks

子组件

const Child = (props,ref) => {
  const [state, setstate] = useState('我是子组件')
  const changText = (e)=>{
    setstate(e)
  }  
  useImperativeHandle(ref, () => ({
    changText
  }));

  return (
    <div>
      {state}
    </div>
  )
}

export default forwardRef(Child)

注:

  • 子组件要使用forwardRef 进行包裹,useImperativeHandle 中的方法只暴露给父组件使用,组件自身无法调用,两者都想使用,如上写法;
  • 子组件接受ref时,也要接受props,否则会报错

2.3 refs 在高阶组件中的使用

calss高阶组件类型

思路:定义好ref 以别名的方式传入HOC组件中,在调用HOC中导出的组件 添加ref

父组件

import { Button } from 'antd'
import HocIndex from './HocIndex'
import User from './User'

const HocUser = HocIndex(User);
export default class RefHOC extends Component {
  constructor(props) {
    super(props)
    this.classHocRef = createRef(null)
    this.inputRef = createRef(null)
    this.handClick = this.handClick.bind(this)
  }

  handClick(){
    this.classHocRef.current.updatFn(this.inputRef.current.value)
  }

  render() {
    return (
      <div>
        <HocUser name={'name'} refText={this.classHocRef} />
        <input ref={this.inputRef} />
        <Button type="primary" onClick={this.handClick}>
          Submit
        </Button>
      </div>
    )
  }
}

高阶组件

function HocIndex(WrappedComponent) {
  return class WrappingComponent extends React.Component {
    render() {
      const props ={...this.props,ref:this.props.refText}
      return (
          <WrappedComponent {...props} />
      )
    }
  }
}

export default HocIndex;

子组件

export default class User extends Component {
  constructor(props){
    super(props)
    this.state={
      name:'我是Hoc组件'
    }
  }

  updatFn(e){
    this.setState({
      name:e
    })
  }

  render() {
    return (
      <div>{this.state.name}</div>
    )
  }
}

hooks高阶组件类型

父组件

import React, { useRef } from 'react'
import HooksUser from './HooksUser';
import { Button } from 'antd'
const Index = () => {
  const hocRef = useRef(null);

  const changeInput=()=>{
    hocRef.current.changText('父组件修改子组件')
  }
  return (
    <div>
      我是hooks 组件
      <HooksUser ref={hocRef} name='user' />
      <div>
        <Button type="primary" onClick={changeInput}>
          Submit
        </Button>
      </div>
    </div>
  )
}

export default Index

高阶组件

import React,{forwardRef} from 'react'

const HookHoc = Component => {
  
  const HocIndex = ({hocRef,...props})=> <Component ref={hocRef} {...props} />
  
  return forwardRef((props,ref)=> <HocIndex {...props} hocRef={ref}/>)
}

export default HookHoc

子组件

import HookHoc from './HookHoc'

const HooksUser = (props,ref) => {
  const [state, setstate] = useState('我是hooks 子组件');

  const changText = (e)=>{
    setstate(e)
  }

  useImperativeHandle(ref, () => ({
    changText
  }));

  return (
    <div>
      {state}
    </div>
  )
}

export default HookHoc(forwardRef(HooksUser))

2.4 回调的方式设置refs

  • 能够更准确的控制何时refs 被设置和解除
  • React 会在组件挂载时,调用 ref 回调函数并传入 DOM元素(或React实例),当卸载时调用它并传入 null。在 componentDidMountcomponentDidUpdate 触发前,React 会保证 Refs 一定是最新的。
  • 可以在组件间传递回调形式的 refs.

class类型组件

import { Button } from 'antd'
export default class index extends Component {
  constructor(props) {
    super(props);
    this.textInput = null;
    
    this.setTextInputRef = element => {
      this.textInput = element;
    };
    this.focusTextInput = () => {
      if (this.textInput) this.textInput.focus();
      console.log('this.textInput',this.textInput.value)
    };
  }
  componentDidMount() {
    this.focusTextInput();
  }

  render() {
    return (
      <div>
        <input
          type="text"
          ref={this.setTextInputRef}
        />
        <Button type="primary" onClick={this.focusTextInput}>
          Submit
        </Button>
      </div>
    );
  }
}

hooks类型组件

import { Button } from 'antd';
import React, { useEffect } from 'react';

function MyInput(props) {
  return (
    <input type="text" ref={props.inputRef} />
  )
}

const HooksFnRef = () => {
  let ref = null;
  useEffect(() => {
    ref.focus();
    console.log('ref', ref)
  }, [ref]);
  const getInputVlaue = () => {
    console.log(ref.value)
  }
  return (
    <div>
      <MyInput inputRef={ele => ref = ele} />
      <Button type="primary" onClick={getInputVlaue}>
        Submit
      </Button>
    </div>
  )
}

export default HooksFnRef

3.refs中遇到的一个小坑

回调refs 存在 如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次第一次传入参数 null,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。

4.总结

  • ref内容发生改变时,组件不会进行render;可以通过调用子组件的方法来实现需要的子组件进行更新,减少其余组件的渲染;
  • 绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调 ref 来实现
  • 官方不建议滥用refs,尽量减少项目中refs的使用

写的若有问题欢迎各位道友直接指出。在此感激不进;