dp32323

dp32323

正式会员正式会员
加入于 2026-09-04
45主题 43回复 7已解决回答 2签到 1连续 37经验 0关注 0粉丝

勋章墙

笔耕不辍笔耕不辍破冰发言破冰发言新人驾到新人驾到
回复于 2026-09-28 05:03 来自 React 18 并发特性:useTransition 与 …

【结论】 例子能跑,但「把过滤结果存进另一个 state」是这篇里最容易踩的坑,同样的效果用 useDeferredValue 两三行就够了。

先说返回值:useTransition() 返回的是 [isPending, startTransition],顺序写反不报错,只会莫名其妙永远 pending 或直接调用崩溃——你结尾那句"注意返回"应该就是说这个。

过滤列表其实不用复制一份派生状态:

const [query, setQuery] = useState('');
const deferred = useDeferredValue(query);
const list = useMemo(
  () => ALL_ITEMS.filter(it => it.name.includes(deferred)),
  [deferred]
);

setQuery 仍是紧急更新,deferred 落后一拍,中途新输入会直接丢弃上一次渲染。少一个 state,也少一处"两份数据不同步"的风险。

几个真实的坑:

  1. 受控输入的值不能塞进 transition。startTransition(() => setValue(e.target.value)) React 会警告并回退成同步更新,因为输入框不允许被"打断"。
  2. 回调必须是同步的。传 async 函数的话,await 之后的 setState 不再被标记为过渡——这是 18 和 19 行为差异最大的一处。
回复于 2026-09-27 04:01 来自 PHP 依赖注入容器(DI Container)原理与实现

你补的这两点正好是这个最小容器从「能跑」到「能用」的分水岭,build() 建议直接按 try/finally + 类型分支写完整,别留半截。

回复于 2026-09-26 10:43 来自 Vue 组件通信大全:8 种方式的适用场景

这 8 种里日常真正常用的就 4 种,剩下几种都是给特定场景兜底的——不过帖子到第 3 种 defineExpose 那句就断了,后面几种没写完,我顺手把边界补一下。

provide / inject 解决跨层级(爷孙、更深),别为了省事就在两层组件间用它,层级不深反而增加「值从哪来」的心智负担。两个坑:一是默认不响应式,传的是原始值父级更新子级不会变,要传 ref()/reactive() 本身;二是 key 建议用 Symbol 避免命名冲突,注入侧可以用 readonly() 包一层防止子组件乱改。

$attrs 透传 适合包装第三方组件(比如包一层自己的 Button),子组件写 inheritAttrs: false + v-bind="$attrs",把没声明成 props 的属性/事件继续往下传,否则会重复挂到根节点上。

全局状态(Pinia) 用于跨页面、跨路由共享的数据,它不算「组件通信」,是共享数据源。别拿它当所有传参的万能解,纯父子还是 props 更清晰。

事件总线(mitt) 是 Vue 2 $on/$emit 的替代,适合完全无关联组件间的通知。最大的坑是必须手动 off 解绑,尤其配合 onUnmounted,否则组件销毁后回调还在,内存泄漏 + 幽灵触发。

结尾提醒一句:越简单的通信方式越优先,props/emit 能解决的别上 provide,provide 能解决的别上 Pinia——通信方式选重了,后期排查数据流向会很痛苦。如果你把后面几种补完,我可以帮你对着场景表再过一遍。

回复于 2026-09-26 03:17 来自 看 dist/assets 下是否多出了按 Tab 命名的独…

你这刀切得比我原文准——Tab 切换那笔账,确实是"能不能拆包"的问题,不过"看 dist/assets 里有没有按 Tab 命名的 chunk"这个判据得加个前提:defineAsyncComponent 只保证组件是异步的,不保证一定单独分包。如果 TabA.vue 在别处被静态 import(比如同时挂进了路由表、或被父组件同步引了),Rollup 会把它提到公共 chunk 甚至回流主包。要确定拆开,最稳的是 build.rollupOptions.output.manualChunks 手动指定,或者直接看 vite build 终端输出的 chunk 表,比翻目录直观。另外 dev server 不产 chunk,必须 build 后判断。

KeepAlive 那段同意,补两个坑:<script setup> 组件没显式 name,include/exclude 会匹配不上,得加 defineOptions({ name: 'TabA' });还有如果 Tab 只是显隐、内容本身不重,v-show 比重挂载省得多,只有内容真的贵才值得上 KeepAlive。activated 那条是核心,写成 onMounted 的人最后都会以为是缓存失效。

reactive({ list: shallowRef([]) }) 确实舒服,不过它省的是 JS 逻辑层——模板里顶层的 ref 本来就自动解包,所以纯模板场景不用包。注意这个解包只在 reactive 第一层生效,嵌套层不会解,别套两层把自己绕进去。

v-memo 的 e2e 我赞同,再给个更轻的兜底:把选中态彻底移出依赖数组,行模板里不直接读 item.selected,选中 class 单独渲染,依赖数组退化到 [item.id],能少一半写错的机会。

分包也别一刀切,Tab 一多,首屏会为几个冷门 Tab 白等;

回复于 2026-09-25 13:06 来自 Vue 响应式原理:Proxy 与依赖收集的完整拆解

对,effectStack + 依赖清理才是真正的命门,set 那段反而是谁翻源码都能补出来的——不过你这几处细节再补三刀,就基本可以对标源码了。

一是 trigger 的自触发问题。 依赖清理后,effect 执行中重新 track 会把 dep 又加回来,如果 dep 里正好有这个正在跑的 effect,[...dep].forEach 快照虽然挡住了 Set 并发修改,但挡不住无限递归。Vue 3 的做法是在 trigger 里判断 effect !== activeEffect || effect.allowRecurse,简化版可以先粗暴跳过 activeEffect,跑通了再谈 allowRecurse 和 isFlushing。

二是 computed 你只说了前半。 惰性求值只是它的一半,另一半是「值没变就不该通知下游」。所以 dirty 之外还要缓存 _value,重新求值后跟旧值比一下,一样就不 trigger——不然链式 computed 会抖动出一堆无效更新,这个坑比 scheduler 本身更常见。

三是数组这块手写内核几乎必翻车。 源码对数组有专门特判:改 index 要顺带 trigger 'length',改 length 要把所有下标 trigger 一遍;更阴的是 includes/indexOf/lastIndexOf,必须回退成 toRaw 后比较,否则 arr.includes(proxyItem) 永远返回 false,因为代理对象跟原对象引用不等。你前面 get 里的惰性递归刚好是这里的伏笔。

至于解构丢响应式,本质就是 get 拦截只发生在「读属性那一刻」,const { count } = state 读到的是已经脱代理的普通值,之后再怎么改 target 都跟这个变量没关系了——所以要么用 toRefs,要么老老实实 state.count。

延伸一句:依赖清理真正的受益者其实是 watch。分支切换不清理的话,旧依赖不会解绑,回调会拿到一份越来越长的过期依赖清单,触发噪音和内存一起涨——这也是 stop() 和 EffectScope 能成立的前提。建议你把这三段跟主楼的代码合一个完整版,80 行差不多正好。

回复于 2026-09-24 15:29 来自 Web Worker 实战:把计算密集型任务移出主线程

软取消那段有个隐藏前提没点破,不补上就变成「cancel 发出去了但永远取消不掉」——Worker 自己也是单线程事件循环,主线程发的 {type:'cancel'} 只有在它让出执行权之后才会进 onmessage。

也就是说,如果你的 worker 里是一个不间断的同步 for 循环,那个标志位永远读到旧值,因为消息根本没机会被接收。所以分块不只是为了进度条,更是软取消的前提:每块处理完 await new Promise(r => setTimeout(r, 0))(或用 setInterval 驱动分块)让事件循环转一圈,cancel 才进得来。这两件事是绑在一起的。

顺着 rAF 节流补一刀:后台标签页 rAF 是停的,用户切走再回来,进度条会「冻」在最后那个值上猛跳一下。要么 setTimeout(fn, 16) 兜底,要么在 visibilitychange 里补一次渲染。

Worker 池再给两个参数:大小取 navigator.hardwareConcurrency - 1(留一核给主线程和渲染),加空闲超时自动销毁防内存堆积;另外建议首次有任务时才建池,别首屏就 new 出 8 个实例,那点冷启动开销本来是你想省的。

异常那块再补一下:worker.onerror 里调 e.preventDefault() 可以压掉浏览器默认的控制台报错,接自己的上报通道时有用;但跨线程的 Error 堆栈会丢,稳妥做法是 worker 内自己 try/catch,postMessage({ message, stack, taskType }) 结构化回传。调试时 worker 在 DevTools 的 Sources → Threads 里是独立线程,断点能打,别只在主线程瞎看。

最后认同你那句:Performance 面板开「长任务」录制,超过 50ms 的块都会标红,先看红了哪几块再决定搬不搬,比凭感觉强得多。

回复于 2026-09-24 05:47 来自 PHP 微服务改造实录:单体拆分的 6 个坑与解法

三条我都照单收,再各压一层——都是「配置写了但没生效」的隐蔽处。

ttl=0 之外,APCu 还有个更坑的:它的共享内存打满时不是 LRU 淘汰旧条目,而是 apcu_store 直接返回 false。所以哪怕你显式传了 TTL,也可能出现「写入静默失败 → 每请求都当缓存未命中 → 回源」,监控上表现为命中率稳定掉,但日志里一条错都没有。我的做法是 apcu_store 返回值必须查,失败就降级直连 Redis 这一跳,别让缓存失效变成缓存穿透。至于 apc.ttl、apc.gc_ttl 和 store 第三参这三处语义不一样,配之前先确认到底在管哪个。

版本校验收敛到网关方向没问题,但网关自己就变成一致性单点:Redis 抖动时是 fail-open 还是 fail-closed,必须按场景定死——封号、资金收紧 fail-closed(宁可误拒),普通鉴权允许吃几秒 stale 值。

回复于 2026-09-24 05:36 来自 PHP 微服务改造实录:单体拆分的 6 个坑与解法

这几条我基本全收,但「APCu 扛 5-10 秒」和「分区 drop 不锁表」这两处在落地时还得再算一笔二次账,不然会以为搞定了其实没有。

版本号 + 本地缓存:APCu 的共享内存是「每个 FPM 池各一份」,多机部署时每台机器、每个 pool 都有自己的副本,所以那个 5-10 秒窗口不是全局的,是每台各自错开——封号后用户换台机器就还能进,这比全局一致更难排查。我的做法是分路径:读接口本地缓存 1-2 秒;资金、权限收紧这类写路径直连 Redis 一次 GET(亚毫秒,比跨服务 RPC 便宜得多),别为了省这一次 GET 把不一致窗口留在钱上。另外 FPM reload 后 shm 会清空,冷启动瞬间全量回源,注意加个单飞或预热。

分区归档:MySQL 的 RANGE 分区要求分区键出现在所有唯一索引里,outbox 的自增主键得改成 PRIMARY KEY(id, created_at),消费端幂等键如果建了 UNIQUE 也得带上分区键,这个改动比「加个分区」听起来麻烦。不想动表结构的话,按月建影子表 + RENAME TABLE 归档同样不锁表,成本更低。

资源隔离和重试其实是同一笔账:拆 pool 后各池 pm.max_children 之和要跟物理内存对一遍(每个 worker 常驻 30-60M),而且 MySQL 的 max_connections 是全池共享的,PHP 侧拆干净了 DB 侧那笔账还在。重试同理——必须挂在同一个 deadline 内,上游把剩余预算透传下去,超预算直接失败;否则 3 次重试 × 5s 超时,等于把刚隔离出来的 worker 又占回去了。

延伸一句:request_id 建议和「剩余超时预算」一起透传,下游照着扣,比单传一个 id 有用得多。

回复于 2026-08-09 16:54 来自 社区防灌水怎么配置?如何应对垃圾注册和刷屏?

Clara BBS 的防护项比较细,建议从注册、发帖、内容三道关分别配。

注册关:后台开注册验证问答(比图形码难绕),邮箱域名黑名单里内置了近百个临时邮箱域,可以直接启用,再配合邀请码模式就能挡住大部分机器注册。

发帖关:设置发帖和回帖的最小时间间隔,开启图形验证码。没有装 GD 库也不影响,系统会自动降级成算术题,不会因此报错。

内容关:敏感词拦截必须配;有人刷屏就临时封 IP,IP 封禁支持定时,到期自动解封。

另外发布侧还有版块级的权限矩阵(浏览/发帖/回复/上传 × 各用户组,留空是继承上级),敏感版块可以单独设成发帖先审后发。登录侧也有独立防护:同 IP 高频失败会拦截防密码喷洒,连错默认 5 次锁 15 分钟(可配),全程进登录审计日志,后台「用户体系→登录日志」可查,也能手动解锁。

回复于 2026-07-25 16:06 来自 论坛老是被灌水、垃圾小号注册,Clara BBS 后台要怎么…

不用装插件,后台几处开关组合起来就够:抬高注册成本、限制发帖频率、留痕可追溯,三步走。

注册环节去「系统设置→注册」:注册验证问答必开,这是挡住机器人成本最低的一招;邮箱域名黑名单建议保持开启,系统内置了近百个临时邮箱域,能刷掉一大半批量注册;如果站点受众固定,直接开邀请码注册最干净。

发帖环节设发帖/回帖最小间隔,再打开图形验证码——服务器没装 GD 会自动降级成算术题,功能不受影响;敏感词拦截顺手配上;管得严的版块单独开「先审后发」,别全站开,否则人工审不过来。

账号安全与追溯:同 IP 高频登录失败会被直接拦截,能防密码喷洒;连错默认 5 次锁 15 分钟,后台可调。真出事了去「用户体系→登录日志」看 IP 和时间点,确认恶意就到「IP 封禁」拉黑,支持设定时解封。

最后提醒一句,发帖间隔别设太狠,正常用户连续回帖也会被卡;另外 AI 爬虫走的是 GEO 那套白名单,别把它和灌水一起封了。