在 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 有几个特点:
- 只能通过
dispatch()
方法来更新数据;要更新的数据要在组件重新执行后才能取到; - 数据更新后,会引起组件的重新执行,并将最新的数据更新到页面上;
- 变量的声明周期是当前组件的生命周期,当前组件被销毁后,hook 也会被销毁;
- 组件多次被调用时,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 的主要特点有:
- 可以存储任何类型的数据;
- 存储的数据,在组件的整个生命周期内都有效,而且只在生命周期内有效,组件被销毁后,存储的数据也就被销毁了;
- 内容被修改时,不会引起组件的重新渲染;(重点)
- 内容被修改,是会立即生效的;
- 内容的读写操作,都是在 current 属性上操作的,没有额外的 get, set 等方法;
我们可以稍微对比下 useRef()
和 useState()
之间的区别。更新 useRef()
中的数据不会引起组件的刷新,而且在更新数据后,可以马上获取到最细的数据。
组件外的变量
组件内不是用 hook 创建出来的变量我称之为局部变量,组件外的变量我称之为全局变量。
如在同一个文件中但定义在函数组件外的变量,挂在window
上的变量,或者全局状态管理(如 useContext、Mobx、Redux 等)里的变量,都属于全局变量。
局部变量在函数组件每次执行后都会被重置,而组件外的全局变量,则是独立于函数组件之外的,并不会因函数组件的刷新而重置数据。
全局变量有几个特点:
- 在当前页面周期内有效,只要不刷新页面,就一直维持最新的数据;
- 组件被多次调用时,共享该全局变量;
比如上面我们给组件设置 id 的样例,globalIndex
即是全局变量,脱离于组件之外,并能在多次调用的组件之间共享。
之前看到过一个同学写的代码,想用一个变量来标记下某方法是否是第一次调用,初始时是 false,该方法调用后置为 true。结果该同学将变量放到了全局,导致下次再次进入该组件时,出现问题。
按说每次调用该组件时,标记初始时都应该是 false,但该同学将用于标记的变量放到了全局,导致下次进来时,初始值已经是 true 了。
若只是想进行标记或者计数,我们可以利用useRef()
的特点,将数据放到useRef()
的 hook 里。
总结
我们在上面稍微了解了下这几种存储数据的区别,我们再用表格再对比下。
差异 | useRef() | useState() | 全局变量 |
---|---|---|---|
存储的数据类型 | 全部 | 全部 | 全部 |
数据的生命周期 | 当前所在组件的生命周期 | 当前所在组件的生命周期 | 当前页面的生命周期 |
组件被多次引用时 | 每个数据都是独立的 | 每个数据都是独立的 | 共享该数据 |
是否引起组件重新渲染 | 否 | 是 | 否 |
是否立即生效 | 立即生效 | 下次渲染时生效 | 立即生效 |
各位同学可以依据自己的使用场景,选择更适合自己的数据存储方式。