React中的hook之useRef

143 阅读4分钟

1. useRef介绍

react开发中,我们通常使用useRef做什么操作?大部分开发者应该都会使用useRef来操作DOM,其实useRef还有一个比较常用的途径,官方给其的定义是:- 用 ref 引用一个值 

简单来说,使用useRef来引用一个值,类似于useState,但不同的是,useRef的改变不会触发组件更新

2.何时使用useRef

1.最常用的是操作页面DOM,当我们需要获取页面属性时,可以使用js的方法, document.getElementById('title'),但会使得页面变得更加复杂,更好的方法是绑定useRef,通过useRef拿到DOM属性,例如当前组件的长宽,控制视频播放或暂停,页面整体的缩放等等..

2.当我们在js或jsx中创建一个值时,定义一个变量使用 let ,定义一个常量使用 const,通常在于用户的交互中,useState记录用户的操作并做出反应,可以给用户更好的体验

但在部分程序中,我们希望可以有值保存数据,但是不必要展示给用户,而更改此数据,也不必要重新渲染,此时,useRef的作用就体现出来了,例如:

当我们需要记录用户点击按钮次数,控制台可以依次打印出1,2,3...

import { useRef } from "react";

const Test: React.FC = () => {
  const btn = useRef(0);
  function hangNext() {
    btn.current++;
    console.log(btn.current);
  }
  return <button onClick={hangNext}>下一页</button>;
};
export default Test;

此时,你一定会有疑问,使用let定义一个变量,同样的,当点击按钮时,btn同样可以更新递加,并打印出1,2,3...

const Test: React.FC = () => {
  let btn = 0;
  function hangNext() {
    btn++;
    console.log(btn);
  }
  return <button onClick={hangNext}>下一页</button>;
};
export default Test;

但是,当我们把页面稍微增加点功能,如: 每次点击按钮都会切换page,使组件重新渲染,此时,btn继续递加,但如果使用let btn=0,我们会发现,btn的值永远是1..,因为重新渲染组件后,let也会重新创建,此时useRef的优点将体现出来: 你可以在重新渲染之间 存储信息,

import { useRef, useState } from "react";

const Test: React.FC = () => {
  const [page, setPage] = useState(false);
  const btn = useRef(0);
  function hangNext() {
    setPage(!page);
    btn.current++;
    console.log(btn);
  }
  return <button onClick={hangNext}>{page ? "下一页" : "上一页"} </button>;
};
export default Test;

综上可以得出:

当我们定义常量时,使用const,例:const city=["上海","北京"]

当我们定义变量,更新不必要渲染页面时,使用useRef,例:const count=useRef(0)

当我们定义变量,更新时需要重新渲染页面,与用户形成交互时,使用useState,例:const [count,setCount]=useState(0)

3.使用方法

使用useRef时,可以把他作为一个变量,如同js中操作一样,可以根据需要定义为string,number,object ,当我们需要发送更改的数据时,可以直接使用value.current,

//创建
const count=useRef(0)
const countObj=useRef({})
const countArr=useRef([])

//使用
count.current=1
countObj.current.name='小明'
countArr.current=[1,2,3]

同样的,当我们使用useRef去操作DOM时,需要使用ref绑定到DOM上,然后拿到其属性

import {useEffect, useRef } from "react";

const Test: React.FC = () => {
  const divRef = useRef(null);
  console.log(1, divRef);  // 1 {current: null} ,在组件渲染阶段,DOM还未创
  useEffect(() => {
    console.log(2, divRef); // 2 {current: div} ,绑定成功,可以拿到DOM
  }, []);
  return <div ref={divRef}></div>;
};
export default Test;

4.进阶-useImperativeHandle

在React组件中,我们通常会在子组件中,调用父组件的变量和方法,但在某些情况下,我们需要在父组件中,使用子组件的变量和方法,此时应当如何操作?

在React的官方解释中,推荐使用Props解决问题,仅当无法使用Props解决问题时,可以考虑使用useRef 例:

const Test = () => {
  return (
    <>
      <Card />
      <button>点击按钮</button>
    </>
  );
};
export default Test;

const Card = () => {
  function handCard() {
    alert("点击了按钮");
  }
  return <button onClick={handCard}>按钮</button>;
};

当我们需求输入框填入数据后,点击父组件的button,希望调用子组件的handCard方法时,可以使用useRef,暴露出子组件的方法:

import { useRef, useImperativeHandle, forwardRef } from "react";
const Test = () => {
  const cardRef = useRef(null);
  return (
    <>
      <Card ref={cardRef} />
      <button  onClick={() => cardRef.current?.handCard()>点击按钮</button>
    </>
  );
};
export default Test;

const Card = forwardRef((props, ref) => {
  function handCard() {
    alert("点击了按钮");
  }
  useImperativeHandle(
    ref, () => { return { handCard }
    },[]);
  return <button onClick={handCard}>按钮</button>;
});

5.总结

useRef需要注意点有两个:

1.定义更新不需要渲染到页面的变量(区别常量)

2.操作DOM时,注意组件渲染周期(页面挂载完成后,才能使用)

纸上学来终觉浅,欲知此事要躬行!