理解hooks原理 --- useRef

April 13, 2022

前言

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 的另外一种用法。