辅助hook
useImmediateEffect
与useEffect类似 在依赖数组变化后 立刻同步地执行副作用
ts
import { useRef } from 'react'
/** 副作用函数 接受旧的依赖项 */
export type ImmediateEffect<Deps extends any[]> = (oldDeps?: Deps) => ImmediateEffectCleanup
/** 清理函数 */
export type ImmediateEffectCleanup = (() => any) | undefined | void
export function useImmediateEffect<Deps extends any[]>(effect: ImmediateEffect<Deps>, deps: Deps) {
const prevDepsRef = useRef<Deps>()
const prevCleanupRef = useRef<ImmediateEffectCleanup>()
if (isDepsChanged(prevDepsRef.current, deps)) {
prevCleanupRef.current?.()
prevCleanupRef.current = effect(prevDepsRef.current)
}
prevDepsRef.current = deps
}
function isDepsChanged<Deps extends any[]>(prevDeps: Deps | undefined, deps: Deps) {
if (!prevDeps || prevDeps.length !== deps.length) return true
return prevDeps.some((prevValue, i) => !Object.is(prevValue, deps[i]))
}
useSemiControlledValue
取得一个值的半受控版本.外界传入的value变化时,此值也会立刻突变;在传入value不变时,此值可以自行变更.
ts
import { useRef, useState } from 'react'
import { useMemoizedFn } from 'ahooks'
import { useImmediateEffect } from '../useImmediateEffect'
export function useSemiControlledValue<V = any>(valueController: {
value?: V
onChange?: (val: V) => void
}) {
const { value, onChange } = valueController
const [changedValue, setChangedValue] = useState(value)
const currentValueRef = useRef(value)
useImmediateEffect(() => {
currentValueRef.current = changedValue
}, [changedValue])
useImmediateEffect(() => {
currentValueRef.current = value
}, [value])
const currentValue = currentValueRef.current
// 对外暴露的更新函数 参数是一个新值或者更新函数
const onInnerChange: (arg: V | ((val?: V) => V)) => void = useMemoizedFn((arg: any) => {
const newValue = typeof arg === 'function' ? arg(currentValue) : arg
setChangedValue(newValue)
onChange?.(newValue)
})
return [currentValue, onInnerChange] as const
}
useComposition
处理输入法合成事件.在本地维护一个输入法无关的value,在合成事件结束后,向外更新合成后的值
ts
import { CompositionEventHandler, ChangeEventHandler, useState } from 'react'
import { useMemoizedFn } from 'ahooks'
import { useSemiControlledValue } from '../useSemiControlledValue'
export function useComposition(valueController: {
value?: string
onChange: (val: string) => void
}) {
const [isComposing, setComposing] = useState(false)
const [value, onInnerChange] = useSemiControlledValue({
value: valueController.value,
})
// onChange时更新本地value
const onChange = useMemoizedFn<ChangeEventHandler<HTMLInputElement>>((e) => {
onInnerChange(e.target.value)
})
const onCompositionStart = useMemoizedFn(() => {
setComposing(true)
})
// 输入法合成后更新外部value
const onCompositionEnd = useMemoizedFn<CompositionEventHandler<HTMLInputElement>>((e) => {
setComposing(false)
valueController.onChange?.((e.target as HTMLInputElement).value)
})
const compositionProps: CompositionProps = {
value,
onChange,
onCompositionStart,
onCompositionEnd,
}
return {
isComposing,
compositionProps,
}
}
使用例
ts
const [search,setSearch] = useState<string>()
const { compositionProps } = useComposition({ value: search, onChange: setSearch })
<input {...compositionProps} />