React 灵魂:深入剖析 Hooks 与副作用(Side Effects)陷阱

在 Jetpack Compose 中,你一定很熟悉 @Composable 函数、rememberMutableState 以及 LaunchedEffect。这些概念深受 React 的启发。理解 React 的 Hooks,你就掌握了函数式组件的灵魂。

1. 状态管理:useState vs remember { mutableStateOf() }

在 Compose 中,状态变化会触发重组 (Recomposition)。在 React 中,状态变化会触发重新渲染 (Re-render)。

Compose

kotlin 复制代码
@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }
    Button(onClick = { count++ }) {
        Text("Count: $count")
    }
}

React Native

javascript 复制代码
import React, { useState } from 'react';
import { Button, Text, View } from 'react-native';
​
export default function Counter() {
  // useState 返回一个数组:[当前状态, 更新状态的函数]
  const [count, setCount] = useState(0);
​
  return (
    <View>
      <Text>Count: {count}</Text>
      <Button title="Increment" onPress={() => setCount(count + 1)} />
    </View>
  );
}

关键区别 :React 的组件会在每次状态更新时重新执行整个函数useState 保证了在多次执行中,count 能够保持其最新的状态,这与 Compose 的 remember 作用非常类似。

2. 副作用:useEffect vs LaunchedEffect / DisposableEffect

在声明式 UI 中,"副作用" (Side Effect) 指的是那些不属于 UI 渲染逻辑的操作,例如:网络请求、订阅事件、操作定时器等。

在 Compose 中,处理副作用有 LaunchedEffect (挂起函数副作用) 和 DisposableEffect (需要清理的副作用)。 在 React 中,它们被统一为一个大杀器:useEffect

基本用法

scss 复制代码
import React, { useState, useEffect } from 'react';
​
export default function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
​
  useEffect(() => {
    // 这里的代码在组件挂载后,或者依赖项发生变化时执行
    fetchData(userId).then(data => setUser(data));
    
    // 返回一个清理函数 (Cleanup function),相当于 DisposableEffect 的 onDispose
    return () => {
      cancelFetch();
    };
  }, [userId]); // 依赖数组 (Dependency Array)
  
  // ...
}

依赖数组陷阱 (The Dependency Array Trap)

useEffect 的第二个参数是依赖数组 ,它是初学者最容易踩坑的地方,也直接对应 Compose LaunchedEffect(key1, key2) 中的 keys。

  • 不传依赖数组 useEffect(() => {...})每次渲染后都会执行!极易导致无限循环和性能问题。
  • 传空数组 useEffect(() => {...}, []):只在组件首次挂载 时执行一次(相当于 Android 的 onCreate / Compose 的 LaunchedEffect(Unit))。
  • 传变量 useEffect(() => {...}, [userId]):在首次挂载,以及 userId 发生变化后执行。

陷阱警告 :如果你在 useEffect 内部使用了某个组件作用域内的变量(如 state 或 props),你必须将其加入依赖数组中,否则内部拿到的将是旧版本渲染时的闭包值(Stale Closure)。

3. 避免不必要的重渲染:useMemo 与 useCallback

Compose 编译器非常聪明,它会自动跳过参数未变的组合函数的重组。然而,React 默认会在父组件渲染时,重新渲染其所有子组件。

为了优化性能,React 提供了 useMemo (相当于 Compose 的 remember + 计算逻辑) 和 useCallback

scss 复制代码
// 缓存计算结果,只有 items 改变时才重新计算
const expensiveResult = useMemo(() => computeExpensive(items), [items]);
​
// 缓存函数引用,避免每次渲染生成新的回调导致子组件不必要的重渲染
const handleClick = useCallback(() => {
  doSomething(count);
}, [count]);

核心总结

React 的函数组件每次渲染都是一次函数的完整执行。useState 保持状态跨越渲染周期,而 useEffect 负责将那些"纯渲染"之外的脏活累活(网络请求、订阅)隔离开来。深入理解"闭包"以及"依赖数组",你就能避开 React 开发中 80% 的奇怪 Bug。

相关推荐
Json____15 分钟前
java-宿舍安全卫生检查系统项目源码
java·前端·javascript·课程设计·it学习·wwwoop.com
lichenyang45326 分钟前
鸿蒙ArkUI 键盘避让终极踩坑指南:RESIZE 无效、Column 布局遮挡、折叠屏适配全解
前端
爱勇宝36 分钟前
公司没给活干,却因为员工看手机把人开了:法院判赔11万元
前端·后端·程序员
Patrick_Wilson37 分钟前
iOS Safari 按钮点击无响应问题修复指南
前端·ios·safari
Csvn1 小时前
模块化与工程化:CJS / ESM / 打包器,到底怎么协作?
前端
hunterandroid1 小时前
[鸿蒙从零到一] ArkTS Sendable 数据模型与跨线程共享实战
前端
又见情义1 小时前
RK3568 + RTL8211F 网络唤醒(WOL)功能适配全记录
android·网络·驱动开发
AICoder码农王1 小时前
Vitest 实战:从一个真实项目看懂测试套件
前端·前端框架
用户0934077735141 小时前
HarmonyOS WPS Open SDK 实践:registerApp 鉴权与就绪门禁
android·typescript·harmonyos