news 2026/10/2 22:32:52

在React中复刻Vue的watch与computed:自定义Hooks实践指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
在React中复刻Vue的watch与computed:自定义Hooks实践指南

如果你所在的团队刚从 Vue 全家桶切到 React 技术栈,你一定听过这样的对话:“这个数据变了,我想监听一下做点事,在 Vue 里写个 watch 就行了,React 怎么写?”——答:useEffect。“我想要一个根据列表和关键词算出来的过滤结果,难道每个组件都要复制一遍?”——答:useMemo。道理是这个道理,但真写起来,总有那么几处别扭:依赖数组漏一个,闭包拿旧值,回调时机和想象中不一样。于是有人开始琢磨:能不能在 React 里复刻 Vue 的 watch 和 computed?我就是其中一个。

先说清楚前提:我并不是想把 Vue 的写法强行塞进 React,让代码变成四不像。真正有价值的,是 Vue 在“响应式派生状态”和“副作用侦听”这两个场景下交出的开发体验——少声明依赖、多自动跟踪、缓存按需失效。这两件事 React 其实都有对应方案,只是需要自己动手打磨。如果封装得好,React 代码依然是 React,只是用起来更顺手。

下面所有内容,都是我实际调研、试错、压测之后沉淀下来的做法,既包括可以直接拿去用的成品实现,也包括实现背后的思考,以及一些翻车现场。不一定是最优解,但至少是我目前用过最顺手的版本。

1. 为什么要在 React 里复刻 Vue 的 watch 和 computed

1.1 从 Vue 切到 React 后的“手写依赖”阵痛

我见过太多从 Vue 转 React 的同事,写第一周组件时被 useEffect 咬了一口又一口。印象最深的一个场景:一个搜索面板,输入关键词、拉远程数据、根据结果过滤列表。Vue 那边一行 computed 加一个 watch 就能搞定的事,React 这边要 useEffect + useMemo + useRef 三个 Hook 一起上,依赖数组还得反复核对。

问题不是 React 做不到,而是把复杂度交给了开发者。Vue 的响应式系统在背后默默帮你维护了“谁变了、谁依赖谁”的关系,React 则需要你在每一个 Hook 后面手动管理依赖。人肉维护依赖数组,就像手工记账,记多了浪费性能,记漏了拿到的就是过期数据。这种痛苦在组件复杂起来之后会被无限放大。

1.2 React 不是没有,而是把复杂度交给了开发者

公平地说,useMemo 和 useEffect 分别覆盖了 computed 与 watch 的九成能力。useMemo 有缓存,useEffect 能感知依赖变化,两者都经过 React 调度器的统一管理。但拆开看就会发现,和 Vue 的语义差着细节:

  • useMemo 的依赖是浅比较,对象内部变化不会触发重新计算;
  • useEffect 拿不到新旧值,只能自己在 render 阶段存 ref;
  • useEffect 默认 mount 后也会执行一次,Vue 的 watch 默认不执行;
  • 两者都要求你把依赖列表写全,写错又是另一种 bug。

于是很自然想到:能不能把这些 React 的“痛点”封装成 Vue 那种表达的 Hook?让 watch 就是 watch,computed 就是 computed。我做这件事也不是为了标新立异,纯粹是为了提高团队开发和维护效率。

1.3 我要复刻的不是 API,是语义

封装之前我给自己定了几条原则:

  • 对外 API 尽量贴近 Vue 的 watch/computed,让迁移过来的同学零学习成本;
  • 内部实现必须符合 React 的调度模型,不搞破坏并发安全的黑魔法;
  • 缓存和侦听的边界要清晰,不能为了像 Vue 而放弃 React 的性能模型。

所以我最终交出的是两个 Hook:useWatch和useComputed。它们不是 Vue 响应式系统的移植,而是借了 Vue 的“壳”,套上 React 的“核”。下面先把 Vue 的语义拆清楚,再看我怎么一点一点实现。

2. 先把语义对齐:Vue 的“依赖跟踪”与 React 的“手动依赖”

2.1 computed 的本质:带缓存的声明式派生值

Vue 的 computed 有三个特性:声明式、惰性求值、依赖跟踪。你在模板里写{{ total }},它不会立刻执行,只有真正被读取时才开始计算。计算过程中访问到的响应式属性会被记录下来,当这些属性发生变化,下一次读取时才会重新计算,否则直接返回上一次的结果。

这就是一个很典型的“缓存函数”:输入没变,输出直接命中缓存;输入变了,下次调用重新跑一遍。关键是这个“输入”不是你自己声明的,而是函数体里每一次属性访问自动收集的。这个体验用 React 的 useMemo 是无法直接复刻的,因为 useMemo 只能靠你手动给它一个依赖数组,它自己完全不关心函数内部读取了什么。

2.2 watch 的本质:带新旧值的异步副作用调度器

watch 则是另一种东西:它不产生派生值,只在数据变化后执行一段副作用。它的回调里带(newValue, oldValue),这看起来很简单,但背后有两个容易被忽略的机制:一是异步调度,同一次事件循环里的多次数据变化会合并成一次回调;二是深度监听,传入对象时可以监听到内部属性的变化。

在 React 里,useEffect 勉强对应“数据变化后执行副作用”,但旧值拿不到、深度监听不支持、immediate 没有,异步合并的时机也由 React 的批处理机制决定。几个细节差距叠加起来,才让人总觉得 useEffect 用起来不如 watch 顺手。

2.3 一张表看清四者区别

能力Vue computedReact useMemoVue watchReact useEffect
依赖声明方式自动收集手动数组自动/显式手动数组
缓存机制有有无无
回调参数无无newValue/oldValue无
首次是否执行访问时执行挂载时执行默认否,可配 immediate挂载后执行
深度监听依赖到属性不支持支持不支持
执行时机同步、按需渲染期同步响应式异步commit 后异步

从表里能直观看出,computed 与 useMemo 最像,watch 与 useEffect 最像。但“最像”不等于“就是”,差的正是那些让开发痛苦的细节。

2.4 Vue 能做到自动依赖跟踪的底层原因

Vue 3 的自动依赖跟踪靠的是 Proxy。它把响应式对象包装了一层,任何属性的读取都会被拦截并记录到“当前正在运行的计算”里,任何属性的写入都会触发依赖该属性的更新队列。React 没有这个,它的数据流是显式的:你 setState,组件重渲染,Hooks 根据依赖数组决定要不要重跑。

所以要在 React 里实现 Vue 的 watch 和 computed,最关键的就是补上这套“读取感知”的能力。要么用手动依赖收窄感知范围,要么自己造一个 Proxy 工具箱。这也是第三、第四章的核心差异,我会分别展开。

3. useWatch 完整实现:从零写出带新旧值、immediate、deep 的侦听器

3.1 最小可用版:old/new 与首次不触发

先写最核心的部分:当被侦听的值发生变化时,回调能拿到新旧值。这里的难点在于,useEffect 本身没有旧值概念,我们得用一个 ref 把上一次的值存下来,在每次 effect 触发时对比更新。

import { useEffect, useRef } from 'react'; type WatchCallback<T> = (newValue: T, oldValue: T | undefined) => void; interface WatchOptions { immediate?: boolean; deep?: boolean; } export function useWatch<T>( value: T, callback: WatchCallback<T>, options: WatchOptions = {} ) { const callbackRef = useRef(callback); callbackRef.current = callback; const optionsRef = useRef(options); optionsRef.current = options; const isFirstRun = useRef(true); const oldValueRef = useRef<T | undefined>(undefined); const watchTarget = options.deep ? JSON.stringify(value) : value; useEffect(() => { const oldValue = oldValueRef.current; if (isFirstRun.current) { isFirstRun.current = false; if (optionsRef.current.immediate) { callbackRef.current(value, oldValue); } } else { callbackRef.current(value, oldValue); } oldValueRef.current = value; }, [watchTarget]); }

这个版本的关键在于 oldValueRef:第一次 effect 执行时它还是 undefined,回调触发后立刻把本次的 value 写回 ref,这样下一次变化时 ref 里自然就是旧值。闭包问题也被 callbackRef 解决了——即使外层 callback 在每次渲染中都是新函数,我们的 effect 总是调用最新的那个。

默认情况下,组件挂载后不会触发回调,这符合 Vue watch 的行为。想要首次执行就打开immediate: true。

3.2 immediate:首帧就执行

从代码里能看到 immediate 的实现逻辑:isFirstRun 这个标记在首次 effect 执行时被消费,如果选项里带了 immediate,就在那个时刻主动调用一次 callback。注意此时 oldValue 传的是 undefined,和 Vue 3 的处理一致。

这个能力在 React 里用 useEffect 实现时经常被忽略:很多人直接用 useEffect 的首次执行充当 immediate,但随后依赖变化时又会重复触发,导致逻辑写两遍。useWatch 把“挂载时只跑一次”和“变化时跑”统一进同一个入口,代码自然就简洁了。

3.3 deep:监听对象内部变化,以及绕不开的 oldValue 问题

deep 的实现是这一段最需要谨慎的地方。React 的依赖数组做的是Object.is比较,对象引用不变化时 useEffect 不会触发,所以当options.deep为 true,我用了JSON.stringify(value)作为依赖数组的项。对象内部属性变化,序列化字符串跟着变,effect 就会被触发。

但这里必须提醒几个坑:

  • 每次 render 都会重新执行JSON.stringify,如果被侦听对象很大,这个成本不能忽视;
  • 如果对象的键顺序不稳定(比如后端返回字段顺序每次都不同),stringify 结果会变化,导致不必要的触发;
  • oldValue 在 deep 模式下只有“上一次渲染时保存的引用”意义,并不是真正的旧状态快照。因为 React 没有在 setter 执行时拍快照的能力,对象在两次 render 之间被原地修改,oldValueRef 里存的其实是同一个对象引用。

所以我的建议是:deep 模式尽量用在“我只关心变化本身,不依赖旧值做精细对比”的场景,比如同步到 localStorage、上报日志、请求接口。如果确实需要旧值快照,手动在修改前JSON.parse(JSON.stringify(obj))存一份,成本自己评估。

3.4 实测:监听搜索词写入历史记录

拿一个最常见的搜索框场景验证一下。用户输入关键词,触发一个 handleSearch,这段副作用的触发时机和参数都和 Vue watch 保持了一致:

function SearchPanel() { const [keyword, setKeyword] = useState(''); useWatch( keyword, (newValue, oldValue) => { console.log(`搜索词从 ${oldValue} 变为 ${newValue}`); // 在这里发请求、写本地历史、埋点上报 }, { immediate: true } ); return <input value={keyword} onChange={(e) => setKeyword(e.target.value)} />; }

实际跑起来你会发现,和 Vue 的体验几乎一致:首次渲染就执行一次,之后每次变化都能拿到新旧值,组件周边代码没有任何多余的状态。如果换成原生 useEffect,你得在 render 阶段写一个 ref 保存值,还得判断是否是第一次挂载,代码至少多出七八行。

4. useComputed 实现:从手动依赖版到 Proxy 自动追踪版

4.1 为什么 useMemo 不等于 computed

useMemo 看起来是 React 官方给 computed 的答案,但它和 computed 的差距主要在两个地方:

一是依赖数组需要人肉维护。漏一个依赖,内部闭包拿到的就是旧值;多一个依赖,缓存命中率下降,性能白白损耗。这个问题的隐蔽性在于:它不报错,只是结果偶尔不对,排查成本很高。

二是依赖的粒度是“值”而不是“属性”。你对一个对象做 useMemo,只要引用变化,整个对象上的任何字段修改都会触发重算。而 Vue 的 computed 在字段粒度上做依赖追踪,可以精细到user.name。

所以我想做的 useComputed,重点就是解决这两个问题:把依赖数组的维护成本降下来,让缓存失效规则更可控。

4.2 手动依赖版:把缓存失效规则握在自己手里

先给一个适合大多数场景的版本。它的实现逻辑很简单:用 ref 存上次计算的依赖数组和结果,每次 render 时比较依赖是否变化,变化就重算,否则返回缓存值。

import { useRef } from 'react'; export function useComputed<T>(factory: () => T, deps: unknown[]): T { const cacheRef = useRef<{ deps: unknown[]; value: T }>(); if ( cacheRef.current === null || cacheRef.current === undefined || !depsEqual(cacheRef.current.deps, deps) ) { cacheRef.current = { deps, value: factory() }; } return cacheRef.current.value; } function depsEqual(a: unknown[], b: unknown[]): boolean { if (a.length !== b.length) return false; return a.every((dep, index) => Object.is(dep, b[index])); }

这个版本就是“自己实现的 useMemo”,它的价值不在技术含量,而在你可以继续往上加逻辑。比如按深度比较对象、打印缓存命中日志、手动失效缓存。我最初封装它,是为了让团队里从 Vue 过来的人更容易理解“派生值”这个概念,代码里没有 Hook 的魔法,只有缓存和比较,反而更好讲。

4.3 自动依赖版:用 Proxy 让 computed 自己知道依赖了什么

要真正做到“自动收集依赖”,就得在 React 里重建一个轻量级响应式容器。我用 Proxy 包装状态对象,在 get 时记录当前计算读取了哪些属性,在 set 时通知订阅者重新渲染。核心思路和 Vue 3 一致,只是范围收窄到我们自己创建的状态容器。

import { useEffect, useRef, useState } from 'react'; type Listener = () => void; type AnyRecord = Record<string, any>; interface ReactiveStore<T extends AnyRecord> { proxy: T; version: number; subscribe: (listener: Listener) => () => void; } let activeComputation: Map<ReactiveStore<any>, number> | null = null; export function createReactiveStore<T extends AnyRecord>( initialData: T ): ReactiveStore<T> { let version = 0; const listeners = new Set<Listener>(); const proxy = new Proxy(initialData, { get(target, key) { // 当前处于计算函数执行中时,记录这个 store 被读取了 if (activeComputation) { activeComputation.set(store, version); } return target[key]; }, set(target, key, newValue) { target[key] = newValue; version += 1; listeners.forEach((listener) => listener()); return true; }, }); const store: ReactiveStore<T> = { proxy, get version() { return version; }, subscribe(listener) { listeners.add(listener); return () => listeners.delete(listener); }, }; return store; } export function useComputed<T>( factory: () => T, stores: ReactiveStore<any>[] ): T { const [, forceUpdate] = useState(0); const cacheRef = useRef<{ deps: Map<ReactiveStore<any>, number>; value: T; }>(); useEffect(() => { const unsubscribers = stores.map((store) => store.subscribe(() => forceUpdate((n) => n + 1)) ); return () => unsubscribers.forEach((unsubscribe) => unsubscribe()); }, []); const previous = cacheRef.current; let shouldRecompute = true; if (previous) { shouldRecompute = false; for (const [store, cachedVersion] of previous.deps) { if (store.version !== cachedVersion) { shouldRecompute = true; break; } } } if (shouldRecompute) { activeComputation = new Map(); const value = factory(); const depsSnapshot = new Map(activeComputation); activeComputation = null; cacheRef.current = { deps: depsSnapshot, value }; } return cacheRef.current!.value; }

核心机制是两个部分配合:createReactiveStore 负责状态容器,useComputed 负责缓存和订阅。当计算函数执行时,所有被访问的属性都会被记录到 activeComputation 中;计算完成后,我们把依赖的版本号快照存下来。之后任何 store 的 version 变化,订阅者触发的重渲染都会走到版本比对逻辑,只有真正被读取过的 store 版本变了,才会重新执行 factory。

说白了,这里的 Proxy 就是给 React 补上了“读取感知”能力,computed 从此不需要手动声明依赖。

4.4 实测:过滤列表与购物车小计

我拿电商项目的一个常见列表做了压测:商品列表和关键词都在响应式 store 里,过滤结果用 useComputed 计算。

const store = createReactiveStore({ products: [ { name: 'React 进阶', price: 59 }, { name: 'Vue 实战', price: 49 }, { name: 'TypeScript 入门', price: 39 }, ], keyword: '', currency: 'CNY', }); function ProductList() { const filtered = useComputed( () => store.proxy.products.filter((p) => p.name.includes(store.proxy.keyword)), [store] ); const total = useComputed( () => filtered.reduce((sum, p) => sum + p.price, 0), [store] ); return ( <div> <input value={store.proxy.keyword} onChange={(e) => { store.proxy.keyword = e.target.value; }} /> <ul> {filtered.map((p) => ( <li key={p.name}>{p.name}</li> ))} </ul> <footer>合计:{total}</footer> </div> ); }

在真实运行中,当你输入一个字符,只会触发两次计算:filtered 重算一次,total 因为 filtered 数组引用变了跟着重算一次。而当你修改 currency 字段时,两个计算都不会重跑,因为它们的函数体根本没读过 currency。这种精度是手动依赖数组很难稳定达到的,尤其是在团队里大家水平参差不齐的情况下。

这个实现默认假定创建 store 后依赖列表不发生变化,所以订阅只注册一次。如果你需要动态增删 store,需要像维护普通副作用一样去维护订阅逻辑。

5. 实测对比与取舍:什么时候用自定义 Hook,什么时候别用

5.1 这些 Hook 适合解决的场景

从我几个项目的实际使用来看,useWatch和useComputed最适合解决以下问题:

  • 从 Vue 迁移过来的老项目,重构期间保持业务逻辑不变,降低切换阵痛;
  • 组件内派生逻辑嵌套较深,比如过滤、排序、分组,多个组件共享同一份派生结果;
  • 需要监听某个状态变化并触发一系列外部副作用(请求、存储、埋点),又希望代码可读性高一些;
  • 团队里新人较多,想要减少 useMemo/useEffect 误用率的时候。

第三个场景尤其值得一提。原本一个监听多处触发的代码,用 useEffect 写很容易出现首帧触发、依赖遗漏、多次调用等问题。收敛成 useWatch 后,心智负担显著降低,新人在 code review 时也不需要反复推敲依赖数组的口径。

5.2 不建议使用的场景

我必须强调,不是所有地方都适合换这套 Hook。React 的官方设计不是摆设,下面这些情况我强烈建议保留原始写法:

  • 简单的一次性副作用,比如组件挂载时请求一次数据,直接用 useEffect + 空依赖最清晰;
  • 简单的派生值,比如const fullName = first + ' ' + last,这种用变量就够了,任何缓存都是浪费;
  • 需要和第三方非响应式库深度联动时,比如接入 D3、Leaflet 这类外部生态,原始 useEffect 更可控;
  • 团队还处于理解 React 心智的初期,过早引入“类 Vue”抽象反而会造成概念混淆。

说到底,useWatch 和 useComputed 是锦上添花的工具,不是银弹。核心目标永远是让代码清晰、易维护,而不是为了“像 Vue”而牺牲 React 本身的模型。

5.3 和现成状态管理方案的对比

如果你已经在用 zustand、valtio 这样的状态库,它们多半自带衍生状态能力,比如 zustand 的 selector、valtio 的派生 proxy。在这种情况下,我不建议再引入一套自研的 useComputed,否则一个项目里存在两套“响应式状态系统”,维护来维护去反而成了新的复杂度。

我做这套 Hook 的场景是项目本身没有引入全局状态管理库,纯粹靠组件本地 state + props 传递。在那种情况下,这套封装既不增加额外依赖,又能把编写体验拉回 Vue 时代,收益和成本十分划算。

6. 接入团队前的几个实战提醒

6.1 watch 回调里更新被 watch 的值,小心死循环

这是我认为最容易翻车又最像“传统 Vue 坑”的地方。如果在 useWatch 的回调里无脑修改同一个被侦听的值,会引发“变化 -> 回调 -> 再变化 -> 再回调”的循环。Vue 里有watchEffect的自动依赖同样会踩这个坑,React 里尤其要小心,因为 useEffect 的依赖比较是值比较,绕不开。

我的处理办法是在回调里加条件判断,或者把更新逻辑迁移到事件处理器里,而不是放在 watch 回调中。比如搜索词的防抖请求,不要在 keyword 变化后直接 setState keyword,而是 setState 一个 loading 标志、写请求状态。

6.2 开发模式 StrictMode 下回调可能触发两次

React 18+ 的 StrictMode 会在开发模式下故意让 effect 执行两次,用来暴露副作用问题。useWatch 底层是 useEffect,所以首次挂载和每次依赖变化时,回调在开发环境可能被调用两次。这不是 bug,是 React 有意为之。

应对方法:

  • 回调必须是幂等的,重复执行不会产生重复副作用;
  • 网络请求在回调里要做防重,例如用 AbortController 取消上一次;
  • 如果只是开发环境调试时多打一条日志,不影响线上行为,可以不管。

6.3 deep 模式的性能与语义陷阱

前面已经说了 JSON.stringify 的开销和 oldValue 的引用问题,这里再补一个实际教训:千万不要对超大对象、循环引用对象盲开 deep。有一回我侦听一个包含上传文件预览 base64 的图片对象,开 deep 后每输入一个字就序列化一次整个对象,页面直接卡顿。

如果只是要监听“对象结构是否变化”,但不在乎变化的路径,可以用自定义比较器替代 deep,比如 “序列化 hash” 或者只抽几个关键字段做对比。这比无脑 deep 更可控。

6.4 在 computed 里做副作用的后果

computed 函数应该在渲染阶段被安全地调用,不应该有网络请求、DOM 操作、定时器这些副作用。我在自动依赖版里,computed 的执行发生在 render 阶段,如果里面塞了副作用,一旦依赖频繁变化,副作用可能被多次执行,且 StrictMode 下还会双跑。

我把“计算”和“副作用”的边界理解为:计算只负责从输入得到输出,副作用交给 watch 去执行。这正是 Vue 里 computed 不写副作用的设计哲学,React 里同样适用。

6.5 团队接入时的约定

最后分享一个团队协作层面的经验。如果你决定把这些 Hook 引入团队,一定要先约定两条硬规则:

第一条,自定义 Hook 的命名必须统一,建议统一前缀,比如useWatch、useComputed,在代码 review 时一眼就能认出这是自定义响应式封装,而不是 React 原生 Hook。

第二条,必须在项目文档里写清楚它们和 useEffect/useMemo 的区别与适用边界。否则过三个月,新来的同事看到 useWatch 可能一脸茫然,甚至把它当成 useEffect 的别名来用。我见过最夸张的情况是有人把 useComputed 的 factory 当 effect 使用,在计算里发起请求,把组件搞出了内存泄漏。

这两条约定不需要很复杂,却能避免很大一部分“看起来像 Vue 又不像 Vue”的混乱。

最后再分享一个我的个人体会。写完这套 Hook 后的很长一段时间里,我并没有感到“React 变得像 Vue”了,反而觉得对 React 的调度模型和数据流有了更清晰的认识。Vue 的自动依赖跟踪确实舒服,但 React 的显式数据流也有它自己的安全感。这套封装最好的结局,不是让你从此不用理解 useEffect 和 useMemo,而是在你真正理解它们之后,多一个趁手的工具可用。如果你也在做类似的迁移项目,希望这篇内容能帮你少踩几个我曾经踩过的坑。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 22:31:35

8G显存也能跑!本地大模型代码生成实战与避坑指南

一直被两个问题卡着&#xff1a;代码里大量的重复性工作占掉我不少时间&#xff0c;而有些涉及内部表结构和业务规则的代码又没法随便往云端AI平台上扔。后来我把目光放到了本地大模型上&#xff0c;摸了一圈下来发现&#xff0c;手头这块8G显存的NVIDIA显卡其实还挺能打——前…

作者头像 李华
网站建设 2026/10/2 22:31:34

Hindsight:开源浏览器取证工具解析Chrome历史

看到“hindsight”这个词&#xff0c;懂行的朋友可能先想到心理学里的“后见之明”——事后回头看&#xff0c;总觉得事情本该显而易见。但在数字取证这个圈子里&#xff0c;Hindsight 是另一张名片&#xff1a;它是一个开源的浏览器取证工具&#xff0c;专门用来解析 Chrome /…

作者头像 李华
网站建设 2026/10/2 22:30:32

OTFS信道估计实战:PRS-OMP算法在高速移动场景下的落地要点

简介&#xff1a;本资源是一份面向通信工程高年级本科生、研究生及无线通信方向研究者的学术型技术文档&#xff0c;聚焦高速移动场景下OTFS调制系统的信道估计算法优化问题。针对OFDM在高铁、无人机等高多普勒环境下因时变信道导致的ICI严重、信道估计失准等痛点&#xff0c;文…

作者头像 李华
网站建设 2026/10/2 22:30:00

策略梯度算法详解:从REINFORCE到PPO的原理、推导与实战排查

策略梯度这块内容&#xff0c;我其实很早就想写一篇足够系统的梳理了。外面讲策略梯度的文章要么只讲一个PPO&#xff0c;要么数学推导一笔带过&#xff0c;要么代码和理论完全对不上&#xff0c;初学者想靠碎片信息搭起完整认知框架&#xff0c;确实很难。这篇我打算换个思路&…

作者头像 李华
网站建设 2026/10/2 22:29:49

Web拍卖系统开题答辩复盘:并发控制与应答策略全解析

又到了一年一度的毕业设计开题季&#xff0c;我后台收到了不少私信&#xff0c;问得最多的一句话是&#xff1a;"开题答辩到底会问什么&#xff1f;我的系统还没开始写&#xff0c;怎么回答&#xff1f;" 今天我就拿一个特别典型的题目——基于web的拍卖系统设计与实…

作者头像 李华