ComposeHooks 使用指南
ComposeHooks 是一个为 Jetpack Compose/Compose Multiplatform 设计的 Hooks 库,灵感来自 React Hooks 和 ahooks。
核心概念
公开的 useXxx Hook 均提供对应的 rememberXxx 别名,选择你喜欢的命名风格。少数历史 API(如 Table.useTableInstance)仅为兼容保留,优先使用顶层 useXxx/rememberXxx。
平台支持
库以 Kotlin Multiplatform 形式发布(artifact id hooks2),覆盖四个目标:
| 平台 |
target |
说明 |
| Android |
androidTarget |
含平台专属 hooks(生物识别、网络、电池、传感器等) |
| Desktop |
jvm |
桌面专属 useKeyPress |
| iOS |
iosArm64 / iosSimulatorArm64 / iosX64 |
状态/副作用/网络等通用 hooks |
| Web |
wasmJs (browser) |
自 2.4.0 起可用,用于在浏览器中运行 Compose 组件 |
wasmJs 注意事项:
commonMain 中的 hooks 全部可用;平台强相关 hooks(Android 专属、usePersistent 的存储后端等)需在各 target 的 actual 实现中处理或降级。
useRequest/useForm/useRedux 使用的 KClass 仅作 Map 键,wasmJs 下可用;真正的反射调用(.call/.callSuspend/.createType)已隔离在 commonJvmAndroid,不影响 wasmJs 编译。
usePersistent 采用注入式设计(SaveToPersistent<T> 函数类型),wasmJs 下可注入基于 localStorage 的实现。
快速参考
状态管理 Hooks
| Hook |
用途 |
示例 |
useState |
基础状态管理(推荐用 by 委托);派生状态重载 useState(keys) { } 封装 derivedStateOf |
var state by useState("") / val full by useState(first, last) { "$first $last" } |
useStateAsync |
异步初始化状态 |
val state = useStateAsync { fetchDefault() } |
useGetState |
解构使用的状态管理(推荐) |
val (state, setState, getState) = useGetState(0) |
useControllable |
受控/非受控组件 |
val (state, setValue) = useControllable(default) |
useResetState |
带重置功能的状态 |
val (state, setState, reset) = useResetState("init") |
useBoolean |
布尔状态管理 |
val (state, toggle, set, setTrue, setFalse) = useBoolean(false) |
useToggle |
两值切换 |
val (state, toggle) = useToggle("A", "B") |
useToggleEither |
不同类型切换 |
val (state, toggle) = useToggleEither("left", 100) |
useToggleVisible |
切换内容可见性 |
val (content, toggle) = useToggleVisible { Text("Hi") } |
useReducer |
Redux 风格状态管理 |
val (state, dispatch) = useReducer(reducer, initialState) |
useRef |
不触发重组的引用 |
val ref = useRef(0) |
useCreation |
创建复杂对象(类似 useMemo),返回 Ref<T> |
val obj by useCreation { ExpensiveObj() } |
usePersistent |
轻量级持久化状态 |
val (state, setState) = usePersistent("key", "default") |
usePrevious |
获取前一个值 |
val prev = usePrevious(state) |
useLatestRef |
始终返回最新值的引用 |
val ref = useLatestRef(state) |
useLatestState |
始终返回最新值的 State<T> |
val latest by useLatestState(value) |
useLastChanged |
最后变更时间 |
val time = useLastChanged(source) |
useAutoReset |
自动重置状态 |
var state by useAutoReset("default", 3.seconds) |
集合 Hooks
| Hook |
用途 |
示例 |
useList |
列表状态管理 |
val list = useList(1, 2, 3) |
useListReduce |
列表聚合 |
val sum by useListReduce(list) { a, b -> a + b } |
useMap |
Map 状态管理 |
val map = useMap("key" to "value") |
useImmutableList |
不可变列表 |
val (list, mutate) = useImmutableList(1, 2, 3) |
useImmutableListReduce |
不可变列表聚合 |
val sum by useImmutableListReduce(list) { a, b -> a + b } |
useSorted |
列表排序 |
val sorted by useSorted(list) { a, b -> a.compareTo(b) } |
useCycleList |
循环列表 |
val (current, index, next, prev, go) = useCycleList(persistentListOf("A","B","C")) |
useSelectable |
选择/多选 |
val (selectedItems, isSelected, toggleSelected) = useSelectable(...) |
数值 Hooks
| Hook |
用途 |
示例 |
useInt |
Int 状态 |
val count = useInt(0) |
useLong |
Long 状态 |
val id = useLong(0L) |
useFloat |
Float 状态 |
val ratio = useFloat(0f) |
useDouble |
Double 状态 |
val price = useDouble(0.0) |
useCounter |
计数器 |
val (count, inc, dec, set, reset) = useCounter(0) |
副作用 Hooks
| Hook |
用途 |
示例 |
useEffect |
副作用处理 |
useEffect(dep) { / effect / } |
useMount |
组件挂载时执行 |
useMount { loadData() } |
useUnmount |
组件卸载时执行 |
useUnmount { cleanup() } |
useUnmountedRef |
是否已卸载 |
val unmounted = useUnmountedRef() |
useUpdateEffect |
跳过首次执行的 Effect |
useUpdateEffect(dep) { / effect / } |
usePausableEffect |
可暂停/停止的 Effect |
val (stop, pause, resume) = usePausableEffect(dep) { } |
useDebounceEffect |
防抖 Effect |
useDebounceEffect(dep) { / 500ms后执行 / } |
useThrottleEffect |
节流 Effect |
useThrottleEffect(dep) { / 限频执行 / } |
useBackToFrontEffect |
应用回到前台(Android) |
useBackToFrontEffect { refreshData() } |
useFrontToBackEffect |
应用进入后台(Android) |
useFrontToBackEffect { saveState() } |
防抖与节流
| Hook |
用途 |
示例 |
useDebounce |
防抖值 |
val debounced = useDebounce(value) |
useDebounceFn |
防抖函数 |
val fn = useDebounceFn<String>({ search(it) }) |
useThrottle |
节流值 |
val throttled = useThrottle(value) |
useThrottleFn |
节流函数 |
val fn = useThrottleFn<Int>({ log(it) }) |
定时器与延迟
| Hook |
用途 |
示例 |
useInterval |
定时器 |
useInterval(optionsOf = { period = 1.seconds }) { tick() } |
useTimeout |
延时执行(已废弃,优先用 useTimeoutFn) |
useTimeout(3.seconds) { showNotification() } |
useTimeoutFn |
延时执行(可控) |
val (pending, start, stop) = useTimeoutFn(fn, 3.seconds) |
useTimeoutPoll |
超时轮询 |
useTimeoutPoll({ fetchData() }, 5.seconds) |
useCountdown |
倒计时 |
val (remain, formatted) = useCountdown { targetDate = ... } |
时间与日期
| Hook |
用途 |
示例 |
useNow |
当前时间(定时更新) |
val now = useNow { interval = 1.seconds } |
useTimestamp |
时间戳(定时更新) |
val (ts, pause, resume) = useTimestamp { interval = 100.milliseconds } |
useTimestampRef |
时间戳 Ref 版本 |
val (ref, pause, resume) = useTimestampRef { interval = 100.milliseconds } |
useDateFormat |
日期格式化 |
val formatted = useDateFormat(instant, "YYYY-MM-DD") |
useTimeAgo |
相对时间 |
val ago = useTimeAgo(pastInstant) |
数学运算
| Hook |
用途 |
示例 |
useAbs |
绝对值 |
val abs = useAbs(value) |
useCeil |
向上取整 |
val ceil = useCeil(value) |
useFloor |
向下取整 |
val floor = useFloor(value) |
useRound |
四舍五入 |
val round = useRound(value) |
useTrunc |
截断 |
val trunc = useTrunc(value) |
useMin/useMax |
最小/最大值 |
val min = useMin(a, b) |
usePow |
幂运算 |
val pow = usePow(base, exp) |
useSqrt |
平方根 |
val sqrt = useSqrt(value) |
异步
| Hook |
用途 |
示例 |
useAsync |
简化协程 |
val run = useAsync { fetchData() } |
useCancelableAsync |
可取消协程 |
val (run, cancel, isActive) = useCancelableAsync() |
useMemoizedFn |
记忆化递归函数 |
val fn = useMemoizedFn<T, R> { ... } |
组件通信
| Hook |
用途 |
示例 |
useContext |
跨组件共享状态 |
val theme = useContext(ThemeContext) |
useEventSubscribe |
事件订阅 |
useEventSubscribe<MyEvent> { handle(it) } |
useEventPublish |
事件发布 |
val publish = useEventPublish<MyEvent>() |
useUpdate |
强制重组 |
val forceUpdate = useUpdate() |
网络请求
| Hook |
用途 |
示例 |
useRequest |
网络请求管理 |
val (data, loading, error, request) = useRequest(requestFn) |
useSse |
SSE 流式连接 |
val (data, streaming, error, _, send, cancel) = useSse(streamFn) |
全局状态管理
| Hook |
用途 |
示例 |
useSelector |
从 Store 选择状态 |
val count by useSelector<AppState, Int> { count } |
useDispatch |
获取 dispatch 函数 |
val dispatch = useDispatch<AppAction>() |
useDispatchAsync |
异步 dispatch |
val dispatchAsync = useDispatchAsync<AppAction>() |
useStateMachine |
状态机 |
val (state, send) = useStateMachine(graph) |
撤销/重做
| Hook |
用途 |
示例 |
useUndo |
撤销/重做 |
val (state, set, reset, undo, redo) = useUndo(initial) |
UI 相关
| Hook |
用途 |
示例 |
useClipboard |
剪贴板操作 |
val (copy, paste) = useClipboard() |
useKeyboard |
软键盘显隐控制 |
val (hideKeyboard, showKeyboard) = useKeyboard() |
表单 Hooks
| Hook |
用途 |
示例 |
Form.useForm |
表单实例 |
val form = Form.useForm() |
Form.useWatch |
监听表单值 |
val value = Form.useWatch("field", form) |
Form.useFormInstance |
获取表单实例 |
val form = Form.useFormInstance() |
表格 Hooks
| Hook |
用途 |
示例 |
useTable |
无头表格 |
val table = useTable(data, columns) { ... } |
useTableRequest |
分页请求表格 |
val tableReq = useTableRequest(requestFn) |
平台专属 Hooks
Android
| Hook |
用途 |
示例 |
useBiometric |
生物识别 |
val (openBiometric, isAuthed) = useBiometric() |
useNetwork |
网络状态 |
val network by useNetwork() |
useBatteryInfo |
电池信息 |
val battery by useBatteryInfo() |
useBuildInfo |
设备信息 |
val build = useBuildInfo() |
useScreenInfo |
屏幕信息 |
val screen = useScreenInfo() |
useVibrate |
振动 |
val (shortVibrate, longVibrate) = useVibrate() |
useFlashlight |
手电筒 |
val (turnOn, turnOff) = useFlashlight() |
useWakeLock |
唤醒锁 |
val (request, release, isActive) = useWakeLock() |
useSensor |
传感器 |
useSensor(Sensor.TYPE_ACCELEROMETER) { event -> ... } |
useIlluminance |
光照强度 |
val illuminance = useIlluminance() |
useIdle |
空闲检测 |
val idle = useIdle() |
useScreenBrightness |
屏幕亮度 |
val (setBrightness, initialBrightness) = useScreenBrightness() |
useDisableScreenshot |
禁用截图 |
val (disable, enable, isDisabled) = useDisableScreenshot() |
useWindowFlags |
窗口标志 |
val (add, clear, isAdded) = useWindowFlags(key, flags) |
Desktop
| Hook |
用途 |
示例 |
useKeyPress |
键盘按键检测 |
useKeyPress(Key.Enter) { handleEnter() } |
详细参考
- 状态管理 Hooks: 见 [references/state-hooks.md](references/state-hooks.md)
- 副作用 Hooks: 见 [references/effect-hooks.md](references/effect-hooks.md)
- 工具 Hooks: 见 [references/utility-hooks.md](references/utility-hooks.md)
- 网络请求 Hooks: 见 [references/request-hooks.md](references/request-hooks.md)
- Table 相关 Hooks: 见 [references/table-hooks.md](references/table-hooks.md)
- 表单 Hooks: 见 [references/form-hooks.md](references/form-hooks.md)
- SSE 流式连接: 见 [references/sse-hooks.md](references/sse-hooks.md)
- 平台专属 Hooks: 见 [references/platform-hooks.md](references/platform-hooks.md)
- remember 别名索引: 见 [references/remember-aliases.md](references/remember-aliases.md)
常见模式
1. 受控组件
// 推荐:使用 useGetState 解构
val (text, setText) = useGetState("")
OutlinedTextField(
value = text.value,
onValueChange = setText,
label = { Text("输入") }
)
// 或使用 by 委托
var text by useState("")
OutlinedTextField(
value = text,
onValueChange = { text = it },
label = { Text("输入") }
)
2. 解决闭包问题(来自 UseStateExample.kt)
// 方式1: 使用 useGetState 函数式更新
val (state, setState) = useGetState("initial")
LaunchedEffect(Unit) {
repeat(10) {
delay(1.seconds)
setState { "$it." } // 函数式更新,避免闭包问题
}
}
// 方式2: 使用 by 委托
var byState by useState("initial")
LaunchedEffect(Unit) {
repeat(10) {
delay(1.seconds)
byState += "." // 直接修改,无闭包问题
}
}
// 方式3: 使用 useLatestRef
val (state, setState) = useState("initial")
val stateRef = useLatestRef(state)
LaunchedEffect(Unit) {
repeat(10) {
delay(1.seconds)
setState("${stateRef.current}.") // 通过 ref 获取最新值
}
}
3. 网络请求(来自 Auto&Manual.kt)
// 自动请求
val (userInfoState, loadingState, errorState) = useRequest(
requestFn = { NetApi.userInfo(it) },
optionsOf = {
defaultParams = "junerver" // 自动请求必须设置默认参数
}
)
val userInfo by userInfoState
val loading by loadingState
if (loading) {
Text(text = "loading ...")
}
userInfo?.let { Text(text = it.toString()) }
// 手动请求
val (repoInfoState, loadingState, errorState, request) = useRequest(
requestFn = { it: Tuple2<String, String> ->
NetApi.repoInfo(it.first, it.second)
},
optionsOf = {
manual = true
defaultParams = tuple("junerver", "ComposeHooks")
}
)
TButton(text = "request") { request() }
4. Redux 风格状态管理(来自 UseReducerExample.kt)
// 定义 State 和 Action
data class SimpleData(val name: String, val age: Int)
sealed interface SimpleAction {
data class ChangeName(val newName: String) : SimpleAction
data object AgeIncrease : SimpleAction
}
// 定义 Reducer
val simpleReducer: Reducer<SimpleData, SimpleAction> = { prevState, action ->
when (action) {
is SimpleAction.ChangeName -> prevState.copy(name = action.newName)
is SimpleAction.AgeIncrease -> prevState.copy(age = prevState.age + 1)
}
}
// 使用
val (state, dispatch) = useReducer(
simpleReducer,
initialState = SimpleData("default", 18),
middlewares = arrayOf(logMiddleware())
)
TButton(text = "Change Name") { dispatch(SimpleAction.ChangeName("Alice")) }
TButton(text = "Increase Age") { dispatch(SimpleAction.AgeIncrease) }
Text(text = "State: ${state.value}")
5. 列表操作(来自 UseListExample.kt)
val listState = useList(1, 2, 3)
// 操作方法
listState.add(4) // 添加
listState.add(0, 0) // 插入
listState.removeAt(0) // 删除
listState.removeLast() // 删除最后一个
listState[0] = 10 // 修改
listState.clear() // 清空
listState.shuffle() // 打乱
// 配合 useListReduce
val sum by useListReduce(listState) { a, b -> a + b }
6. 防抖输入(来自 UseDebounceExample.kt)
var inputValue by useState("")
val debouncedValue by useDebounce(
value = inputValue,
optionsOf = {
wait = 500.milliseconds
}
)
OutlinedTextField(
value = inputValue,
onValueChange = { inputValue = it },
label = { Text("Type something...") }
)
Text(text = "Debounced: $debouncedValue")
7. SSE 流式连接
// 自动连接
val (lastEvent, isStreaming, error) = useSse(
streamFn = { params: String -> sseService.subscribe(params) },
optionsOf = {
defaultParams = "topic-1"
onEvent = { event -> println("收到: $event") }
}
)
// 手动连接
val (lastEvent, isStreaming, error, params, send, cancel) = useSse(
streamFn = { url: String -> sseClient.connect(url) },
optionsOf = { manual = true }
)
Button(onClick = { send("https://api.example.com/events") }) {
Text("开始监听")
}