videojs v10 源代码系列解读:09 · 异步更新调度:Promise 门控的微任务批处理

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

上一篇讲了 ReactiveElement 的整体结构,这篇我们钻进它最精妙的部分——更新调度。v10 怎么做到「同一个 tick 里改十个属性,只渲染一次」?答案是 Promise 门控 + async/await 产生的微任务批处理。这套机制看着简单,但有几个细节特别讲究。

先说问题:为什么需要批处理

Web Components 有个性能陷阱:如果你在同一个同步代码块里改了十个响应式属性,每个 setter 都触发一次更新(重渲染),就会渲染十次——极其浪费。

正确做法是「批处理」:把这些变更收集起来,等当前同步代码跑完(微任务边界),一次性渲染。Lit 做到了,v10 的 element 也做到了,但实现方式有个细节差异——08 篇提过,它不靠 queueMicrotask,而是靠 async/await。

构造函数:构建 Promise 门控

一切从构造函数开始。reactive-element.ts:95-119:

// L98-103 — 构建门控
this.#updatePromise = new Promise<boolean>(
  (res) => (this.enableUpdating = res as (requestedUpdate: boolean) => void)
);

这一段我反复读了三遍才理解。它干了什么?

构造函数创建一个未 resolve 的 Promise,把这个 Promise 的 resolve 函数赋值给实例属性 this.enableUpdating。注意这覆盖了 L126 行原型上的 no-op 方法 protected enableUpdating(_requestedUpdate: boolean): void {}。

所以构造之后,this.enableUpdating 就是那个门控 Promise 的 resolve 函数。这个 Promise 一开始是 pending 的——像一扇关着的门。

注释(L98-100)写得很明确:「初始化为未解决的 Promise,以确保元素在首次更新前已连接。」

为什么要这扇门

接着构造函数还做了两件事:

实例属性升级(L105-114):

const { props } = resolve(this.constructor);
for (const name of props.keys()) {
  if (Object.hasOwn(this, name)) {                    // 类字段在实例上设了值
    (this.#instanceProperties ??= new Map()).set(name, this[name]);  // 捕获
    delete this[name];                                 // 删除实例值,让原型 getter/setter 重新可达
  }
}

类字段初始化器(或连接前赋值)会把值放在实例上,这会遮蔽原型访问器。构造函数把这些自身值捕获到 #instanceProperties,然后 delete 掉实例值,让原型 getter/setter 重新可达。捕获的值稍后在 performUpdate 里恢复。

入队首次更新(L116-118):this.requestUpdate();。

但这一步什么都跑不了——因为门控 Promise 还没 resolve。

connectedCallback:开门

reactive-element.ts:152-158:

connectedCallback() {
  this.enableUpdating(true);   // ← 开门!resolve 那个门控 Promise
  for (const c of this.#controllers) c.hostConnected?.();
}

元素挂到 DOM 时,connectedCallback 调用 enableUpdating(true)——这 resolve 了构造时创建的门控 Promise。于是之前卡在 await this.#updatePromise 的首次更新得以继续。

这就是「保证元素挂到 DOM 后才渲染」的机制。第一次 update 必须等到 connectedCallback。在它之前,所有 requestUpdate 都被门控拦住,不会真跑。

requestUpdate:去重 + 入队

reactive-element.ts:202-209:

requestUpdate(name?: string, oldValue?: unknown): void {
  if (name !== undefined) {
    this.#changedProperties.set(name, oldValue);     // L204 — 记录旧值
  }
  if (this.isUpdatePending) return;                   // L207 — 去重守卫
  this.#updatePromise = this.#enqueueUpdate();        // L208
}

这是批处理的核心:

  1. 如果传了属性名,记到 #changedProperties(L204),供 willUpdate/update 里查 changed.has(...)。
  2. L207 去重守卫:已经有更新挂起了,就直接 return。这是批处理的关键——同一个 tick 里后续的属性变更只记 changedProperties,不再调度。
  3. 只有第一次变更(没有挂起更新时),才 #updatePromise = this.#enqueueUpdate()(L208)。

所以你连改十个属性:第一次进 L208 入队,后九次都走 L207 直接 return。最后只渲染一次。

#enqueueUpdate:串行化 + 门控(最精妙的部分)

reactive-element.ts:216-238:

async #enqueueUpdate(): Promise<boolean> {
  this.isUpdatePending = true;                              // L217
  try {
    await this.#updatePromise;                              // L222
  } catch (e) {
    Promise.reject(e);                                      // L226
  }
  const result = this.scheduleUpdate();                     // L229
  if (result != null) {                                     // L233
    await result;                                           // L234
  }
  return !this.isUpdatePending;                             // L237
}

L222 的 await this.#updatePromise 一身二职:既是门控,也是串行化器。我花了点时间才想明白为什么。

关键看 requestUpdate 的 L208 是怎么求值的:this.#updatePromise = this.#enqueueUpdate()。右侧 this.#enqueueUpdate() 会先被求值——它的函数体在第一个 await(L222)之前是同步运行的。所以当 L222 读 this.#updatePromise 时,它读到的是旧值(L208 的重新赋值还没提交)。

两种情况:

  • 构造时调的 requestUpdate:旧值是 L98-103 的未 resolve 门控 Promise。所以 #enqueueUpdate 卡在 L222,直到 connectedCallback 的 enableUpdating(true) resolve 它。
  • 后续更新:旧值是上一次更新的 #enqueueUpdate Promise。所以每次更新都 await 上一次更新——实现串行化。

这一行同时实现了「首次更新等连接」和「更新之间串行化」。非常优雅。

L237 的返回值:驱动 updateComplete

return !this.isUpdatePending(L237)决定了 updateComplete 的布尔值。

正常情况:performUpdate 把 isUpdatePending 清零(L294)后,没人再请求更新,L237 返回 !false === true。

但如果 updated()/firstUpdated() 里又改了属性呢?requestUpdate 会再次把 isUpdatePending 翻回 true 并重新赋值 #updatePromise。于是 L237 返回 !true === false——这正是 updateComplete 为 false 的文档化行为(L350-354):表示「又触发了新更新,你得再等」。

performUpdate:真正干活

reactive-element.ts:268-306:

performUpdate() {
  if (!this.isUpdatePending) return;                        // L272
  if (!this.hasUpdated && this.#instanceProperties) {       // L275-280 — 恢复实例属性
    for (const [name, value] of this.#instanceProperties) {
      this[name] = value;                                    // 走 setter → 记到 changedProperties
    }
    this.#instanceProperties = undefined;
  }
  const changed = this.#changedProperties;                  // L282
  this.willUpdate(changed);                                  // L282-284
  for (const c of this.#controllers) c.hostUpdate?.();      // L286-288 — 广播 controller
  this.update(changed);                                      // L290 — 子类在这里渲染
  this.#changedProperties = new Map();                       // L293
  this.isUpdatePending = false;                              // L294
  for (const c of this.#controllers) c.hostUpdated?.();     // L296-298
  if (!this.hasUpdated) {                                    // L300
    this.hasUpdated = true;                                  // L300 — 先置 true
    this.firstUpdated(changed);                              // L303 — 再调
  }
  this.updated(changed);                                     // L305
}

几个我读时留意的点:

  • L275-280 实例属性恢复:把构造时捕获的 #instanceProperties 通过 setter 赋值回去。因为旧值是 undefined,所以触发 requestUpdate,但 isUpdatePending 已经是 true,所以只记 changedProperties 不重新入队。只在首次更新做(!this.hasUpdated)。
  • L293-294 清理:重置 changedProperties 和 isUpdatePending。在这之后调的 requestUpdate 会开始新的入队。
  • L300 hasUpdated 顺序:hasUpdated 在 firstUpdated 之前置 true。符合 Lit 语义。

scheduleUpdate:可覆盖的调度点

reactive-element.ts:256-258:

protected scheduleUpdate(): void | Promise<unknown> {
  this.performUpdate();
}

这是个可覆盖的调度点。JSDoc(L240-255)甚至给了示例:子类可以返回 requestAnimationFrame 的 Promise 来推迟渲染到下一帧。如果子类返回 Promise,#enqueueUpdate 的 L233-234 会 await 它。

这是个很好的扩展点——比如你想要「所有更新都 rAF 对齐」,覆盖 scheduleUpdate 就行。

updateComplete:等更新完成的句柄

reactive-element.ts:355-357:

get updateComplete(): Promise<boolean> {
  return this.#updatePromise;
}

返回当前的 #updatePromise。因为它每次 requestUpdate(L208)都被重新赋值,所以始终反映最新更新的状态。controller 和外部代码 await element.updateComplete 就能等到当前更新批次完成。

串个完整流程

把全流程串一遍,假设你连改三个属性 a、b、c:

  1. this.a = 1 → setter requestUpdate('a', undefined) → L207 没挂起更新 → L208 #updatePromise = #enqueueUpdate() → #enqueueUpdate 同步跑到 L222 await this.#updatePromise(旧门控)挂起。
  2. this.b = 2 → setter requestUpdate('b', undefined) → L204 记 changedProperties → L207 isUpdatePending 已 true → 直接 return。
  3. this.c = 3 → 同上,直接 return。changedProperties 现在有 a/b/c。
  4. 当前同步代码跑完,微任务边界。门控 Promise 早已被 connectedCallback resolve,#enqueueUpdate 的 L222 继续 → L229 scheduleUpdate → performUpdate → 一次性处理 a/b/c 三个变更。
  5. 渲染一次。

如果 updateComplete 在这期间被 await,它会在步骤 4 完成后 resolve。

为什么不用 queueMicrotask

回到 08 篇提的那个点。Lit 实际上也是用类似机制(async performUpdate + microtask),不是显式 queueMicrotask。v10 的 element 文档(README)说「queueMicrotask 批处理」是不准确的——真正的机制是 async/await 在 L222 产生的微任务挂起。

区别在哪?queueMicrotask(fn) 是显式把 fn 排进微任务队列。而 async function 遇到 await 会立即返回一个 Promise,await 后续代码作为该 Promise 的回调执行——回调本身就是微任务。两者都利用微任务队列,但 async/await 是隐式的、和 Promise 集成更自然(门控、串行化、updateComplete 全靠它)。

小结

这套调度机制我读下来,觉得最值得学的是:

  1. 一个 Promise 同时做门控和串行化——L222 的 await this.#updatePromise 一身二职,代码极简但语义丰富。
  2. 门控保证「挂载后才渲染」——构造时建未 resolve Promise,connectedCallback resolve 它。
  3. 实例属性升级——捕获类字段、删实例值、首次 performUpdate 恢复。
  4. isUpdatePending 去重——批处理的核心,同一 tick 多次变更只入队一次。
  5. scheduleUpdate 可覆盖——子类能改调度策略(如 rAF 对齐)。

下一篇我们看 DestroyMixin——它在 ReactiveElement 之上加了「永久销毁」语义,那个双重 rAF 的设计是防误销毁的关键。

STM32CubeMX-电机控制2-电机相关配置部分 STM32CubeMX-电机霍尔传感器 写在前面的话:之前一直使用库函数,调程序时总会觉得是配置出现问题,要画一些时间去检查配置程序,所以目前转到了STM32CubeMX。写这些内容一方面是整理记录,因为是刚入门,所以难免出现错误,希望能得到大家指正;另一方面是因为自己一个人调试,走了很多弯路,写下来希望能给相同处境的人一些参考。 步骤一: 电机霍尔传感器接口 我目前使用的是STM32F4,电机的... 阅读详情

相关推荐

单相PWM整流器四象限运行原理

简述单相PWM整流器四象限运行原理。

m0_75208156的博客 8301

Web 版翻译者资料库

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

howard2005的专栏 140

Nacos漏洞利用工具V3.0.5实战:从认证绕开到内存马植入的全流程指南

本文详细解析了Nacos漏洞利用工具V3.0.5在实战中的应用,从认证绕开到内存马植入的全流程。文章深入探讨了如何利用Nacos的历史漏洞,如默认口令、认证逻辑绕过、Derby SQL注入及Jraft反序列化,实现权限提升与持久化控制,并提供了防御视角与加固建议,旨在帮助安全人员理解攻击链以加强防护。

h6j7k8l9p0的博客 483

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

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

handsome_sai的博客 272

关于【中孚计算机终端保密检查系统】的卸载

卸载中孚计算机终端保密检查系统

码农之路,向死而生 4万+

强化学习-马尔可夫模型

前文提到了强化学习源自马尔可夫决策过程,本文从马尔可夫概念入手,讲解和马尔可夫有关的模型和概念。在马尔科夫决策过程中,我们有一个智能体,它在一个马尔科夫过程中进行决策,每次决策都会导致状态的转移和获得即时奖励。智能体的目标是通过选择动作来最大化未来奖励的期望值。通常马尔科夫决策过程是个五元组:SAPRγSAPRγS 表示状态集合,代表智能体可能处于的所有状态。A 表示动作集合,代表智能体可以采取的所有动作。

君子之学必日新,日新者日进也。不日新者必日退,未有不进而不退者 8062

【项目推荐】基于 SpringBoot3 + Spring AI 的游泳馆管理系统:Agent + RAG + Function Calling 一次学全

它不是玩具 Demo,而是一个「业务 + AI」双主线交织的真实系统——预约业务在跑,AI 客服在旁边实时查库作答,两条线通过 Function Calling、RAG、站内消息串成一体。面试官问「你做过什么 AI 项目」时,你能从业务场景讲到架构取舍,从 RAG 切分参数讲到降级兜底——这和「我调了一下 ChatGPT 的 API」是完全不同的两个段位。项目已包含完整的建表 SQL、初始化数据、部署文档,克隆下来就能跑。获取方式:见文末说明 / 后台回复关键词获取。—— 完 ——

深耕开发,专注编程实战。分享源码解析、项目搭建、问题排查笔记,沉淀技术,持续更新。 347

用Django框架+爬虫技术实现自动获取可画(Canva)团队会员资格的方法

可画(canva)是一个非常流行的在线平面设计平台,因为它无需专业训练和技能就可以利用其丰富的资源设计出美观、酷炫的作品,因而它受到了极其广泛的设计专业和非专业人群的喜爱本文介绍了一种方法,它采用Django网络框架与爬虫技术相结合,实时监控定时更新可画团队版免费邀请链接的网站页面,一旦链接有更新动作,第一时间获取相关信息,并直接加载这个有效的邀请链接,自动进入可画网站获得团队版使用资格。

hhyyqq的博客 2208

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

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

m0_62937412的博客 336

OpenAI,从推出更强大的Sora 2模型展开探索与未来发展前景+部署方法大全+文末邀请码*12

摘要 OpenAI推出的Sora 2是第二代AI视频生成模型,在多模态内容生成领域实现重大突破。文末足足有12个邀请码,速来速享!!!

aqfcca的博客 2248

芒果YOLO11改进23:主干Backbone篇GhostNet:原创改进高效 GhostNet 网络,华为出品,打造全新检测器

主干Backbone篇GhostNet:原创改进高效 GhostNet 网络,华为出品,打造全新检测器

包括YOLOv5、YOLOv7、YOLOv8等模型改进 404

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

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

人工智能AI技术 97

Fiori Elements Analytical List Page 标题区配置全攻略:从变体、过滤模式到 UI.KPI 的工程化落地

ALP标题区是Fiori Elements Analytical List Page的核心交互区域,承担变体管理、过滤模式切换、KPI展示等关键功能。本文系统解析了标题区的组成结构、可用能力及工程实现要点,包括:1)标题区包含过滤类型切换、全局KPI、变体管理等交互元素;2)详细说明视觉过滤与紧凑过滤的同步规则及实现方式;3)介绍OData V2下通过UI.KPI注解配置标题区KPI的方法;4)提供manifest.json配置示例和CDS注解写法。文章还对比了V2与V4版本的差异,为ALP开发提供实用指导

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 245

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

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

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

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

智能问诊的流程

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

Joey 的工作空间~ 773

OV9734 DATASHEET,完整规格书

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

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

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

naNa_1120的博客 191

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

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

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

2402_85729970的博客 181

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

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

Michael_Jay的博客 62

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

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

2401_85321170的博客 630

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

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

2503_94637398的博客 268

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

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

2402_85729970的博客 388

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

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

xyzzklk的博客 574

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的博客 207

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

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

2502_91284977的博客 235

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

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

wxyzzdh200988的博客 192
上一篇: 2026 年 H5 端 VR/360° 全景视频播放器技术选型与评测
下一篇: videojs v10 源代码系列解读:10 · `DestroyMixin`:双重 rAF 延迟销毁
逻辑玩家
博客等级 码龄19年 75粉丝 108原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值