Skip to content

v-cloak与v-pre 与 v-once(Lesson3 1/3) ​

本页对应 Lesson03,共 5 段视频,约 1 小时 29 分。

学习目标 ​

  • 能说明 v-cloak 解决插值表达式闪烁的原理,编写配套 CSS 与 Vue 实例,并验证挂载完成后该属性被移除。
  • 能分别构造示例验证 v-pre 跳过元素编译、v-once 仅渲染初始值的行为,并准确描述二者差异。
  • 能指出未使用过滤器时格式化商品价格、时间戳的常见实现方式,并说明其在职责分离和复用性上的局限。
  • 能声明局部过滤器,在插值表达式和属性绑定中通过管道符调用过滤器,并验证未注册过滤器时的报错现象。
  • 能编写过滤器完成价格保留指定小数位并添加货币前缀、时间戳转年月日时分秒,并能通过传参与默认值控制格式化结果。
  • 能解释过滤器本质是函数、第一个参数为待过滤数据、后续参数为调用时传入参数,并据此排查过滤器返回值与渲染结果不一致的问题。

知识点 ​

先复现问题:为什么会出现双大括号闪烁 ​

为什么需要:Vue 通过编译模板把 {{item}} 替换成真实数据。如果 Vue 文件加载慢,或实例挂载被延迟,浏览器会先把原始 HTML 渲染出来,用户就会短暂看到 {{item}} 这样的双大括号。

怎么想:转写稿里用 setTimeout 把 new Vue(...) 延迟 3000 毫秒,就是为了放大“尚未挂载”的阶段。此时页面上的 v-for 还没被 Vue 处理,{{item}} 会原样显示。等挂载完成后,列表才变成 1、2、3、4。

怎么做:先按笔记写一个带 v-for 的容器,并用延迟模拟加载慢:

html
<div id="app">
	<p v-for="(item,index) in arr" :key="index">{{item}}</p>
</div>

易错点:不要把“延迟”理解成正式项目必须写 setTimeout。它只是课堂调试手段,用来观察挂载前和挂载后的差异。

v-cloak 的作用:挂载前隐藏,挂载后显示 ​

为什么需要:只靠延迟会看到双大括号,体验很差。我们需要一个标记:Vue 没挂载完时隐藏元素,挂载完成后自动显示。

怎么想:转写稿中在 #app 上加了 v-cloak,然后打开开发者工具的 Elements 面板观察:刷新时元素上有 v-cloak 属性;等挂载完成后,这个属性被 Vue 移除了。也就是说,v-cloak 本身不是用来显示数据的,而是配合 CSS 做“挂载前隐藏”。

怎么做:给容器加 v-cloak:

html
<div id="app" v-cloak>
	<p v-for="(item,index) in arr" :key="index">{{item}}</p>
</div>

易错点:只写 v-cloak 不写 CSS 是看不到效果的。转写稿里一开始也出现“加了 v-cloak 但还能看到双大括号”的情况,原因就是还没有给 [v-cloak] 写隐藏样式。

配合 CSS:用属性选择器隐藏元素 ​

为什么需要:Vue 只负责在挂载完成后移除 v-cloak 属性,隐藏动作要靠 CSS 完成。

怎么想:用属性选择器 [v-cloak] 选中拥有该属性的元素,设置 display:none。挂载前属性存在,元素隐藏;挂载后属性被移除,选择器失效,元素显示。

怎么做:在 head 的 style 中写:

css
[v-cloak]{
	display:none;
}

易错点:样式要能作用到带 v-cloak 的元素;不要写成只对某个子元素生效。转写稿中“机子play读音呢”实际就是 display:none,要写正确。

完整示例与调试过程 ​

下面保留本节笔记代码。转写稿中的调试顺序是:先延迟挂载,观察双大括号;再加 v-cloak,观察属性;最后加 [v-cloak]{display:none;},确认挂载前不显示、挂载后正常显示。

html
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Title</title>
	<style>
		[v-cloak]{
			display:none;
		}
	</style>
	<script src="lib/vue.js"></script>
</head>
<body>
<div id="app" v-cloak>
	<p v-for="(item,index) in arr" :key="index">{{item}}</p>
</div>
</body>
<script>
	// 当挂载完成后,Vue会将v-cloak属性移除。解决屏幕闪烁问题
	// 1- 在未挂载完Vue时,会增加v-cloak属性,为该属性增加样式display:none;将拥有该属性的元素隐藏
	// 2- 当挂载完成后,Vue会将v-cloak属性移除。样式不会起作用,即会显示元素。
	setTimeout(()=>{
		new Vue({
			el:"#app",
			data:{
				arr:[1,2,3,4]
			}
		})
	},3000)
	
</script>
</html>

易错点:v-cloak 要加在需要隐藏的挂载容器上;转写稿里的“微杠破循环”就是 v-for,不要写错指令名。挂载完成后属性会自动移除,不需要手动删除。它解决的是“模板未编译前的闪烁”,不是所有加载问题。

先看本节完整示例 ​

本节围绕两个“只影响编译/响应行为”的指令:v-pre 与 v-once。先保留笔记中的完整代码,后面逐段拆解。

html
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Title</title>
	<script src="lib/vue.js"></script>
</head>
<body>
<div id="app">
	<input type="text" v-model="str">
	<p>{{str}}</p>
	<p v-pre>大家好,你们现在学习的是Vue,可以借助插值表达式{{str}}输出数据状态str</p>
	<p v-once>str的初始值为:{{str}}</p>
</div>
</body>
<script>
	// v-pre:为元素增加该指令后,该元素即被数据响应跳过。(不会对其元素进行解析)
	// v-once:只响应初始值,后续数据再次发生改变,使用该指令的元素不会对数据进行响应。
	new Vue({
		el: "#app",
		data: {
			str: "你现在过的还好吗?"
		}
	})
</script>

运行后,输入框通过 v-model="str" 绑定 str。普通 <p>{{str}}</p> 会随输入变化;而带 v-pre 和 v-once 的两个 <p> 表现不同。转写稿中正是通过“改输入框、看下面几行是否变化”来演示这两个指令的差异。

v-pre:让插值表达式原样显示 ​

为什么需要:有时我们不是要输出 str 的值,而是要把“{{str}} 这种写法本身”展示给别人看,例如做教学提示、文档说明。如果直接写 {{str}},Vue 会把它当成插值表达式解析掉,页面上就看不到双花括号了。转写稿中举例:想提示“可以借助插值表达式 {{str}} 输出数据状态 str”,但直接写会被 Vue 编译,于是需要 v-pre。

怎么想:v-pre 的作用不是“隐藏”,也不是“阻止数据变化”,而是告诉 Vue:这个元素不要按模板去解析。转写稿的表述是“为该元素增加该指令后,该元素即被数据响应跳过,不会对其元素进行解析”。所以元素内部的 {{str}} 会作为普通文本保留下来。

怎么做:在目标元素上添加 v-pre:

html
<p v-pre>大家好,你们现在学习的是Vue,可以借助插值表达式{{str}}输出数据状态str</p>

页面中会原样看到 {{str}}。此时再修改输入框,v-pre 这一行不会因为 str 改变而重新渲染成数据值。

易错点:第一,v-pre 不是把内容藏起来,内容仍然显示,只是不编译;第二,不要把它和 v-cloak 混淆,转写稿中 v-cloak 解决的是挂载前双花括号闪烁问题,而 v-pre 是主动跳过解析;第三,v-pre 加在元素上后,元素内部的模板语法都不应按正常插值来理解,所以不要指望它内部再写 {{str}} 还能输出数据。

v-once:只响应初始值 ​

为什么需要:有些内容只希望在第一次渲染时取一次数据,后续数据变化不再更新这一块。转写稿中写得很直接:“只响应初始值,后续数据再次发生改变,使用该指令的元素不会对数据进行响应。”本节示例用它显示 str 的初始值。

怎么想:普通插值 {{str}} 是响应式的,str 一变,视图就更新;v-once 则把这一处的渲染“定格”在初始渲染结果上。它不是修改 data,也不是让整个页面都失去响应,只影响使用了该指令的元素。

怎么做:在元素上添加 v-once:

html
<p v-once>str的初始值为:{{str}}</p>

初始时显示“str 的初始值为:你现在过的还好吗?”。随后在输入框中修改 str,普通 <p>{{str}}</p> 会变化,而 v-once 这一行仍显示初始值。转写稿中反复演示“再改,它下面还变吗?不变”,就是在确认这一行为。

易错点:第一,v-once 不是“数据只读”,str 本身仍然可以被 v-model 修改;第二,它只让当前元素不随后的数据变化更新,其他普通插值照常响应;第三,不要把 v-once 当成 v-pre:v-once 会解析一次插值并显示初始值,v-pre 则连插值都不解析,直接显示 {{str}} 原文。

对比调试:一行普通插值、一行 v-pre、一行 v-once ​

按本节代码运行时,可以这样观察:

  1. 初始状态:普通行、v-pre 行、v-once 行都显示各自内容。
  2. 在输入框输入新内容:普通行跟着变;v-pre 行仍显示字面量 {{str}};v-once 行仍显示初始值。
  3. 这个对比说明:v-pre 解决“模板语法要不要被解析”,v-once 解决“解析后还要不要继续响应数据变化”。

转写稿中还强调“该元素即被数据响应跳过”“只响应初始值”,这两句话可以作为判断依据:看到 v-pre,先想“跳过解析”;看到 v-once,先想“只渲染一次初始结果”。本节不需要把它们扩大到其他指令上,先把这两个边界记清楚即可。

先明确本节主线 ​

本节笔记只有一句核心定义:过滤器用于对数据进行格式化处理。转写稿前半段复习了动态 script、回调与 Promise,后面还讲了 v-cloak、v-pre、v-once,这些不属于“过滤器”主线;下面只围绕过滤器整理,并把转写稿里“先不用过滤器→发现不便→定义过滤器→调试参数”的过程补出来。本节无图片,代码依据转写稿口述整理。

为什么需要过滤器:模板里直接格式化会越来越乱 ​

为什么需要:商品价格从接口来时常是 12.345,时间常是时间戳。用户看不懂时间戳,价格也通常要保留两位小数并加货币标识。
怎么想:先不用过滤器,直接在插值表达式里写 item.price.toFixed(2),再拼 '¥';时间则写一个 methods.formatTime,模板里调用 {{ formatTime(item.add_time) }}。这样能跑,但模板里混入太多格式化逻辑,复用和阅读都差。
怎么做:转写稿的结论是“专职专责”——格式化数据交给过滤器。过滤器本质是一个函数,返回什么,页面就渲染什么。
易错点:不要把“过滤器”写成“过滤期”;它不是万能的,Vue3 已移除过滤器,Vue2 中才是重点;复杂业务逻辑仍应封装成函数,不要全塞进模板。

过滤器的定义与基本调用 ​

为什么需要:定义后可在插值表达式中用管道符 | 调用,模板更干净。
怎么想:先写使用处,再补定义处。转写稿中先写 {{ item.price | currency }},但此时没有 currency,控制台会报“找不到过滤器”。
怎么做:在 Vue 配置对象的 filters 中定义局部过滤器。示例按转写稿整理:

html
<div id="app">
  <p v-for="item in list" :key="item.id">
    商品名称:{{ item.goods_name }}
    商品价格:{{ item.price | currency }}
    上架时间:{{ item.add_time | formatTime }}
  </p>
</div>
js
new Vue({
  el: '#app',
  data: {
    list: [
      { id: 1, goods_name: '水果葡萄', price: 12.345, add_time: Date.now() }
    ]
  },
  filters: {
    currency(v) {
      return v.toFixed(2)
    }
  }
})

调试时转写稿先让 currency 返回 100,页面价格都变成 100,证明过滤器被调用;再接收参数 v 并输出,确认 v 就是被过滤的价格;最后返回 v.toFixed(2)。
易错点:管道符是竖线 |,不是数字 1;过滤器名要和使用处一致;局部过滤器只在当前 Vue 实例/组件可用;属性绑定中也可用管道符,但转写稿只点明场景,未展开代码。

过滤器参数:第一个参数永远是被过滤的数据 ​

为什么需要:价格有时保留两位,有时保留一位;货币符号可能是 ¥ 也可能是 $,需要参数化。
怎么想:转写稿强调:过滤器函数接收的第一个参数永远是被过滤的数据,从第二个参数开始才是使用时传入的参数。
怎么做:按转写稿整理为:

js
filters: {
  currency(v, n = 2, type = '¥') {
    return type + v.toFixed(n)
  }
}

使用时:{{ item.price | currency }} 默认两位、羊头;{{ item.price | currency(3, '$') }} 表示保留三位并加美元标识。
易错点:不要以为括号里第一个 1 会传给 v;v 始终是管道左边的数据。默认参数要写对,n = 2 表示不传时保留两位;toFixed 返回字符串,拼货币符号时要注意字符串拼接。

时间格式化:先验证调用,再补 padStart ​

为什么需要:时间戳直接展示用户看不懂,要转成年月日时分秒。
怎么想:转写稿先用 methods.formatTime 返回 100,页面都变 100,证明模板调用成功;再在方法里 new Date(t),用 getFullYear()、getMonth() + 1、getDate()、getHours()、getMinutes()、getSeconds() 拼接。
怎么做:位数不足用 toString().padStart(2, '0') 补零。转写稿中“panda”应纠正为 padStart,“get most”是 getMonth,“get day”是 getDate。整理成过滤器:

js
formatTime(t) {
  const time = new Date(t)
  const y = time.getFullYear()
  const m = (time.getMonth() + 1).toString().padStart(2, '0')
  const d = time.getDate().toString().padStart(2, '0')
  const h = time.getHours().toString().padStart(2, '0')
  const min = time.getMinutes().toString().padStart(2, '0')
  const s = time.getSeconds().toString().padStart(2, '0')
  return `${y}-${m}-${d} ${h}:${min}:${s}`
}

易错点:月份要 +1;补零前先转字符串,数字没有 padStart;getDate() 才是“日”,不要和 getDay() 混淆;过滤器返回什么,页面就显示什么。

调试顺序与易错点汇总 ​

为什么需要:转写稿的调试价值在于“先证明调用链,再补格式化”。
怎么想:使用处写过滤器→报找不到→定义 filters→先返回固定值→再输出参数→最后写真实格式化→再传参调默认值。
怎么做:按“固定值验证、参数验证、真实逻辑验证”三步走。
易错点:filters 是对象,不是 methods;过滤器名不要和变量名混淆;模板中管道右侧是过滤器名,不是函数调用时必须加括号,传参才加括号;不要把 v-cloak 等指令混进过滤器定义。

从“原始数据不好看”说起 ​

为什么需要:商品价格是 12.345,上架时间是时间戳,直接渲染给用户看,既不直观也不统一。
怎么想:先不用过滤器,直接在插值里写格式化语句,或者定义方法返回格式化结果,也能完成。但这样会把“数据展示格式”散落在模板和业务逻辑里。
怎么做:Vue2 提供过滤器,专门把数据“过滤”成要展示的样子。转写稿也强调:过滤器的主要作用是对数据进行格式化处理,做到专职专责。
易错点:过滤器不是万能的,它不改变原数据,只决定最终渲染内容;Vue3 已移除过滤器,本节按 Vue2 学习。

过滤器的本质与两种使用位置 ​

为什么需要:知道本质,才能理解参数和返回值。
怎么想:过滤器本质是一个函数,输入被过滤的数据,返回格式化后的内容。
怎么做:使用时写在管道符 | 右边:{{ 数据 | 过滤器名 }};也可带参数:{{ 数据 | 过滤器名(参数1, 参数2) }}。笔记指出应用主要有两种:插值 {{}} 和属性渲染 v-bind:xxx="过滤器"。
易错点:管道符是单个 |,不是逻辑或 ||;过滤器名没定义时,Vue 会报“找不到过滤器”。

定义局部过滤器 filters ​

为什么需要:先声明过滤器,模板里才能使用。
怎么想:在 Vue 配置对象中增加 filters,它的值是一个对象;对象属性名就是过滤器名,属性值是函数。
怎么做:函数返回值就是过滤后要渲染的内容;函数第一个参数固定是“要被过滤的数据”,从第二个参数开始才是使用时传入的参数。比如 100.12345 | currency(1) 对应 currency(v,n){ /* v:100, n:1 */ }。
易错点:不要写成 filter;不要误以为传入的第一个参数是括号里的参数,第一个参数永远是管道符左边的数据。

调试过程:从固定返回到真实格式化 ​

为什么需要:转写稿中先让过滤器返回 100,观察到所有价格都变成 100,从而确认“返回值即渲染内容”。
怎么想:先验证过滤器是否被调用,再逐步换成真实逻辑。
怎么做:currency(v) 中先 return "¥"+v.toFixed(2);需要参数时写 currency2(v,n=2,type="¥"),调用 currency2(1,"$") 时 n 为 1、type 为 $。日期过滤器用 new Date(t) 取出年月日时分秒,再用 padStart(2,0) 补零。
易错点:toFixed 返回字符串;getMonth() 要加 1;补零前先转字符串;参数顺序不能乱。

完整示例代码 ​

html
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Title</title>
	<script src="lib/vue.js"></script>
</head>
<body>
<div id="app">
	<div v-for="item in goodsList" :key="item.id">
		<p>商品名称:{{item.goodsName}}</p>
		<p>商品价格1:{{item.goodsPrice | currency}}</p>
		<p>商品价格2:{{item.goodsPrice | currency2(1,"$")}}</p>
		<p>上架时间:{{item.addTime | date}}</p>
		<hr/>
	</div>
</div>
</body>
<script>
	// 1- 过滤器的本质是一个函数
	// 2- 过滤器应用主要有两种:1-插值{{}} 2-属性渲染v-bind:xxx="过滤器"
	// 3- 过滤器分为全局过滤器以及局部过滤器
	// 4- 过滤器在使用时:
	//    4-1 数据 | 过滤器的名字   (|:管道符)
	//    4-2 数据 | 过滤器的名字(参数1,参数2,...)   (|:管道符)
	// 5- 定义过滤器:
	//    5-1 在Vue配置对象中增加属性filters,值是一个对象
	//    5-2 filters对象的属性名即是过滤器的名字,属性值是函数。
	//    5-3 过滤器函数返回的值即是过滤后要渲染的内容。
	//    5-4 过滤器函数接收的第一个参数即是要被过滤的数据
	//    5-5 过滤器接收的第二个参数开始才是使用过滤器传递的参数
	//        比如使用时 100.12345 | currency(1) ,过滤器  currency(v,n){/*v:100,n:1*/}
	
	new Vue({
		el:"#app",
		data:{
			goodsList:[
				{
					id:1,
					goodsName:"葡萄",
					goodsPrice:12.345,
					addTime:Date.now()
				},{
					id:2,
					goodsName:"梨",
					goodsPrice: 45.678,
					addTime: Date.now()-998887666
				}
			]
		},
		filters:{
			// v是被过滤的数据
			currency(v){
				return "¥"+v.toFixed(2);
			},
			currency2(v,n=2,type="¥"){
				console.log(v,n);
				return type+v.toFixed(n);
			},
			// 声明了一个名字为date的过滤器,t即是要过滤的数据
			date(t){
				const timer = new Date(t);
				return timer.getFullYear()+"-"+
						(timer.getMonth()+1).toString().padStart(2,0)+"-"+
						timer.getDate().toString().padStart(2,0)+" "+
						timer.getHours().toString().padStart(2,0)+":"+
						timer.getMinutes().toString().padStart(2,0)+":"+
						timer.getSeconds().toString().padStart(2,0);
			}
		}
	})
</script>
</html>

易错点小结 ​

为什么需要:过滤器语法短,错误往往藏在参数顺序和声明位置。
怎么想:先看模板有没有写 |,再看 filters 中名字是否一致,最后看函数第一个参数是否当成“被过滤数据”。
怎么做:模板中先写数据,再写管道符,再写过滤器名;传参时按 currency2(1,"$") 的顺序对照函数形参。
易错点:局部过滤器只在当前 Vue 实例可用;过滤器函数必须有返回值;不要修改原数据;日期补零要处理 getMonth()+1;未定义过滤器会直接报错。

课堂画面 ​

课堂画面 1

课堂画面 2

课堂画面 3

课堂画面 4

课堂画面 5

代码示例 ​

示例一:v-cloak 解决插值表达式闪烁 ​

html
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>v-cloak</title>
	<style>
		[v-cloak]{
			display:none;
		}
	</style>
	<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="app" v-cloak>
	<p v-for="(item,index) in arr" :key="index">{{item}}</p>
</div>
</body>
<script>
	// 当挂载完成后,Vue会将v-cloak属性移除。解决屏幕闪烁问题
	// 1- 在未挂载完Vue时,会增加v-cloak属性,为该属性增加样式display:none;将拥有该属性的元素隐藏
	// 2- 当挂载完成后,Vue会将v-cloak属性移除。样式不会起作用,即会显示元素。
	setTimeout(()=>{
		new Vue({
			el:"#app",
			data:{
				arr:[1,2,3,4]
			}
		})
	},3000)
</script>
</html>

要点解释:

  • [v-cloak]{display:none} 是属性选择器,必须写成中括号;写成 v-cloak{} 是元素选择器,不会生效。
  • v-cloak 必须写在 Vue 的挂载元素(这里是 #app)上,写在子元素上无效。
  • 挂载完成后 Vue 会自动把 v-cloak 属性移除,样式失效,元素才显示出来。
  • 易错点:CSS 必须放在 head 中且先于渲染生效;如果不加 setTimeout 延迟(模拟 Vue 加载慢),页面瞬间就编译完,看不到闪烁现象,会误以为指令没用。

示例二:v-pre 与 v-once ​

html
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>v-pre 与 v-once</title>
	<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="app">
	<input type="text" v-model="str">
	<p>{{str}}</p>
	<p v-pre>大家好,你们现在学习的是Vue,可以借助插值表达式{{str}}输出数据状态str</p>
	<p v-once>str的初始值为:{{str}}</p>
</div>
</body>
<script>
	// v-pre:为元素增加该指令后,该元素即被数据响应跳过。(不会对其元素进行解析)
	// v-once:只响应初始值,后续数据再次发生改变,使用该指令的元素不会对数据进行响应。
	new Vue({
		el: "#app",
		data: {
			str: "你现在过的还好吗?"
		}
	})
</script>
</html>

要点解释:

  • v-pre 让 Vue 跳过该元素及其子元素的编译,{{str}} 会原样显示成两个大括号,适合演示语法或防止无用编译。
  • v-once 只做一次性渲染,绑定初始值;之后 str 再变,该 <p> 不会更新(但普通 <p>{{str}}</p> 会变)。
  • 易错点:v-pre 元素内部的 v-model、v-for 等指令也全部失效,别把它加到需要响应式的父容器上;v-once 不是“只改一次”,而是“不再随数据更新”。

示例三:过滤器的基本使用(插值 + 传参 + 局部过滤器) ​

html
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>过滤器</title>
	<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="app">
	<div v-for="item in goodsList" :key="item.id">
		<p>商品名称:{{item.goodsName}}</p>
		<p>商品价格1:{{item.goodsPrice | currency}}</p>
		<p>商品价格2:{{item.goodsPrice | currency2(1,"$")}}</p>
		<p>上架时间:{{item.addTime | date}}</p>
		<hr/>
	</div>
</div>
</body>
<script>
	// 1- 过滤器的本质是一个函数
	// 2- 过滤器应用主要有两种:1-插值{{}} 2-属性渲染v-bind:xxx="过滤器"
	// 3- 过滤器分为全局过滤器以及局部过滤器
	// 4- 过滤器在使用时:
	//    4-1 数据 | 过滤器的名字   (|:管道符)
	//    4-2 数据 | 过滤器的名字(参数1,参数2,...)   (|:管道符)
	// 5- 定义过滤器:
	//    5-1 在Vue配置对象中增加属性filters,值是一个对象
	//    5-2 filters对象的属性名即是过滤器的名字,属性值是函数。
	//    5-3 过滤器函数返回的值即是过滤后要渲染的内容。
	//    5-4 过滤器函数接收的第一个参数即是要被过滤的数据
	//    5-5 过滤器接收的第二个参数开始才是使用过滤器传递的参数

	new Vue({
		el:"#app",
		data:{
			goodsList:[
				{
					id:1,
					goodsName:"葡萄",
					goodsPrice:12.345,
					addTime:Date.now()
				},{
					id:2,
					goodsName:"梨",
					goodsPrice: 45.678,
					addTime: Date.now()-998887666
				}
			]
		},
		filters:{
			// v是被过滤的数据
			currency(v){
				return "¥"+v.toFixed(2);
			},
			currency2(v,n=2,type="¥"){
				console.log(v,n);
				return type+v.toFixed(n);
			},
			// 声明了一个名字为date的过滤器,t即是要过滤的数据
			date(t){
				const timer = new Date(t);
				return timer.getFullYear()+"-"+
						(timer.getMonth()+1).toString().padStart(2,0)+"-"+
						timer.getDate().toString().padStart(2,0)+" "+
						timer.getHours().toString().padStart(2,0)+":"+
						timer.getMinutes().toString().padStart(2,0)+":"+
						timer.getSeconds().toString().padStart(2,0);
			}
		}
	})
</script>
</html>

要点解释:

  • 用法是 数据 | 过滤器名,管道符 | 左边是被过滤的数据,右边是过滤器名;currency2(1,"$") 这种写法才是在传参。
  • 过滤器函数第一个形参永远是“被过滤的数据”,currency2(v,n=2,type="¥") 里的 n、type 才是使用者传进来的实参,顺序很容易搞反。
  • filters 写在 Vue 配置对象里是局部过滤器,只能在 #app 内使用;全局过滤器用 Vue.filter("名字", fn) 注册。
  • 易错点:getMonth() 从 0 开始,必须 +1;月份/日期补零要先 toString() 再 padStart(2,0),因为 padStart 是字符串方法,直接对数字调用会报错。
  • 易错点:过滤器只用于“格式化展示”,不要在过滤器里改原数据;Vue 3 已移除过滤器,面试/迁移时要注意。

常见错误 ​

  1. 现象:页面仍先闪出 {{item}},几秒后才正常渲染。
    原因:只写了 v-cloak,没有配套 [v-cloak]{ display:none; }。
    修法:样式必须存在且选择器正确,v-cloak 加在需要隐藏的挂载根元素上。

  2. 现象:v-pre 元素里的 {{str}} 始终不更新,误以为响应式失效。
    原因:v-pre 会跳过该元素及其子元素的编译,插值表达式不会被解析。
    修法:需要显示动态数据时移除 v-pre;只把它用于展示原始模板或跳过编译的静态片段。

  3. 现象:v-once 元素首次显示后,输入框修改数据但该元素不变化。
    原因:v-once 只按初始值渲染一次,后续数据变化不再响应。
    修法:需要持续响应的区域不要加 v-once;仅用于确定不会变化的初始快照。

  4. 现象:过滤器写成 currency(v,n),使用时传了 1,但 v 不是 1,结果不符合预期。
    原因:过滤器第一个参数永远是被过滤的数据,使用处传入的参数从第二个形参开始接收。
    修法:按 currency(value, n, type) 定义,使用时写 {{ price | currency(2, "$") }}。

  5. 现象:过滤器写了逻辑但页面显示 undefined 或原数据未格式化。
    原因:过滤器函数没有 return,或误把复杂副作用逻辑放进过滤器。
    修法:过滤器必须返回格式化后的值;复杂业务逻辑放 methods/computed,过滤器只做纯格式化。

  6. 现象:局部过滤器在另一个组件/实例中不可用,控制台报“过滤器未定义”。
    原因:在某个 Vue 配置对象的 filters 中定义的是局部过滤器,作用域仅限当前实例/组件。
    修法:当前实例使用就放局部 filters;需要跨组件复用时改用全局过滤器或抽成普通工具函数。

练习 ​

  1. 改错题:下面代码异步挂载 Vue 后仍会闪烁,请指出缺失项并修正。

    html
    &lt;div id="app" v-cloak&gt;&#123;&#123; msg &#125;&#125;&lt;/div&gt;
  2. 选择题:以下场景分别应选 v-pre、v-once 还是都不使用?

    • A. 展示一段包含 {{ }} 的 Vue 教学示例,不希望被编译。
    • B. 展示用户首次进入页面时的初始昵称,后续改名不影响该处展示。
    • C. 商品价格需要随输入框实时变化。
  3. 实现题:在 Vue2 局部过滤器中实现 currency(value, digits = 2, symbol = "¥"),要求保留指定小数位并拼接货币符号,给出模板调用示例。

  4. 改错题:现有过滤器 fmt(v, n),模板写成 {{ price | fmt(price, 2) }},请说明参数问题并改为正确写法。

  5. 实现题:写一个时间格式化过滤器 date(t),把时间戳输出为 YYYY-MM-DD HH:mm:ss,要求月、日、时、分、秒不足两位补 0。

原视频对照 ​

#视频时长
1复习46:07
2v-cloak07:07
3v-pre 与 v-once05:47
4未使用过滤器13:10
5过滤器的基本使用17:44

小结 ​

  1. v-cloak 解决的是“编译前模板闪现”,不是网络慢、组件加载慢的通用 loading 方案。
  2. v-pre 和 v-once 都是在控制“是否编译/是否继续响应”,不要拿它们当样式或性能优化的万能开关。
  3. 过滤器适合“输入一个值、输出一个展示值”的纯格式化场景,不适合承载请求、DOM 操作等副作用。
  4. 过滤器参数顺序是高频踩坑点:第一个参数固定是管道左侧数据,使用处传参从第二个形参开始。
  5. 局部过滤器优先用于当前组件;跨组件复用再考虑全局过滤器或普通工具函数。
  6. 复杂格式化逻辑应优先抽成函数,避免模板中堆叠多个过滤器导致可读性下降。
  7. Vue3 已移除过滤器,Vue2 项目中使用过滤器时要为后续迁移保留“可替换为方法/计算属性”的设计。