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

在 Jetpack Compose 中,你一定很熟悉 @Composable 函数、remember、MutableState 以及 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。

相关推荐
李纲明6 分钟前
WordPress 站点变慢:按 TTFB → 缓存 → 查询 → 前端 分层排查(含配置注意)
前端·缓存·性能优化·wordpress·后端开发
墨天梦9 分钟前
D05_ViewModel与单向数据流
android·kotlin
程序人生88816 分钟前
PDF 转 Word 版式错乱、表格丢失?DocConverter Web 格式互转引擎的保真实践
前端·人工智能·opencv·机器学习·pdf·word
粥里有勺糖1 小时前
安利一下最近用的桌面“Agent” | T3 Code
前端·github·ai编程
蒸鱼Yuzheng1 小时前
Android 构建可复现性:APK 指纹、文件级差异与供应链审计
android·apk·devops·软件供应链·可复现构建
xy34531 小时前
Axure 9.0 动态面板基本操作
前端·ui·html·axure·原型·产品设计
不停喝水2 小时前
【前端转全栈java速通课】 项目实战④7-11节 操作数据库-登录-注册-修改密码-注销-mubatis-plus简化crud-
java·前端·数据库
一水鉴天2 小时前
统一对象拓扑(UOT)设计全链定稿:从三件事分离到三继承 之1 20261010(豆包“查看聊天内容”继续)
前端·css
栖凤3 小时前
Java 的 try-catch 在 Agent 里失效了,我用了 5 个模式才兜住
java·前端·python
粥里有勺糖3 小时前
视野修炼第136期 | 前端小恐龙"Deno"被收购
前端·github·ai编程