管理 object 类型 state 的 Hooks,用法与 class 组件的 this.setState 基本一致。
先来了解一下可变数据和不可变数据的含义和区别如下:
我们知道,React Function Components 中的 State 是不可变数据。所以我们经常需要写类似如下的代码:
setObj((prev) => ({
...prev,
name: 'Gopal',
others: {
...prev.others,
age: '27',
}
}));
通过 useSetState,可以省去对象扩展运算符操作这个步骤,即:
setObj((prev) => ({
name: 'Gopal',
others: {
age: '27',
}
}));
其内部实现也比较简单,如下所示:
const useSetState =>(
initialState: S | (() => S),
): [S, SetState] => {
const [state, setState] = useState(initialState);
// 合并操作,并返回一个全新的值
const setMergeState = useCallback((patch) => {
setState((prevState) => {
// 新状态
const newState = isFunction(patch) ? patch(prevState) : patch;
// 也可以通过类似 Object.assign 的方式合并
// 对象拓展运算符,返回新的对象,保证原有数据不可变
return newState ? { ...prevState, ...newState } : prevState;
});
}, []);
return [state, setMergeState];
};
可以看到,其实就是将对象拓展运算符的操作封装到内部。
还有其他更优雅的方式?我们可以使用 use-immer[1]
useImmer(initialState) 非常类似于 useState。该函数返回一个元组,元组的第一个值是当前状态,第二个是 updater 函数,它接受一个 immer producer 函数或一个值作为参数。
使用如下:
const [person, updatePerson] = useImmer({
name: "Michel",
age: 33
});
function updateName(name) {
updatePerson(draft => {
draft.name = name;
});
}
function becomeOlder() {
updatePerson(draft => {
draft.age++;
});
}
当向更新函数传递一个函数的时候,draft 参数可以自由地改变,直到 producer 函数结束,所做的改变将是不可变的,并成为下一个状态。这更符合我们的使用习惯,可以通过 draft.xx.yy 的方式更新我们对象的值。
这两个都是特殊情况下的值管理。
useBoolean,优雅的管理 boolean 状态的 Hook。
useToggle,用于在两个状态值间切换的 Hook。
实际上,useBoolean 又是 useToggle 的一个特殊使用场景。
先看 useToggle。
// TS 函数重载的使用
function useToggle(): [boolean, Actions ];
function useToggle(defaultValue: T): [T, Actions ];
function useToggle(defaultValue: T, reverseValue: U): [T | U, Actions ];
function useToggle(
// 默认值
defaultValue: D = false as unknown as D,
// 取反
reverseValue?: R,
) {
const [state, setState] = useState(defaultValue);
const actions = useMemo(() => {
const reverseValueOrigin = (reverseValue === undefined ? !defaultValue : reverseValue) as D | R;
// 切换 state
const toggle = () => setState((s) => (s === defaultValue ? reverseValueOrigin : defaultValue));
// 修改 state
const set = (value: D | R) => setState(value);
// 设置为 defaultValue
const setLeft = () => setState(defaultValue);
// 如果传入了 reverseValue, 则设置为 reverseValue。 否则设置为 defautValue 的反值
const setRight = () => setState(reverseValueOrigin);
return {
toggle,
set,
setLeft,
setRight,
};
// useToggle ignore value change
// }, [defaultValue, reverseValue]);
}, []);
return [state, actions];
}
而 useBoolean 是对 useToggle 的一个使用。如下,比较简单,不细说
export default function useBoolean(defaultValue = false): [boolean, Actions] {
const [state, { toggle, set }] = useToggle(defaultValue);
const actions: Actions = useMemo(() => {
const setTrue = () => set(true);
const setFalse = () => set(false);
return {
toggle,
set: (v) => set(!!v),
setTrue,
setFalse,
};
}, []);
return [state, actions];
}
保存上一次状态的 Hook。
其原理,是每次状态变更的时候,比较值有没有发生变化,变更状态:
const defaultShouldUpdate =(a?: T, b?: T) => !Object.is(a, b);
function usePrevious(
state: T,
shouldUpdate: ShouldUpdateFunc= defaultShouldUpdate,
): T | undefined {
// 使用了 useRef 的特性,一直保持引用不变
// 保存上一次值
const prevRef = useRef();
// 当前值
const curRef = useRef();
// 自定义是否更新上一次的值
if (shouldUpdate(curRef.current, state)) {
prevRef.current = curRef.current;
curRef.current = state;
}
return prevRef.current;
}
只在 requestAnimationFrame callback 时更新 state,一般用于性能优化。
window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。
假如你的操作是比较频繁的,就可以通过这个 hook 进行性能优化。
function useRafState(initialState?: S | (() => S)) {
const ref = useRef(0);
const [state, setState] = useState(initialState);
const setRafState = useCallback((value: S | ((prevState: S) => S)) => {
cancelAnimationFrame(ref.current);
ref.current = requestAnimationFrame(() => {
setState(value);
});
}, []);
// unMount 的时候,去除监听
useUnmount(() => {
cancelAnimationFrame(ref.current);
});
return [state, setRafState] as const;
}
用法与 React.useState 完全一样,但是在组件卸载后异步回调内的 setState 不再执行,避免因组件卸载后更新状态而导致的内存泄漏。
代码如下:
在更新的时候,通过 useUnmountedRef 判断如果组件卸载,则停止更新。
function useSafeState(initialState?: S | (() => S)) {
// 判断是否卸载
const unmountedRef = useUnmountedRef();
const [state, setState] = useState(initialState);
const setCurrentState = useCallback((currentState) => {
// 如果组件卸载,则停止更新
if (unmountedRef.current) return;
setState(currentState);
}, []);
return [state, setCurrentState] as const;
}
useUnmountedRef 这个我们之前提过,简单回顾下,其实就是在 hook 的返回值中标记组件为已卸载。
const useUnmountedRef = () => {
const unmountedRef = useRef(false);
useEffect(() => {
unmountedRef.current = false;
// 如果已经卸载,则会执行 return 中的逻辑
return () => {
unmountedRef.current = true;
};
}, []);
return unmountedRef;
};
给 React.useState 增加了一个 getter 方法,以获取当前最新值。
其实现如下:
其实就是通过 useRef 记录最新的 state 的值,并暴露一个 getState 方法获取到最新的。
function useGetState(initialState?: S) {
const [state, setState] = useState(initialState);
// useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内持续存在。
// 使用 useRef 处理 state
const stateRef = useRef(state);
stateRef.current = state;
const getState = useCallback(() => stateRef.current, []);
return [state, setState, getState];
}
这在某一些情况下,可以避免 React 的闭包陷阱。如官网例子:
const [count, setCount, getCount] = useGetState(0);
useEffect(() => {
const interval = setInterval(() => {
console.log('interval count', getCount());
}, 3000);
return () => {
clearInterval(interval);
};
}, []);
假如这里不使用 getCount(),而是直接使用 count,是获取不到最新的值的。
React 的 function Component 的状态管理还是比较灵活,我们可以针对一些场景进行封装和优化,从而更优雅的管理我们的 state 状态,希望 ahooks 这些封装能对你有所帮助。
[1]use-immer: https://github.com/immerjs/use-immer
本文题目:这些Hook更优雅地管理你的状态
转载源于:http://www.shufengxianlan.com/qtweb/news19/348119.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联