Wenzi

React 中 useState 和 useRef 与全局变量的区别

蚊子前端博客
发布于 2024/05/16 11:27
React中有多种数据存储的方式,他们有什么区别呢?

在 React 中,useState(), useRef()和全局变量都可以存储数据,并且在组件刷新后依然能保持原值。但这几种存储方式有什么区别呢?

首先我们要知道的是,函数组件是需要重新执行,才能把最新的数据更新到页面上。而组件内的局部变量,首先不会产生组件的刷新,即更新数据后不会体现页面上;同时,在组件重新执行后,局部变量的数据会被重置。

例如下面的组件,只让变量 count 自增,页面上依然展示 0;而更新了 timestamp 后,count 会被重置为 0:

const App = () => {
  const [timeStamp, setTimeStamp] = useState(0);
  let count = 0;

  return (
    <div>
      <p>count: {count}</p>
      <p>timeStamp: {timeStamp}</p>
      <div>
        <button
          onClick={() => {
            ++count;
            console.log(count);
          }}
        >
          add count
        </button>
        <button onClick={() => setTimeStamp(Date.now())}>timestamp</button>
      </div>
    </div>
  );
};

useState() 的使用 #

我们在平时存储、更新数据时,用useState()比较多。该 hook 有几个特点:

  1. 只能通过 dispatch() 方法来更新数据;要更新的数据要在组件重新执行后才能取到;
  2. 数据更新后,会引起组件的重新执行,并将最新的数据更新到页面上;
  3. 变量的声明周期是当前组件的生命周期,当前组件被销毁后,hook 也会被销毁;
  4. 组件多次被调用时,hook 都是独立调用的,只在当前次的组件内生效,并且不会互相影响;

假如我们有个组件,可能会多次被调用,然后我们给每次的调用一个 id,方便来操作 dom 元素等。可以这样实现:

let globalIndex = 0;
const App = () => {
  const [id] = useState(globalIndex++); // 只在组件初始化时,才产生id,然后再也不变

  return <div id={`c-${id}`}></div>;
};

此后 globalIndex 无论怎么变化,都不会引起当前组件中 id 的变化。

有的场景中,我们会把 props 中的数据放到 hook 中,若下面代码的这种用法,props 中的数据再变化时,是不会影响到count的。

const App = (props) => {
  const [count, setCount] = useState(props.count);

  return (
    <div>
      {count}
      <button onClick={() => setCount(count + 1)}>click me</button>
    </div>
  );
};

若我们想在 props 中数据变化时,也能更新 state,可以通过useEffect()来监听 props 的变动。

const App = (props) => {
  const [count, setCount] = useState(props.count);

  useEffect(() => {
    setCount(props.count);
  }, [props.count]);

  return (
    <div>
      {count}
      <button onClick={() => setCount(count + 1)}>click me</button>
    </div>
  );
};

使用 useState() 可以方便地在函数组件中管理状态,避免了使用类组件时需要编写繁琐的生命周期方法和构造函数。

useRef() 的使用 #

有些不太了解 useRef() 这个 hook 的同学,以为它只能存储 dom 元素。

实际上,useRef()能存储任何类型的数据。

function App() {
  const domRef = useRef(null); // 存储dom元素
  const startMovePointRef = useRef({ x: -1, y: -1 }); // 在移动场景中,存储开始移动时的坐标

  // 按下鼠标时,记录下坐标
  const handleMouseDown = (event) => {
    startMovePointRef.current = {
      x: event.clientX,
      y: event.clientY,
    };
  };

  return <div ref={domRef} onMouseDown={handleMouseDown}></div>;
}

function useInterval(callback, delay) {
  const callbackRef = useRef();

  useEffect(() => {
    callbackRef.current = callback;
  });
}

从上面的几个例子中可以看到,useRef()中可以用来存储任何类型的数据,比如 dom 元素,object 类型,回调函数等。甚至连 new Map() 也可以存储。

这个 hook 的主要特点有:

  1. 可以存储任何类型的数据;
  2. 存储的数据,在组件的整个生命周期内都有效,而且只在生命周期内有效,组件被销毁后,存储的数据也就被销毁了;
  3. 内容被修改时,不会引起组件的重新渲染;(重点)
  4. 内容被修改,是会立即生效的;
  5. 内容的读写操作,都是在 current 属性上操作的,没有额外的 get, set 等方法;

我们可以稍微对比下 useRef()useState() 之间的区别。更新 useRef() 中的数据不会引起组件的刷新,而且在更新数据后,可以马上获取到最细的数据。

组件外的变量 #

组件内不是用 hook 创建出来的变量我称之为局部变量,组件外的变量我称之为全局变量。

如在同一个文件中但定义在函数组件外的变量,挂在window上的变量,或者全局状态管理(如 useContext、Mobx、Redux 等)里的变量,都属于全局变量。

局部变量在函数组件每次执行后都会被重置,而组件外的全局变量,则是独立于函数组件之外的,并不会因函数组件的刷新而重置数据。

全局变量有几个特点:

  1. 在当前页面周期内有效,只要不刷新页面,就一直维持最新的数据;
  2. 组件被多次调用时,共享该全局变量;

比如上面我们给组件设置 id 的样例,globalIndex即是全局变量,脱离于组件之外,并能在多次调用的组件之间共享。

之前看到过一个同学写的代码,想用一个变量来标记下某方法是否是第一次调用,初始时是 false,该方法调用后置为 true。结果该同学将变量放到了全局,导致下次再次进入该组件时,出现问题。

按说每次调用该组件时,标记初始时都应该是 false,但该同学将用于标记的变量放到了全局,导致下次进来时,初始值已经是 true 了。

若只是想进行标记或者计数,我们可以利用useRef()的特点,将数据放到useRef()的 hook 里。

总结 #

我们在上面稍微了解了下这几种存储数据的区别,我们再用表格再对比下。

useRef() useState() 全局变量
存储的数据类型 全部 全部 全部
数据的生命周期 当前所在组件的生命周期 当前所在组件的生命周期 当前页面的生命周期
组件被多次引用时 每个数据都是独立的 每个数据都是独立的 共享该数据
是否引起组件重新渲染
是否立即生效 立即生效 下次渲染时生效 立即生效

各位同学可以依据自己的使用场景,选择更适合自己的数据存储方式。

标签:reacthook
阅读(578)
Simple Empty
No data