import { DependencyList, EffectCallback, FocusEvent, Ref, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react' import { usePrefix } from '../ConfigProvider' import { fillRef } from '.' // 使用 shared.less 里面的动画样式 export function useAnimationCls() { const spinCls = usePrefix('spin') return { spinCls } } /** 管理 focus 状态 */ export function useFocused( extraHandlers?: { onFocus?: (e: FocusEvent) => void onBlur?: (e: FocusEvent) => void }, disabled = false, initialValue = false ) { const { onFocus, onBlur } = extraHandlers || {} const [focused, setFocused] = useState(initialValue) // React issue: https://github.com/facebook/react/issues/9142 // 浏览器在元素变为 disabled 时触发的 blur event(可能)会被 React 忽略,也就不会执行 `handleBlur` 更新组件的 `focused` 状态 // - 各浏览器行为不统一,存在浏览器本身不触发 blur event / native blur event 不被 React 忽略的情况 // FIXME: 临时解决组件 focus 状态问题,React 修复后可以去掉 useEffect(() => { // FIXME: 原则上应该在 `disabled && focused` 时调用 `handleBlur` // 但由于此时拿不到 event 也不方便构造,extraHandlers.onBlur 执行可能会出错,先继续忽略吧.. if (disabled) { setFocused(false) } }, [disabled]) const handleFocus = useCallback((e: FocusEvent) => { setFocused(true) onFocus?.(e) }, [onFocus]) const handleBlur = useCallback((e: FocusEvent) => { setFocused(false) onBlur?.(e) }, [onBlur]) const focusedHandlers = useMemo( () => ({ onFocus: handleFocus, onBlur: handleBlur }), [handleFocus, handleBlur] ) return { focused, focusedHandlers } } /** 将多个 ref 合并成一个 */ export function useRefs(...refs: Array | undefined>): Ref { const mergedRef = useCallback((value: T | null) => { refs.forEach(ref => { if (ref != null) { fillRef(ref, value) } }) }, refs) // eslint-disable-line react-hooks/exhaustive-deps return mergedRef } /** 支持受控或不受控输入的 value 逻辑 */ export function useValue({ valueFromProps, defaultValue, onChange }: { valueFromProps?: V defaultValue: V onChange?: (v: V, ...restV: R[]) => void }) { const initialValue = valueFromProps !== undefined ? valueFromProps : defaultValue const [innerValue, setInnerValue] = useState(initialValue) const value = valueFromProps !== undefined ? valueFromProps : innerValue const setValue = useCallback((v: V, ...restV: R[]) => { setInnerValue(v) onChange?.(v, ...restV) }, [onChange]) return { value, setValue } } // 第一次加载不触发,之后的每次 rerender 都触发 export function useUpdate(effect: EffectCallback, deps?: DependencyList) { const didMountRef = useRef(false) useEffect(() => { if (didMountRef.current === false) { didMountRef.current = true return } return effect() // eslint-disable-next-line react-hooks/exhaustive-deps }, deps) } /** 获取当前组件实例是否存活(是否 mounted)的信息 */ export function useIsMounted() { const isMountedRef = useRef(false) useEffect(() => { isMountedRef.current = true return () => { isMountedRef.current = false } }, []) return isMountedRef } /** * 更安全的 useState,防止组件 unmount 后仍然被 setState 导致报错 * > 使用此方法只能 suppress 错误信息,潜在的内存泄漏问题仍无法避免,需要确定用法后使用 */ export function useSafeState(initial: S | (() => S)) { const isUnmountedRef = useRef(false) const [state, setState] = useState(initial) useEffect(() => () => { isUnmountedRef.current = true }, []) const setSafeState = useCallback((nextState: S) => { if (!isUnmountedRef.current) { setState(nextState) } }, []) return [state, setSafeState] as const } export const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect type Fn = (...args: A) => R // 参考1:https://reactjs.org/docs/hooks-faq.html#how-to-read-an-often-changing-value-from-usecallback // 参考2: https://github.com/reactjs/rfcs/blob/useevent/text/0000-useevent.md // 该 hook 可以用于避免开发者传入的未经缓存(useCallback)的方法触发页面重渲染 export function useUnstableEvent(fn: Fn) { const ref = useRef>(fn) useLayoutEffect(() => { ref.current = fn }, [fn]) return useCallback>((...arg) => { const handler = ref.current return handler(...arg) }, []) } export function useForceUpdate() { const [, updateState] = useState({}) return useCallback(() => updateState({}), []) }