vue3.x 内置指令有哪些?

Vue 3 提供了 14 个内置指令,用于在模板中实现响应式行为、DOM 操作和性能优化。

一 、v-cloak

v-cloak 是 Vue 的一个编译控制指令 ,用于解决 未编译的模板(Mustache 标签)短暂显示 的问题(即"闪烁"现象)。它不依赖于响应式数据,只在组件编译阶段起作用。

v-cloak 的原理非常简单,分为两个层面:

  1. CSS 隐藏 :开发者需要编写 CSS 规则,例如 [v-cloak] { display: none; }。这样,带有 v-cloak 属性的元素会一开始就被隐藏。
  2. Vue 自动移除 :当 Vue 编译完该组件的模板后,会自动移除 元素上的 v-cloak 属性,从而让元素显示出来。

因此,用户看到的效果是:模板内容在编译完成前是隐藏的,编译完成后立即显示,不会出现未编译的 {{ message }} 一闪而过。

在典型的基于 SFC(单文件组件)和构建工具(Vite、Webpack)的 Vue 项目中,模板会在编译时 被预编译为 render 函数,浏览器最终执行的是 render 函数生成的虚拟 DOM,不会 包含原始的 {{ message }} 语法。因此不会看到未编译的插值闪烁现象。

二、v-pre

v-pre 是 Vue 的一个编译跳过指令,用于告诉 Vue 编译器跳过该元素及其所有子元素的编译过程,直接输出原始内容。

【示例】

js 复制代码
<template>
  <div v-pre>
    {{ message }}
    <p v-text="message">这是一个段落</p>
  </div>
</template>
js 复制代码
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
	return _openBlock(), _createElementBlock("div", null, [..._cache[0] || (_cache[0] = [_createTextVNode(
		" {{ message }} ",
		-1
		/* CACHED */
	), _createElementVNode(
		"p",
		{ "v-text": "message" },
		"这是一个段落",
		-1
		/* CACHED */
	)])]);
}

解析器遇到 v-pre 指令时,会将当前节点及其所有子节点标记的属性转为普通属性。

因为没有生成任何响应式相关的渲染代码,这些节点在组件更新时不会被重新渲染或 diff。们始终以静态 HTML 的形式存在。

三、v-on

在 Vue 3 的事件处理体系中,v-on 指令是连接用户操作与 JavaScript 代码的核心纽带。无论是点击按钮、输入文本还是按下键盘,v-on 都扮演着"信号接收器"的角色,让开发者能够以声明式的方式响应用户的每一次交互

v-on 是什么?

v-on 是 Vue 3 中用于监听 DOM 事件 并在事件触发时执行指定代码的指令。它的简写形式是 @,这是开发中最常使用的写法

html 复制代码
<!-- 完整写法 -->
<button v-on:click="handleClick">点击我</button>

<!-- 简写写法(最常用) -->
<button @click="handleClick">点击我</button>

v-on 基本使用方式

模板编译器通过检查 v-on 的值是否是合法的 JavaScript 标识符或属性访问路径来判断使用哪种处理器:

  1. 有括号的表达式(如 count++handleClick())→ 内联语句处理器
  2. 无括号的标识符(如 handleClickobj.method)→ 方法事件处理器

内联事件处理器

直接将 JavaScript 代码写在 v-on 的值中,适用于简单逻辑。当 v-on 的值是合法的 JavaScript 表达式或标识符时,Vue 会自动识别并处理。

【示例】简单内联表达式

html 复制代码
<button @click="count++">点击我</button>
js 复制代码
const count = ref(0);

【示例】多语句,用分号隔开

html 复制代码
  <button
    @click="
      count--;
      console.log(count);
    "
  >
    点击我
  </button>

编译结果

js 复制代码
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
	return _openBlock(), _createElementBlock("button", { onClick: _cache[0] || (_cache[0] = ($event) => {
		$setup.count--;
		console.log($setup.count);
	}) }, " 点击我 ");
}

【示例】内联箭头函数

html 复制代码
  <button
    @click="
      (event) => {
        count++;
        console.log(event, count);
      }
    "
  >
    点击我
  </button>

编译结果

js 复制代码
import { openBlock as _openBlock, createElementBlock as _createElementBlock } from "/node_modules/.vite/deps/vue.js?v=b3e6ce82";
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
	return _openBlock(), _createElementBlock("button", { onClick: _cache[0] || (_cache[0] = (event) => {
		$setup.count++;
		console.log(event, $setup.count);
	}) }, " 点击我 ");
}

【示例】在内联处理器中可以直接调用方法并传递参数

html 复制代码
<button @click="handleClick($event)">点击我</button>

【示例】在内联处理器中可以直接调用方法并传递参数

js 复制代码
<button @click="handleClick($event, 'click')">点击我</button>
js 复制代码
const handleClick = (event: PointerEvent, type: string) => {
  count.value++;
  console.log("点击了按钮", event, type);
};

【示例】

js 复制代码
<template>
  <div>
    <p>当前数字 {{ count }}</p>
    <button
      @click.stop="
        handleClick();
        handleClick2($event);
      "
    >
      增加
    </button>
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";
const count = ref(0);

const handleClick = () => {
  count.value++;
};

const handleClick2 = (event: Event) => {
  console.log(event);
};

defineOptions({
  name: "CloudView",
});
</script>

编译结果

js 复制代码
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
	return _openBlock(), _createElementBlock("div", null, [_createElementVNode(
		"p",
		null,
		"当前数字 " + _toDisplayString($setup.count),
		1
		/* TEXT */
	), _createElementVNode("button", { onClick: _cache[0] || (_cache[0] = _withModifiers(($event) => {
		$setup.handleClick();
		$setup.handleClick2($event);
	}, ["stop"])) }, " 增加 ")]);
}

【示例】

js 复制代码
<template>
  <div>
    <p>当前数字 {{ count }}</p>
    <button
      @click="handleClick3"
      @click.stop="
        handleClick();
        handleClick2($event);
      "
    >
      增加
    </button>
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";
const count = ref(0);

const handleClick = () => {
  count.value++;
};

const handleClick2 = (event: Event) => {
  console.log(event);
};

const handleClick3 = () => {
  console.log("点击了按钮");
};

defineOptions({
  name: "CloudView",
});
</script>

编译结果

js 复制代码
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
	return _openBlock(), _createElementBlock("div", null, [_createElementVNode(
		"p",
		null,
		"当前数字 " + _toDisplayString($setup.count),
		1
		/* TEXT */
	), _createElementVNode("button", { onClick: [$setup.handleClick3, _cache[0] || (_cache[0] = _withModifiers(($event) => {
		$setup.handleClick();
		$setup.handleClick2($event);
	}, ["stop"]))] }, " 增加 ")]);
}

方法事件处理器

对于复杂逻辑,推荐使用方法作为事件处理器。方法事件处理器会自动接收原生 DOM 事件对象作为参数。

【示例】默认传递原生事件对象

html 复制代码
<button @click="handleClick">点击我</button>

<button v-on:click="handleClick">点击我</button>
js 复制代码
const handleClick = (event: PointerEvent) => {
  count.value++;
  console.log("点击了按钮", event);
};

编译结果

js 复制代码
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
	return _openBlock(), _createElementBlock("button", { onClick: $setup.handleClick }, "点击我");
}

修饰符

一、事件修饰符

事件修饰符 是 Vue 为 v-on 指令(@)提供的特殊后缀,以声明式的方式解决事件处理中常见的底层 DOM 操作,避免在方法中手动调用 event.preventDefault()event.stopPropagation() 等。

【示例 一】捕获模式而非冒泡 capture

js 复制代码
<template>
  <button @click.capture="handleClick">点击我</button>
</template>

编译结果

js 复制代码
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
	return _openBlock(), _createElementBlock(
		"button",
		{ onClickCapture: $setup.handleClick },
		"点击我",
		32
		/* NEED_HYDRATION */
	);
}

【示例 二】事件只触发一次,然后自动解绑 once

js 复制代码
<button v-on:click.once="handleClick4">点击我</button>

编译结果

js 复制代码
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
	return _openBlock(), _createElementBlock(
		"button",
		{ onClickOnce: $setup.handleClick },
		"点击我",
		32
		/* NEED_HYDRATION */
	);
}

【示例 三】提示浏览器不会调用 preventDefault(),提升滚动性能

js 复制代码
<template>
  <button @click.passive="handleClick">click</button>
</template>

编译结果

js 复制代码
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
	return _openBlock(), _createElementBlock(
		"button",
		{ onClickPassive: $setup.handleClick },
		"click",
		32
		/* NEED_HYDRATION */
	);
}

【示例 四】阻止默认行为 prevent event.preventDefault()

html 复制代码
<template>
  <a target="_blank" href="https://baidu.com" @click.prevent="handleClick">点击我</a>
</template>

编译结果

js 复制代码
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
	return _openBlock(), _createElementBlock("a", {
		target: "_blank",
		href: "https://baidu.com",
		onClick: _withModifiers($setup.handleClick, ["prevent"])
	}, "点击我");
}

【示例 五】仅当 event.target === 当前元素 时触发 self

js 复制代码
<template>
  <button @click.self="handleClick">click</button>
</template>

编译结果

js 复制代码
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
	return _openBlock(), _createElementBlock("button", { onClick: _withModifiers($setup.handleClick, ["self"]) }, "click");
}

【示例 六】停止事件冒泡 stop event.stopPropagation()

html 复制代码
<button v-on:click.stop="handleClick">点击我</button>

编译结果

js 复制代码
import { withModifiers as _withModifiers, openBlock as _openBlock, createElementBlock as _createElementBlock } from "/node_modules/.vite/deps/vue.js?v=b3e6ce82";
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
	return _openBlock(), _createElementBlock("button", { onClick: _withModifiers($setup.handleClick, ["stop"]) }, "点击我");
}

【 Why?】oncepassivecapture修饰符会编译成onClickCapture这样的属性,而其他修饰符则通过_withModifiers辅助函数处理?

capture, once, passiveaddEventListener 的底层配置项。它们是浏览器在事件绑定初期 就需要确定的参数,可以在一个事件监听器上同时生效,所以 Vue 可以通过在编译阶段 修改属性名(如 onClick -> onClickCapture),在最终生成的原生事件绑定中一次性配置。

stopprevent 则是对 事件回调函数行为的包装 。它们必须在事件触发时的 回调执行阶段 才能判断并生效,因此需要 Vue 在运行时(runtime)动态地创建一个包装函数(wrapper) ,在这个函数里按顺序执行 stopPropagation()preventDefault() 等操作,最后才调用你定义的回调。

二、鼠标修饰符

  1. .left 左键(默认)
  2. .right 右键
  3. .middle 中键

【示例】

js 复制代码
<template>
  <button @click.left="handleClick">click</button>
</template>
js 复制代码
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
	return _openBlock(), _createElementBlock("button", { onClick: _withModifiers($setup.handleClick, ["left"]) }, "click");
}

【示例】

js 复制代码
<template>
  <button @click.middle="handleClick">click</button>
</template>
js 复制代码
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
	return _openBlock(), _createElementBlock(
		"button",
		{ onMouseup: _withModifiers($setup.handleClick, ["middle"]) },
		"click",
		32
		/* NEED_HYDRATION */
	);
}

【示例】

js 复制代码
<template>
  <button @click.right="handleClick">click</button>
</template>
js 复制代码
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
	return _openBlock(), _createElementBlock(
		"button",
		{ onContextmenu: _withModifiers($setup.handleClick, ["right"]) },
		"click",
		32
		/* NEED_HYDRATION */
	);
}

对比编译区别?

  • .left: 保留原有事件(如 click),修饰符仅用于运行时筛选。
  • .right: 事件名被替换为 contextmenu,这是一个专用于处理右键菜单的浏览器原生事件。
  • .middle: 事件名被替换为 mouseup,用于监听鼠标按键(包括中键)的释放动作

三、系统修饰符

Vue 为 v-on 指令提供了系统修饰符,用于实现仅在按下指定按键时才触发鼠标或键盘事件的监听器。

修饰符 对应按键 (Windows) 对应按键 (macOS)
.ctrl Ctrl Control
.alt Alt Option (⌥)
.shift Shift Shift
.meta Windows (⊞) Command (⌘)

【示例】ctrl

在 Windows 操作系统上,使用 Vue 的 @click.ctrl 修饰符时,click 事件会正常触发,并且事件处理函数会执行。 在 macOS 系统中,Control + 点击 的默认行为是触发右键菜单(上下文菜单) ,而不是 click 事件。因此,浏览器会优先派发 contextmenu 事件,而 click 事件根本不会被触发。

js 复制代码
<template>
  <button @click.ctrl="handleClick">click</button>
</template>
js 复制代码
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
	return _openBlock(), _createElementBlock("button", { onClick: _withModifiers($setup.handleClick, ["ctrl"]) }, "click");
}

【示例】 meta

在 Windows 系统中,.meta 修饰符对应的是代表 Win 键 (⊞); 在 macOS 系统中,Vue 的 .meta 修饰符对应的是 Command (⌘) 键。

js 复制代码
<template>
  <button @click.meta="handleClick">click</button>
</template>
js 复制代码
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
	return _openBlock(), _createElementBlock("button", { onClick: _withModifiers($setup.handleClick, ["meta"]) }, "click");
}

四、按键修饰符

修饰符 对应按键 说明
.enter Enter 键 回车
.tab Tab 键 制表符
.delete Delete 或 Backspace 删除键(两者都匹配)
.esc Escape 退出键
.space Space 空格键
.up 上箭头 ArrowUp
.down 下箭头 ArrowDown
.left 左箭头 ArrowLeft
.right 右箭头 ArrowRight
.page-up PageUp 上翻页
.page-down PageDown 下翻页
.home Home 行首
.end End 行尾

可以直接使用字母或数字作为修饰符(例如 .a.1),Vue 会将其转换为对应的 event.key 值。

【示例】

js 复制代码
<template>
  <input @keyup.a="handleClick" />
</template>
js 复制代码
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
	return _openBlock(), _createElementBlock(
		"input",
		{ onKeyup: _withKeys($setup.handleClick, ["a"]) },
		null,
		32
		/* NEED_HYDRATION */
	);
}

【示例】

js 复制代码
<template>
  <input @keyup.1="handleClick" />
</template>

编译阶段

一、解析指令 (Parse)

<button @click="count++">这类模板解析为包含v-on指令信息的AST(抽象语法树)节点,包括事件名click、处理函数表达式count++

二、转换格式 (Transform)

这是核心环节,由transformOn函数完成。

  • 处理事件名:将静态的@click规范化为onClick,用于VNode的props
  • 处理动态事件名:处理@[eventName],生成可解析动态事件名的代码。
  • 包装处理函数:将如count++的内联语句,包装成接收$event参数的事件处理函数$event => (count++)

三、生成代码 (Codegen)

最终生成render函数,包含一个VNode,其props对象里有一个onClick属性,值为包装后的事件处理函数。

运行时阶段

当组件在浏览器中运行时,核心任务就是高效地将虚拟DOM映射到真实DOM上。

一、首次挂载

渲染器执行render函数生成VNode,patch过程中会为真实DOM绑定事件,主要依赖createInvoker这个工厂函数。

createInvoker:事件更新的性能关键createInvoker创建了一个特殊的函数(invoker),充当连接Vue虚拟DOM事件和真实浏览器事件的稳定桥梁

二、更新阶段

当父组件重新渲染导致事件处理函数改变时,渲染器会发现onClick属性变了,进入patchEvent逻辑。

  1. 发现改变:onClick属性的新值和旧值不同。
  2. 更新invoker:Vue不会 调用removeEventListeneraddEventListener(传统方式性能差),而是找到该事件对应的invoker对象,直接更新其value属性。
  3. 自动生效:由于invoker函数本身没有变,DOM上的监听器无需任何改动。下次事件触发时,执行的invoker会调用其value属性上已指向的新的事件处理函数

四、v-once

在 Vue 3 的指令集中,v-once 是一个用于性能优化 的内置指令。它的核心作用是告诉 Vue: "这个元素及其所有子元素,只渲染一次,后续无论数据如何变化,都不要再更新它们了。"

html 复制代码
<p>更新{{ title }}</p>
<p v-once>静态 {{ title }}</p>

示例

js 复制代码
<template>
  <div v-once>
    <p>当前计数: {{ count }}</p>
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";
const count = ref(0);
defineOptions({
  name: "CloudView",
});
</script>

编译结果

js 复制代码
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
	return _cache[0] || (_setBlockTracking(-1, true), (_cache[0] = _createElementVNode("div", null, [_createElementVNode(
		"p",
		null,
		"当前计数: " + _toDisplayString($setup.count),
		1
		/* TEXT */
	)])).cacheIndex = 0, _setBlockTracking(1), _cache[0]);
}

编译阶段

一、解析与标记

在编译的转换(Transform)阶段,transformOnce 函数会识别带有 v-once 指令的节点。 找到后,会为其打上"静态"标记,并在上下文中设置标记 context.inVOnce = true,同时将该指令从AST节点中移除。 该标记还会被递归地应用到其所有子节点上,确保整个子树都被视为静态内容。

二、代码生成

生成(Generate)阶段,被打上标记的节点会通过 context.cache() 方法进行缓存。 最终,生成的渲染函数会直接引用一个模块级常量(即被静态提升的节点),而不是在每次渲染时重新创建,从而最大程度地减少运行时开销。

运行阶段

  1. 首屏渲染:组件首次渲染时,render 函数会正常执行,并使用缓存的VNode来创建DOM。
  2. 跳过更新:当组件内的响应式数据发生变化,触发重新渲染时,Vue 的 diff 算法会检测到这些静态节点上的 PatchFlags.STATIC 标志。一旦识别到该标志,渲染器会完全跳过对该节点及其子树的所有更新流程,直接复用第一次渲染时缓存的VNode和对应的真实DOM

transformOnce

vue3-core/packages/compiler-core/src/transforms/vOnce.ts

transformOnce 是 Vue 3 编译器(compiler-core)中用于处理 v-once 指令的节点转换函数。它在 AST 转换阶段识别带有 v-once 指令的元素,并标记该子树为"一次性渲染"区域,最终通过缓存机制使其在后续渲染中直接被复用。

js 复制代码
const transformOnce: NodeTransform = (node, context) => {
  // 只处理元素节点,查找节点上的 v-once 指令
  if (node.type === NodeTypes.ELEMENT && findDir(node, 'once', true)) {
    // 检查节点是否已经被处理过,避免重复处理
    if (seen.has(node) || context.inVOnce || context.inSSR) {
      return
    }
    seen.add(node)
    context.inVOnce = true // 标记为 v-once 处理中
    // 注入运行时辅助函数,用于暂时禁用块追踪(Block Tracking)
    context.helper(SET_BLOCK_TRACKING)
    return () => {
      context.inVOnce = false
      const cur = context.currentNode as ElementNode | IfNode | ForNode
      if (cur.codegenNode) {
        cur.codegenNode = context.cache(
          cur.codegenNode,
          true /* isVNode */, // 缓存 VNode 节点
          true /* inVOnce */, // 标记为 v-once 处理中,运行时会在首次渲染后永久复用该 VNode
        )
      }
    }
  }
}

五、v-memo

v-memo 的核心机制是:它接收一个依赖值数组,并缓存该元素及其子树的虚拟 DOM(VNode)。只有当数组中的某个依赖项的值与上一次渲染不同时,Vue 才会重新渲染该子树;否则,将直接复用缓存,跳过整个渲染和差异比对(diff)过程。

使用

js 复制代码
<template>
  <div v-memo="[count]">
    {{ count }}
  </div>
</template>

编译结果

js 复制代码
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
	return _withMemo([$setup.count], () => (_openBlock(), _createElementBlock("div", null, [_createTextVNode(
		_toDisplayString($setup.count),
		1
		/* TEXT */
	)])), _cache, 0);
}

编译阶段

在编译阶段,Vue 的 transformMemo 转换器会识别并处理 v-memo 指令。它只对元素节点生效,并跳过服务端渲染(SSR)场景。

其核心任务是,将带有 v-memo 的节点(_createVNode 调用)包裹在一个名为 _withMemo 的运行时辅助函数调用中。

运行阶段

运行时,渲染函数开始执行。当执行到编译阶段生成的 _withMemo 函数时,核心逻辑如下:

1、执行 _withMemo 函数:该函数接收编译时传入的依赖数组、渲染函数、缓存对象和缓存索引。

2、判断是否命中缓存:它会根据索引查找 _cache 对象中是否已存在 VNode。如果存在,则通过 isMemoSame 函数,使用 Object.is 逐一比较新旧依赖数组中的每一项是否完全一致。

3、渲染或复用

  • 命中缓存:如果依赖数组各项均未改变,_withMemo 将直接返回缓存的 VNode,从而完全跳过了执行渲染函数、创建新 VNode 以及后续的 diff 和 DOM 更新。
  • 未命中缓存:如果依赖数组发生变化,_withMemo 则会执行传入的渲染函数,生成 新的 VNode,并将其更新到缓存中,以供下一次渲染使用

六 、v-if | v-else-if | v-else

Vue 3 中的 v-ifv-else-ifv-else 是用于条件渲染的指令,它们根据表达式的真假值,决定是否将元素或组件渲染到 DOM 中。

使用

【示例】 基本使用

js 复制代码
<template>
  <div>
    <h3>v-if 指令</h3>
    <div v-if="show">{{ title }}</div>
    <div v-else>暂无数据</div>
    <button @click="show = !show">切换显示</button>
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";

const title = ref("列表");
const show = ref(false);
</script>

【示例】

html 复制代码
<template>
  <div>
    <div v-if="type === 1">primary</div>
    <div v-else-if="type === 2">暂无数据</div>
    <div v-else>其他</div>
    <button @click="type++">切换显示</button>
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";

const type = ref(0);
</script>

编译结果

js 复制代码
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
  return _openBlock(), _createElementBlock("div", null, [
    $setup.type === 1 ? (_openBlock(), _createElementBlock("div", _hoisted_1, "primary")) : $setup.type === 2 ? (_openBlock(), _createElementBlock("div", _hoisted_2, "\u6682\u65E0\u6570\u636E")) : (_openBlock(), _createElementBlock("div", _hoisted_3, "\u5176\u4ED6")),
    _createElementVNode("button", {
      onClick: _cache[0] || (_cache[0] = ($event) => $setup.type++)
    }, "\u5207\u6362\u663E\u793A")
  ]);
}

编译阶段

一、解析阶段:构建条件链表

编译器在解析模板时,会将连续的 v-ifv-else-ifv-else 节点合并为一个 条件节点NodeTypes.IF),其 branches 属性存储各个分支。

二、转换阶段:生成条件表达式

transformIf 函数(packages/compiler-core/src/transforms/vIf.ts)中,编译器将条件节点转换为一个 三元运算符链 ,并为每个分支包裹 openBlock() / createBlock() 调用,因为每个分支都是一个独立的 Block。

三、代码生成阶段:输出 JavaScript

最终生成的代码是一个嵌套的三元运算符,每个分支都使用 openBlock() / createBlock() 来创建 Block。

v-if 会被编译器转换为条件语句 (三元运算符或 if 分支),在生成的渲染函数中,根据条件返回不同的虚拟 DOM。

运行阶段

  • 当条件改变时,Vue 的响应式系统触发重新渲染。
  • 渲染函数重新执行,如果条件从假变为真,则创建新的 VNode 并挂载到 DOM;如果从真变为假,则移除对应的 VNode。

七、v-show

v-show 是用于条件显示的内置指令。与 v-if 不同,v-show 不会销毁或重建元素,而是通过切换 CSS 的 display 属性来控制元素的可见性。这意味着元素始终存在于 DOM 中,只是被隐藏或显示。

v-show 的使用

【示例】基本使用

js 复制代码
<template>
  <div>
    <div v-show="show">this 展示区</div>
    <button @click="show = !show">切换显示</button>
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";

const show = ref(true);
</script>

使用限制

  1. v-show 不支持 <template> 元素,因为 <template> 不会生成真实的 DOM 节点,无法应用 display 样式。
  1. v-show 没有 v-elsev-else-if 的配套指令,它仅单独控制单个元素的显隐。
  2. v-show 必须要有表达式。
  1. 不要与 v-for 同时使用。v-forv-show 同时使用虽然不会报错,但会导致每次列表变化时重新计算显示状态,性能较差。推荐将 v-show 放在 v-for 内部的元素上,或者使用计算属性过滤后再用 v-for

编译阶段

v-show 会被编译为一个指令,生成一个用于控制 display 的绑定。

运行阶段

当绑定的值变化时,Vue 会直接更新该元素的 style.display 属性(设为 none 或移除/恢复原值)。

八、v-for

【示例】基础使用

js 复制代码
<template>
  <div>
    <ul>
      <li v-for="item in books" :key="item">
        <span>
          {{ item }}
        </span>
      </li>
    </ul>
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";

const books = ref(["book1", "book2", "book3", "book4", "book5"]);
</script>

编译结果

js 复制代码
import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, toDisplayString as _toDisplayString, createElementVNode as _createElementVNode } from "/node_modules/.vite/deps/vue.js?v=efe42f93";
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
  return _openBlock(), _createElementBlock("div", null, [
    _createElementVNode("ul", null, [
      (_openBlock(true), _createElementBlock(
        _Fragment,
        null,
        _renderList($setup.books, (item) => {
          return _openBlock(), _createElementBlock("li", { key: item }, [
            _createElementVNode(
              "span",
              null,
              _toDisplayString(item),
              1
              /* TEXT */
            )
          ]);
        }),
        128
        /* KEYED_FRAGMENT */
      ))
    ])
  ]);
}

【示例】

js 复制代码
<template>
  <div>
    <ul>
      <li v-for="(item, index) in books" :key="item + index">
        <span>
          {{ item }}
        </span>
      </li>
    </ul>
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";

const books = ref(["book1", "book2", "book3", "book4", "book5"]);
</script>

注意事项

  1. 必须为每个列表项提供一个唯一的 key,且不建议使用 index 作为 key(除非列表是静态的且不会重新排序)
  2. 在 Vue 3 中,v-if 的优先级高于 v-for (与 Vue 2 相反)。这意味着当它们同时用在同一个元素上时,v-if 会先执行,无法访问 v-for 作用域内的变量。
  3. v-for 可以遍历对象的属性,顺序基于 Object.keys() 的返回值。不建议直接遍历对象
  4. v-for 可以接受整数,渲染指定数量的元素。
  5. 避免在 v-for 内部使用复杂的计算属性或方法:每次重新渲染都会重新执行,建议将计算结果提前到列表数据源中。
  6. 使用 v-memo 缓存子树(Vue 3.2+):当子树的依赖很少变化时,使用 v-memo 跳过不必要的更新。

编译阶段

1、在 parse 阶段,v-for="..." 中的表达式会以原始的字符串形式被记录在 AST 节点的 props 属性中,尚未被处理。

2、转换。

解析 v-for="(item, index) in list" 字符串,提取出 source(数据源,如 list)、value(迭代项,如 item)和 key(索引,如 index),并存入一个专门的 ForParseResult 对象中。

构建 ForNode:基于解析结果,原始的节点会被替换成一个新的、类型为 ForNode 的 AST 节点。

3、生成代码。

最终生成的渲染函数会包含对 _renderList 这个运行时辅助函数的调用。

九、v-text

v-text 是 Vue 3 中用于更新元素文本内容 的内置指令。它将数据绑定到 DOM 元素的 textContent 属性,确保视图与数据保持同步。与插值语法 {{ }} 相比,v-text 提供了一种更显式的方式来控制元素的全部文本内容,并且会完全覆盖元素原有的子节点

基本使用

js 复制代码
<template>
  <div v-text="title"></div>
</template>

<script setup lang="ts">
import { ref } from "vue";

const title = ref("这是一段话题。<p>这是段落1。</p>");
</script>

展示效果

编译结果

【示例】

js 复制代码
<template>
  <div v-text="count"></div>
</template>

<script setup lang="ts">
import { ref } from "vue";
const count = ref(0);
defineOptions({
  name: "CloudView",
});
</script>

编译结果

js 复制代码
const _hoisted_1 = ["textContent"];
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
	return _openBlock(), _createElementBlock("div", { textContent: _toDisplayString($setup.count) }, null, 8, _hoisted_1);
}

【示例】插值{{}}

js 复制代码
<template>
  <div>
    {{ count }}
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";
const count = ref(0);
defineOptions({
  name: "CloudView",
});
</script>

编译结果

js 复制代码
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
	return _openBlock(), _createElementBlock(
		"div",
		null,
		_toDisplayString($setup.count),
		1
		/* TEXT */
	);
}

使用限制

  1. 避免与子节点共存:v-text 会覆盖元素的所有子内容,因此在同一个元素上,不应同时使用 v-text 并编写其他子节点
  1. 仅用于纯文本:v-text 会将 HTML 标签作为纯文本转义输出,不能解析 HTML 结构
  2. 相比于插值语法,v-text 避免了模板编译时的碎片化文本节点,性能上微乎其微,但可读性较差,通常推荐使用 {{ }}

编译阶段

v-text 会被解析为指令,生成代码直接设置 textContent

运行阶段

当绑定的数据变化时,Vue 会更新该元素的 textContent 属性。

十、v-html

v-html 是 Vue 3 中用于将原始 HTML 字符串渲染为真实 DOM 元素 的内置指令。与 v-text 或插值语法不同,v-html 会将其内容作为 HTML 解析并插入到元素中,而不是作为纯文本。

【示例】基本使用

js 复制代码
<template>
  <div v-html="title"></div>
</template>

<script setup lang="ts">
import { ref } from "vue";

const title = ref("这是一段话题。<p>这是段落1。</p>");
</script>

编译结果

十一、v-bind

v-bind 指令是数据驱动视图的核心桥梁。能够将 JavaScript 数据动态绑定到 HTML 属性、组件属性(props)甚至 CSS 样式上。当绑定的数据发生变化时,视图会自动更新------你只需关心数据的变化,Vue 会高效地完成 DOM 的更新工作,无需手动操作 DOM。

v-bind是什么?

v-bind 是 Vue 3 中用于动态绑定一个或多个属性 的指令,可以绑定 HTML 元素的原生属性(如 srchreftitle 等),也可以绑定组件的 props,还能绑定 classstyle 等特殊属性

v-bind 使用

语法

html 复制代码
<!-- 完整语法 v-bind:属性名="JavaScript表达式" -->
<div v-bind:title="title">这是一个div</div>

<!-- 缩写语法(推荐使用) :属性名="JavaScript表达式"  -->
<div :title="title">这是一个div</div>

<!-- 当属性名与变量名完全相同时,可以省略表达式,直接写成 `:属性名` -->
<div :title>这是一个div</div>

基础使用

HTML 属性 和 DOM 属性的区别?

  1. HTML 属性只负责初始状态,不会自动同步到 DOM 属性。
  2. DOM 属性是当前状态 ,用户交互或 JS 修改后会更新。
    例如:用户在 <input> 中输入新内容,DOM 属性 value 改变,但 HTML 属性 value 不会变

【示例】绑定 HTML 属性

写在 HTML 标签上的静态文本,由浏览器解析后成为 DOM 节点的初始值。属性名通常是全小写。

html 复制代码
<div :aria-label="title">hello</div>

因为 DOM 中不存在 ariaLabel 属性(虽然可以通过 setAttribute 设置),Vue 会将其作为 HTML 属性处理。

【示例】data-* 自定义属性

js 复制代码
<div :data-id="title">ID</div>

【示例】 绑定 DOM 属性

浏览器解析 HTML 后生成的 DOM 对象上的动态属性,可以通过 JavaScript 读写,值会随用户交互变化。

html 复制代码
<div v-bind:title="title">这是一个div</div>
<div :title="title">这是一个div</div>
<div :title>这是一个div</div>
js 复制代码
const title = ref("hello vue3");

绑定 class 和 style

它们既是 HTML 属性,又是 DOM 属性,但 Vue 做了特殊增强(支持对象、数组语法),最终仍然通过 DOM 属性机制应用。

html 复制代码
<!-- 根据 isActive 动态切换 active 类 -->
<div :class="{ active: isActive, 'text-danger': hasError }"></div>
html 复制代码
<div :class="[activeClass, errorClass]"></div>
html 复制代码
<div :class="[{ active: isActive }, errorClass]"></div>
html 复制代码
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
js 复制代码
<div :style="styleObject"></div>

const styleObject = reactive({
  color: 'red',
  fontSize: '13px'
})

修饰符

1、.camel 将短横线命名的属性名转换为驼峰式

将 HTML 属性名中的短横线(kebab-case)转换为驼峰式(camelCase),以便绑定到使用驼峰命名的 DOM 属性。

2、.prop 将绑定绑定为 DOM 属性而非 HTML 属性

强制将绑定值设置为 DOM 属性(Property),而不是 HTML 属性(Attribute)。

3、.attr 将绑定强制绑定为 HTML 属性

强制将绑定值设置为 HTML 属性(Attribute),通过 setAttribute 设置。

十二、v-model

v-model 是 Vue.js 中用于实现双向数据绑定的核心指令。

v-model 的使用

【示例】在原生表单元素上:v-model 等价于 :value(或对应属性)加上 @input(或对应事件)事件监听。

js 复制代码
<template>
  <div>
    <input type="text" v-model="roleName" placeholder="请输入角色名称" />
  </div>
</template>
<script lang="ts" setup>
import { ref } from "vue";

const roleName = ref("");
</script>

在原生元素上:v-model="username" 等价于 :value="username" @input="username = $event.target.value"

【示例】多个v-model

js 复制代码
<template>
  <div>
    <input type="text" v-model="roleName" placeholder="请输入角色名称" /><br />
    <input type="text" v-model="roleID" placeholder="请输入角色ID" />
  </div>
</template>
<script lang="ts" setup>
import { ref } from "vue";

const roleName = ref("");
const roleID = ref("");
</script>

【示例】组件上使用

js 复制代码
<template>
  <TabOne v-model:name="name" v-model:id="id" />
</template>

<script setup lang="ts">
import TabOne from "@/pages/cloud/components/tabOne.vue";
import { ref } from "vue";

const name = ref("CloudView");
const id = ref("ccd");
defineOptions({
  name: "CloudView",
});
</script>

【示例】组件上使用

js 复制代码
// 父组件
<template>
  <TabOne v-model:tabName="name" v-model:tabId="id" />
</template>

<script setup lang="ts">
import TabOne from "@/pages/cloud/components/tabOne.vue";
import { ref } from "vue";

const name = ref("CloudView");
const id = ref("ccd");
defineOptions({
  name: "CloudView",
});
</script>
js 复制代码
// 子组件
<template>
  <div>
    <input v-model="tabName" />
    <input v-model="tabId" />
  </div>
</template>
<script setup lang="ts">
const tabName = defineModel("tabName");
const tabId = defineModel("tabId");

defineOptions({
  name: "TabOneView",
});
</script>

原生元素修饰符

  1. .lazy,改为 change 事件同步。默认情况下,v-modelinput 事件触发时同步数据(输入框每次按键都更新)。添加 .lazy 修饰符后,改为在 change 事件触发时同步(通常是 失焦回车时)。
  2. .number ,自动转为数字。将用户的输入自动转换为数字类型。如果输入无法被 parseFloat() 转换,则返回原始字符串。
  3. .trim ,自动去除首尾空格。自动过滤用户输入内容首尾的空白字符(空格、制表符、换行符等)。

【示例】

js 复制代码
<template>
  <div>
    <input v-model.trim="tabName" />
    <input v-model.number="tabId" />
  </div>
</template>

组件修饰符

【示例】组件添加修饰符

js 复制代码
// 父组件
<template>
  <TabOne v-model:tabName.max="name" v-model:tabId.upper="id" />
</template>

<script setup lang="ts">
import TabOne from "@/pages/cloud/components/tabOne.vue";
import { ref } from "vue";

const name = ref("CloudView");
const id = ref("ccd");

defineOptions({
  name: "CloudView",
});
</script>
js 复制代码
// 子组件
<template>
  <div>
    <input v-model="tabName" />
    <input v-model="tabId" />
  </div>
</template>
<script setup lang="ts">
const [tabName, tabNameModifiers] = defineModel("tabName", {
  set(val: string) {
    console.log("tabName", tabNameModifiers, val);
    if (tabNameModifiers.max) {
      return val.slice(0, 10);
    }
    return val;
  },
});
const [tabId, tabIdModifiers] = defineModel("tabId", {
  set(val: string) {
    return tabIdModifiers.upper ? val.toUpperCase() : val.toLowerCase();
  },
});

defineOptions({
  name: "TabOneView",
});
</script>

使用限制

  1. 必须有表达式。
  2. 不能绑定 props
  3. 不能是常量

编译阶段:模板转化

一、 解析(Parse)

parse 函数将模板代码解析成抽象语法树(AST) 。此时,v-model 指令还是一个特殊的节点。

二、转换(Transform)

transform 函数会识别出 v-model 节点,并调用 transformModel 函数,把节点转换成两条 props

  • 对于原生元素:valueon:input
  • 对于自定义组件:modelValueon:update:modelValue

三、生成(Generate)

generate 函数将转化后的 AST 生成最终的 render 函数。至此,v-model 指令已不复存在,AST 已被静态展开。

运行阶段:渲染与更新

  1. 执行 render 函数:浏览器执行编译阶段生成的 render 函数,生成虚拟 DOM(VNode)
  2. 处理 props:在生成 VNode 的过程中,render 函数会识别 modelValueonUpdate:modelValue,并将其作为普通的 propsevent 处理。
  3. 挂载与更新:Vue 的运行时系统会根据 VNode 创建或更新真实 DOM。当用户交互触发 update:modelValue 事件时,父组件中绑定的数据就会被更新,从而触发新一轮的渲染。

十三、v-slot

Vue 3 中的 v-slot 指令用于定义插槽(slot),它是 Vue 组件化体系中实现内容分发和组件复用的核心机制。

插槽的使用?

插槽允许父组件向子组件传递模板内容 ,子组件通过 <slot> 元素定义内容的放置位置。v-slot 指令用于在父组件中声明传递给子组件的内容。

  • 默认插槽:没有名字的插槽。
  • 具名插槽:有名字的插槽,用于多内容分发。
  • 作用域插槽:子组件可以将数据回传给父组件,父组件利用这些数据渲染内容。

默认插件

【示例】父组件直接嵌套内容

js 复制代码
// 父组件
<template>
  <TabTwo>
    <p>插槽内容-默认</p>
  </TabTwo>
</template>

<script setup lang="ts">
import TabTwo from "./cloud/components/tabTwo.vue";

defineOptions({
  name: "CloudView",
});
</script>

编译结果

js 复制代码
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
	return _openBlock(), _createBlock($setup["TabTwo"], null, {
		default: _withCtx(() => [..._cache[0] || (_cache[0] = [_createElementVNode(
			"p",
			null,
			"插槽内容-默认",
			-1
			/* CACHED */
		)])]),
		_: 1
	});
}
js 复制代码
// 子组件
<template>
  <div>
    <slot></slot> <!-- 插槽出口 -->
  </div>
</template>
<script setup lang="ts">
defineOptions({
  name: "TabTwoView",
});
</script>

【示例】<template> 配合 v-slot:default 或简写 #default

v-slot:default

js 复制代码
// 父组件
<template>
  <TabTwo>
    <template v-slot:default>
      <p>插槽内容-默认</p>
    </template>
  </TabTwo>
</template>

简写方式

js 复制代码
// 父组件
<template>
  <TabTwo>
    <template #default>
      <p>插槽内容-默认</p>
    </template>
  </TabTwo>
</template>
js 复制代码
<template>
  <div>
    <slot></slot>
  </div>
</template>
<script setup lang="ts">
defineOptions({
  name: "TabTwoView",
});
</script>

具名插槽

子组件定义多个 <slot>,用 name 属性区分。

【示例】

js 复制代码
// 父组件
<template>
  <TabTwo>
    <template v-slot:default>
      <p>插槽内容-默认</p>
    </template>

    <template #header>
      <p>这里是头部</p>
    </template>

    <template #footer>
      <p>这里是脚部</p>
    </template>
  </TabTwo>
</template>

<script setup lang="ts">
import TabTwo from "./cloud/components/tabTwo.vue";

defineOptions({
  name: "CloudView",
});
</script>
js 复制代码
// 子组件
<template>
  <div>
    <slot name="header"></slot>
    <slot></slot>
    <slot name="footer"></slot>
  </div>
</template>

【示例】条件插槽

js 复制代码
// 子组件
<template>
  <div>
    <slot name="header" :header="info.header"></slot>
    <slot :list="info.list"></slot>
    <slot v-if="$slots.footer" name="footer" :footer="info.footer"></slot>
  </div>
</template>
<script setup lang="ts">
import { reactive } from "vue";

const info = reactive({
  header: "这里是头部",
  footer: "这里是脚部",
  list: ["item1", "item2", "item3"],
});
defineOptions({
  name: "TabTwoView",
});
</script>

编译结果

js 复制代码
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
	return _openBlock(), _createElementBlock("div", null, [
		_renderSlot(_ctx.$slots, "header", { header: $setup.info.header }),
		_renderSlot(_ctx.$slots, "default", { list: $setup.info.list }),
		_ctx.$slots.footer ? _renderSlot(_ctx.$slots, "footer", {
			key: 0,
			footer: $setup.info.footer
		}) : _createCommentVNode("v-if", true)
	]);
}

【示例】动态插槽名称

js 复制代码
// 父组件
<template>
  <button @click="handleClick">切换</button>
  <TabTwo>
    <template v-slot:[slotName]="{ data }"> {{ data }} </template>
  </TabTwo>
</template>

<script setup lang="ts">
import TabTwo from "./cloud/components/tabTwo.vue";
import { ref } from "vue";
const slotName = ref("header");

const handleClick = () => {
  const flag = Math.random() > 0.5;
  slotName.value = flag ? "header" : "footer";
};
defineOptions({
  name: "CloudView",
});
</script>
js 复制代码
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
	return _openBlock(), _createElementBlock(
		_Fragment,
		null,
		[_createElementVNode("button", { onClick: $setup.handleClick }, "切换"), _createVNode(
			$setup["TabTwo"],
			null,
			{
				[$setup.slotName]: _withCtx(({ data }) => [_createTextVNode(
					_toDisplayString(data),
					1
					/* TEXT */
				)]),
				_: 2
			},
			1024
			/* DYNAMIC_SLOTS */
		)],
		64
		/* STABLE_FRAGMENT */
	);
}

作用域插槽

子组件在 <slot> 上绑定属性(称为插槽 prop ),父组件通过 v-slot 接收这些数据,从而实现父组件模板使用子组件内部数据。

js 复制代码
// 父组件
<template>
  <TabTwo>
    <template v-slot:default="{ list }">
      <p>插槽内容-默认</p>
      <ul v-for="item in list" :key="item">
        <li>{{ item }}</li>
      </ul>
    </template>

    <template #header="{ header }">
      <h1>这里是头部</h1>
      <p>{{ header }}</p>
    </template>

    <template #footer="{ footer }">
      <h1>这里是脚部</h1>
      <p>{{ footer }}</p>
    </template>
  </TabTwo>
</template>

编译结果

js 复制代码
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
	return _openBlock(), _createBlock($setup["TabTwo"], null, {
		default: _withCtx(({ list }) => [_cache[0] || (_cache[0] = _createElementVNode(
			"p",
			null,
			"插槽内容-默认",
			-1
			/* CACHED */
		)), (_openBlock(true), _createElementBlock(
			_Fragment,
			null,
			_renderList(list, (item) => {
				return _openBlock(), _createElementBlock("ul", { key: item }, [_createElementVNode(
					"li",
					null,
					_toDisplayString(item),
					1
					/* TEXT */
				)]);
			}),
			128
			/* KEYED_FRAGMENT */
		))]),
		header: _withCtx(({ header }) => [_cache[1] || (_cache[1] = _createElementVNode(
			"h1",
			null,
			"这里是头部",
			-1
			/* CACHED */
		)), _createElementVNode(
			"p",
			null,
			_toDisplayString(header),
			1
			/* TEXT */
		)]),
		footer: _withCtx(({ footer }) => [_cache[2] || (_cache[2] = _createElementVNode(
			"h1",
			null,
			"这里是脚部",
			-1
			/* CACHED */
		)), _createElementVNode(
			"p",
			null,
			_toDisplayString(footer),
			1
			/* TEXT */
		)]),
		_: 1
	});
}
js 复制代码
// 子组件
<template>
  <div>
    <slot name="header" :header="info.header"></slot>
    <slot :list="info.list"></slot>
    <slot name="footer" :footer="info.footer"></slot>
  </div>
</template>
<script setup lang="ts">
import { reactive } from "vue";

const info = reactive({
  header: "这里是头部",
  footer: "这里是脚部",
  list: ["item1", "item2", "item3"],
});
defineOptions({
  name: "TabTwoView",
});
</script>

注意事项

  1. v-slot 只能用在 template 标签 或组件上。
  2. v-slot 用在组件上,只能是默认的情况。
  3. 如果同时使用默认插槽和具名插槽,默认插槽的内容必须用 <template #default> 包裹(除非你只提供默认插槽且不与其他插槽混用)。

编译阶段

一、解析(Parse)

模板中的 <template v-slot:header="slotProps"> 会被解析成 AST 节点,其中包含:

  • slotName:插槽名(如 header
  • slotProps:作用域变量名(如 slotProps
  • 子节点:插槽内部的模板内容

二、转换(Transform)

编译器会对 AST 进行转换,将 v-slot 转换为 render 函数中的插槽定义。

转换的结果是:每个插槽会被编译成一个函数,该函数接收子组件传递的插槽 prop 作为参数,并返回插槽内容的虚拟 DOM。

三、生成(Generate)

最终生成可执行的 render 函数。

对于子组件,其 render 函数中会访问 $slots 对象;

对于父组件,render 函数会生成一个插槽对象 作为子组件的第三个参数(即 childrenslots)。

运行阶段

当父组件的 render 函数执行时,它会计算每个插槽的内容,并为每个插槽生成一个函数。这些函数被收集到一个对象中,作为子组件创建时的 slots 属性传递。

子组件在渲染时,会通过 $slots 属性访问父组件传递的插槽对象。

相关推荐
米丘2 小时前
Vue 3.x 模板编译优化:静态提升、预字符串化与 Block Tree
前端·vue.js·编译原理
We་ct2 小时前
HTML5 原生拖拽 API 基础原理与核心机制
前端·javascript·html·api·html5·浏览器·拖拽
是上好佳佳佳呀2 小时前
【前端(八)】CSS3 属性值笔记:渐变、自定义字体与字体图标
前端·笔记·css3
踩着两条虫2 小时前
VTJ:核心引擎
前端·低代码·ai编程
GISer_Jing3 小时前
AI时代前端开发者成长计划
前端·人工智能
方安乐3 小时前
网页设计:自动适配浏览器深色/浅色模式
前端·html5
qq_12084093713 小时前
Three.js 工程向:后处理性能预算与多 Pass 链路优化
前端·javascript
南棱笑笑生3 小时前
20260422给万象奥科的开发板HD-RK3576-PI适配瑞芯微原厂的Buildroot时使用mpg123播放mp3音频
前端·javascript·音视频·rockchip
小小码农Come on3 小时前
QPainter双缓冲区实现一个简单画图软件
linux·服务器·前端