React新特性-Hooks之useRef(七)

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

系列目录

useRef的功能

说起ref你可能会想到class 中的获取真实DOM节点的ref。没错,useRef有两种功能。

  • 获取子组件或者DOM节点的句柄
  • 渲染周期之间共享数据的存储(类似class组件中实例属性)

获取子组件或者DOM节点的句柄

import React, { useState, useEffect, useRef } from 'react'

const TestInput = (props) => {
  const [inputValue, setInputValue] = useState('')
  const inputEl = useRef(null);
  useEffect(() => {
    // `current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus();
  })

  return (
    <div>
      <input ref={inputEl} value={inputValue} onChange={(e) => setInputValue(e.target.value)} /> {inputValue}
      <p>{props.visible}</p>
    </div>
  )
}
export default TestInput

创建个变量inputEl,页面渲染时将input的真实DOM存储到inputEl.current中,页面渲染完后执行副作用useEffect,利用真实DOMinput输入框获取焦点。

渲染周期之间共享数据的存储

useRef()Hook 不仅可以用于DOM refs,ref对象是一个 current 属性可变且可以容纳任意值的通用容器,类似于一个 class 的实例属性。

你可以在 useEffect 内部对其进行写入:

function Timer() {
  const intervalRef = useRef();

  useEffect(() => {
    const id = setInterval(() => {
      // ...
    });
    intervalRef.current = id;
    return () => {
      clearInterval(intervalRef.current);
    };
  });

  // ...
}

把定时器的ID存入到useRef中,这样这个定时器ID不仅仅在useEffect可以拿到,而且可以在整个组件函数中都可以获取到。

利用useRef获取上一轮的 props 或 state

function Counter() {
  const [count, setCount] = useState(0);

  const prevCountRef = useRef();
  useEffect(() => {
    prevCountRef.current = count;
  });
  const prevCount = prevCountRef.current;

  return (
    <>
      <h1>Now: {count}, before: {prevCount}</h1>
      <button onClick={()=>setCount((count)=>count+1)}>增加</button>
    </>
  );
}

利用页面先渲染先拿到count值为0,然后此时prevCount刚刚创建还是个undefined,当页面渲染完成进入副作用useEffect中,进行赋值操作prevCountRef.current = count。这时候count的值就保存到current中,但是页面不会重新渲染。

点击按钮增加时页面重新渲染,count为2,然后prevCount还存储这上次的1值,只有等进入useEffect中重新赋值为才能改变。

上面的代码有一点错综复杂,我们可以把它抽取成一个自定义 Hook

function Counter() {
  const [count, setCount] = useState(0);
  const prevCount = usePrevious(count);
  return (
    <>
      <h1>Now: {count}, before: {prevCount}</h1>
      <button onClick={()=>setCount((count)=>count+1)}>增加</button>
    </>
  );
}

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

这里我们创建了个自定义的Hook叫做usePrevious,来获取上一次的state。

避免重新创建 useRef() 的初始值

function Image(props) {
  // ⚠️ IntersectionObserver 在每次渲染都会被创建
  const ref = useRef(new IntersectionObserver(onIntersect));
  // ...
}

useRef不会像useState那样接受一个特殊的函数重载。相反,你可以编写你自己的函数来创建并将其设为惰性的

function Image(props) {
  const ref = useRef(null);

  // ✅ IntersectionObserver 只会被惰性创建一次
  function getObserver() {
    if (ref.current === null) {
      ref.current = new IntersectionObserver(onIntersect);
    }
    return ref.current;
  }

  // 当你需要时,调用 getObserver()
  // ...
}

这避免了在一个对象被首次真正需要之前就创建它。


文章作者: jackie chen
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 jackie chen !
评论
 上一篇
React新特性-Hooks之useMemo和useCallback(八) React新特性-Hooks之useMemo和useCallback(八)
把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。 系列目录 React新特性-Context(一) React新特性-L
2019-07-24
下一篇 
React新特性-Hooks之useContext(六) React新特性-Hooks之useContext(六)
useContext 跟class组件Context是一样的效果,Context能够允许数据跨域组件层级直接传递到任何的子组件身上。 详细请参考: React新特性-Context(一) 系列目录 React新特性-Context(一)
2019-07-22
  目录