Vue3 响应式概念 ↔ React 对应方案(类组件与函数组件)对照笔记

目的:用你已经熟悉的 Vue3 心智模型(响应式数据 / 计算属性 / watch / watchEffect)作锚点,建立 React 中对应概念的映射。React 没有「响应式系统」这一统一内核,而是把「状态」和「副作用」拆成显式 API,理解这个差异是关键。


📋 一图速览(核心映射表)

Vue3 概念 React 函数组件(Hooks) React 类组件
ref / reactive(响应式数据) useState / useReducer / useRef this.state + this.setState
computed(计算属性) useMemo(派生值) 类字段 getter / render 中直接计算 / PureComponent
watch(侦听特定源) useEffect(带依赖数组) / useLayoutEffect componentDidUpdate 中比对 prevProps/prevState
watchEffect(自动追踪依赖) useEffect(无依赖数组 = 每次渲染都跑) 暂无直接对应,靠手动在生命周期里组织
watchimmediate useEffect 天然「挂载即执行」 componentDidMount
watchdeep 靠依赖数组里引用具体字段 / useDeepCompareEffect 手动深比较
readonly / shallowRef useMemo 缓存 / React.memo / useRef(可变但不触发渲染) shouldComponentUpdate 控制
effectScope / onScopeDispose 自定义 Hook 返回清理函数 + useEffect 清理 生命周期方法组合

🔰 第一部分:Vue3 概念回顾(作为对照基准)

1. 响应式数据:ref / reactive

  • Vue3 用 Proxy 把数据变成「响应式对象」,读取即收集依赖,修改即触发更新。
  • 组件自动「订阅」用到的响应式数据,无需手动声明依赖。

2. 计算属性:computed

  • 基于其他响应式数据派生出来的值,带缓存(依赖不变则不重算)。

3. 侦听:watchwatchEffect

  • watch显式 指定要监听的源(一个或多个),源变化才执行回调,支持 immediate / deep / 新旧值。
  • watchEffect隐式自动收集回调里用到的所有响应式依赖,立即执行一次并持续追踪。

🔰 第二部分:React 函数组件(Hooks)的对应实现

React 的核心理念:UI 是状态的纯函数UI = f(state))。状态变了就重新执行整个函数(重新渲染),而不是「精准更新某个字段」。

1. 响应式数据 → useState / useReducer / useRef

jsx 复制代码
import { useState, useReducer, useRef } from 'react'

// ✅ 等价于 Vue 的 ref(基础类型 / 任意值)
function Counter() {
  const [count, setCount] = useState(0)   // count 即响应式数据,setCount 即修改入口
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}

// ✅ 等价于 Vue 的 reactive(复杂对象 / 多字段联动)
function useForm() {
  const [form, setForm] = useReducer(
    (state, action) => ({ ...state, [action.field]: action.value }),
    { name: '', age: 0 }
  )
  // setForm({ field: 'name', value: 'lmw' })
  return form
}

// ✅ useRef:可变值,但修改不会触发重新渲染(≈ Vue 的 shallowRef 不动 UI,或纯粹的实例变量)
function Timer() {
  const timerRef = useRef(null)
  useEffect(() => {
    timerRef.current = setInterval(() => console.log('tick'), 1000)
    return () => clearInterval(timerRef.current)
  }, [])
  return null
}

关键差异

  • Vue 里 count.value++ 直接改;React 里必须调用 setCount(不可直接改 state 本身,它是不可变数据)。
  • React 的「响应式」本质是:调用 setState → 组件函数重跑 → 返回新 JSX → 框架 diff 后更新 DOM。不是 Proxy 代理。

2. 计算属性 → useMemo

jsx 复制代码
import { useMemo } from 'react'

function Order({ items, discount }) {
  // ✅ 等价于 Vue 的 computed:依赖 (items, discount) 不变则不重算
  const total = useMemo(() => {
    const sum = items.reduce((s, i) => s + i.price * i.qty, 0)
    return sum * (1 - discount)
  }, [items, discount])

  return <div>合计:{total}</div>
}
Vue computed React useMemo
默认带缓存 默认带缓存(依赖数组)
可写(get/set) 只做派生,不能直接「赋值」回去(如需回写请用 useState + useEffect
在模板里像普通属性用 在 JSX / 逻辑里像普通变量用

⚠️ 注意:useMemo 主要为了性能优化(避免每次渲染重复计算),并非语义上的「响应式计算」。React 官方强调不要把它当成 Vue 的 computed 来依赖其「保证执行时机」。

3. 侦听 watch → useEffect(带依赖数组)

jsx 复制代码
import { useEffect } from 'react'

function Profile({ userId }) {
  const [user, setUser] = useState(null)

  // ✅ 等价于 Vue 的 watch(userId, ...):userId 变化才执行
  useEffect(() => {
    let cancelled = false
    fetch(`/api/user/${userId}`)
      .then(r => r.json())
      .then(data => { if (!cancelled) setUser(data) })
    return () => { cancelled = true }   // ✅ 清理函数 ≈ Vue watch 的清理 / onScopeDispose
  }, [userId])   // ← 依赖数组就是「监听源」

  return <div>{user ? user.name : '加载中'}</div>
}
  • [userId] 就是「我要监听 userId」的显式声明------和 Vue watch 显式指定源一致。
  • useEffect清理函数(return)对应 Vue 的停止侦听 / 资源释放。
  • 没有 immediate 选项?useEffect 在挂载时本来就执行一次 ,天然等于 watch(..., { immediate: true })

4. 侦听 watchEffect → useEffect(无依赖数组)

jsx 复制代码
// ✅ 等价于 Vue 的 watchEffect:回调里用到什么就「追踪」什么,且立即执行
function Logger({ a, b }) {
  useEffect(() => {
    console.log(`a=${a}, b=${b}`)   // 用到 a、b → 它们变了就重跑
  })   // ← 不传依赖数组 = 每次渲染都执行(≈ watchEffect 的自动追踪,但粒度是「整个渲染」)
  return null
}

⚠️ 严格说 React 没有「自动追踪某个变量」的机制。不传依赖数组 = 每次渲染都跑;传空数组 [] = 只挂载跑一次(既不追踪也不重跑)。最接近 watchEffect 心智 的是「不传依赖数组」,但代价是每次渲染都执行,所以实战中几乎总是显式传依赖数组 (回到 watch 模式)。

5. useLayoutEffect:同步副作用(贴近 DOM 的 watch)

jsx 复制代码
import { useLayoutEffect, useRef } from 'react'

function Measured() {
  const ref = useRef(null)
  useLayoutEffect(() => {
    // DOM 更新后、浏览器绘制前同步执行,适合读取布局/测量
    console.log(ref.current.getBoundingClientRect())
  }, [])
  return <div ref={ref}>测量我</div>
}
  • 等价于「需要在 DOM 变更后、绘制前同步做点事」的 watch(Vue 里没有严格对应的同步钩子,nextTick 反而更接近微任务)。

6. 组合复用 → 自定义 Hook(≈ Composition API / setup

jsx 复制代码
// ✅ 自定义 Hook 就是 Vue composable 的等价物
function useUser(userId) {
  const [user, setUser] = useState(null)
  useEffect(() => { /* fetch */ }, [userId])
  return user
}

Vue 的 setup()return { ... } 导出给模板用;React 的自定义 Hook 直接 return 给调用方用。


🔰 第三部分:React 类组件的对应实现

类组件没有 Hooks,状态与生命周期都挂在组件实例上。它是 React 早期的写法,现在新项目基本用函数组件,但理解它有助于看懂老代码、也更能体会 Hooks 解决了什么痛点(逻辑难复用、this 绑定混乱)。

1. 响应式数据 → this.state + this.setState

jsx 复制代码
import React, { Component } from 'react'

class Counter extends Component {
  constructor(props) {
    super(props)
    this.state = { count: 0 }           // ✅ 等价于 Vue 的 reactive({ count: 0 })
    this.handleClick = this.handleClick.bind(this)
  }

  handleClick() {
    this.setState(prev => ({ count: prev.count + 1 }))  // ✅ 修改入口
  }

  render() {
    return <button onClick={this.handleClick}>{this.state.count}</button>
  }
}
  • this.state 是响应式数据,this.setState 是修改入口(异步合并更新)。
  • 等价于 Vue:data() 返回的对象 + 直接改即响应式。

2. 计算属性 → render 中直接计算 / getter

jsx 复制代码
class Order extends Component {
  state = { items: [], discount: 0.1 }

  // ✅ 用 getter 模拟 computed(但每次 render 都重算,无缓存)
  get total() {
    const sum = this.state.items.reduce((s, i) => s + i.price * i.qty, 0)
    return sum * (1 - this.state.discount)
  }

  render() {
    return <div>合计:{this.total}</div>
  }
}
  • 想要缓存?可把派生值存进 state 并在 componentDidUpdate 里更新(麻烦,这正是 Hooks 用 useMemo 简化的地方)。
  • 想要「像 computed 一样自动跟着变」:类组件没有内建,纯靠 render 每次重算。

3. 侦听 watch → componentDidUpdate

jsx 复制代码
class Profile extends Component {
  state = { user: null }

  // ✅ 等价于 watch(userId):比对前后 props
  componentDidUpdate(prevProps) {
    if (prevProps.userId !== this.props.userId) {
      this.fetchUser(this.props.userId)
    }
  }

  componentDidMount() {
    this.fetchUser(this.props.userId)   // ✅ 等价于 watch immediate
  }

  fetchUser(userId) {
    fetch(`/api/user/${userId}`).then(r => r.json()).then(user => this.setState({ user }))
  }

  render() { /* ... */ }
}
  • componentDidUpdate(prevProps, prevState) 里手动比对新旧值 = Vue watch(newVal, oldVal)
  • componentDidMount = immediate: true
  • 清理/取消请求:在 componentWillUnmount 里做(≈ onScopeDispose)。

4. 派生状态 / 控制重渲染

jsx 复制代码
class List extends Component {
  // ✅ 等价于 Vue 的 shallowRef / 只读:返回 false 阻止无谓更新
  shouldComponentUpdate(nextProps, nextState) {
    return nextProps.items !== this.props.items
  }
  render() { /* ... */ }
}

// ✅ 等价于 React.memo(函数组件):自动浅比较
class PureList extends React.PureComponent {
  render() { /* 自动对 props/state 浅比较 */ }
}

🔰 第四部分:心智模型的根本差异(最重要)

Vue:响应式系统(自动追踪)

javascript 复制代码
数据变了 ──(Proxy 自动通知)──> 用到它的地方自动更新
  • 声明 依赖关系(模板里用了谁、computed/watch 里用了谁),框架自动追踪
  • 改数据即可,UI 自己跟着变。

React:状态不可变 + 显式触发重渲染

复制代码
setState ──> 组件函数重跑 ──> 生成新虚拟 DOM ──> diff ──> 更新真实 DOM
  • React 不知道你改了「哪个字段」,它只知道「你调用了 setState」。
  • 然后整个组件函数重新执行,你用到的 state 自然是最新的。
  • 副作用(请求、订阅、DOM 操作)必须显式 放进 useEffect 并声明依赖数组,否则要么不执行、要么无限循环。

💡 一句话记忆:Vue 是「数据驱动、自动订阅」;React 是「状态不可变、整体重渲染、副作用显式声明」。


🔰 第五部分:常见坑对照

场景 Vue3 React 函数组件
修改数组/对象 reactive 直接改,ref.value 必须返回新引用 (如 setList([...list, x])),直接 list.push 不生效
派生值缓存 computed 自动 useMemo(仅优化,非语义保证)
监听多个源 watch([a, b], ...) useEffect(fn, [a, b])
立即执行 watch(..., { immediate: true }) useEffect 默认挂载即执行
卸载清理 onScopeDispose / watch 返回 stop useEffect 的 return 清理函数 / componentWillUnmount
拿到旧值 watch 回调参数 (newV, oldV) useEffect 闭包捕获的是「上一次渲染的值」;或 useRef 手动存

🎯 总结练习(给自己出的对照题)

  1. Vue 的 const double = computed(() => count.value * 2) → React 函数组件怎么写?(const double = useMemo(() => count * 2, [count])
  2. Vue 的 watch(count, v => localStorage.setItem('c', v)) → React 类组件怎么写?(componentDidUpdate(prev){ if(prev.count!==this.state.count) localStorage.setItem('c', this.state.count) }
  3. Vue 的 watchEffect(() => console.log(count.value)) → React 函数组件怎么写?(useEffect(() => { console.log(count) }) 不传依赖数组;或更好的是 useEffect(() => {...}, [count])

关联笔记:Vue 3 响应式三剑客:ref、shallowRef、reactive;Vue3 watch与watchEffect详解;React16与React18学习笔记

相关推荐
windliang1 小时前
Claude Code 源码分析(二):CC 的心脏 queryLoop Agent主循环
前端·ai编程·claude
英勇无比的消炎药1 小时前
拖文件进来会发生什么:TinyRobot DragOverlay 与 v-dropzone 协同指南
vue.js
Revolution611 小时前
reactive 对象重新赋值后,表单为什么没有恢复默认值
前端·vue.js·面试
米饭同学i1 小时前
微信小程序 input maxlength 遇上输入法拼音被截断?这是最完整的解决方案
前端
董员外1 小时前
RAG 系统进化论(二):Naive RAG,检索增强生成的最小闭环
前端·人工智能·后端
半个落月1 小时前
React useEffect 详解:执行时机、依赖数组与副作用清理
react.js
脾气有点小暴1 小时前
ECharts 伪 3D 柱状图完整注释 + 实现原理说明
前端·3d·信息可视化·vue·echarts
掘金一周2 小时前
看看大家每月的成本有多少 | 沸点周刊 7.30
前端·人工智能·后端
ji_shuke2 小时前
从零深入:基于 Playwright + Pytest + Allure 的企业级 Web 端到端自动化测试框架实战
前端·自动化测试·docker·jenkins·pytest·allure·playwright