videojs v10 源代码系列解读:11 · `@videojs/store`:目标附加式响应状态

地图打印与 GeoTIFF 导出:框选区域、矢量标绘、一键出图 空间智能实验室 Vol.19:地图打印与 GeoTIFF 导出:框选区域、矢量标绘、一键出图 「把这块区域导成一张带地理坐标的高清图」是个很朴素的需求,但朴素不等于简单。你要在浏览器里框一个框、画几个箭头标个文字、叠一层 阅读详情

进入卷二的最后两篇——@videojs/store。这是 v10 的响应式状态基石。整个播放器的状态都建立在它之上。它有两个最特别的设计:一是「目标附加式」(状态先于 target 存在),二是跨容器的微任务批处理。这篇讲状态原语 createState 和 store 工厂,下一篇讲 slice/combine/selector。

先理解「目标附加式」

大多数状态库(Redux、Zustand)的状态和「消费者」是绑定的。但 v10 的 store 有个反直觉的设计:状态在 target(媒体元素/容器)存在之前就可以创建。

为什么这么设计?因为播放器经常处于「先有 UI、后加载媒体」的状态。比如页面渲染时播放按钮就显示了,但 <video> 还没 attach。这时候播放器状态(paused、currentTime)得能存在,只是值为初始默认。等媒体 attach 了,再通过 attach(target) 把状态和真实媒体连起来。

反过来,detach 把状态重置回初始值。这让「切换媒体源」变得干净——detach 旧的、attach 新的,状态自动重置。

这种「状态可以脱离 target 存在」的模型,就叫「目标附加式」(target-attached)。

createState:状态容器 + 跨容器批处理

状态原语在 store/src/core/state.ts(111 行)。核心是 StateContainer 类 + 一套模块级批处理设施。

模块级批处理设施(state.ts:20-33)

这是我觉得最值得讲的部分。看顶层这几个绑定:

let isFlushScheduled = false;                    // L20
function scheduleFlush(): void {                  // L21-25
  if (isFlushScheduled) return;                   // L22 — 守卫
  isFlushScheduled = true;                        // L23
  queueMicrotask(flush);                          // L24 — 只调度一次
}

const pendingContainers = new Set<StateContainer<any>>();   // L27

export function flush(): void {                   // L29-33
  isFlushScheduled = false;                       // L30 — 重置
  for (const container of pendingContainers) container.flush();  // L31
  pendingContainers.clear();                       // L32
}

pendingContainers 是个模块级 Set,以 StateContainer 实例为键。scheduleFlush 用 isFlushScheduled 守卫,保证整个进程的 queueMicrotask(flush) 最多入队一次。

这里的精妙在于 Set 的去重:同一个容器的 patch 被调 10 次,第 2-10 次只是 add 已经存在的同一对象——不重复,flush 只遍历它一次。而且这是跨容器的——容器 A 和容器 B 的 patch 会在同一个微任务里一起 flush。

所以你在同一个同步 tick 里写 A.patch(); A.patch(); B.patch(); A.patch();,结果是 pendingContainers = {A, B},一个微任务,A 和 B 各通知监听器一次。十次 patch,两次通知。

StateContainer 类(state.ts:37-102)

三个私有字段(L38-40):

  • #current: T——实际值,通过 Object.freeze 保持不可变。
  • #listeners = new Set<StateChange>()——订阅者集合。
  • #pending = false——是否已脏等待 flush。

构造函数(L42-44)冻结初始对象:

this.#current = Object.freeze({ ...initial });

展开会丢弃原型并拷贝自身可枚举属性;freeze 让任何试图改 .current 的消费者静默失败(或严格模式抛错)。不可变是强制的。

patch(partial)(L50-70):

patch(partial: Partial<T>): void {
  const next = { ...this.#current };              // L51 — 克隆冻结状态
  let changed = false;                            // L53
  for (const key in partial) {
    if (!hasOwnProp.call(partial, key)) continue;  // L56 — 跳过继承键
    if (!Object.is(this.#current[key], partial[key])) {  // L60 — Object.is 判变
      next[key] = partial[key];                    // L61
      changed = true;                              // L62
    }
  }
  if (changed) {                                   // L66 — 没变就什么都不做
    this.#current = Object.freeze(next);
    this.#markPending();
  }
}

关键:L60 用 Object.is 检测每个键是否真变(正确处理 NaN 和 ±0)。只有真变了才赋值。L66 的 if (changed)——partial 里没有任何变化时,什么都不会发生:不 freeze、不入队、不通知。

#markPending()(L97-101):

#markPending(): void {
  this.#pending = true;
  pendingContainers.add(this);           // L99 — 加入模块级集合
  scheduleFlush();                       // L100 — 调度单次微任务
}

所以 patch 本身从不直接通知监听器。它只把容器注册到 pendingContainers 并确保安排一次 flush。真正的通知在微任务的 flush 里。

flush() 实例方法(L91-95):

flush() {
  if (!this.#pending) return;            // L92-93
  this.#pending = false;
  for (const fn of this.#listeners) fn(); // L94 — 通知,无参
}

监听器收不到参数——必须自己从 .current 读新值(这是 StateChange = () => void 的设计,L3)。

subscribe + AbortSignal 取消(state.ts:72-89)

subscribe(callback: StateChange, options?: SubscribeOptions): () => void {
  const signal = options?.signal;
  if (signal?.aborted) return noop;              // L74 — 已 abort 不注册
  this.#listeners.add(callback);                  // L76
  if (!signal) return () => this.#listeners.delete(callback);  // L78-80
  const onAbort = () => this.#listeners.delete(callback);
  signal.addEventListener('abort', onAbort, { once: true });    // L83
  return () => {                                  // L85-88
    signal.removeEventListener('abort', onAbort);
    this.#listeners.delete(callback);
  };
}

又是 AbortSignal 取消模式(07 篇讲过)。几个细节:

  • L74 已 abort 直接返回 noop(来自 utils/function),不注册。
  • L83 { once: true }——abort 监听器触发后自动从 signal 移除。
  • L85-88 返回的 detach 函数双向清理——无论 abort 还是手动 detach,监听器注册表和 signal 都清干净。

createState<T>(initial)(L104-106)只是 return new StateContainer(initial) 的薄包装,返回的是 WritableState<T> 接口而非具体类。

store 工厂:柯里化 + 目标附加

store/src/core/store.ts(178 行)是 store 的核心。

为什么是两层柯里化(store.ts:13-17)

export function createStore<Target = unknown>(): <State>(
  slice: Slice<Target, State>,
  options?: StoreOptions<Target, State>
) => Store<Target, State> {
  return <State>(slice, options = {}): Store<Target, State> => { ... };
}

两层函数的原因是类型变量分阶段绑定:调用方先 createStore<MediaEl>() 锁定 Target,再传 slice 时让 State 自动推导。如果用一个函数同时收两个泛型参数,就得手动写 State,丧失推导。零运行时成本,纯类型技巧。

Store 符号 Symbol.for('@videojs/store')(L9)是进程全局的,所以 isStore(L150-152)跨 realm/iframe 也能工作。

闭包状态(store.ts:21-28)

内层函数捕获:

let target: Target | null = null;                    // L21 — attach 前为 null
let destroyed = false;                               // L22
const setupAbort = new AbortController();            // L24 — 进程级生命周期信号
const signals = new AbortControllerRegistry();       // L25 — attach 作用域注册表
let state: WritableState<State>;                     // L28 — 容器,稍后赋值

注意 state 声明时未初始化(L28),在构建 slice 状态后才赋值(L45)。这种声明和赋值分离,是为了让 StateContext.get/set(L41-42)通过闭包引用稍后的 state。

构建初始状态(store.ts:30-45)

validate()(L30-33):destroyed 抛 DESTROYED 错,target 为空抛 NO_TARGET 错。

StateContext 对象(L35-43)字面量构建:

  • target: () => { validate(); return target! }(L36-39)——每次调都重新校验,attach 前读 target 会抛错。
  • signals(L40)——直接引用注册表。
  • get: () => state.current(L41)。
  • set: (partial) => state.patch(partial)(L42)。

然后 const initialState = slice.state(ctx)(L35),state = createState(initialState)(L45)。

重要顺序:slice 的 state() 在 state = createState(...) 之前调用,但 ctx 的 get/set 关闭了 state 绑定。所以 slice.ts:43-46 的 JSDoc 明确警告:get/set「在 action 闭包内部使用是安全的(在 state() 初始化期间不安全)」——state() 初始化期间别调 ctx.set(),但你返回的 action 闭包可以稍后调。

状态键暴露为 store getter(store.ts:66-71)

for (const key of Object.keys(initialState)) {           // L66
  Object.defineProperty(store, key, {
    get: () => state.current[key],                       // L68 — 始终读最新值
    enumerable: true,                                    // L69
  });
}

每个状态键变成 store 上的惰性、实时访问器。每次访问 store.paused 都重新读 state.current.paused,所以批处理 flush 后的修改立即反映。enumerable: true 让键出现在 Object.keys(store) 里,匹配 Store & State 的结构类型。

这就是为什么你能直接 store.paused 而不用 store.state.paused。

attach(store.ts:81-120)—— 04 篇详细讲过,这里强调几个点

  • L85 signals.reset()——在设新 target 之前 abort 所有旧附件信号,mint 全新 base。
  • L91 signal: signals.base——新的 attach 作用域 base 信号。
  • L104 slice.attach?.(attachContext)——触发所有 feature 的 attach(combine 扇出,12 篇讲)。
  • L119 返回 detach。

detach vs destroy(store.ts:122-134)

这两个容易混,我特意区分一下:

detach()(L122-127):可恢复。signals.reset() + target = null + state.patch(initialState)——状态重置回初始。store 可以再 attach 新 target。

destroy()(L129-134):终态。destroyed = true + 调 detach() + setupAbort.abort()——终止进程级 setup 信号。之后任何 attach 都在 L82 抛错。

区别就是:detach 后还能复活,destroy 是永久的。这和 element 的 hostDisconnected vs hostDestroyed(10 篇)是同一套思想。

store 的错误处理

reportError(store.ts:140-146):有 options.onError 就调,否则 console.error('[vjs-store]', error)。未处理的 slice 错误会变成可见诊断,不会静默崩溃。combine 的 attach 扇出(12 篇)会用到它。

小结

store 的状态层有几个我觉得很值得学的设计:

  1. 跨容器微任务批处理——模块级 pendingContainers Set + 单次 queueMicrotask(flush),同一 tick 多次 patch 只通知一次。
  2. 不可变强制——Object.freeze + 每次 patch 克隆。
  3. Object.is 判变——partial 里没真变就不入队、不通知。
  4. AbortSignal 取消订阅——双向清理防泄漏。
  5. 目标附加式——状态先于 target 存在,attach/detach 驱动 setup/teardown。
  6. detach 可恢复 vs destroy 永久——和 element 的双层销毁语义一致。
  7. 状态键实时 getter——store.paused 直接读最新值。

下一篇讲 slice 契约、combine 组合、selector 切片、以及 AbortControllerRegistry 的完整模型(07 篇讲过 utils 基础,12 篇讲 store 怎么用它)。

Fortify SCA 24.4 最新可用版 Fortify Static Code Analyzer(Fortify SCA) 是一款由 Micro Focus(原惠普旗下)开发的 静态应用安全测试(SAST)工具,专注于在软件开发早期通过分析源代码或编译后的二进制文件,识别潜在的安全漏洞、代码缺陷和合规性问题。以下是其核心特点与功能:核心功能 静态代码分析 在不运行程序的情况下,通过语法、语义分析检测代码中的安全漏洞(如 SQL 注入、跨站脚本(XSS)、缓冲区溢出等)和代码质量问题。 阅读详情

相关推荐

反无人机作战体系全解析:从低慢小探测到分层拦截

随着小型无人机在军事和民用领域的广泛使用,低慢小目标探测与拦截成为低空安全的核心挑战。传统防空体系面对成本低廉、飞行高度低、雷达特征小的无人机集群时,暴露出探测难、识别难、处置成本失衡等问题。为应对威胁,反无人机体系采用分层闭环设计,融合雷达、光电、声学与无线电侦测实现多源感知,通过电子战软杀伤与导弹、高炮、激光等硬杀伤手段协同配合,并借助自动化指控系统缩短“传感器到射手”的响应时延。该体系既可支撑战场要地防护,也能为民用低空安防提供参考。围绕分层反制逻辑,梳理探测、软硬杀伤、指控布防及实战短板,提供理解反

weixin_42533120的博客 334

Web 版翻译者资料库

本项目是桌面版《翻译者资料库》的 Web 移植版,基于 Spring Boot 3.3、Thymeleaf、Bootstrap 5.3 与 MySQL 8.0 构建。它面向翻译写作提供资料、词汇、句库、文本工具、对照制作、查找、备份恢复及旧库迁移等功能,完整继承桌面版核心业务规则,支持一键启动与离线访问。

howard2005的专栏 145

别再被网站识别成机器人了!用Python的undetected_chromedriver+Selenium实现无痕自动化

本文详细介绍了如何利用Python的undetected_chromedriver和Selenium实现无痕自动化,突破现代网站的反爬系统。通过修正浏览器指纹、模拟人类操作行为,并结合实战案例,帮助开发者构建难以被识别的自动化脚本,适用于电商监控等场景。

weixin_30367169的博客 253

Jackson 序列化 / 反序列化注解 完整版笔记

Jackson 序列化 / 反序列化注解 完整版笔记

handsome_sai的博客 283

立创开源桌面激光雕刻机DIY全解析:从悬臂结构、Arduino主控到Marlin固件

本文详细解析了基于立创开源平台的桌面激光雕刻机DIY全过程。重点介绍了悬臂式机械结构的设计与优化,阐述了采用Arduino Mega 2560主控与RAMPS 1.4扩展板的经典控制方案,并深入讲解了Marlin固件的关键配置步骤,包括电机步数计算与激光功能启用,为创客和电子爱好者提供了一套完整的从硬件搭建到软件调试的实战指南。

weixin_42510201的博客 386

WinDbg调试工具安装全攻略:从下载到配置的保姆级教程(Windows 10/11适用)

本文提供了一份详细的WinDbg调试工具安装与配置指南,适用于Windows 10/11用户。文章对比了WinDbg Preview独立版与SDK内置经典版的区别,并手把手指导从Microsoft Store或Windows SDK完成安装。核心部分重点讲解了如何配置符号文件路径与源代码路径,这是让WinDbg发挥强大调试功能的关键步骤,并针对常见安装问题提供了实用的排错方法。

hp777的博客 387

MCP 协议会成为前端 AI 工程的标准接口吗?

MCP(Model Context Protocol)正在成为连接 AI 模型与外部工具、数据源的一层通用协议。本文从 MCP 的核心机制出发,分析它在浏览器与前端工程中的落地边界,讨论它能否成为前端 AI 工程的标准接口,并给出当下更务实的架构选择建议。

m0_62937412的博客 361

NVIDIA Isaac Sim 联合 VS Code 开发调试环境配置

在 Visual Studio Code (VS Code) 中打开 Isaac Sim 软件包主文件夹,即可打开该工作区。注意建议在学习教程和示例时使用 Visual Studio Code。另请参见有关 VS Code 工作区的详细信息,请参阅 Visual Studio Code (VS Code) 文档。

2506_90492529的博客 3104

LangGraph 本地 build 频频翻车,最终在服务器跑通流式对话

先说结论:我本地那个,一次都没成功过。三次,三次,全挂在拉镜像上。而顺得像刚上过油。这就很像我高中那辆自行车:骑着能走,一上锁就坏。那天晚上我盯着终端里那行红字看了很久,想的不是技术方案,想的是——为什么同一个工具,可以一边让我觉得自己很行,一边让我觉得自己不太行。

人工智能AI技术 119

YOLOv11【第二章:核心模块进阶篇·第28节】DynamicHead动态检测头设计!

🏆 本文收录于专栏 《YOLOv11实战:从入门到深度优化》。本专栏围绕 YOLOv11 的改进、训练、部署与工程优化 展开,系统梳理并复现当前主流的 YOLOv11 实战案例与优化方案,内容目前已覆盖 分类、检测、分割、追踪、关键点、OBB 检测 等多个方向。

**My Coding Family** 1万+

Transformer 在自动驾驶中的应用:深度对比特斯拉(Tesla)、小鹏(XNet)、华为(ADS 2.0)在 Transformer 架构应用上的异同

自动驾驶汽车是当前人工智能和机器学习领域的热点研究方向之一。其核心技术之一就是基于深度学习的感知和决策系统。在感知系统中,目标检测和跟踪、语义分割、场景理解等计算机视觉任务至关重要。而在决策系统中,规划和控制模块需要对当前状态进行建模并做出相应的决策。近年来,Transformer模型在自然语言处理领域取得了突破性进展,并逐步被应用到计算机视觉等其他领域。相比于传统的卷积神经网络和循环神经网络,Transformer模型具有建模长距离依赖关系的能力,可以更好地捕捉输入序列中的全局信息。

AI天才研究院 864

海外平台频繁弹验证?从 DNS、WebRTC 到浏览器指纹的环境异常排查清单

在运营多地区海外平台账号时,即使代理节点通畅、TCP 握手成功,仍可能遇到频繁人机验证、地区限制、播放失败等异常。核心结论是:这类问题往往不在网络传输层,而在浏览器环境参数与网络出口之间的逻辑自洽性。本文从 DNS 泄漏、WebRTC 穿透、时区与语言匹配、Canvas/WebGL 指纹、TLS 特征五个维度,给出一份可复现的环境异常排查清单,并对比个人手动配置与工程化环境管理方案的适用边界。适用于跨境运营、多账号运维团队。

P1Browser【p1指纹浏览器】的博客 195

面经-2023-联发科MTK-数字芯片设计

面经-2023-联发科MTK-数字芯片设计

w0shishabi的博客 2944

智能问诊的流程

本文介绍基于FastAPI构建的医疗多Agent系统MediAgent,通过症状分析、诊断推理与治疗建议等模块协同,实现从问诊到报告生成的完整工作流。系统采用前后端分离架构,结合MySQL、RabbitMQ与Docker实现工程化部署,支持异步化验结果接入。虽当前状态暂存于内存,但已具备可扩展性,未来可通过Redis持久化、消息队列联动及医疗安全机制完善,应用于慢病管理、辅助决策等场景,推动AI在医疗流程中的落地应用。

Joey 的工作空间~ 780

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

信号链电路怎么选运放?奥特四款信号链运放解析【选型参考】

在模拟电路设计中,运放作为信号链的“第一道门槛”,其微小误差往往决定了整个系统的成败。

naNa_1120的博客 192

流式输出 SSE:实现 AI 对话打字机效果后端开发

摘要解决传统 HTTP 请求在 LLM 长文本生成时导致的客户端长时间阻塞与 TTFT 极高的问题。适用于需为 AI 应用提供打字机效果、优化交互体验的后端开发工程师。读完可获得基于 FastAPI 与 SSE(Server-Sent Events)技术的流式响应实现代码、协议规范及生产环境避坑指南。关键词SSE、流式输出、FastAPI、打字机效果、TTFT、大模型推理背景与问题大模型(LLM)生成完整回复通常耗时数秒至数十秒,传统请求-响应模型导致用户界面长时间无响应,体验极差。

2402_85729970的博客 184

基于 YOLOv8 的森林火灾检测系统(Web 版)

本项目面向林区早期火情识别场景,使用YOLOv8完成火焰(Fire)与烟雾(Smoke)两类目标的检测模型训练,并以FastAPI构建 Web 检测平台,把模型能力封装为可直接登录使用的系统。平台除常规的图片、批量、视频、摄像头检测外,还提供同图多权重对比、数据集浏览与标注核对、模型热切换,以及把训练搬到网页上的能力:可在浏览器配置超参数发起真实训练,训练中在线调整学习率、查看进度与动态曲线,训练完成后下载产物或直接应用到检测。

Michael_Jay的博客 64

身份验证漏洞专题(三):持久登录、密码重置与密码修改中的凭据绑定缺陷

密码登录与双因素认证是身份验证的主干流程,但一个完整的认证体系还依赖三类辅助机制:持久登录("记住我")、密码重置、密码修改。这三类机制的共同特征是:它们同样携带或验证凭据,同样直接决定认证状态的归属,但实现时获得的防护强度往往远低于登录页面本身。攻击者绕不开登录接口时,常常可以从这些辅助入口进入。

2401_85321170的博客 637

2026 年前端框架排行榜

前端框架是用于构建用户界面的工具集合,通过预设组件、网格系统和样式规范提升开发效率与可访问性。2026年主流框架包括:React(生态领先,适合全栈)、Next.js(SSR/SEO优化,主流选择)、Vue(学习友好,中小团队首选)、Angular(企业级稳定)、Svelte与Astro(性能突出)。趋势显示,服务端渲染、TypeScript、AI工具融合及轻量化成为关键方向。就业建议:优先学React+Next.js;追求易用选Vue;企业级项目可学Angular;追求极致性能可尝试Svelte或Astr

lxttzlove的博客 166

二进制木马免杀-Python 分离免杀与 Web 挂马

二进制木马免杀-Python 分离免杀与 Web 挂马

2503_94637398的博客 269

Vue 前端开发 AI 对话页面:流式消息渲染、文件上传

关键点是不能手动设置 Content-Type 为 multipart/form-data,必须由浏览器自动生成带有 boundary 的请求头,否则后端无法解析。思路:将对象序列化为 JSON 字符串,作为 FormData 的一个普通字段(如 formData.append(‘payload’, JSON.stringify(data))),后端接收该字段后再反序列化。若上传大文件(>10MB),需实现独立的分片上传接口,在提交对话前先获取文件 id 或 url,再随文本发送引用。移除输入框的禁用态;

2402_85729970的博客 393

在Open-WebUI中提升向量化速度

让Open-WebUI AI聊天的向量化过程不超过10秒,而不是默认的20+分钟……

xyzzklk的博客 582

Writeup 4 0xGame 2021 · command (WEB)

前端限制不可信:永远不要相信前端的任何限制(如disabled按钮、JS 验证等)。攻击者可以直接构造 HTTP 数据包绕过前端校验,所有的安全限制必须在后端实现。命令执行/注入(RCE):Web 应用如果直接调用系统命令且未对输入进行严格过滤,攻击者可以利用;||等符号拼接恶意命令。绕过技巧:在 CTF 中,如果遇到命令执行被过滤,常见的绕过方式包括:${IFS}$IFS$9等。。cat /fla?或cat /fl*。Linux 基础。

hengdonghui的博客 131

使用HTML+CSS+JavaScript实现文件上传弹窗

使用HTML+CSS+JavaScript实现文件上传弹窗

bjzhang75的博客 256

从一次检测到持续验证,重新认识 Web 应用安全测试

围绕漏洞修复后再次出现的问题,结合 Burp Suite DAST 与 GraphQL 示例,解析周期检测、证据管理和整改复测的实现思路。

m0_75220675的博客 321

ArkWeb 混合开发的内存清理与桥接陷阱

起初我以为是 H5 内部的定时器没有清理,结果接了 DevEco Studio 的 Profiler 一看,全是 ArkTS 侧的原生对象没有释放。改用 RESIZE 后,键盘弹出只会压缩窗口高度,底部的输入框会自适应滚动到可视区域,页面顶部的元素则岿然不动。综上所述,混合开发绝不是简单的拼接,管好桥接的生命周期,做好跨域兜底,才是稳定运行的前提。如果不去主动干预,它会把你的原生内存吃得连骨头都不剩。在 H5 的输入框打字时,软键盘一弹出来,整个 Web 页面就被顶得飞起,连顶部的原生导航栏都不见了。

2502_91284977的博客 235

热工测点布线改造难题?IDCB 远程 I/O 采集前端简化现场布线

IDCB智能数据采集前端基于Modbus-RTU协议,支持热电偶、热电阻、直流信号多类型混采,单设备多通道接入,减少模块选型压力。双总线冗余设计保障数据连续性,适配锅炉、窑炉等高可靠性工况。采用总线式布线替代点对点接线,大幅减少电缆用量,配合组态软件或LCD面板,实现远程监控与现场调试便捷化。适用于脱硫脱硝、就地监控及系统改造项目,具备抗干扰、隔离保护、掉电存参等特性,有效提升热工测点采集系统的稳定性与运维效率。

wxyzzdh200988的博客 192
上一篇: videojs v10 源代码系列解读:10 · `DestroyMixin`:双重 rAF 延迟销毁
下一篇: videojs v10 源代码系列解读:12 · Slice 组合、选择器与取消注册表
逻辑玩家
博客等级 码龄19年 75粉丝 108原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值