理解hooks原理 --- useRef

2022年4月13日

前言

react hooks 是 React 16.8 的新增特性。 它可以让我们在函数组件中使用 state 、生命周期以及其他

react 特性,而不仅限于 class 组件。react hooks 的出现,标示着

react 中不会在存在无状态组件了,只有类组件和函数组件。具体可查看官网。

优势

函数组件不能使用 state,遇到交互更改状态等复杂逻辑时不能更好地支持,hooks 让函数组件更靠近 class 组件,拥抱函数式编程。

解决副作⽤问题,hooks 出现可以处理数据获取、订阅、定时执行任务、手动修改

ReactDOM 这些⾏为副作用,进行副作用逻辑。比如 useEffect。 更好写出有状态的逻辑重用组件。

让复杂逻辑简单化,比如状态管理:useReducer、useContext。

函数式组件比 class 组件简洁,开发的体验更好,效率更⾼,性能更好。 更容易发现无用的状态和函数。

useRef 介绍

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

本质上,useRef 就像是可以在其 .current 属性中保存一个可变值的“盒子”。

然而,useRef() 比 ref 属性更有用。它可以很方便地保存任何可变值,其类似于在 class 中使用实例字段的方式。

这是因为它创建的是一个普通 Javascript 对象。而 useRef() 和自建一个 {current: …}
对象的唯一区别是,useRef 会在每次渲染时返回同一个 ref 对象。

请记住,当 ref 对象内容发生变化时,useRef 并不会通知你。变更 .current 属性不会引发组件重新渲染。如果想要在 React
绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调 ref 来实现。
*/
import React, { useRef } from "react";
const refContainer = useRef(initialValue);
javascript

useRef 有什么作用呢?

作用于 Dom 元素 获取子组件的实例(只有类组件可用) 在函数组件中的一个全局变量,不会因为重复 render 重复申明, 类似于类组件的

this.xxx useRef 使用 作用于 Dom 元素

const UseRefComp=()=>{
		//创建ref
		const inputRef=useRef()
		const getValue= () => {
		//访问ref
			console.log(inputRef.current.value) }
		//挂载
		return (
		<div>
		<input ref={inputRef} type="text"> <button onClick={getValue}>获取input的
		值</button> </div>
) }
javascript

获取子组件的实例(只有类组件可用)

// 使用 ref 子组件必须是类组件
class Children extends PureComponent {
  render() {
    const { count } = this.props;
    return <div>{count}</div>;
  }
}

function App() {
  const [count, setCount] = useState(0);
  const childrenRef = useRef(null);
  // const
  const onClick = useMemo(() => {
    return () => {
      console.log("button click");
      console.log(childrenRef.current);
      setCount((count) => count + 1);
    };
  }, []);
  return (
    <div>
      点击次数: {count}
      <Children ref={childrenRef} count={count}></Children>
      <button onClick={onClick}>点我</button>
    </div>
  );
}
javascript

在函数组件中的一个全局变量,不会因为重复 render 重复申明, 类似于类组件的 this.xxx

简单例子:

function Test() {
  const t = useRef(null);

  const handleClick = () => {
    t.current = setTimeout(() => {
      console.log("timer");
    }, 1000);
  };
  const handleClear = () => clearTimeout(t.current);

  return (
    <>
      <button onClick={handleClick}>start</button>
      <button onClick={handleClear}>clear</button>
    </>
  );
}
javascript

有些情况下,我们需要保证函数组件每次 render 之后,某些变量不会被重复申明,比如说 Dom 节点,定时器的 id 等等,在类组件中, 我们完全可以通过给类添加一个自定义属性来保留,比如说 this.xxx, 但是函数组件没有 this,自然无法通过这种方法使用,有的朋友说,我可以用 useState 来保留变量的值, 但是 useState 会触发组件 render,在这里完全是不需要的,我们就需要使用 useRef 来实现了,具体看下面例子

function App() {
  const [count, setCount] = useState(0);
  const timer = useRef(null);
  let timer2;

  useEffect(() => {
    let id = setInterval(() => {
      setCount((count) => count + 1);
    }, 500);

    timer.current = id;
    timer2 = id;
    return () => {
      clearInterval(timer.current);
    };
  }, []);

  const onClickRef = useCallback(() => {
    clearInterval(timer.current);
  }, []);

  const onClick = useCallback(() => {
    clearInterval(timer2);
  }, []);

  return (
    <div>
      点击次数: {count}
      <button onClick={onClick}>普通</button>
      <button onClick={onClickRef}>useRef</button>
    </div>
  );
}
javascript

当我们们使用普通的按钮去暂停定时器时发现定时器无法清除,因为 App 组件每次 render,都会重新申明一次 timer2, 定时器的 id 在第二次 render 时,就丢失了,所以无法清除定时器,针对这种情况, 就需要使用到 useRef,来为我们保留定时器 id,类似于 this.xxx,这就是 useRef 的另外一种用法。