这个系列是将 ahooks 里面的所有 hook 源码都进行解读,通过解读 ahooks 的源码来熟悉自定义 hook 的写法,提高自己写自定义 hook 的能力,希望能够对大家有所帮助。
为了和代码原始注释区分,个人理解部分使用 ///
开头,此处和 三斜线指令没有关系,只是为了做区分。
往期回顾
- ahooks 源码解读系列
- ahooks 源码解读系列 - 2
- ahooks 源码解读系列 - 3
- ahooks 源码解读系列 - 4
- ahooks 源码解读系列 - 5
- ahooks 源码解读系列 - 6
- ahooks 源码解读系列 - 7
- ahooks 源码解读系列 - 8
每日一篇,提神醒脑~ 今天进入 State 部分 hooks
State
useUrlState
“将内部状态写在脸上”
同步指定状态到页面路径的搜索参数中
/// 使用 query-string 来解析路径
import { parse, stringify } from 'query-string';
import { useMemo, useRef, useState } from 'react';
/// 要求使用 `react-router` 5.0 以上版本
import { useHistory, useLocation } from 'react-router';
export interface Options {
navigateMode?: 'push' | 'replace'; /// 状态变更时使用 push 模式还是 replace 修改路径
}
const parseConfig = {
skipNull: true,
skipEmptyString: true,
parseNumbers: false,
parseBooleans: false,
};
interface UrlState {
[key: string]: any;
}
export default <S extends UrlState = UrlState>(initialState?: S | (() => S), options?: Options) => {
type state = Partial<{ [key in keyof S]: any }>;
const { navigateMode = 'push' } = options || {};
const location = useLocation();
const history = useHistory();
const [, update] = useState(false);
const initialStateRef = useRef(
/// 支持传入对象或者一个得到对象的方法来作为初始值
typeof initialState === 'function' ? (initialState as () => S)() : initialState || {},
);
/// 解析当前路径的 search 部分
const queryFromUrl = useMemo(() => {
return parse(location.search, parseConfig);
}, [location.search]);
/// 根据当前路径和初始值得到当前的初始搜索条件,也就是我们需要的当前状态
const targetQuery: state = useMemo(
() => ({
...initialStateRef.current,
...queryFromUrl,
}),
[queryFromUrl],
);
const setState = (s: React.SetStateAction<state>) => {
/// 同样支持传入对象或者方法
const newQuery = typeof s === 'function' ? (s as Function)(targetQuery) : s;
/// 和 useUpdate hook 功能一致
// 1. 如果 setState 后,search 没变化,就需要 update 来触发一次更新。比如 demo1 直接点击 clear,就需要 update 来触发更新。
// 2. update 和 history 的更新会合并,不会造成多次更新
update((v) => !v);
/// 变更页面路径。由于状态是由当前路径计算得到的,所以相当于变更了状态值
history[navigateMode]({
hash: location.hash,
search: stringify({ ...queryFromUrl, ...newQuery }, parseConfig) || '?',
});
};
return [targetQuery, setState] as const;
};
useSetState
“别整这些没用的,我就会一把梭”
import { useCallback, useState } from 'react';
import { isFunction } from '../utils';
const useSetState = <T extends object>(
initialState: T = {} as T,
): [T, (patch: Partial<T> | ((prevState: T) => Partial<T>)) => void] => {
const [state, setState] = useState<T>(initialState);
/// 使用 merge 而不是替换设置新值,和 class 组件的 `this.setState` 基本一致
const setMergeState = useCallback((patch) => {
setState((prevState) => ({ ...prevState, ...(isFunction(patch) ? patch(prevState) : patch) }));
}, []);
return [state, setMergeState];
};
export default useSetState;
useToggle
“万能开关”
import { useState, useMemo } from 'react';
type IState = string | number | boolean | undefined;
export interface Actions<T = IState> {
setLeft: () => void;
setRight: () => void;
toggle: (value?: T) => void;
}
function useToggle<T = boolean | undefined>(): [boolean, Actions<T>];
function useToggle<T = IState>(defaultValue: T): [T, Actions<T>];
function useToggle<T = IState, U = IState>(
defaultValue: T,
reverseValue: U,
): [T | U, Actions<T | U>];
function useToggle<D extends IState = IState, R extends IState = IState>(
defaultValue: D = false as D, /// 默认值为 false
reverseValue?: R,
) {
const [state, setState] = useState<D | R>(defaultValue);
const actions = useMemo(() => {
/// 如果没有设置取反值则直接对正值取反
const reverseValueOrigin = (reverseValue === undefined ? !defaultValue : reverseValue) as D | R;
// 切换返回值
const toggle = (value?: D | R) => {
// 强制返回状态值,适用于点击操作
if (value !== undefined) {
setState(value);
return;
}
/// 使用 === 来和正值比较,如果相同则设置状态为反值,否则设置为正值
/// 所以整个逻辑都是没有对反值做过比较的,也就是说反值随便是什么乱七八糟的值都无关紧要,只会对正值进行 === 判断
setState((s) => (s === defaultValue ? reverseValueOrigin : defaultValue));
};
// 设置默认值
const setLeft = () => setState(defaultValue);
// 设置取反值
const setRight = () => setState(reverseValueOrigin);
return {
toggle,
setLeft,
setRight,
};
}, [defaultValue, reverseValue]);
return [state, actions];
}
export default useToggle;
useBoolean
一个并没有什么用的管理布尔值的 hook
import { useMemo } from 'react';
import useToggle from '../useToggle';
export interface Actions {
setTrue: () => void;
setFalse: () => void;
toggle: (value?: boolean | undefined) => void;
}
export default function useBoolean(defaultValue = false): [boolean, Actions] {
const [state, { toggle }] = useToggle(defaultValue);
const actions: Actions = useMemo(() => {
/// 使用 useToggle 实现布尔值切换,但是 useToggle 默认就是切换布尔值,直接用 useToggle 不香嘛?
const setTrue = () => toggle(true);
const setFalse = () => toggle(false);
return { toggle, setTrue, setFalse };
}, [toggle]);
return [state, actions];
}
useMap & useSet
“给我盯紧那小子”
可以被监听到变化版本的 Map 和 Set
import { useMemo, useState, useCallback } from 'react';
function useMap<K, T>(initialValue?: Iterable<readonly [K, T]>) {
const initialMap = useMemo<Map<K, T>>(
() => (initialValue === undefined ? new Map() : new Map(initialValue)) as Map<K, T>,
[],
);
const [map, setMap] = useState(initialMap);
const stableActions = useMemo(
() => ({
set: (key: K, entry: T) => {
setMap((prev) => {
const temp = new Map(prev);
temp.set(key, entry);
return temp;
});
},
setAll: (newMap: Iterable<readonly [K, T]>) => {
setMap(new Map(newMap));
},
remove: (key: K) => {
setMap((prev) => {
const temp = new Map(prev);
temp.delete(key);
return temp;
});
},
reset: () => setMap(initialMap),
}),
[setMap, initialMap],
);
const utils = {
get: useCallback((key) => map.get(key), [map]),
...stableActions,
};
return [map, utils] as const;
}
export default useMap;
/* eslint-disable max-len */
import { useState, useMemo, useCallback } from 'react';
function useSet<K>(initialValue?: Iterable<K>) {
const initialSet = useMemo<Set<K>>(
() => (initialValue === undefined ? new Set() : new Set(initialValue)) as Set<K>,
[],
);
const [set, setSet] = useState(initialSet);
const stableActions = useMemo(
() => ({
add: (key: K) => {
setSet((prevSet) => {
const temp = new Set(prevSet);
temp.add(key);
return temp;
});
},
remove: (key: K) => {
setSet((prevSet) => {
const temp = new Set(prevSet);
temp.delete(key);
return temp;
});
},
reset: () => setSet(initialSet),
}),
[setSet, initialSet],
);
const utils = {
has: useCallback((key: K) => set.has(key), [set]),
...stableActions,
};
return [set, utils] as const;
}
export default useSet;
以上内容由于本人水平问题难免有误,欢迎大家进行讨论反馈。