React hook中useRef钩子函数详解

132 阅读2分钟

为什么用ref

在如下代码中,log会打印什么?

function App() {
  const [count, setCount] = useState(0)
  let num = 0
  const handelClick = () => {
    setCount(count + 1)
    num++
    console.log(num)
  }
  return (
    <>
      <button onClick={handelClick}>点击</button>
      {count}
    </>
  )
}

每次点击按钮都会重新触发函数的执行,由于num并没有记忆功能,每次执行函数都会从新建作用域中找num的值,则初始值都为0,故log打印‘1’. 通过引入react的内置hook中useRef钩子函数让num记忆。而与useState不同的是ref返回的是对象,用num.current的语法拿到当前值. 更改后的代码

  let num = useRef(0)
  const handelClick = () => {
    setCount(count + 1)
    num.current++
    console.log(num.current)
  }

因为useRef根本就没有更新,没有让函数重新走一遍。所以num的值可以直接在useRef()中更改。并且不能 {num.current} 直接渲染到页面中。

useRef和useState不同

image.png

使用场景

如想点击按钮后加入定时器,当用户点击多次时会触发多个定时器虽然可以用 clearInterval()把多余的定时器清掉,一旦调用当前组件就无法清理。所以用ref保证不管在哪种作用域中都能记忆值。、

  const time = useRef(null)
  const handelClick = () => {
    clearInterval(time.current)
    time.current = setInterval(() => {
      console.log(334)
    }, 1000)
  }

一旦重复点击按钮,会先清理上一次的值,开启新的定时器也从初始值开始记忆。

操作DOM的用法:

为防止每次执行组件时重新执行DOM,用ref防止重新操作DOM 用对象的方式ref={myref}将DOM与ref关联起来,myref.current拿到DOM

function App() {
  const myref = useRef(null)
  const handelClick = () => {
    console.log(myref.current.innerHTML)
    myref.current.style.backgroundColor = 'blue'
  }
  return (
    <>
      <button ref={myref} onClick={handelClick}>
        点击
      </button>
    </>
  )
}
export default App

用遍历操作DOM时不能在map()方法内部声明ref const myref = useRef(null)再与DOM关联ref={myref} ,因为函数是从上往下执行,这样做会报错。所以应该采用回调函数的写法 ref={(myref) => { myref.style.backgroundColor = 'yellow' }}

function App() {
  const myref = useRef(0)
  const list = [
    { id: 1, text: 'qqq' },
    { id: 2, text: 'bbb' },
    { id: 3, text: 'www' },
  ]
  return (
    <>
      {list.map((item) => {
        return (
          <li
            key={item.id}
            ref={(myref) => {
              myref.style.backgroundColor = 'yellow'
            }}
          >
            {item.text}
          </li>
        )
      })}
    </>
  )
}
export default App

forwardRef转发组件

组件不同于DOM元素,不能直接用ref={ref}绑定获取,需要用forwardRef转发。

const Myinput = forwardRef(function Myinput(props, ref) {
  return (
    <>
      <input type="text" ref={ref} />
    </>
  )
})
function App() {
  const myref = useRef(null)
  const handelClick = () => {
    myref.current.focus()
    myref.current.style.background = 'yellow'
  }
  return (
    <>
      <button onClick={handelClick}>点击</button>
      <Myinput ref={myref} />
      输入
    </>
  )
}
export default App

useImperativeHandle定义ref暴露

用forwardRef后ref被暴露出来可以任由改动,useImperativeHandle钩子函数用于定义ref想要暴露出来允许改动的方法,样式等。这个钩子接收两个参数:一个 ref,以及一个返回对象,该对象包含了可以被父组件通过 ref 调用的方法。

const Myinput = forwardRef(function Myinput(props, ref) {
  const inputref = useRef(null)
  useImperativeHandle(ref, () => {
    return {
      focus() {
        return inputref.current.focus
      },
      interoptions() {
        inputref.current.style.backgroundColor = 'yellow'
      },
    }
  })
  return (
    <>
      <input type="text" ref={inputref} />
    </>
  )
})
function App() {
  const myref = useRef(null)
  const handelClick = () => {
    myref.current.focus()
    myref.current.interoptions()
  }
  return (
    <>
      <button onClick={handelClick}>点击</button>
      <Myinput ref={myref} />
      输入
    </>
  )
}
export default App