一篇搞懂UseRef(详解)

ref属性和useRef的区别

首先要明确的一件事是:在类组件和原生组件中,可以直接通过ref属性拿到元素的真实DOM,而在函数类型组件中,如果直接给组件绑定ref属性是没有效果的。
例如下面的代码:

//子组件
function Child(props){
    return (
        <div>
            <button>child</button>
        </div>
    )
}

function MyCom(props){
    const childButton = useRef();
    const click = () => {
        console.log(childButton)
        //{current: undefined}拿不到真实的dom
    }

    return (
        <div>
            //绑定ref
            <Child ref={childButton} />
            <button onClick={click} >123</button>
        </div>

    )
}

这个问题会在后面说,先说一下在类组件中,ref的几种使用方式:

  1. 字符串方式
  2. 函数方式
  3. React.createRef方法

每种方式如何使用,这里也不细说了。主要了解的是在Hook里面,useRef启到了什么作用?

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内持续存在。

这句话是官网上用来描述useRef的。也就是说useRef并非单单用来处理获取真实dom,而是像state一样的用来管理数据的API。
只不过它比较常见的使用方式是用来管理真实的dom元素。

函数组件中useState,useRef,普通变量的区别

在函数组件中,我们可以通过const或者let直接定义变量,也可以通过useState定义变量,现在还可以通过useRef定义变量。而这几种定义变量方式的区别在于什么?\

function MyCom(props){
  let num = 0 //每次更新都初始化
  const [stateNum,setStateNum] = useState(0)
  const refNum = useRef(0);
  const click = () => {
    num++; //不会更新组件
    refNum.current ++; //不会更新组件
    setStateNum(stateNum + 1) //会更新组件
  }
  return (
    <div>
      <span>{num}</span>
      <br />
      <span>{refNum.current}</span>
      <br />
      <span>{stateNum}</span>
      <br />
      <button onClick={click} >123</button>
    </div>
  )
}

基于上面的代码,我们解释一下num,stateNum,refNum之间的不同之处:\

  1. num,每次组件更新都要进行初始化,num值的改变不会引起组件的更新
  2. stateNum,可以保存变量的状态,值改变的时候会引起组件的更新
  3. refNum,可以保存变量的状态,值改变的时候不会引起组件的更新

所以只有setState可以让组件更新,而更新后,refNum和stateNum可以保存上一次更新的记录,而num要重新进行初始化
refNum类似于在类组件中,下面的写法:

class MyCom extends React.Component {
  num = 0;

  click = () => {
    this.num++;
    this.setState({})
  }

  render(){
    return (<div onClick={this.click}>{this.num}</div>)
  }
}

React.forwardRef方法

回到第一个问题,在函数组件里面,并不能直接通过ref来获取真实的DOM元素。所以React提供了forwardRef方法,能将函数组件里的ref进行转发。

const Child = React.forwardRef((props,ref) => {
  return (
    <div>
      <button ref={ref}>child</button>
    </div>
  )
})

function MyCom(props){
  const childButton = useRef();
  const click = () => {
    console.log(childButton)
  }
  return (
    <div>
      <Child ref={childButton} />
      <button onClick={click} >123</button>
    </div>
  )
}

forward接受的回调函数接收两个参数,第一个参数是props,第二个就是ref。而两者都来自于父组件。通过forward,父组件可以拿到子组件任何位置元素的真实DOM。

只有useRef创建的变量才能传入ref里吗

上面的代码,通过useRef创建了变量childButton。最终通过ref将子组件button的DOM元素绑定给了childButton,不知道你是否尝试过另一种创建childButton的方式。

const childButton = {current: null};

其实这种方式是可以的。也就是说,想要拿到真实DOM元素,并非一定要是用useRef,也可以通过定义变量{current: null}的方式。
而两者的区别在于(官网给出的解释):

而 useRef() 和自建一个 {current: ...} 对象的唯一区别是,useRef 会在每次渲染时返回同一个 ref 对象。

useImperativeHandle

通常来讲,我们拿到真实DOM之后,可能需要调用它自身的某个方法。比如input输入框的focus方法。
而通过上面的方式,会拿到整个真实DOM,基于这种情况,React提供了一个hook,来自定义暴露给父组件的ref。

const Child = React.forwardRef((props,ref) => {
  const inputRef = useRef();
  //自定义focus和getType方法,并且将对象绑定给父组件传来的ref上
  useImperativeHandle(ref,() => (
     {
      focus: () => {
        inputRef.current.focus()
      },
      getType: () => {
        console.log('input')
      }
    }
  ))
  return (
    <div>
      <input ref={inputRef} />
    </div>
  )
})


function MyCom(props){
  const childRef = {current: null};
  const click = () => {
    childRef.current.focus()
    childRef.current.getType();
  }
  return (
    <div>
      <Child ref={childRef} />
      <button onClick={click} >123</button>
    </div>
  )
}

以上就是和useRef相关的内容,具体的细节可以通过查找对应的API去学习。官网上的文档还是很清晰的

你可能感兴趣的:(javascript,前端,react.js)