useSafeState-保障 React 状态的安全性
摘要:ahooks
是一个可靠的 React Hooks 库。本文将详细介绍 useSafeState
这个 Hook,帮助您理解其工作原理和应用场景。官方地址:ahooks useSafeState
简介
useSafeState
是一个用于管理可安全更新的状态的 Hook。相较于 useState
,useSafeState
提供了以下两个主要改进:
-
安全性 :当组件被卸载时,
useSafeState
会停止状态更新,避免因状态更新导致的错误。 -
可选的初始状态 :
useSafeState
允许提供一个初始状态,或者提供一个函数来生成初始状态。
相信大家也见到过如下图所示的警告:
以上问题意思就是说React组件在卸载的时候还在更新状态,这可能会导致内存泄漏的风险,而修复的方式就是在useEffect钩子函数中取消所有的监听以及异步任务,即停止更新状态。
useSafeState实现原理
useSafeState
就是为了处理掉这个问题的。如果没有这个hook,我们可以思考一下这个hook的实现原理。分为2步:
-
如何判断组件是否卸载。
-
如何阻止状态的更新。
针对这2个问题,我们就来探讨一下这个hook的实现原理,其实这2个问题也可以分别封装成2个hook。
如何判断组件是否卸载
首先我们来看解决第1个问题的hook,我们可以创建一个不需要渲染的状态,这里我们只是需要用这个状态来做判断,不需要渲染,因此就要用到useRef
,它是一个 React Hook,它能帮助引用一个不需要渲染的值。然后我们可以在useEffect钩子函数中,设置这个值,并返回一个cleanup函数,这个函数执行时机就是在组件卸载的时候,因此我们可以在这个cleanup函数中修改这个值。代码如下所示:
ini
import { useEffect, useRef } from 'react';
const useUnmountedRef = () => {
const unMountedRef = useRef(false);
useEffect(() => {
unMountedRef.current = false;
return () => {
// 组件卸载时,设置为true,代表组件已经卸载
unMountedRef.current = true;
}
},[]);
return unMountedRef;
}
export default useUnmountedRef;
如何停止更新状态
接下来是解决第二个问题,我们将使用useState
来定义状态,这个hook接收一个状态或者是一个返回状态的函数,然后我们使用useCallback
定义一个函数,在函数当中去改变这个状态,在该函数当中,我们将会使用useUnmountedRef
来判断当前组件是否已经卸载,如果为true,就代表不需要更改该状态,最后将状态和修改该状态的函数返回。
javascript
import { useState,useCallback } from 'react';
import useUnmountedRef from './useUnmountedRef.tsx';
const useSafeState = <S>(state:S | () => S) => {
// 获取组件是否卸载的状态
const unMountedRef = useUnmountedRef();
// 使用useState定义一个状态
const [state, setState] = useState(state);
// 定义更改状态的函数,接收一个更改后的状态参数,用来更改状态
const setNewState = useCallback((newState) => {
// 判断如果当前组件已卸载,则不执行更新状态
if(unMountedRef.current){
return;
}
setState(newState);
},[]);
// 返回状态和修改状态的函数
return [state,setNewState] as const;
}
export default useSafeState;
注:
as const
是 TypeScript 中的一个用于修饰符,它可以被用来修改类型推断的行为。
在ahooks中,还对useSafeState
的类型做了增强,如下所示:
javascript
function useSafeState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];
function useSafeState<S = undefined>(): [S | undefined, Dispatch<SetStateAction<S | undefined>>];
如何避免内存泄漏问题?
useSafeState
的设计和实现方式考虑了避免内存泄漏的问题:
-
使用
useUnmountedRef
来检测组件是否卸载,以停止状态更新,避免因状态更新导致的错误。 -
使用
useCallback
缓存函数,并自动清除缓存,以避免内存泄漏。 -
设计独立于其他状态的状态更新方式,确保状态仅依赖于初始状态或生成函数,有效避免内存泄漏问题。
综上所述,useSafeState
通过多种方式避免内存泄漏,包括使用 useUnmountedRef
检测组件状态,使用 useCallback
缓存函数并自动清除,以及设计独立于其他状态的状态更新方式。这些设计理念和实现方式,使得 useSafeState
能更好地管理状态,并避免了内存泄漏问题,使得React
应用更加安全健壮。
应用场景
useSafeState
主要适用于以下场景:
-
安全状态管理:确保在组件被卸载时停止状态更新,避免错误。
-
独立状态更新 :用于更新与其他状态无关的变化,如通过函数生成状态或使用与
useState
类似的 API。
下面是使用useSafeState
的示例:
基础用法示例
javascript
import React, { useSafeState } from 'react';
function MyComponent() {
const [state, setState] = useSafeState({ count: 0 });
const incrementCount = () => {
// 使用 set 方法来修改状态
setState((state) => ({ count: state.count + 1 }));
};
return (
<div>
<h1>{state.count}</h1>
<button onClick={incrementCount}>+</button>
</div>
);
}
在这个示例中,我们使用useSafeState
来创建一个状态变量count
,并提供了一个incrementCount
方法来修改状态。当点击"+"按钮时,incrementCount
方法会被调用,它使用setState
方法来修改状态变量count
的值。
自定义初始化状态示例
javascript
import React from 'react';
import { useSafeState } from './useSafeState.tsx';
const CustomComponent = () => {
// 获取缓存中的数据,如无则初始化一个字符串
const [message, setMessage] = useSafeState(() => {
const initialMessage = localStorage.getItem('message');
return initialMessage || 'Hello, World!';
});
// 更新数据,并缓存
const handleSaveMessage = newMessage => {
setMessage(newMessage);
localStorage.setItem('message', newMessage);
};
return (
<div>
<p>{message}</p>
<button onClick={() => handleSaveMessage('New Message')}>保存消息</button>
</div>
);
}
export default CustomComponent;
在这个示例中,我们使用useSafeState
来创建一个状态变量message
,并提供了一个handleSaveMessage
方法来修改状态。当点击"保存消息"按钮时,handleSaveMessage
方法会被调用,它使用setMessage
方法来修改状态变量message
的值,并且我们还将数据使用localStorage
缓存了下来,修改的时候同样也会修改缓存的值。
总结
使用useSafeState
可以帮助我们保障 React 状态的安全性,避免状态的不一致和数据的丢失。它提供了一种简单而安全的方式来管理状态,让我们可以更加自信地编写 React 组件。