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

我的博客地址:React 中 useState 和 useRef 与全局变量的区别 - 蚊子的前端博客

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

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

欢迎关注我的号:前端小茶馆

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值