Vue2上
Vue2
一、Vue基础
(一) Vue简介
Vue官网
- Vue(全称 Vue.js)是一套用于构建用户界面的渐进式 JavaScript 框架
- Vue的特点
1.采用组件化模式
,提高代码复用率,且让代码更好维护
2.声明式
编码,让编码人员无需直接操作DOM.提高开发效率
3.使用虚拟`DOM
+优秀的Diff算法
,尽量复用DOM 节点
<!-- 展示人员列表的容器 -->
<ul id="list"></ul><script type="text/javascript">
//一些人的数据
let persons = [{id:'001',name:'张三',age:18},{id:'002',name:'李四',age:19},{id:'003',name:'王五',age:20}
]//准备html字符串
let htmlStr = ''//遍历数据拼接html字符串
persons.forEach( p => {htmlStr += `<li>${p.id} - ${p.name} - ${p.age}</li>`
});//获取list元素
let list = document.getElementById('list')//修改内容(亲自操作DOM)
list.innerHTML = htmlStr
</script>
展示:
(二)创建Vue实例
在Vue中,一切都从创建Vue实例开始。通过new Vue()
来初始化一个Vue实例,并传入一个选项对象,该对象包含了实例的配置信息。
const vm = new Vue({// 用于指定Vue实例挂载到的DOM元素,// 可以是CSS选择器字符串,也可以是DOM元素本身el: '#app', // 存放数据,这些数据是响应式的,数据供el所指定的容器去使用。// 即当数据发生变化时,视图会自动更新data: {message: 'Hello Vue!',count: 0},// 定义方法,这些方法可以在模板、生命周期钩子等地方调用methods: {increment() {this.count++;}},// 生命周期钩子函数,在实例创建完成后调用created() {console.log('Vue实例已创建');}
});
<div id="app"><p>{{ message }}</p><p>Count: {{ count }}</p><button @click="increment">Increment</button>
</div>
注意事项:
el
指定的DOM元素必须在Vue实例创建之前存在于页面中,否则会报错。- 不要在
data
选项中使用箭头函数,因为箭头函数没有自己的this
,会导致this
指向错误。
(三):Vue核心
1.1 初识Vue
- 掌握Vue实例创建(
new Vue({配置项})
),理解el
(挂载DOM,关联Vue实例与页面容器 )、data
(存储响应式数据,供模板使用 )基础配置- 熟悉插值语法
{{ 表达式 }}
,指令(如v-bind
、v-if
等)基本用法,体会声明式渲染优势
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>初识Vue</title><!-- 引入Vue --><script type="text/javascript" src="../js/vue.js"></script>
</head><body><!-- 初识Vue:1.想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象;2.root容器里的代码依然符合html规范,只不过混入了一些特殊的Vue语法;3.root容器里的代码被称为【Vue模板】;--><!-- 准备好一个容器 --><div id="root"><h1>Hello, {{name}}</h1></div><script type="text/javascript">Vue.config.productionTip = false // 阻止 vue 在启动时生成生产提示。// 创建Vue实例new Vue({el: '#root', // el用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串。data: { // data中用于存储数据,数据供el所指定的容器去使用,值我们暂时先写成一个对象。name: '珊瑚世纪'}})</script>
</body></html>
1.2 数据绑定
- 区分单向绑定(
v-bind
,数据→DOM,如绑定属性 )与双向绑定(v-model
,数据↔DOM,常用于表单元素,自动同步视图和数据 )- 掌握在表单(输入框、单选框等)场景中
v-model
的使用,理解其语法糖原理(结合v-bind:value
和v-on:input
)
1.2.1 文本插值:使用双大括号{{ }}
将数据显示在页面上。
<p>{{ message }}</p>
也可以在双大括号中使用JavaScript表达式,如{{ message.toUpperCase() }}
。
1.2.2. 属性绑定:使用v-bind
指令(简写为:
)来绑定HTML属性。
<!-- 绑定图片的src属性 -->
<img :src="imageUrl" alt="Product Image">
<!-- 绑定按钮的disabled属性 -->
<button :disabled="isButtonDisabled">Submit</button>
1.2.3. 双向数据绑定:使用v-model
指令实现表单元素和数据之间的双向绑定,常用于input
、textarea
、select
等元素。
<input v-model="message">
等价于:
<input :value="message" @input="message = $event.target.value">
以下是一个完整的 HTML 页面示例,结合 Vue.js 实现 单向数据绑定(v-bind) 和 双向数据绑定(v-model) 的效果,同时演示简单的事件监听(点击事件):
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><!-- 引入 Vue.js --><script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script><title>Vue 单向 & 双向绑定 + 事件示例</title><style>.container {margin: 20px;padding: 20px;border: 1px solid #ccc;max-width: 500px;}input {margin: 5px 0;padding: 6px;width: 200px;}button {margin-top: 10px;padding: 8px 15px;cursor: pointer;}</style>
</head>
<body><!-- Vue 挂载容器 --><div id="app" class="container"><!-- 单向数据绑定:v-bind 或简写 :value 数据变化会更新视图,但输入不会改变 data(需配合事件手动更新) --><h3>单向数据绑定(v-bind)</h3><input type="text" :value="message" placeholder="单向绑定,输入不会直接改 data"><!-- 按钮点击事件:v-on 或简写 @click 手动更新 data,演示单向绑定的“单向”特性 --><button @click="updateMessage">点击更新单向绑定数据</button><p>当前单向绑定的数据:{{ message }}</p><hr><!-- 双向数据绑定:v-model 数据变化更新视图,视图输入也会直接更新 data --><h3>双向数据绑定(v-model)</h3><input type="text" v-model="messageTwo" placeholder="双向绑定,输入会直接改 data"><p>当前双向绑定的数据:{{ messageTwo }}</p></div><script>// 创建 Vue 实例new Vue({el: '#app',data: {// 单向绑定的数据message: '初始单向数据',// 双向绑定的数据messageTwo: '初始双向数据'},methods: {// 点击事件:手动更新单向绑定的 messageupdateMessage() {// 这里可以自定义更新逻辑,比如拼接内容this.message = '更新后的单向数据:' + new Date().toLocaleTimeString();}}});</script>
</body>
</html>
1.3模版语法
1.3.2模板语法相关知识及核心要点解析
1. 插值语法
- 语法:
{{ 表达式 }}
- 作用:用于在页面中直接渲染 Vue 实例的数据 ,解析标签体内容,
- 比如示例里 “你好,jack” ,实际开发中
{{ jack }}
会被 Vue 实例data
里的jack
变量值替换,实现数据到视图的动态展示。
写法:{{xxx}},xxx 是 js 表达式,且可以直接读取到 data 中的所有属性。
2. 指令语法
- 语法:Vue 指令以
v-
开头 - 作用:用于解析标签,操作 DOM、处理事件、数据绑定等复杂逻辑 。
- 示例里的链接,常配合
v-bind
(可简写为:
,用于绑定属性,比如href
)、v-on
(可简写为@
,用于绑定事件,比如点击事件)等指令,让页面元素具备交互性,像点击链接跳转、按钮触发方法等,是实现 Vue 动态交互的关键语法 。
简单说,插值语法主打简单数据展示,指令语法负责复杂交互与 DOM 控制 ,共同支撑 Vue 模板把数据和逻辑渲染成页面。
这个示例展示了 Vue 模板的核心语法:
-
插值语法
{{ message }}
:显示数据属性{{ user.name }}
:访问对象属性{{ isShow ? '显示' : '隐藏' }}
:表达式计算
-
指令语法
v-bind:href
:动态绑定属性====》v-bind:
可以简写成:
v-on:click
:事件监听v-model
:双向数据绑定v-if/v-else
:条件渲染v-for
:列表渲染
-
响应式交互
- 点击按钮修改数据
- 输入框实时同步数据
- 列表动态渲染
运行后可以体验以下效果:
- 点击"点击修改消息"按钮更新标题
- 在输入框输入内容实时显示
- 列表项会按数据自动生成
- 根据分数显示不同状态
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Vue模板语法示例</title><!-- 引入Vue.js --><script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
</head>
<body><div id="app"><!-- 插值语法示例 --><h1>{{ message }}</h1><p>{{ user.name }} 的年龄是 {{ user.age }}</p><p>{{ isShow ? '显示状态' : '隐藏状态' }}</p><!-- 指令语法示例 --><!-- <a v-bind:href="url">Vue官网</a> --><a :href="url">Vue官网</a>// v-bind:可以简写成:<!--<button v-on:click="changeMessage">点击修改消息</button> --><button @click="changeMessage">点击修改消息</button>//`v-on`(可简写为`@` <input v-model="inputValue" type="text"><p>输入的值: {{ inputValue }}</p><!-- 条件渲染 --><p v-if="score >= 60">及格</p><p v-else>不及格</p><!-- 列表渲染 --><ul><li v-for="(item, index) in list" :key="index">{{ index }} - {{ item }}</li></ul></div><script>new Vue({el: '#app',data: {message: 'Hello Vue!',user: { name: '张三', age: 25 },isShow: true,url: 'https://cn.vuejs.org',inputValue: '',score: 85,list: ['苹果', '香蕉', '橙子']},methods: {changeMessage() {this.message = '消息已修改';}}});</script>
</body>
</html>
1.3.2Vue的插值语法和指令语法详细解释
1. 插值语法
插值语法是Vue中最基本的数据绑定方式,它使用双大括号{{ }}
将表达式嵌入到HTML模板中。Vue会自动将表达式的值渲染到DOM中,并在数据发生变化时更新DOM。
1.1 显示数据属性
<div id="app"><p>{{ message }}</p> <!-- 显示data中的message属性 -->
</div><script>new Vue({el: '#app',data: {message: 'Hello Vue!'}});
</script>
1.2 访问对象属性
<div id="app"><p>{{ user.name }}的年龄是{{ user.age }}</p> <!-- 访问对象的属性 -->
</div><script>new Vue({el: '#app',data: {user: {name: '张三',age: 25}}});
</script>
1.3 表达式计算
<div id="app"><p>{{ isShow ? '显示状态' : '隐藏状态' }}</p> <!-- 三元表达式 --><p>{{ number * 2 }}</p> <!-- 数学运算 --><p>{{ message.split('').reverse().join('') }}</p> <!-- 调用JavaScript方法 -->
</div><script>new Vue({el: '#app',data: {isShow: true,number: 10,message: 'Hello'}});
</script>
2. 指令语法
指令是带有v-
前缀的特殊属性,用于在模板中执行特殊的响应式行为。
2.1 v-bind
:动态绑定属性
- 功能:将HTML元素的属性(如href、src、class等)与Vue实例的数据属性绑定,使属性值随数据变化而更新。
- 应用场景:动态设置链接地址、图片路径、元素样式、禁用状态等。
- 缩写:
:
,例如:href="url"
等价于v-bind:href="url"
。
<div id="app"><a v-bind:href="url">Vue官网</a> <!-- 完整写法 --><a :href="url">Vue官网</a> <!-- 缩写 --><img :src="imageUrl" alt="图片"> <!-- 绑定图片地址 --><div :class="{ active: isActive }">动态类名</div> <!-- 动态绑定class -->
</div><script>new Vue({el: '#app',data: {url: 'https://cn.vuejs.org',imageUrl: 'https://example.com/image.jpg',isActive: true}});
</script>
2.2 v-on:click
- 事件监听
- 功能:为DOM元素绑定事件监听器,当事件触发时执行Vue实例中定义的方法。
- 应用场景:处理用户交互(点击、输入、提交等)、实现组件通信、触发数据更新。
- 缩写:
@
,例如@click="handleClick"
等价于v-on:click="handleClick"
。 - 修饰符:支持
.prevent
(阻止默认行为)、.stop
(停止事件冒泡)等。
<div id="app"><button v-on:click="sayHello">点击我</button> <!-- 完整写法 --><button @click="sayHello">点击我</button> <!-- 缩写 --><button @click="increase(1)">增加</button> <!-- 传递参数 --><input @keyup.enter="submit"> <!-- 按键修饰符 -->
</div><script>new Vue({el: '#app',data: {count: 0},methods: {sayHello() {alert('Hello!');},increase(num) {this.count += num;},submit() {console.log('提交表单');}}});
</script>
2.3 v-model
- 双向数据绑定
- 功能:在表单元素(如input、textarea、select)和Vue实例的数据之间创建双向绑定,数据变化自动更新视图,视图输入自动更新数据。(只能用在表单元素上)
- 原理:本质是
v-bind
和v-on
的语法糖(如:value
+@input
)。 - 应用场景:表单数据收集、用户输入实时同步。
- 支持的表单元素:文本框、单选框、复选框、下拉选择框等。
<div id="app"><input v-model="message" type="text"><p>输入的内容: {{ message }}</p><textarea v-model="content"></textarea><p>文本内容: {{ content }}</p><input type="checkbox" v-model="isChecked"><p>是否选中: {{ isChecked }}</p><select v-model="selected"><option value="A">选项A</option><option value="B">选项B</option></select><p>选中的值: {{ selected }}</p>
</div><script>new Vue({el: '#app',data: {message: '',content: '',isChecked: false,selected: 'A'}});
</script>
2.4 v-if
/ v-else
- 条件渲染
- 功能:根据表达式的值动态插入或移除DOM元素,值为
true
时渲染元素,false
时销毁元素。 - 特点:
- 会真实创建或移除DOM节点,适用于运行时条件很少改变的场景。
- 可与
v-else-if
、v-else
连用,形成多条件判断。 - 使用
<template>
标签包裹多个元素以实现分组条件渲染。
<div id="app"><p v-if="score >= 60">及格</p><p v-else>不及格</p><div v-if="type === 'A'">类型A</div><div v-else-if="type === 'B'">类型B</div><div v-else>其他类型</div><!-- 用key管理可复用的元素 --><template v-if="loginType === 'username'"><label>用户名</label><input type="text" key="username-input"></template><template v-else><label>邮箱</label><input type="email" key="email-input"></template>
</div><script>new Vue({el: '#app',data: {score: 85,type: 'B',loginType: 'username'}});
</script>
2.5 v-for
- 列表渲染
- 功能:基于数组或对象循环渲染DOM元素,每个循环项可访问索引或键值。
- 语法:
- 遍历数组:
v-for="item in items"
或v-for="(item, index) in items"
- 遍历对象:
v-for="(value, key) in object"
- 遍历数组:
- key属性:必须为每个循环项提供唯一的
:key
,帮助Vue识别节点变化,优化渲染效率。 - 应用场景:渲染列表、表格、动态组件等。
<div id="app"><ul><li v-for="item in list" :key="item.id">{{ item.name }}</li></ul><ul><li v-for="(item, index) in list">{{ index }} - {{ item.name }}</li></ul><ul><li v-for="(value, key) in user">{{ key }}: {{ value }}</li></ul><!-- 遍历数字 --><span v-for="n in 10">{{ n }} </span>
</div><script>new Vue({el: '#app',data: {list: [{ id: 1, name: '苹果' },{ id: 2, name: '香蕉' },{ id: 3, name: '橙子' }],user: {name: '张三',age: 25,gender: '男'}}});
</script>
其他常用指令
v-show
:条件显示
<div v-show="isVisible">这个元素会根据isVisible的值显示或隐藏</div>
v-html
:渲染HTML
<div v-html="htmlContent"></div><script>new Vue({el: '#app',data: {htmlContent: '<h2>这是HTML内容</h2>'}});
</script>
v-once
:只渲染一次
<span v-once>这个值不会随数据变化而更新: {{ message }}</span>
以下是 Vue 中 el
和 data
选项的常见写法及相关选项的对比,附带示例代码和注意事项:
el
选项的写法
- 字符串选择器(最常见)
new Vue({el: '#app', // 对应 HTML 中的 <div id="app"></div>
})
- DOM 节点引用
const appElement = document.getElementById('app');
new Vue({el: appElement,
})
- 延迟挂载(不设置
el
)
const vm = new Vue({// 无 el 选项data: { message: 'Hello' }
});// 手动挂载
vm.$mount('#app');
data
选项的写法
- 根实例(对象形式)
new Vue({el: '#app',data: {message: 'Root Instance',count: 0}
})
- 组件(函数形式)
// 全局组件
Vue.component('my-component', {data() {return {componentMessage: 'Component Data',items: []}}
});// 局部组件
export default {data() {return {localData: 'Local Component'}}
}
- 合并父组件传递的 props
export default {props: ['initialCount'],data() {return {count: this.initialCount || 0 // 基于 prop 初始化}}
}
相关选项对比
** 1. el
vs $mount
**
场景 | el | $mount |
---|---|---|
立即挂载 | ✅ | ✅(手动调用) |
延迟挂载 | ❌ | ✅ |
服务端渲染 | ❌ | ✅ |
2. 数据选项对比
选项 | 根实例 | 组件 | 响应式 |
---|---|---|---|
data 对象 | ✅(推荐) | ❌(共享状态) | ✅ |
data 函数 | ✅(可选) | ✅(必须) | ✅ |
computed | ✅ | ✅ | ✅(依赖追踪) |
props | ❌ | ✅ | ✅(单向数据流) |
<!DOCTYPE html>
<html>
<head><title>Vue el与data示例</title><script src="https://cdn.tailwindcss.com"></script><link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet"><script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script><script>tailwind.config = {theme: {extend: {colors: {primary: '#3B82F6',secondary: '#10B981',neutral: '#6B7280',},fontFamily: {sans: ['Inter', 'system-ui', 'sans-serif'],},},}}</script><style type="text/tailwindcss">@layer utilities {.content-auto {content-visibility: auto;}.card-shadow {box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);}}</style>
</head>
<body class="bg-gray-50 min-h-screen"><div class="container mx-auto px-4 py-8 max-w-4xl"><header class="text-center mb-12"><h1 class="text-[clamp(2rem,5vw,3rem)] font-bold text-gray-800 mb-2">Vue.js <span class="text-primary">el</span> 与 <span class="text-secondary">data</span> 示例</h1><p class="text-neutral text-lg">探索Vue实例中el与data的不同写法</p></header><main class="space-y-8"><!-- 示例1: el的字符串选择器写法 --><div class="bg-white rounded-xl p-6 card-shadow transition-all hover:shadow-lg"><h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center"><i class="fa fa-code text-primary mr-2"></i>示例1: el使用字符串选择器</h2><div class="grid grid-cols-1 md:grid-cols-2 gap-6"><div class="bg-gray-50 p-4 rounded-lg"><h3 class="font-medium text-gray-700 mb-2">代码</h3><pre class="text-sm font-mono text-gray-800 bg-gray-100 p-3 rounded overflow-x-auto">
new Vue({el: '#app1',data: {message: '这是通过字符串选择器挂载的Vue实例',count: 0}
})</pre></div><div class="bg-gray-50 p-4 rounded-lg"><h3 class="font-medium text-gray-700 mb-2">渲染结果</h3><div id="app1" class="p-4 border border-gray-200 rounded"><p>{{ message }}</p><button @click="count++" class="bg-primary text-white px-4 py-2 rounded hover:bg-primary/90 transition-colors">点击: {{ count }}</button></div></div></div></div><!-- 示例2: el的DOM节点写法 --><div class="bg-white rounded-xl p-6 card-shadow transition-all hover:shadow-lg"><h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center"><i class="fa fa-code text-primary mr-2"></i>示例2: el使用DOM节点引用</h2><div class="grid grid-cols-1 md:grid-cols-2 gap-6"><div class="bg-gray-50 p-4 rounded-lg"><h3 class="font-medium text-gray-700 mb-2">代码</h3><pre class="text-sm font-mono text-gray-800 bg-gray-100 p-3 rounded overflow-x-auto">
const app2Element = document.getElementById('app2');
new Vue({el: app2Element,data: {message: '这是通过DOM节点引用挂载的Vue实例',items: ['苹果', '香蕉', '橙子']}
})</pre></div><div class="bg-gray-50 p-4 rounded-lg"><h3 class="font-medium text-gray-700 mb-2">渲染结果</h3><div id="app2" class="p-4 border border-gray-200 rounded"><p>{{ message }}</p><ul class="list-disc pl-5 mt-2"><li v-for="item in items" :key="item">{{ item }}</li></ul></div></div></div></div><!-- 示例3: 延迟挂载写法 --><div class="bg-white rounded-xl p-6 card-shadow transition-all hover:shadow-lg"><h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center"><i class="fa fa-code text-primary mr-2"></i>示例3: 延迟挂载 (无el选项)</h2><div class="grid grid-cols-1 md:grid-cols-2 gap-6"><div class="bg-gray-50 p-4 rounded-lg"><h3 class="font-medium text-gray-700 mb-2">代码</h3><pre class="text-sm font-mono text-gray-800 bg-gray-100 p-3 rounded overflow-x-auto">
const vm3 = new Vue({data: {message: '这是延迟挂载的Vue实例',show: false},methods: {toggleShow() {this.show = !this.show;}}
});// 3秒后手动挂载
setTimeout(() => {vm3.$mount('#app3');
}, 3000);</pre></div><div class="bg-gray-50 p-4 rounded-lg"><h3 class="font-medium text-gray-700 mb-2">渲染结果</h3><div id="app3" class="p-4 border border-gray-200 rounded"><p v-if="!show" class="text-neutral italic">Vue实例正在挂载中...</p><div v-else><p>{{ message }}</p><button @click="toggleShow" class="bg-secondary text-white px-4 py-2 rounded hover:bg-secondary/90 transition-colors">切换状态</button><p v-if="show" class="mt-2 text-green-600">状态: 已挂载</p></div></div></div></div></div><!-- 示例4: data的对象式写法 --><div class="bg-white rounded-xl p-6 card-shadow transition-all hover:shadow-lg"><h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center"><i class="fa fa-code text-secondary mr-2"></i>示例4: data的对象式写法 (根实例)</h2><div class="grid grid-cols-1 md:grid-cols-2 gap-6"><div class="bg-gray-50 p-4 rounded-lg"><h3 class="font-medium text-gray-700 mb-2">代码</h3><pre class="text-sm font-mono text-gray-800 bg-gray-100 p-3 rounded overflow-x-auto">
new Vue({el: '#app4',data: {title: '对象式data示例',user: {name: '张三',age: 25}}
})</pre></div><div class="bg-gray-50 p-4 rounded-lg"><h3 class="font-medium text-gray-700 mb-2">渲染结果</h3><div id="app4" class="p-4 border border-gray-200 rounded"><h3 class="font-medium text-lg">{{ title }}</h3><p>姓名: {{ user.name }}</p><p>年龄: {{ user.age }}</p></div></div></div></div><!-- 示例5: data的函数式写法 --><div class="bg-white rounded-xl p-6 card-shadow transition-all hover:shadow-lg"><h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center"><i class="fa fa-code text-secondary mr-2"></i>示例5: data的函数式写法 (组件模式)</h2><div class="grid grid-cols-1 md:grid-cols-2 gap-6"><div class="bg-gray-50 p-4 rounded-lg"><h3 class="font-medium text-gray-700 mb-2">代码</h3><pre class="text-sm font-mono text-gray-800 bg-gray-100 p-3 rounded overflow-x-auto">
// 定义组件
Vue.component('counter-component', {data() {return {count: 0}},template: `<div class="p-4 border border-gray-200 rounded"><p>组件计数器: {{ count }}</p><button @click="count++" class="bg-secondary text-white px-4 py-2 rounded hover:bg-secondary/90 transition-colors">点击增加</button></div>`
});// 创建根实例
new Vue({el: '#app5'
});</pre></div><div class="bg-gray-50 p-4 rounded-lg"><h3 class="font-medium text-gray-700 mb-2">渲染结果</h3><div id="app5" class="space-y-4"><counter-component></counter-component><counter-component></counter-component><p class="text-sm text-neutral mt-2">注意: 每个组件实例都有独立的data状态</p></div></div></div></div></main><footer class="mt-12 text-center text-neutral"><p>© 2025 Vue.js 示例演示 | 使用 Tailwind CSS 和 Font Awesome 构建</p></footer></div><script>// 示例1: el的字符串选择器写法new Vue({el: '#app1',data: {message: '这是通过字符串选择器挂载的Vue实例',count: 0}});// 示例2: el的DOM节点写法const app2Element = document.getElementById('app2');new Vue({el: app2Element,data: {message: '这是通过DOM节点引用挂载的Vue实例',items: ['苹果', '香蕉', '橙子']}});// 示例3: 延迟挂载写法const vm3 = new Vue({data: {message: '这是延迟挂载的Vue实例',show: false},methods: {toggleShow() {this.show = !this.show;}}});// 3秒后手动挂载setTimeout(() => {vm3.$mount('#app3');}, 3000);// 示例4: data的对象式写法new Vue({el: '#app4',data: {title: '对象式data示例',user: {name: '张三',age: 25}}});// 示例5: data的函数式写法Vue.component('counter-component', {data() {return {count: 0}},template: `<div class="p-4 border border-gray-200 rounded"><p>组件计数器: {{ count }}</p><button @click="count++" class="bg-secondary text-white px-4 py-2 rounded hover:bg-secondary/90 transition-colors">点击增加</button></div>`});// 创建根实例new Vue({el: '#app5'});</script>
</body>
</html>
特殊场景示例
- 动态组件挂载
// 创建实例但不立即挂载
const MyComponent = Vue.extend({template: '<div>{{ message }}</div>',data() {return { message: 'Dynamic Component' }}
});// 手动创建并挂载到指定元素
new MyComponent().$mount('#dynamic-app');
- 响应式数据变更
export default {data() {return {user: { name: 'John' }}},methods: {updateName() {// 响应式更新this.user.name = 'Jane';// 非响应式添加属性(避免!)this.user.age = 30;// 正确方式:使用 Vue.setthis.$set(this.user, 'age', 30);}}
}
data与el的2种写法
1.el有2种写法
(1).new Vue
时候配置el
属性。
(2).先创建Vue实例,随后再通过vm.$mount('#root')
指定el
的值。
2.data有2种写法
(1).对象式
(2).函数式 如何选择:目前哪种写法都可以,以后学习到组件时,data必须使用函数式,否则会报错。
3.一个重要的原则:
由Vue管理的函数,一定不要写箭头函数,一旦写了箭头函数,this就不再是Vue实例了。
注意事项
- 组件
data
必须是函数:确保每个实例的数据独立。 - 响应式限制:
- 初始化后添加的属性需使用
Vue.set
。 - 深层对象变更需触发更新(如
this.items = [...this.items, newItem]
)。
- 初始化后添加的属性需使用
- 服务端渲染(SSR):
el
选项在 SSR 中无效,需使用$mount
。- 避免在
data
中使用浏览器专属 API(如window
)。
根据项目需求选择合适的写法,组件化开发时优先使用函数式 data
和延迟挂载模式。
1.4 数据监测
- 理解Vue响应式原理,基于
Object.defineProperty
的 getter/setter 劫持数据变化- 知道直接修改数组索引、长度不触发响应式更新(需用
push
、splice
等变异方法 ),对象新增属性需用Vue.set
或this.$set
1.4.1、核心作用
Object.defineProperty
是 JavaScript 提供的精细控制对象属性行为的底层 API,能自定义属性的:
- 是否可被遍历(枚举
enumerable
) - 是否可被修改(可写
writable
) - 是否可被删除/重新配置(可配置
configurable
) - 甚至自定义“获取值”“设置值”的逻辑(
get
/set
拦截)
Vue 2 的响应式原理,就是通过它劫持对象属性,实现数据变化时自动更新视图。
1.4.2、语法与参数
Object.defineProperty(目标对象, 属性名, {value: '属性值', // 基础值(与 get/set 互斥)enumerable: false, // 是否可枚举(遍历)writable: false, // 是否可修改值configurable: false, // 是否可删除/重新配置get() { ... }, // 取值时触发(与 value 互斥)set(newVal) { ... } // 赋值时触发(与 value 互斥)
})
参数说明(重点关注 enumerable
/writable
/configurable
):
参数 | 作用 | 默认值 |
---|---|---|
value | 直接定义属性的值(简单类型/对象) | - |
enumerable | 控制属性是否能被 for...in 、Object.keys 等枚举 | false |
writable | 控制属性是否能被 赋值修改(obj.xxx = newVal ) | false |
configurable | 控制属性是否能被 删除(delete obj.xxx )或 重新定义(再次调用 defineProperty ) | false |
get /set | 劫持属性的“读”“写”行为(实现响应式的核心) | - |
1.4.3、关键行为解析(枚举、可写、可配置)
- 枚举(
enumerable
):控制“能否被遍历”
enumerable: true
:属性会被for...in
、Object.keys
、Object.entries
等枚举到enumerable: false
:属性会被跳过,但仍可直接访问(obj.xxx
)
示例:
const obj = {};
Object.defineProperty(obj, 'name', {value: '张三',enumerable: true // 可枚举
});
Object.defineProperty(obj, 'age', {value: 18,enumerable: false // 不可枚举
});console.log(Object.keys(obj)); // 输出: ["name"](age 被跳过)
for (let key in obj) {console.log(key); // 输出: "name"(age 被跳过)
}
console.log(obj.age); // 仍可直接访问: 18
- 可写(
writable
):控制“能否被赋值修改”
writable: true
:允许obj.xxx = newVal
修改值writable: false
:禁止赋值修改,强行赋值会静默失败(严格模式下报错)
示例(非严格模式):
const obj = {};
Object.defineProperty(obj, 'name', {value: '张三',writable: false // 禁止修改
});obj.name = '李四'; // 静默失败(非严格模式)
console.log(obj.name); // 仍为: "张三"
示例(严格模式,需加 'use strict'
):
'use strict';
const obj = {};
Object.defineProperty(obj, 'name', {value: '张三',writable: false
});obj.name = '李四'; // 报错: Cannot assign to read only property...
- 可配置(
configurable
):控制“能否被删除/重新定义”
configurable: true
:允许delete obj.xxx
删除属性- 再次调用
Object.defineProperty
重新配置属性
configurable: false
:禁止- 无法删除属性(
delete
会返回false
) - 无法修改除
value
/writable
外的配置(严格模式下报错)
- 无法删除属性(
示例 1:允许删除
const obj = {};
Object.defineProperty(obj, 'name', {value: '张三',configurable: true
});delete obj.name;
console.log('name' in obj); // 输出: false(已删除)
示例 2:禁止删除/重新配置
const obj = {};
Object.defineProperty(obj, 'name', {value: '张三',configurable: false
});// 尝试删除
delete obj.name;
console.log('name' in obj); // 输出: true(删除失败)// 尝试重新配置(严格模式下报错)
'use strict';
Object.defineProperty(obj, 'name', { // 报错: Cannot redefine property...value: '李四'
});
- 进阶:
get
/set
劫持(响应式核心)
get
和set
是更强大的配置,能拦截属性的“读”和“写”,实现复杂逻辑(如 Vue 的数据响应式)。
示例:模拟 Vue 的 data
响应式
let _age = 18; // 私有变量,避免直接访问
const obj = {};Object.defineProperty(obj, 'age', {get() { console.log('读取 age,当前值:', _age);return _age; },set(newVal) { console.log('修改 age,新值:', newVal);_age = newVal;// 这里可触发视图更新(如 Vue 的更新逻辑)}
});// 测试
obj.age; // 触发 get: 输出 "读取 age,当前值: 18"
obj.age = 20; // 触发 set: 输出 "修改 age,新值: 20"
obj.age; // 触发 get: 输出 "读取 age,当前值: 20"
关键点:
get
/set
与value
互斥,不能同时用get
无参数,set
接收新值参数- 常用于数据劫持(如 Vue 2 响应式)、属性封装(替代传统
getXxx
/setXxx
)
1.4.4、实际场景应用
- 数据保护(冻结属性)
通过writable: false + configurable: false
,冻结对象属性,防止被修改/删除:
const user = {};
Object.defineProperty(user, 'id', {value: '1001',writable: false,configurable: false,enumerable: true
});// 无法修改/删除
user.id = '1002'; // 静默失败
delete user.id; // 失败
- Vue 2 响应式原理简化版
Vue 2 用Object.defineProperty
劫持data
中的属性,实现“数据变化 → 视图更新”:
function defineReactive(obj, key, val) {Object.defineProperty(obj, key, {get() {console.log('读取属性,可触发依赖收集');return val;},set(newVal) {if (newVal === val) return;val = newVal;console.log('属性修改,可触发视图更新');}});
}const data = { name: 'Vue' };
defineReactive(data, 'name', 'Vue');data.name; // 触发 get
data.name = 'Vue3'; // 触发 set,模拟视图更新
- 属性封装(替代 Class 的 getter/setter)
用get
/set
封装属性,隐藏内部逻辑:
const person = {_age: 18 // 约定私有变量(实际仍可访问)
};Object.defineProperty(person, 'age', {get() {return this._age;},set(newVal) {if (newVal < 0 || newVal > 120) {console.error('年龄不合法');return;}this._age = newVal;}
});person.age = 200; // 输出: 年龄不合法
4.数据代理
以下是一个清晰的 JavaScript 数据代理完整示例,模拟通过 obj1 代理访问 obj2 的 prop 属性,直接用 obj1.prop 操作 obj2.prop,包含详细注释和运行验证:
// 1. 定义原始对象 obj2
const obj2 = {prop: '我是 obj2 的属性值'
};// 2. 定义代理对象 obj1
const obj1 = {};// 3. 用 Object.defineProperty 实现数据代理
Object.defineProperty(obj1, 'prop', {// 读取 obj1.prop 时,返回 obj2.prop 的值get() {return obj2.prop;},// 修改 obj1.prop 时,同步修改 obj2.prop 的值set(newValue) {obj2.prop = newValue;}
});// 4. 测试代理效果
console.log('通过 obj1 访问 prop:', obj1.prop); // 输出: 我是 obj2 的属性值// 修改 obj1.prop,实际修改的是 obj2.prop
obj1.prop = '通过 obj1 修改后的值';
console.log('修改后,obj2.prop 的值:', obj2.prop); // 输出: 通过 obj1 修改后的值// 直接修改 obj2.prop,验证 obj1.prop 能同步读取
obj2.prop = '直接修改 obj2.prop 的值';
console.log('直接修改 obj2 后,obj1.prop 的值:', obj1.prop); // 输出: 直接修改 obj2.prop 的值
代码说明
- 核心逻辑:
- 通过 Object.defineProperty 给 obj1 定义 prop 属性,利用 get/set 拦截对
obj1.prop 的读写操作,代理到 obj2.prop。 - 读 obj1.prop → 实际读 obj2.prop;写 obj1.prop → 实际写 obj2.prop。
- 通过 Object.defineProperty 给 obj1 定义 prop 属性,利用 get/set 拦截对
- 验证流程:
- 先读 obj1.prop,验证代理读取。
- 写 obj1.prop,验证代理修改。
- 直接修改 obj2.prop,验证 obj1.prop 能同步获取最新值。
const data = {message: 'Hello, world!'
};const proxy = {};
Object.keys(data).forEach(key => {Object.defineProperty(proxy, key, {get() {return data[key];},set(newValue) {data[key] = newValue;return newValue;}});
});console.log(proxy.message); // 输出:Hello, world!
proxy.message = 'Changed message';
console.log(data.message); // 输出:Changed message
在上述代码中,通过遍历 data 对象的所有属性,使用 Object.defineProperty 给 proxy 对象定义同名属性,并在
get 和 set 方法中操作 data 对象对应的属性,从而实现了通过 proxy 对象代理访问 data 对象的属性。
如果 obj2 有多个属性想被 obj1 代理,可以用循环批量处理:
const obj2 = {prop1: '值1',prop2: '值2',prop3: '值3'
};const obj1 = {};// 批量代理 obj2 的所有属性到 obj1
Object.keys(obj2).forEach(key => {Object.defineProperty(obj1, key, {get() {return obj2[key];},set(newValue) {obj2[key] = newValue;}});
});// 测试
console.log(obj1.prop1); // 输出: 值1
obj1.prop2 = '新值2';
console.log(obj2.prop2); // 输出: 新值2
1.4.5总结
Object.defineProperty
是 JavaScript 精细控制属性行为的底层 API,核心配置:enumerable
:控制是否可枚举writable
:控制是否可修改configurable
:控制是否可删除/重新配置get
/set
:劫持属性读写,实现响应式等复杂逻辑
- 常见场景:数据保护(冻结属性)、响应式框架(如 Vue 2)、属性封装
- 注意:
configurable: false
后,大部分配置无法修改,需谨慎使用
1.5 MVVM模型
- M:Model 模型,对应data中的数据(数据层,如
data
里的数据 )、 - V :View 视图 ,模版(视图层,DOM结构 )、
- VM:ViewModel 视图模型,Vue实例对象(Vue实例,连接Model和View,处理逻辑、数据绑定 )
1.6 事件处理
- 掌握
v-on
(简写@
)绑定事件,事件处理器(methods中定义函数 ),事件对象$event
传递与使用- 了解事件修饰符(
.stop
阻止冒泡、.prevent
阻止默认行为等 )、按键修饰符(.enter
等,简化按键监听逻辑 )
1.事件的基本使用:
- 使用
v-on:xxx
或@xxx
绑定事件,其中xxx是事件名; - 事件的回调需要配置在methods对象中,最终会在vm上;
- methods中配置的函数,不要用箭头函数!否则this就不是vm了;
- methods中配置的函数,都是被Vue所管理的函数,this的指向是vm 或 组件实例对象;
@click="demo"
和@click="demo($event)"
效果一致,但后者可以传参;
data: { name: ‘xxx’ }:定义 Vue 实例的数据,{{name}} 可在页面中插值显示该数据。
methods: { … }:定义事件处理函数,showInfo1 和 showInfo2 分别处理按钮的点击事件,函数内部的 this 指向 Vue 实例 vm(因为是普通函数,受 Vue 管理 ;若用箭头函数,this 指向就不是 vm 了,这也呼应了文本说明里的第 3 点)。
<!DOCTYPE html>
<html><body><h2>欢迎来到{{name}}学习</h2><!-- <button v-on:click="showInfo">点我提示信息</button> --><button @click="showInfo1">点我提示信息1(不传参)</button><button @click="showInfo2($event,66)">点我提示信息2(传参)</button></div><script type="text/javascript">Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。const vm = new Vue({el: '#root',data: {name: '尚硅谷',},methods: {showInfo1(event) {// console.log(event.target.innerText)// console.log(this) //此处的this是vmalert('同学你好!')},showInfo2(event, number) {console.log(event, number)// console.log(event.target.innerText)// console.log(this) //此处的this是vmalert('同学你好!!')}}})</script>
</body></html>
在 Vue 中,事件修饰符是用于简化 DOM 事件处理的特殊语法,允许你在模板中直接控制事件的行为(如阻止冒泡、取消默认行为等),而无需在方法内部编写额外的代码。以下是对 Vue 事件修饰符的详细解析,结合示例和应用场景:
2、核心事件修饰符及其作用
1. .prevent
- 阻止默认行为
- 作用:等同于调用
event.preventDefault()
,常用于阻止表单提交、链接跳转等浏览器默认行为。 - 示例:
<!-- 阻止表单提交 --> <form @submit.prevent="handleSubmit"><button type="submit">提交</button> </form><!-- 阻止链接跳转 --> <a href="https://example.com" @click.prevent="handleClick">点击不跳转</a>
- 场景:表单验证、自定义链接行为。
2. .stop
- 阻止事件冒泡
- 作用:等同于调用
event.stopPropagation()
,防止事件向上级 DOM 元素传播。 - 示例:
<div @click="outerClick"><button @click.stop="innerClick">点击按钮</button> <!-- 点击按钮不会触发 outerClick --> </div>
- 场景:嵌套组件点击冲突(如模态框背景点击关闭,但内部按钮点击不关闭)。
3. .once
- 事件仅触发一次
- 作用:事件绑定后只触发一次,之后自动解绑。
- 示例:
<button @click.once="buyTicket">购买票(仅一次有效)</button>
- 场景:支付按钮防重复点击、一次性引导提示。
4. .capture
- 事件捕获模式
- 作用:改变事件触发顺序,先从外层元素开始捕获,再到内层(默认是冒泡阶段触发)。
- 示例:
<div @click.capture="outerCapture"> <!-- 先触发 --><button @click="innerClick">点击</button> <!-- 后触发 --> </div>
- 场景:需要优先处理外层元素事件的特殊需求。
5. .self
- 仅自身触发
- 作用:只有当事件的
target
是元素本身时才触发,过滤子元素触发的事件。 - 示例:
<div @click.self="closeModal"><div class="modal-content"><button>内部按钮</button> <!-- 点击此按钮不会触发 closeModal --></div> </div>
- 场景:点击模态框背景关闭模态框,但点击内部内容不关闭。
6. .passive
- 提升滚动性能
- 作用:告诉浏览器事件处理函数不会调用
preventDefault()
,从而避免浏览器等待事件处理而阻塞滚动。 - 示例:
<div @scroll.passive="handleScroll"><!-- 长内容滚动区域 --> </div>
- 场景:滚动监听(如无限滚动加载),可显著提升移动端滚动流畅度。
3、组合使用修饰符
修饰符可以串联使用,例如:
<!-- 阻止点击事件冒泡且取消默认行为 -->
<a @click.stop.prevent="doSomething">链接</a><!-- 仅触发一次且阻止默认行为 -->
<button @click.once.prevent="submitForm">提交(仅一次)</button>
4、按键修饰符(针对键盘事件)
Vue 提供了专门用于键盘事件的修饰符,例如:
<!-- 仅在按下 Enter 键时触发 -->
<input @keyup.enter="submit"><!-- 按下 Ctrl + S 保存 -->
<input @keydown.ctrl.s="save"><!-- 常见按键别名 -->
.enter .tab .delete .esc .space .up .down .left .right
5、系统修饰键(与鼠标/键盘组合)
用于处理同时按下多个键的情况:
<!-- 按下 Ctrl 并点击时触发 -->
<button @click.ctrl="doSomething">Ctrl+点击</button><!-- 修饰键别名 -->
.ctrl .alt .shift .meta(Mac 上是 Command 键,Windows 上是 Windows 键)
6、为什么使用事件修饰符?
- 代码更简洁:避免在方法内部编写
event.preventDefault()
或event.stopPropagation()
。 - 关注点分离:将 DOM 事件处理逻辑放在模板中,而非组件方法。
- 性能优化:
.passive
修饰符可提升滚动等高频事件的性能。
7、注意事项
- 修饰符顺序:顺序可能影响行为,例如
.prevent.self
与.self.prevent
的区别:.prevent.self
:仅自身触发且阻止默认行为。.self.prevent
:只有事件target
是自身时才阻止默认行为。
- 兼容性:
.passive
修饰符在旧浏览器可能不支持,需配合 polyfill 使用。
8、总结
修饰符 | 作用 |
---|---|
.prevent | 阻止默认行为(如表单提交) |
.stop | 阻止事件冒泡 |
.once | 事件仅触发一次 |
.capture | 事件捕获模式(从外到内触发) |
.self | 仅自身触发(过滤子元素) |
.passive | 提升滚动性能(不阻止默认行为) |
.enter | 键盘 Enter 键触发 |
.ctrl | 配合 Ctrl 键触发 |
9.键盘事件在Vue中,键盘事件是处理用户输入的重要方式,常用于表单验证、快捷键实现、游戏控制等场景。以下是Vue中常用的键盘事件及其用法:
1. 基本键盘事件修饰符
Vue提供了以下常用的键盘事件修饰符,可直接在模板中使用:
修饰符 | 对应的键盘按键 |
---|---|
.enter | Enter键 |
.tab | Tab键 |
.delete | Delete(删除)或Backspace(退格)键 |
.esc | Escape键 |
.space | Space(空格)键 |
.up | 上箭头键 |
.down | 下箭头键 |
.left | 左箭头键 |
.right | 右箭头键 |
.ctrl | Ctrl键(配合其他按键使用) |
.alt | Alt键 |
.shift | Shift键 |
.meta | Meta键(Windows键/Command键) |
2. 事件绑定语法
在Vue模板中,键盘事件通常通过 v-on
指令(缩写为 @
)绑定到DOM元素上,并可添加修饰符:
<template><div><!-- 监听Enter键按下 --><input v-model="message" @keyup.enter="submit" /><!-- 监听Ctrl+S组合键 --><input @keydown.ctrl.s="save" /><!-- 监听Delete键 --><input @keydown.delete="handleDelete" /><!-- 自定义按键码(例如keyCode=13) --><input @keyup.13="submit" /></div>
</template>
3. 自定义按键修饰符
如果需要使用非标准按键或特定键盘布局,可以通过 Vue.config.keyCodes
自定义按键修饰符:
// 全局注册自定义按键修饰符
Vue.config.keyCodes.f1 = 112; // F1键
Vue.config.keyCodes.mediaPlayPause = 179; // 媒体播放/暂停键
<template><div><input @keyup.f1="showHelp" /><button @keydown.mediaPlayPause="togglePlay">播放/暂停</button></div>
</template>
4. 键盘事件对象属性
在事件处理函数中,可以通过参数访问原生DOM事件对象的属性:
属性 | 描述 |
---|---|
event.key | 按键的字符串表示(如 "Enter" 、"a" ) |
event.code | 按键的物理代码(如 "KeyA" 、"Enter" ) |
event.keyCode | 按键的数字代码(已逐渐被弃用,推荐使用 key 或 code ) |
event.ctrlKey | 是否按下Ctrl键(布尔值) |
event.shiftKey | 是否按下Shift键 |
event.altKey | 是否按下Alt键 |
event.metaKey | 是否按下Meta键 |
2.Vue 未提供别名的按键,可以使用按键原始的 key 值去绑定,但注意要转为 kebab-case(短横线命名)
3. 系统修饰键(用法特殊):ctrl、alt、shift、meta
(1). 配合 keyup 使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。
(2). 配合 keydown 使用:正常触发事件。
4. 也可以使用 keyCode 去指定具体的按键(不推荐)
5.Vue.config.keyCodes. 自定义键名 = 键码,可以去定制按键别名
5. 组合键示例
以下是几个常见的组合键使用场景:
<template><div><!-- Ctrl+Enter提交表单 --><textarea @keydown.ctrl.enter="submitForm"></textarea><!-- Shift+方向键选择文本 --><input @keydown.shift.up="selectTextUp" /><!-- Alt+S保存 --><input @keydown.alt.s="saveWithAlt" /></div>
</template><script>
export default {methods: {submitForm() {console.log('提交表单');},selectTextUp() {console.log('向上选择文本');},saveWithAlt() {console.log('使用Alt+S保存');}}
}
</script>
6. 注意事项
- 按键兼容性:不同浏览器或操作系统可能对
event.key
和event.code
的返回值略有差异,建议进行兼容性测试。 - 修饰符顺序:组合键的修饰符顺序通常不影响触发(如
.ctrl.s
和.s.ctrl
效果相同)。 - 按键码弃用:
event.keyCode
已被弃用,优先使用event.key
或event.code
。 - 全局监听:如果需要在整个应用中监听键盘事件,可以在
mounted
钩子中使用window.addEventListener
。
下面是一个基于Vue的键盘事件演示示例,展示了如何监听键盘按键、处理按键事件以及获取按键信息。
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Vue键盘事件演示</title><script src="https://cdn.tailwindcss.com"></script><link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet"><script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script><script>tailwind.config = {theme: {extend: {colors: {primary: '#3B82F6',secondary: '#10B981',accent: '#8B5CF6',dark: '#1F2937',},fontFamily: {sans: ['Inter', 'system-ui', 'sans-serif'],},}}}</script><style type="text/tailwindcss">@layer utilities {.content-auto {content-visibility: auto;}.keyboard-key {@apply inline-flex items-center justify-center h-12 w-12 md:h-14 md:w-14 rounded-lg bg-gray-100 text-gray-800 shadow-md transition-all duration-200 border border-gray-200;}.keyboard-key.pressed {@apply bg-primary/20 border-primary text-primary scale-95;}.key-info {@apply transition-all duration-300;}.key-info.updated {@apply bg-primary/10;}}</style>
</head>
<body class="bg-gray-50 min-h-screen font-sans"><div id="app" class="container mx-auto px-4 py-8 max-w-4xl"><header class="text-center mb-10"><h1 class="text-[clamp(1.8rem,5vw,2.8rem)] font-bold text-dark mb-2">Vue键盘事件演示</h1><p class="text-gray-600 max-w-2xl mx-auto">探索Vue中键盘事件的使用方法。在下方输入框中按下任意键,或点击虚拟键盘上的按键,查看事件信息。</p></header><main class="bg-white rounded-2xl shadow-lg p-6 md:p-8 mb-8"><!-- 输入区域 --><div class="mb-8"><label for="key-input" class="block text-sm font-medium text-gray-700 mb-2">输入区域</label><div class="relative"><input type="text" id="key-input" v-model="inputValue"@keydown="handleKeydown"@keyup="handleKeyup"class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-primary/50 focus:border-primary outline-none transition-all"placeholder="在这里输入内容,触发键盘事件..."><div class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400"><i class="fa fa-keyboard-o"></i></div></div></div><!-- 按键信息展示 --><div class="mb-8"><h2 class="text-lg font-semibold text-gray-800 mb-4">按键信息</h2><div class="grid grid-cols-1 md:grid-cols-3 gap-4"><div class="bg-gray-50 p-4 rounded-lg key-info" :class="{ 'updated': keyUpdated === 'key' }"><p class="text-xs text-gray-500 mb-1">按键</p><p class="text-xl font-medium">{{ lastKey || '未按下任何键' }}</p></div><div class="bg-gray-50 p-4 rounded-lg key-info" :class="{ 'updated': keyUpdated === 'code' }"><p class="text-xs text-gray-500 mb-1">按键代码</p><p class="text-xl font-medium">{{ lastKeyCode || '无' }}</p></div><div class="bg-gray-50 p-4 rounded-lg key-info" :class="{ 'updated': keyUpdated === 'modifiers' }"><p class="text-xs text-gray-500 mb-1">修饰键</p><p class="text-xl font-medium">{{ modifierKeys.length > 0 ? modifierKeys.join(', ') : '无' }}</p></div></div></div><!-- 事件日志 --><div class="mb-8"><div class="flex justify-between items-center mb-4"><h2 class="text-lg font-semibold text-gray-800">事件日志</h2><button @click="clearEvents" class="text-sm text-primary hover:text-primary/80 transition-colors flex items-center"><i class="fa fa-trash-o mr-1"></i> 清空日志</button></div><div class="max-h-60 overflow-y-auto bg-gray-50 rounded-lg border border-gray-200"><ul class="divide-y divide-gray-200"><li v-for="(event, index) in eventLog" :key="index" class="p-3 hover:bg-gray-100 transition-colors"><div class="flex justify-between"><span class="text-sm font-medium text-gray-800">{{ event.type }}</span><span class="text-xs text-gray-500">{{ formatTime(event.time) }}</span></div><p class="text-sm text-gray-600 mt-1"><span class="font-medium">按键:</span> {{ event.key }} <span class="mx-1">|</span><span class="font-medium">代码:</span> {{ event.code }}</p></li></ul></div></div><!-- 虚拟键盘 --><div><h2 class="text-lg font-semibold text-gray-800 mb-4">虚拟键盘</h2><div class="bg-gray-50 p-4 rounded-lg"><div class="keyboard mb-2"><div class="flex justify-center mb-2"><div v-for="key in ['Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P']" :key="key"class="keyboard-key mx-1":class="{ 'pressed': key === lastKey }"@mousedown="simulateKeydown(key)"@mouseup="simulateKeyup(key)"@mouseleave="simulateKeyup(key)">{{ key }}</div></div><div class="flex justify-center mb-2"><div v-for="key in ['A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L']" :key="key"class="keyboard-key mx-1":class="{ 'pressed': key === lastKey }"@mousedown="simulateKeydown(key)"@mouseup="simulateKeyup(key)"@mouseleave="simulateKeyup(key)">{{ key }}</div></div><div class="flex justify-center"><div v-for="key in ['Z', 'X', 'C', 'V', 'B', 'N', 'M']" :key="key"class="keyboard-key mx-1":class="{ 'pressed': key === lastKey }"@mousedown="simulateKeydown(key)"@mouseup="simulateKeyup(key)"@mouseleave="simulateKeyup(key)">{{ key }}</div></div></div><p class="text-xs text-gray-500 text-center mt-2">点击虚拟键盘上的按键,模拟真实键盘输入</p></div></div></main><footer class="text-center text-gray-500 text-sm"><p>Vue键盘事件演示 | 使用Vue 2.7.14构建</p></footer></div><script>new Vue({el: '#app',data: {inputValue: '',lastKey: '',lastKeyCode: '',modifierKeys: [],eventLog: [],keyUpdated: '',keyUpdateTimer: null},methods: {handleKeydown(event) {this.updateKeyInfo(event, 'keydown');this.updateModifierKeys(event);},handleKeyup(event) {this.updateKeyInfo(event, 'keyup');this.updateModifierKeys(event);},updateKeyInfo(event, eventType) {// 更新按键信息this.lastKey = event.key;this.lastKeyCode = event.code;// 添加事件到日志this.eventLog.unshift({type: eventType,key: event.key,code: event.code,time: new Date()});// 限制日志数量if (this.eventLog.length > 10) {this.eventLog.pop();}// 添加更新动画效果this.triggerUpdateAnimation('key');if (['keydown', 'keyup'].includes(eventType)) {this.triggerUpdateAnimation('code');}if (['keydown', 'keyup'].includes(eventType) && this.modifierKeys.length > 0) {this.triggerUpdateAnimation('modifiers');}},updateModifierKeys(event) {const modifiers = [];if (event.ctrlKey) modifiers.push('Ctrl');if (event.shiftKey) modifiers.push('Shift');if (event.altKey) modifiers.push('Alt');if (event.metaKey) modifiers.push('Meta');this.modifierKeys = modifiers;},triggerUpdateAnimation(keyType) {// 清除之前的定时器if (this.keyUpdateTimer) {clearTimeout(this.keyUpdateTimer);}// 添加更新类this.keyUpdated = keyType;// 300ms后移除更新类this.keyUpdateTimer = setTimeout(() => {this.keyUpdated = '';}, 300);},clearEvents() {this.eventLog = [];},formatTime(date) {return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}:${date.getSeconds().toString().padStart(2, '0')}`;},simulateKeydown(key) {// 创建并分发键盘按下事件const event = new KeyboardEvent('keydown', {key: key,code: `Key${key}`,bubbles: true,cancelable: true});document.getElementById('key-input').dispatchEvent(event);// 更新输入值this.inputValue += key;},simulateKeyup(key) {// 创建并分发键盘释放事件const event = new KeyboardEvent('keyup', {key: key,code: `Key${key}`,bubbles: true,cancelable: true});document.getElementById('key-input').dispatchEvent(event);}}});</script>
</body>
</html>
这个演示展示了Vue中键盘事件的使用方法,包括:
- 使用
@keydown
和@keyup
监听键盘事件- 访问键盘事件对象的属性(如
key
、code
、ctrlKey
等)- 实现键盘事件的视觉反馈
- 记录和显示键盘事件日志
- 通过JavaScript模拟键盘事件
你可以在输入框中直接按键触发事件,也可以点击下方的虚拟键盘来模拟按键。界面会实时显示按键信息、按键代码以及是否使用了修饰键(Ctrl、Shift、Alt、Meta)。
1.7 计算属性与监视
- 计算属性(
computed
):基于已有数据计算新数据,缓存结果(依赖不变则不重复计算 ),适合复杂数据推导场景(如全名由姓和名拼接 )- 监视(
watch
):监听数据变化执行回调,可深度监视(deep: true
)对象/数组内部变化,适合异步操作、复杂逻辑响应(如监听路由变化做权限校验 )
计算属性:
- ** 定义**:要用的属性不存在,要通过已有属性计算得来。
- 原理:底层借助了Object.defineProperty方法提供的getter和setter。
- get函数什么时候执行?
(1). 初次读取时会执行一次。
(2). 当依赖的数据发生改变时会被再次调用。 - 优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便。
- 备注:
- 计算属性最终会出现在vm上,直接读取使用即可。
- 如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生
一、基础定义与核心作用
计算属性是 Vue 中用于基于已有数据派生新数据的工具,解决“需要一个依赖其他数据计算得出的属性”场景。比如通过 firstName
和 lastName
拼接出 fullName
,核心是让模板更简洁,专注展示逻辑,而非复杂计算逻辑。
<template><div><p>姓:{{ firstName }}</p><p>名:{{ lastName }}</p><!-- 直接使用派生的计算属性 --><p>全名:{{ fullName }}</p> </div>
</template><script>
export default {data() {return {firstName: '张',lastName: '三'}},computed: {//简写:相当于get()fullName() {return this.firstName + this.lastName }}
}
</script>
二、底层原理与执行机制
Vue 利用 Object.defineProperty
的 getter/setter
实现计算属性:
getter
:负责计算并返回派生值,初次访问计算属性、依赖数据变化时触发。setter
(可选):处理计算属性被主动修改的逻辑,需手动编写,修改时要更新依赖数据。
computed: {fullName: {get() { return this.firstName + this.lastName},set(newValue) { const [first, last] = newValue.split(' ')this.firstName = firstthis.lastName = last}}
}
computed:{fullName:{//get有什么作用?当有人读取fullName时,get就会被调用,且返回值就作为fullName的值//get什么时候调用?1.初次读取fullName时。2.所依赖的数据发生变化时。get(){console.log('get被调用了')// console.log(this) //此处的this是vmreturn this.firstName + '-' + this.lastName},//set什么时候调用?当fullName被修改时。set(value){console.log('set',value)const arr = value.split('-')this.firstName = arr[0]this.lastName = arr[1]}}
}
三、缓存特性与性能优势
计算属性自带缓存:依赖数据不变时,多次访问会直接复用结果,避免重复计算。比如 fullName
依赖 firstName
和 lastName
,只要两者不变,不管调用多少次 fullName
,get
只执行一次。对比 methods
方法(每次调用都重新执行函数),计算属性在复杂计算场景下性能更优。
四、监视(watch)的基础概念与用法
(一)核心作用
watch
用于监听数据变化并执行自定义逻辑,适合处理异步操作(如请求接口)、复杂数据响应(如对象深层属性变化)。
监视属性watch
- 当被监视的属性变化时,回调函数自动调用,进行相关操作
- 监视的属性必须存在,才能进行监视!
- 监视的两种写法:
(1). new Vue时传入watch配置
(2). 通过vm.$watch监视
(二)基本语法(选项式 API)
export default {data() {return {count: 0}},watch: {// 监听 count 的变化count(newVal, oldVal) { console.log('count 从', oldVal, '变为', newVal)}}
}
(三)深度监视(处理对象/数组)
以下是提取的文本内容:
深度监视:
(1).Vue中的watch默认不监测对象内部值的改变(一层)。
(2).配置deep:true可以监测对象内部值改变(多层)。
备注:
(1).Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以!
(2).使用watch时根据数据的具体结构,决定是否采用深度监视。
// 监听多级结构中某个属性的变化
watch: {'numbers.a'(newVal) {console.log('a 改变了,新值:', newVal)},'numbers.b'(newVal) {console.log('b 改变了,新值:', newVal)}
}
// 监听多级结构中所有属性的变化
numbers: {deep: true, handler() {console.log('numbers改变了')}
}
监听对象/数组时,默认无法感知内部变化,需开启 deep: true
:
export default {data() {return {user: { name: '张三' }}},watch: {user: {handler(newVal) {console.log('user 变化:', newVal)},deep: true }}
}
(四)高级配置:选项与优化
1. 立即执行(immediate: true
)
监听时首次加载就执行回调(默认仅数据变化时触发):
watch: {count: {handler(newVal) {console.log("count 初始值:", newVal);},immediate: true, // 初始化时执行一次},
},
2. 深度监视的性能问题
deep: true
会递归遍历对象所有属性,可能影响性能。优化方案:
- 仅监听对象的特定属性(而非整个对象):
watch: {// 监听 user.name(字符串路径语法)"user.name"(newVal) { console.log("user.name 变化:", newVal);}, },
- 手动控制遍历深度(如仅监听第一层属性)。
3. 停止监听(unwatch
)
在某些场景(如组件销毁前)需主动停止监听,可通过 $watch
方法 实现(返回 unwatch
函数):
export default {mounted() {// 手动创建监听,返回取消函数const unwatch = this.$watch("count", (newVal) => { console.log("count 变化:", newVal);if (newVal > 10) {unwatch(); // 满足条件时停止监听}});},
};
(五)监听路由变化(场景示例)
结合 Vue Router,监听路由跳转做权限校验:
export default {watch: {'$route'(to, from) {if (to.path === '/admin' && !isAdmin) {// 无权限则拦截this.$router.push('/login') }}}
}
监视简写
1. 基础简写:直接用回调函数
当不需要配置 deep
或 immediate
时,可以直接写回调函数:
watch: {// 完整写法(含配置项)isHot: {// immediate: true, // 初始化时执行 handler// deep: true, // 深度监视(对象/数组内部变化)handler(newValue, oldValue) {console.log('isHot被修改了', newValue, oldValue)},},// 简写形式(省略配置项,直接写回调)/* isHot(newValue, oldValue) {console.log('isHot被修改了', newValue, oldValue)}*/
}
// 完整配置形式(含选项)
/*
vm.$watch('isHot', {immediate: true, // 初始化时执行 handlerdeep: true, // 深度监视handler(newValue, oldValue) {console.log('isHot被修改了', newValue, oldValue)}
})
*/// 简写形式(直接传回调)
vm.$watch('isHot', function(newValue, oldValue) {console.log('isHot被修改了', newValue, oldValue)
})
支持两种用法:
选项式 watch:在组件选项中静态配置。
实例方法 vm.$watch:在组件逻辑中动态创建(比如根据条件决定是否监视)。
2. 深度监听的简写限制
- 简写形式无法直接配置
deep: true
,若需深度监听,必须用完整对象形式:
watch: {// 错误:简写无法开启 deep// user(newVal) { ... } // 仅监听 user 引用变化,不监听内部属性// 正确:必须用完整形式开启 deepuser: {handler(newVal) {console.log("user 内部变化了", newVal);},deep: true // 深度监听所有嵌套属性}
}
3. 立即执行的简写限制
- 简写形式无法直接配置
immediate: true
,若需初始化时立即执行一次,必须用完整形式:
watch: {// 错误:简写无法立即执行// count(newVal) { ... } // 初始化时不执行,仅变化时执行// 正确:必须用完整形式开启 immediatecount: {handler(newVal) {console.log("count 初始值:", newVal); // 初始化时立即执行},immediate: true}
}
4. 组合式 API(Vue 3)中的简写
在 setup()
函数中使用 watch
时,同样支持直接传入回调函数:
<script setup>
import { ref, watch } from "vue";const count = ref(0);
const user = ref({ name: "张三" });// 基本监听(简写)
watch(count, (newVal, oldVal) => {console.log(`count 变化:${oldVal} → ${newVal}`);
});// 深度监听(需手动配置第三个参数)
watch(user,(newVal) => {console.log("user 内部变化", newVal);},{ deep: true } // 深度监听
);
</script>
5. 何时用简写?何时用完整形式?
场景 | 推荐写法 | 示例 |
---|---|---|
简单监听(无需深度/立即) | 简写 | count(newVal) { ... } |
需要深度监听 | 完整形式 | user: { handler, deep: true } |
需要立即执行 | 完整形式 | count: { handler, immediate: true } |
监听多个数据源 | 完整形式(组合式 API) | watch([source1, source2], callback) |
总结
- 简写:直接用回调函数
(newVal, oldVal) => {}
,适用于简单监听场景。- 完整形式:用对象配置
{ handler, deep, immediate }
,适用于需要额外选项(深度监听、立即执行)的场景。
五、计算属性 vs 监视:场景对比
以下是提取的内容:
computed和watch之间的区别
- computed能完成的功能,watch都可以完成。
- watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作。
两个重要的小原则
- 所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象。
- 所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数、promise的回调函数等),最好写成箭头函数,这样this的指向才是vm 或 组件实例对象。
功能 | 计算属性(computed) | 监视(watch) |
---|---|---|
核心场景 | 数据派生、简单依赖推导(如拼接字符串) | 异步操作、复杂逻辑响应(如权限校验) |
是否缓存 | 是(依赖不变则复用结果) | 无缓存,数据变化即触发回调 |
语法复杂度 | 声明式写法,简洁直观 | 需配置回调函数,逻辑复杂时更灵活 |
依赖关系 | 自动追踪依赖(基于代码里的 this.xxx ) | 需手动指定监听的数据源(如 user.name ) |
六、实战示例:结合使用的典型场景
需求:输入框实时搜索,用 computed
处理输入过滤,用 watch
监听搜索结果变化做异步请求。
<template><div><input v-model="searchQuery" placeholder="搜索..." /><ul><li v-for="item in filteredList" :key="item.id">{{ item.name }}</li></ul></div>
</template><script>
export default {data() {return {searchQuery: '',rawList: [ { id: 1, name: 'Vue 入门' },{ id: 2, name: 'Vue 进阶' }]}},computed: {// 计算属性:根据搜索词过滤列表filteredList() {return this.rawList.filter(item => item.name.includes(this.searchQuery))}},watch: {// 监听过滤后的结果,模拟异步请求filteredList(newVal) {console.log('搜索结果变化,触发模拟请求:', newVal)// 这里可写真实接口请求逻辑...}}
}
</script>
流程说明:
computed
的filteredList
实时根据searchQuery
过滤原列表,利用缓存避免重复计算。watch
监听filteredList
,变化时执行“模拟请求”逻辑,实现数据派生 + 异步响应的完整流程。
七、总结
- 计算属性:专注“数据派生”,用声明式写法简化模板逻辑,自带缓存提升性能,适合简单依赖推导场景。
- 监视:专注“数据变化响应”,灵活处理异步、复杂逻辑,需手动配置监听规则,适合独立于 UI 渲染的副作用操作。
- 实际开发中,两者常配合使用:
computed
做数据整理,watch
处理后续异步/复杂逻辑,让代码分工更清晰。
1.8 class与style绑定
- 掌握对象语法(
:class="{ active: isActive }"
)、数组语法(:class="[activeClass]"
)动态绑定class- 同理,用对象/数组语法绑定内联样式(
:style
),实现样式动态切换(如根据状态改变元素颜色、尺寸 )
1.认识
操作元素的 class 列表和内联样式是数据绑定的一个常见需求。因为它们都是 attribute,所以我们可以用
v-bind
处理它们:只需要通过表达式计算出字符串结果即可。不过,字符串拼接麻烦且易错。因此,在将 v-bind 用于 class 和 style
时,Vue.js 做了专门的增强。表达式结果的类型除了字符串之外,还可以是对象或数组。
绑定样式:
1.class样式
写法:class="xxx"
xxx可以是字符串、对象、数组。
字符串写法适用于:类名不确定,要动态获取。
对象写法适用于:要绑定多个样式,个数不确定,名字也不确定。
数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。
2. style样式
:style="{fontSize: xxx}"
其中xxx是动态值。 :style="[a,b]"
其中a、b是样式对象。
2.绑定 HTML Class
2.1字符串写法
绑定class样式–字符串写法–适用于:样式的类名不确定,需要动态指定。
<!-- HTML 结构 -->
<div id="root"><div class="basic" :class="mood" @click="changeMood">{{name}}</div>
</div><script>
Vue.config.productionTip = falsenew Vue({el: '#root',data: {name: '谷谷',mood: 'normal'},methods: {changeMood() {//this.mood = 'happy'const arr=['happy','sad','normal']const index=Math.floor(Math.random()*3)this.mood=arr[index]}}
})
</script>
2.2对象语法
绑定class样式–对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用
我们可以传给 v-bind:class
一个对象,以动态地切换 class:
<div :class="{ active: isActive }"></div>
上面的语法表示 active 这个 class 存在与否将取决于数据 property isActive
的 “真实”。
你可以在对象中传入更多字段来动态切换多个 class。此外,v-bind:class 指令也可以与普通的 class attribute 共存。当有如下模板:
<divclass="static":class="{ active: isActive, 'text-danger': hasError }"
></div>
和如下 data:
data: {isActive: true,hasError: false
}
结果渲染为:
<div class="static active"></div>
当 isActive 或者 hasError 变化时,class 列表将相应地更新。例如,如果 hasError 的值为 true,class 列表将变为 “static active text-danger”。
绑定的数据对象不必内联定义在模板里:
<div
:class="classObject"></div>
data: {classObject: {active: true,'text-danger': false}
}
渲染的结果和上面一样。我们也可以在这里绑定一个返回对象的计算属性。这是一个常用且强大的模式:
<div :class="classObject"></div>
data: {isActive: true,error: null
},
computed: {classObject: function () {return {active: this.isActive && !this.error,'text-danger': this.error && this.error.type === 'fatal'}}
}
2.3数组语法
绑定class样式–数组写法,适用于:要绑定的样式个数不确定、名字也不确定
动态向
<div>
添加 / 移除多个 class(实现样式切换)。
我们可以把一个数组传给 v-bind:class,以应用一个 class 列表:
<div
:class="[activeClass, errorClass]"></div>
data: {activeClass: 'active',errorClass: 'text-danger'
}
渲染为:
<div class="active text-danger"></div>
如果你也想根据条件切换列表中的 class,可以用三元表达式:
<div :class="[isActive ? activeClass : '', errorClass]"></div>
这样写将始终添加 errorClass,但是只有在 isActive 是 truthy[1] 时才添加 activeClass。
不过,当有多个条件 class 时这样写有些繁琐。所以在数组语法中也可以使用对象语法:
<div :class="[{ active: isActive }, errorClass]"></div>
用法示例:
1. 代码实现
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>Vue Class 数组语法示例</title><!-- 引入 Vue --><script src="https://cdn.jsdelivr.net/npm/vue@2"></script><style>/* 演示用的样式 */.active {color: red;font-weight: bold;}.highlight {background: yellow;}.border {border: 2px solid #000;}</style>
</head>
<body><div id="app"><!-- 数组语法绑定 class --><div class="base" :class="classArray" style="padding: 20px; margin: 20px;">我是动态 class 演示区域</div><!-- 按钮切换 class 数组 --><button @click="toggleClass">切换样式</button></div><script>new Vue({el: '#app',data: {// 动态 class 数组(初始值)classArray: ['active', 'highlight']},methods: {toggleClass() {// 点击时切换 class(移除 border,添加/移除 active)if (this.classArray.includes('border')) {// 移除 border,切换 activethis.classArray = ['active', 'highlight'];} else {// 添加 border,移除 activethis.classArray = ['border', 'highlight'];}}}})</script>
</body>
</html>
效果说明
- 初始状态:
<div>
的class
是base active highlight
,文字红色加粗、背景黄色。 - 点击按钮:
- 第一次点击:
classArray
变为['border', 'highlight']
,class
变为base border highlight
,新增边框、移除红色文字。 - 再次点击:切回初始状态,循环切换。
- 第一次点击:
根据不同条件,动态决定是否添加某些 class(如“仅当 isSpecial
为 true
时,添加 special
class”)。
2. 代码实现
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>Vue Class 数组语法进阶</title><script src="https://cdn.jsdelivr.net/npm/vue@2"></script><style>.special {box-shadow: 0 0 10px rgba(255, 0, 0, 0.5);}.large {font-size: 24px;}</style>
</head>
<body><div id="app"><div class="base" :class="[isSpecial ? 'special' : '', isLarge ? 'large' : '']" style="padding: 20px; margin: 20px;">动态样式:{{ classArray }}</div><button @click="toggleSpecial">切换特殊样式</button><button @click="toggleLarge">切换大字体</button></div><script>new Vue({el: '#app',data: {isSpecial: false,isLarge: false},computed: {// 用计算属性动态生成 class 数组(也可直接在模板中写三元表达式)classArray() {return [this.isSpecial ? 'special' : '',this.isLarge ? 'large' : '']}},methods: {toggleSpecial() {this.isSpecial = !this.isSpecial;},toggleLarge() {this.isLarge = !this.isLarge;}}})</script>
</body>
</html>
效果说明
- 初始状态:
isSpecial
和isLarge
均为false
,class
只有base
,样式无特殊效果。- 点击“切换特殊样式”:
isSpecial
变为true
,class
新增special
,出现红色阴影。- 点击“切换大字体”:
isLarge
变为true
,class
新增large
,字体变大。
3、核心知识点总结
场景 | 实现方式 | 优点 |
---|---|---|
固定动态 class 列表 | :class="[ 'active', 'highlight' ]" | 简单直接,适合静态管理 |
结合条件判断 | :class="[ isSpecial ? 'special' : '', isLarge ? 'large' : '' ]" | 灵活控制单个 class 的显隐 |
动态增删 class(复杂逻辑) | 用 methods /computed 返回数组,结合 includes /splice 操作数组 | 适配复杂业务逻辑 |
关键原理:
Vue 会监听 class
绑定的数组变化,自动更新 DOM 的 class
属性,触发样式重渲染。数组语法的核心是用 JavaScript 逻辑动态生成 class 列表,让样式与数据状态联动。
3.绑定内联样式
3.1对象语法
v-bind:style 的对象语法十分直观——看着非常像 CSS,但其实是一个 JavaScript 对象。CSS property 名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用引号括起来) 来命名:
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
data: {activeColor: 'red',fontSize: 30
}
直接绑定到一个样式对象通常更好,这会让模板更清晰:
<div :style="styleObject"></div>
data: {styleObject: {color: 'red',fontSize: '13px'}
}
同样的,对象语法常常结合返回对象的计算属性使用。
3.2数组语法
v-bind:style 的数组语法可以将多个样式对象应用到同一个元素上:
<div :style="[baseStyles, overridingStyles]"></div>
data:{
baseStyles:{
fontSize:'40px',
color:'red'
},
overridingStyles:{
backgroundColor:'orange'
}}
<div :style="StylesArry"></div>data:{
StylesArry:[{
'40px',
color:'red'
},
{
backgroundColor:'orange'
}]
}
1.9 条件渲染
- 熟悉
v-if
(条件性渲染DOM,切换时销毁/重建 )、v-show
(切换CSSdisplay
属性,DOM始终存在 )差异及适用场景(频繁切换用v-show
,条件复杂/少切换用v-if
) - 配合
v-else
、v-else-if
实现多分支条件渲染 .
条件渲染:
1.v-if写法:(1).v-if="表达式"(2).v-else-if="表达式"(3).v-else="表达式"适用于:切换频率较低的场景。特点:不展示的DOM元素直接被移除。注意:v-if可以和:v-else-if、v-else一起使用,但要求结构不能被“打断”。2.v-show写法:v-show="表达式"适用于:切换频率较高的场景。特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉3.备注:使用v-if的时,元素可能无法获取到,而使用v-show一定可以获取到。
在 Vue 中,条件渲染 是指根据表达式的真假,动态控制 DOM 元素的显示与隐藏。核心指令是 v-if
、v-else-if
、v-else
和 v-show
,以下是详细解析:
1.9.1、核心指令对比(v-if
vs v-show
)
指令 | 渲染逻辑 | 场景推荐 | 本质区别 |
---|---|---|---|
v-if | 条件为 false 时,销毁/移除 DOM | 切换频率低(如权限控制) | 操作 DOM 树(销毁/重建) |
v-show | 条件为 false 时,隐藏 DOM(通过 display: none ) | 切换频率高(如选项卡切换) | 操作 CSS(显示/隐藏) |
1.9.2、v-if
系列用法(条件渲染 DOM)
1. 基本用法(v-if
+ v-else
)
<template><div><!-- 条件为真时显示 --><div v-if="isLogin">欢迎回来!</div> <!-- 条件为假时显示 --><div v-else>请登录</div> </div>
</template><script>
export default {data() {return {isLogin: false, // 控制显示内容};},
};
</script>
2. 多条件分支(v-else-if
)
<template><div><div v-if="score >= 90">优秀</div><div v-else-if="score >= 60">合格</div><div v-else>不及格</div></div>
</template><script>
export default {data() {return {score: 85, // 控制显示分支};},
};
</script>
3. 结合 <template>
(分组条件)
需同时控制多个元素的显示,可包裹在 <template>
中(无额外 DOM 节点):
<template v-if="isLogin"><p>用户名:{{ username }}</p><button>退出登录</button>
</template>
<p v-else>请登录</p>
1.9.3、v-show
用法(条件显示 DOM)
<template><div><!-- 条件为 false 时,添加 style="display: none" --><button v-show="canEdit" @click="edit">编辑</button> </div>
</template><script>
export default {data() {return {canEdit: true, // 控制按钮显示/隐藏};},
};
</script>
1.9.4、关键区别与最佳实践
-
性能权衡:
v-if
有更高的切换开销(销毁/重建 DOM),但初始渲染开销小(条件为假时不渲染)。v-show
有更高的初始渲染开销(始终渲染 DOM),但切换开销极小(仅改 CSS)。
-
场景选择:
- 权限控制、首次加载无需显示的内容 → 用
v-if
(如“管理员专属按钮”)。 - 频繁切换的元素(如选项卡、开关) → 用
v-show
(如“夜间模式开关”)。
- 权限控制、首次加载无需显示的内容 → 用
1.9.5、进阶技巧(条件渲染的复杂场景)
1. 配合计算属性(复杂逻辑)
<template><div><!-- 用计算属性简化条件 --><p v-if="showWelcome">欢迎语</p> </div>
</template><script>
export default {data() {return {isLogin: true,hasNewMsg: false,};},computed: {showWelcome() {// 复杂条件判断return this.isLogin && !this.hasNewMsg; },},
};
</script>
2. 条件渲染与列表渲染结合
<template><ul><!-- 仅渲染已完成的任务 --><li v-if="task.done" v-for="task in tasks" :key="task.id"> {{ task.name }}</li></ul>
</template><script>
export default {data() {return {tasks: [{ id: 1, name: "学习 Vue", done: true },{ id: 2, name: "写代码", done: false },],};},
};
</script>
1.9.6、注意事项
-
v-if
与v-for
优先级:- 同时使用时,
v-if
优先级更高(Vue 2 行为),可能导致意外渲染。 - 解决:用计算属性过滤列表后再遍历(推荐)。
- 同时使用时,
-
v-show
不支持<template>
:v-show
不能用在<template>
上(因为<template>
不是真实 DOM),需用v-if
替代。
总结
v-if
:适合权限控制、低频切换场景,直接操作 DOM 树。v-show
:适合高频切换场景,通过 CSS 控制显示。- 复杂条件推荐用计算属性简化逻辑,提升可读性。
根据场景选择指令,可优化性能并让代码更简洁!
1.10 列表渲染
- 用
v-for
遍历数组(v-for="(item, index) in list"
)、对象(v-for="(value, key) in obj"
),需绑定key
(推荐唯一标识,如ID,帮助Vue高效更新DOM,避免复用错误 ) - 了解数组变异方法对列表渲染的影响,及非变异方法(如
filter
)需返回新数组触发更新
v-for指令:
1.用于展示列表数据
2.语法:v-for="(item, index) in xxx" :key="yyy"
3.可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
在 Vue 里,列表渲染 核心靠 v - for
指令实现,用于循环遍历数组、对象等数据,动态生成 DOM 列表。下面从基础到进阶,完整梳理用法:
1.10.1、基础用法(遍历数组)
1. 基本语法
<template><ul><!-- item 是数组元素,index 是索引(可选) --><li v-for="(item, index) in list" :key="item.id"> {{ index }} - {{ item.name }}</li></ul>
</template><script>
export default {data() {return {list: [{ id: 1, name: 'Vue' },{ id: 2, name: 'React' },]}}
}
</script>
:key
:必传(Vue 识别节点的唯一标识,优化渲染),推荐用数据唯一 ID(如item.id
)。
2. 遍历纯数组(如 [1,2,3]
)
<template><ul><!-- value 是数组元素,index 是索引 --><li v-for="(value, index) in [1,2,3]" :key="index"> {{ index }} - {{ value }}</li></ul>
</template>
1.10.2、遍历对象
1. 遍历对象属性
<template><ul><!-- value: 属性值, key: 属性名, index: 遍历索引(可选) --><li v-for="(value, key, index) in user" :key="key"> {{ index }} - {{ key }}: {{ value }}</li></ul>
</template><script>
export default {data() {return {user: { name: '张三', age: 20, gender: '男' }}}
}
</script>
- 输出:
0 - name: 张三
1 - age: 20
2 - gender: 男
<li v-for="(char, index) of str" :key="index"> {{char}}--{{index}}<script>neww.Vue({
data:{
str:'hello'
}
})</script>
<!-- str 是 data 根级属性,直接用 --><li v-for="(char,index) of str" :key="index"><!-- 访问 car 内的 str --><li v-for="(char,index) of car.str":key="index">
// 若 str 是根级属性,修改后模板会更新this.str = 'hi'; // 若 str 在 car 内,需这样修改才会触发更新 this.car.str = 'hi'; this.$forceUpdate(); // 或用 Vue.set ```
<div>
<h2>测试遍历指定次数</h2>
<ul><li v-for="(number,index) of 5" :key="index">{{index}}-{{number}}</li>
</ul>
</div>
这是 Vue 中使用 v - for 遍历数字 5 的场景,会循环 5 次,index 是索引(从 0 开始),number 的值依次为 1到 5(因为 v - for 遍历数字时,number 从 1 开始计数 ,index 从 0 开始计数 ) 。
1.10.3、进阶技巧(列表渲染的复杂场景)
1. 结合 v-if
条件渲染
<template><ul><!-- 仅渲染已完成的任务 --><li v-for="task in tasks" v-if="task.done" :key="task.id">{{ task.name }}</li></ul>
</template><script>
export default {data() {return {tasks: [{ id: 1, name: '学习', done: true },{ id: 2, name: '运动', done: false },]}}
}
</script>
- 注意:
v-for
优先级高于v-if
(Vue 2 行为),建议用计算属性过滤数据后再遍历(避免性能问题)。
2. 动态修改数组(触发响应式更新)
Vue 能监听的数组方法(修改数组会触发视图更新):
-
push()
、pop()
、shift()
、unshift()
、splice()
、sort()
、reverse()
-
示例:
this.list.push({ id: 3, name: 'Angular' }); // 视图会更新
-
非响应式修改(需用
this.$set
修复):// 直接修改索引不会触发更新 this.list[0] = { id: 1, name: 'Vue3' }; // 正确写法:用 $set this.$set(this.list, 0, { id: 1, name: 'Vue3' });
1.10.4、性能优化(v-for
关键细节)
1. 合理使用 :key
- 用唯一 ID(如
item.id
)做key
,避免用index
(会导致删除/插入元素时 DOM 乱序)。 - 反例(不推荐):
<li v-for="(item, index) in list" :key="index">
2. 避免在 v-for
中做复杂逻辑
- 复杂计算(如过滤、格式化)抽离到计算属性或
methods
:<template><!-- 推荐:用计算属性预处理数据 --><li v-for="item in filteredList" :key="item.id"> </template><script> export default {computed: {filteredList() {return this.list.filter(item => item.done);}} } </script>
1.10.5、Vue 3 差异(组合式 API 写法)
在 <script setup>
中,v-for
用法不变,仅数据定义方式变化:
<template><ul><li v-for="item in list" :key="item.id">{{ item.name }}</li></ul>
</template><script setup>
import { reactive } from 'vue';// 响应式数组
const list = reactive([ { id: 1, name: 'Vue' },{ id: 2, name: 'React' },
]);
</script>
总结
- 核心指令:
v-for
是列表渲染的基础,需配合:key
优化。 - 响应式修改:用 Vue 提供的数组方法(或
$set
)确保视图更新。 - 性能优化:优先用唯一 ID 做
key
,复杂逻辑抽离到计算属性。
1.11 key的原理
默认key:index
react、vue中的key有什么作用?(key的内部原理)
-
虚拟DOM中key的作用:
key是虚拟DOM对象的标识,当状态中的数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下: -
对比规则:
(1). 旧虚拟DOM中找到了与新虚拟DOM相同的key:
①. 若虚拟DOM中内容没变,直接使用之前的真实DOM!
②. 若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
(2). 旧虚拟DOM中未找到与新虚拟DOM相同的key
创建新的真实DOM,随后渲染到页面。
3.** 用index作为key可能会引发的问题:**
-
若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新 ==> 界面效果没问题,但效率低。 -
如果结构中还包含输入类的DOM:
会产生错误DOM更新 ==> 界面有问题。 -
开发中如何选择key?:
-
最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值。
-
如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
1.12 列表过滤
1.监视watch
<div id="root"><h2>人员列表</h2><!-- 关键:v-model 绑定 keyword --><input type="text" v-model="keyword" placeholder="请输入名字"> <ul><li v-for="(p,index) of filPerons" :key="index">{{p.name}}-{{p.age}}-{{p.sex}}</li></ul>
</div><script>
Vue.config.productionTip = falsenew Vue({el: '#root',data: {keyword: '', // 用于接收输入框内容persons: [{id:'001',name:'马冬梅',age:19,sex:'女'},{id:'002',name:'周冬雨',age:20,sex:'女'},{id:'003',name:'周杰伦',age:21,sex:'男'},{id:'004',name:'温兆伦',age:22,sex:'男'}],filPerons:[]},watch: {// 此时 val 就是 keyword 的新值(输入框内容)// keyword(val) { // console.log('keyword被改了', val)// }keyword:{immediate:ture,handler(val){this.filPerons=this.persons.filter((p)=>{return p.name.indexOf(val) !== -1
})}}}
})
</script>
v-model=“keyword” 让输入框和 keyword 双向绑定:
输入框内容变化 → keyword 自动更新;
keyword 代码中被修改 → 输入框内容自动同步。
watch 监听 keyword,此时 val 就是输入框的实时内容。
相当于:
<!-- v-model 本质:value + input 事件 -->
<input :value="keyword" @input="keyword = $event.target.value" >
在 Vue 中实现列表过滤通常有两种方式:计算属性和方法。
2、核心实现方式
1. 计算属性(推荐)
<template><div><input v-model="searchText" placeholder="搜索..."><ul><!-- 直接使用过滤后的列表 --><li v-for="item in filteredList" :key="item.id">{{ item.name }}</li></ul></div>
</template><script>
export default {data() {return {originalList: [{ id: 1, name: "苹果" },{ id: 2, name: "香蕉" },{ id: 3, name: "葡萄" }],searchText: "" // 搜索关键词};},computed: {filteredList() {// 过滤逻辑:返回包含搜索词的项return this.originalList.filter(item =>item.name.toLowerCase().includes(this.searchText.toLowerCase()));}}
};
</script>
关键点:
- 响应式过滤:
searchText
变化时自动触发计算属性更新。 - 性能优化:Vue 会缓存计算结果,仅依赖数据变化时才重新计算。
2. 方法(适用于复杂逻辑)
<template><div><input v-model="searchText" placeholder="搜索..."><ul><!-- 调用方法过滤 --><li v-for="item in filterList()" :key="item.id">{{ item.name }}</li></ul></div>
</template><script>
export default {data() {return {originalList: [...],searchText: ""};},methods: {filterList() {return this.originalList.filter(item =>item.name.toLowerCase().includes(this.searchText.toLowerCase()));}}
};
</script>
区别:
方法每次渲染都会执行,而计算属性仅在依赖变化时执行。
3、进阶过滤技巧
1. 多条件过滤
computed: {filteredList() {return this.originalList.filter(item => {// 同时满足多个条件const nameMatch = item.name.includes(this.searchText);const categoryMatch = this.selectedCategory ? item.category === this.selectedCategory : true;return nameMatch && categoryMatch;});}
}
2. 模糊搜索(含拼音)
// 需要引入拼音库(如 pinyin.js)
import pinyin from "pinyin";computed: {filteredList() {return this.originalList.filter(item => {// 匹配中文或拼音const name = item.name.toLowerCase();const py = pinyin(item.name, { style: pinyin.STYLE_NORMAL }).join("");const keyword = this.searchText.toLowerCase();return name.includes(keyword) || py.includes(keyword);});}
}
3. 延迟搜索(防抖)
避免频繁触发过滤:
<template><input v-model="searchText" @input="debounceSearch" placeholder="搜索..." >
</template><script>
export default {data() {return {searchText: "",debounceTimer: null};},methods: {debounceSearch() {clearTimeout(this.debounceTimer);this.debounceTimer = setTimeout(() => {// 延迟300ms执行搜索// 此处可触发过滤逻辑(如更新计算属性依赖)}, 300);}}
};
</script>
4、完整示例(带分类筛选)
<template><div><input v-model="searchText" placeholder="搜索商品..."><select v-model="selectedCategory"><option value="">全部分类</option><option value="水果">水果</option><option value="蔬菜">蔬菜</option></select><ul><li v-for="item in filteredList" :key="item.id">{{ item.name }} - {{ item.category }}</li></ul></div>
</template><script>
export default {data() {return {originalList: [{ id: 1, name: "苹果", category: "水果" },{ id: 2, name: "香蕉", category: "水果" },{ id: 3, name: "胡萝卜", category: "蔬菜" }],searchText: "",selectedCategory: ""};},computed: {filteredList() {return this.originalList.filter(item => {const nameMatch = item.name.toLowerCase().includes(this.searchText.toLowerCase());const categoryMatch = this.selectedCategory? item.category === this.selectedCategory: true;return nameMatch && categoryMatch;});}}
};
</script>
1.13 列表排序
computed: {filPerons() {// 1. 过滤:筛选出 name 包含 keyword 的项const arr = this.persons.filter((p) => {return p.name.indexOf(this.keyword) !== -1;});// 2. 排序:如果需要排序(sortType 有值)if (this.sortType) {arr.sort((p1, p2) => {// 根据 sortType 决定升序/降序return this.sortType === 1 ? p2.age - p1.age // 降序(sortType=1): p1.age - p2.age; // 升序(sortType≠1)});}// 3. 返回处理后的数组return arr;}
}
data() {return {persons: [{ name: '张三', age: 20 },{ name: '李四', age: 25 },{ name: '王五', age: 18 },],keyword: '张', // 搜索关键字sortType: 1, // 1=降序,0=升序};
}
补充:在 JavaScript 中,
sort()
是数组的一个原地排序方法,用于对数组元素进行排序。以下是关于sort()
的核心知识点、常见问题及在 Vue 中的应用场景:基础用法
- 默认排序(字符串排序)
// 默认按字符串 Unicode 码点排序 console.log(arr); // [1, 10, 5, 9](不是数值大小!)
- 自定义排序(数值/对象排序) 通过传入比较函数
(a, b) => ...
定义排序规则:// 数值升序 arr.sort((a, b) => a - b); // [1, 5, 9, 10]// 数值降序 arr.sort((a, b) => b - a); // [10, 9, 5, 1]// 对象按属性排序 const users = [ { name: 'Alice', age: 25 }, { name: 'Bob', age: 20 }, ]; users.sort((a, b) => a.age - b.age); // 按 age 升序
比较函数规则 比较函数
(a, b)
返回值决定排序结果:
- 返回值 < 0:
a
排在b
前面(升序)。- 返回值 = 0:顺序不变。
- 返回值 > 0:
b
排在a
前面(降序)。
- 字符串排序(中文/英文)
// 英文按字母顺序 ['banana', 'apple', 'cherry'].sort(); // ['apple', 'banana', 'cherry']// 中文按拼音排序(需用 localeCompare) ['张三', '李四', '王五'].sort((a, b) => a.localeCompare(b, 'zh')); // ['李四', '王五', '张三']
- 复杂对象多条件排序
const products = [ { name: '手机', price: 2000, sales: 100 }, { name: '电脑', price: 5000, sales: 50 }, { name: '平板', price: 3000, sales: 100 }, ];// 先按销量降序,销量相同则按价格升序 products.sort((a, b) => { if (a.sales !==b.sales) {return b.sales - a.sales; // 销量降序 } return a.price - b.price; // 价格升序 });
在 Vue 中的应用
- 计算属性动态排序
<template> <ul><li v-for="user in sortedUsers" :key="user.id">{{ user.name }} - {{ user.age }}</li> </ul> </template><script> export default { data() {return {users: [{ id: 1, name: '张三', age: 25 },{ id: 2, name: '李四', age: 20 },],sortType: 'asc' // 排序类型:asc(升序)/ desc(降序)}; }, computed: {sortedUsers() {return [...this.users].sort((a, b) => {return this.sortType === 'asc' ? a.age - b.age : b.age - a.age;});} } }; </script>
- 点击按钮切换排序
<template> <div><button @click="toggleSort">切换排序</button><ul><li v-for="item in sortedList" :key="item.id">{{ item.name }}</li></ul> </div> </template><script> export default { data() {return {list: [...],isAsc: true}; }, methods: {toggleSort() {this.isAsc = !this.isAsc;this.list.sort((a, b) => this.isAsc ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name));} } };</script>
- 修改原数组
sort()
是原地排序,会直接修改原数组。若需保留原数组,应先复制:this.users.sort(...);// 错误:直接排序(修改原数组)// 正确:复制后排序(不影响原数组)[...this.users].sort(...);
- 字符串排序陷阱
// 错误:按字符串比较[5, 10, 1].sort(); // [1, 10, 5](不是数值大小!)// 正确:明确指定数值比较函数 [5, 10, 1].sort((a, b) => a - b); // [1, 5, 10]
场景 | 示例代码 |
---|---|
数值升序 | arr.sort((a, b) => a - b) |
数值降序 | arr.sort((a, b) => b - a) |
对象按属性排序 | users.sort((a, b) => a.age - b.age) |
中文拼音排序 | names.sort((a, b) => a.localeCompare(b, 'zh')) |
多条件排序 | 先比较主条件,再比较次要条件 |
Vue 动态排序 | 用计算属性或方法处理,配合响应式数据触发更新 |
掌握 sort()
的核心规则和常见场景,可高效处理前端列表排序需求。
1. 计算属性(推荐)
<template><div><button @click="sortType = 'name'">按名称排序</button><button @click="sortType = 'age'">按年龄排序</button><button @click="reverse = !reverse">切换顺序</button><ul><li v-for="item in sortedList" :key="item.id">{{ item.name }} - {{ item.age }}</li></ul></div>
</template><script>
export default {data() {return {originalList: [{ id: 1, name: "张三", age: 25 },{ id: 2, name: "李四", age: 20 },{ id: 3, name: "王五", age: 30 }],sortType: "name", // 当前排序字段reverse: false // 是否降序};},computed: {sortedList() {// 复制数组避免修改原数据const sorted = [...this.originalList].sort((a, b) => {if (this.sortType === "name") {return a.name.localeCompare(b.name); // 字符串排序} else if (this.sortType === "age") {return a.age - b.age; // 数字排序}return 0;});// 根据 reverse 决定是否反转return this.reverse ? sorted.reverse() : sorted;}}
};
</script>
关键点:
- 不修改原数据:通过
[...this.originalList]
创建副本进行排序。 - 字符串排序:使用
localeCompare
处理中文/特殊字符。 - 响应式更新:
sortType
或reverse
变化时自动触发重新排序。
2. 方法(动态排序)
<template><div><button @click="sort('name')">按名称排序</button><button @click="sort('age')">按年龄排序</button><ul><li v-for="item in list" :key="item.id">{{ item.name }} - {{ item.age }}</li></ul></div>
</template><script>
export default {data() {return {list: [{ id: 1, name: "张三", age: 25 },{ id: 2, name: "李四", age: 20 },{ id: 3, name: "王五", age: 30 }]};},methods: {sort(key) {this.list.sort((a, b) => {if (typeof a[key] === "string") {return a[key].localeCompare(b[key]);}return a[key] - b[key];});}}
};
</script>
区别:
方法会直接修改原数组,适合一次性排序;计算属性更适合需要响应式更新的场景。
3.进阶排序技巧
1. 多字段排序
computed: {sortedList() {return [...this.originalList].sort((a, b) => {// 先按年龄排序,年龄相同则按名称排序if (a.age !== b.age) {return a.age - b.age;}return a.name.localeCompare(b.name);});}
}
2. 自定义排序规则
computed: {sortedList() {return [...this.originalList].sort((a, b) => {// 自定义优先级:VIP用户优先,再按年龄if (a.isVIP && !b.isVIP) return -1;if (!a.isVIP && b.isVIP) return 1;return a.age - b.age;});}
}
3. 异步排序(如服务器端排序)
<template><div><button @click="fetchSortedData('name')">按名称排序</button><ul><li v-for="item in list" :key="item.id">{{ item.name }}</li></ul></div>
</template><script>
export default {data() {return {list: [],loading: false};},methods: {async fetchSortedData(sortBy) {this.loading = true;try {const res = await fetch(`/api/data?sort=${sortBy}`);this.list = await res.json();} catch (error) {console.error(error);} finally {this.loading = false;}}}
};
</script>
4、完整示例(带排序指示器)
<template><div><table><thead><tr><th @click="sortBy('name')">名称 <span v-if="sortType === 'name'">{{ reverse ? '↓' : '↑' }}</span></th><th @click="sortBy('age')">年龄 <span v-if="sortType === 'age'">{{ reverse ? '↓' : '↑' }}</span></th></tr></thead><tbody><tr v-for="item in sortedList" :key="item.id"><td>{{ item.name }}</td><td>{{ item.age }}</td></tr></tbody></table></div>
</template><script>
export default {data() {return {originalList: [{ id: 1, name: "张三", age: 25 },{ id: 2, name: "李四", age: 20 },{ id: 3, name: "王五", age: 30 }],sortType: null,reverse: false};},computed: {sortedList() {if (!this.sortType) return this.originalList;return [...this.originalList].sort((a, b) => {const valueA = a[this.sortType];const valueB = b[this.sortType];if (typeof valueA === "string") {return valueA.localeCompare(valueB);}return valueA - valueB;}).reverse(this.reverse);}},methods: {sortBy(key) {// 切换排序字段或反转顺序if (this.sortType === key) {this.reverse = !this.reverse;} else {this.sortType = key;this.reverse = false;}}}
};
</script>
1.14 Vue监测数据的原理
Vue 实现数据监测的核心原理是数据劫持结合发布 - 订阅模式,以下是详细的解析:
Vue监视数据的原理:
1. vue会监视data中所有层次的数据。
new Vue({data: {user: {name: 'John',age: 30,hobbies: ['reading', 'coding']}}
});
上述 user 对象及其所有属性(包括嵌套的 hobbies 数组)都会被递归转换为响应式数据。2. 如何监测对象中的数据?通过setter实现监视,且要在new Vue时就传入要监测的数据。(1).对象中后追加的属性,Vue默认不做响应式处理const vm = new Vue({data: {user: {name: 'John'}}
});// 非响应式:Vue 不会监听 age 属性
vm.user.age = 30; (2).如需给后添加的属性做响应式,请使用如下API:Vue.set(target, propertyName/index, value) 或 vm.$set(target, propertyName/index, value)
手动触发 Vue 的响应式机制,为新增属性创建 getter/setter.
eg:
// 响应式:Vue 会监听 age 属性
Vue.set(vm.user, 'age', 30);
// 或使用实例方法
vm.$set(vm.user, 'age', 30); 3. 如何监测数组中的数据?通过包裹数组更新元素的方法实现,本质就是做了两件事:(1).调用原生对应的方法对数组进行更新。(2).重新解析模板,进而更新页面。4.在Vue修改数组中的某个元素一定要用如下方法:1.使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()2.Vue.set() 或 vm.$set()特别注意:Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象 添加属性!!! 原因:vm 实例和根数据对象(即 data 返回的对象)在 Vue 初始化时已被特殊处理,直接添加属性会破坏响应式系统的结构。
Vue 首先通过数据劫持(
Object.defineProperty()
或Proxy
)拦截数据的读取和修改操作,在读取数据时进行依赖收集(将 Watcher 添加到对应属性的 Dep 中),在数据修改时触发 Dep的notify
方法,进而通知所有相关的 Watcher 执行update
操作,最终实现视图的更新,这就是 Vue 监测数据并实现响应式的原理。
数据劫持
在 Vue 中,使用 Object.defineProperty()
方法(在 ES6 之后也可以使用 Proxy
)对数据对象的属性进行劫持,来实现对数据的监听。
Object.defineProperty()
实现方式:
Vue 在初始化数据时,会遍历数据对象的所有属性,并使用Object.defineProperty()
把这些属性重新定义到一个内部对象上。比如有如下数据:
const data = {message: 'Hello Vue'
};
Vue 内部会类似这样处理:
function observe(data) {if (!data || typeof data!== 'object') {return;}Object.keys(data).forEach(key => {defineReactive(data, key, data[key]);});
}function defineReactive(obj, key, val) {observe(val); // 递归处理嵌套对象Object.defineProperty(obj, key, {get() {// 依赖收集,这里暂不展开,后面会说return val;},set(newVal) {if (newVal === val) {return;}val = newVal;observe(newVal); // 对新值进行监听// 发布更新,通知订阅者更新视图notify(); }});
}observe(data);
在 get
方法中,可以进行依赖收集操作,也就是记录哪些地方使用了这个属性,以便后续属性变化时通知对应的地方更新。在 set
方法中,当属性值发生变化时,会触发更新逻辑,通知相关的订阅者。
Vue监测数据的原理-对象
Vue 在初始化数据时,会遍历对象的所有属性,利用Object.defineProperty()
方法重新定义这些属性,将其转换为具有 getter 和 setter 函数的属性,从而实现对数据的劫持和监听。
数据劫持(Object.defineProperty 方式,Vue 2.x 主要采用)
<!DOCTYPE html>
<html>
<head>
<meta charset-"utf-8"/>
<title>document</title>
</head><body><script type="text/javascript">let data={name:'天山'// address:'北京'},object.defineProperty(data, 'name',{get(){return data.name},set(val){data.name=val}})</script></body>
</html>
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>数据监视示例</title>
</head>
<body>
<script>let data = {name: '天山',address: '北京'}// 创建一个监视实例对象,监视 data 属性变化const obs = new Observer(data)console.log(obs)// 模拟修改属性,测试监视setTimeout(() => {data.name = '新名称' console.log('修改后 data.name:', data.name)console.log('修改后 obs 劫持的 name:', obs.name) }, 1000);function Observer(obj) {//汇总对象中所有属性形成一个数组const keys = Object.keys(obj)//遍历keys.forEach((k) => {// 关键:通过 Object.defineProperty 劫持 obj 的属性到 Observer 实例Object.defineProperty(this, k, {get() {console.log(`获取属性 ${k}:`, obj[k]) return obj[k]},set(val) {console.log(`设置属性 ${k} 为:`, val) obj[k] = val// 这里可扩展“属性变化后的响应逻辑”,比如通知视图更新(Vue 核心逻辑)}})})}
</script>
</body>
</html>
核心逻辑说明
1.数据劫持:
用Object.defineProperty
把data
的属性 “代理” 到Observer
实例上,读取 / 修改 data.xxx 时,实际会触发 Observer 的 get/set。
2.监听效果:
访问data.name
或obs.name
会触发 get,打印日志;
修改data.name
会触发 set,除了修改值,还能在这里写 “属性变化后要执行的逻辑”(比如 Vue 里的视图更新)。
Vue.set()方法
在Vue中,Vue.set()
方法(也可以使用 this.$set()
,在组件实例中使用 )主要用于解决当向响应式对象中添加一个新属性时,Vue无法检测到该属性变化的问题,从而确保视图能够正确更新。下面详细介绍:
出现问题的原因
Vue 实现响应式的原理是通过 Object.defineProperty()
(在2.x版本中,Vue 3.x 主要使用 Proxy
)对数据对象的属性进行劫持,在初始化时遍历对象的属性,将它们转换为 getter/setter 形式来监听变化。但如果在之后新增一个原本不存在的属性,Vue 无法自动对其进行响应式处理,也就无法通知视图更新。例如:
const vm = new Vue({data: {obj: {name: 'Alice'}}
})
// 直接添加新属性,Vue无法检测到变化
vm.obj.age = 25;
上述代码中,直接给 obj
添加 age
属性,虽然数据层面上 obj
发生了变化,但是 Vue 并不知道,视图也就不会更新。
Vue.set() 方法的使用
Vue.set()
方法接收三个参数,语法如下:
Vue.set(target, key, value)
target
:要添加属性的对象,可以是组件实例的data
中的对象,也可以是普通的 JavaScript 对象。key
:要添加的属性名,是一个字符串或者 Symbol 类型。value
:要添加的属性值,可以是任意类型的数据。
** 示例**
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>Vue.set()示例</title><script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
</head><body><div id="app"><p>姓名:{{ obj.name }}</p><p v-if="obj.age">年龄:{{ obj.age }}</p><button @click="addAge">添加年龄属性</button></div><script>new Vue({el: '#app',data: {obj: {name: 'Bob'}},methods: {addAge() {// 使用 Vue.set() 方法添加属性,确保 Vue 能检测到变化Vue.set(this.obj, 'age', 30); }}});</script>
</body></html>
在上述示例中,点击按钮时通过 Vue.set()
方法给 obj
对象添加了 age
属性,此时 Vue 能够检测到该变化,视图也会根据新添加的属性进行更新显示。
等价的 this.$set() 使用
在 Vue 组件实例中,也可以使用 this.$set()
来达到相同的效果,它是 Vue.set()
的一个便捷访问方式:
export default {data() {return {list: [],person: {name: 'Charlie'}};},methods: {addToArray() {// 向数组中添加元素this.$set(this.list, this.list.length, 'new item'); },addPropertyToObject() {// 向对象中添加属性this.$set(this.person, 'age', 28); }}
};
this.$set()
不仅可以用于向对象添加属性,还可以用于向响应式数组中指定位置添加元素,确保 Vue 能够正确追踪数组的变化,进而更新视图。
注意事项
- 当使用
Vue.set()
时,确保target
是一个对象或者数组。如果传入的target
不是对象或数组,会导致程序出错。- 在 Vue 3 中,虽然响应式原理有所变化(基于
Proxy
),但仍然保留了类似的 API(如app.config.globalProperties.$set
等 )来解决新增属性无法响应式的问题。
Vue监测数据的原理-数组
这些是 JavaScript 中数组的变异方法(会直接修改原数组),功能分别是:
● push
:向数组末尾添加一个或多个元素,返回新长度。
● pop
:删除数组最后一个元素,返回该元素。
● shift
:删除数组第一个元素,返回该元素。
● unshift
:向数组开头添加一个或多个元素,返回新长度。
● splice
:删除、替换或添加数组元素,返回被修改的元素。
● sort
:对数组元素排序,默认按字符串 Unicode 码点排序,可自定义排序规则。
● reverse
:反转数组元素顺序 。
在Vue中,监测数组数据的变化主要有多种方式,在Vue 2.x和Vue 3.x中实现原理有所不同,下面分别进行介绍:
1. push
:末尾添加元素
- 功能:在数组末尾添加一个或多个元素,返回新数组长度
- 原生用法:
const arr = [1, 2]; const newLength = arr.push(3, 4); // arr 变为 [1,2,3,4],newLength 是 4
- Vue 重写后特点:
添加的新元素会被 Vue 转为响应式(若元素是对象/数组),同时触发视图更新。
比如data
里的数组list: [1,2]
,执行this.list.push(3)
,Vue 会:- 把
3
转为响应式(如果是对象继续递归监听) - 通知依赖该数组的视图重新渲染
- 把
2. pop
:删除末尾元素
- 功能:删除数组最后一个元素,返回被删除的元素
- 原生用法:
const arr = [1, 2, 3]; const removed = arr.pop(); // arr 变为 [1,2],removed 是 3
- Vue 重写后特点:
删除后触发视图更新,确保页面同步数组最新状态。
比如this.list.pop()
后,Vue 会检测到数组变化,重新渲染使用list
的视图部分。
3. shift
:删除开头元素
- 功能:删除数组第一个元素,返回被删除的元素
- 原生用法:
const arr = [1, 2, 3]; const removed = arr.shift(); // arr 变为 [2,3],removed 是 1
- Vue 重写后特点:
同pop
,删除后触发视图更新。
例:this.list.shift()
执行后,页面里依赖list
的渲染会自动更新。
4. unshift
:开头添加元素
- 功能:在数组开头添加一个或多个元素,返回新数组长度
- 原生用法:
const arr = [2, 3]; const newLength = arr.unshift(0, 1); // arr 变为 [0,1,2,3],newLength 是 4
- Vue 重写后特点:
新增元素转为响应式,触发视图更新。
如this.list.unshift(0)
,Vue 会处理新元素的响应式,并通知视图重新渲染。
5. splice
:删除/插入/替换元素
- 功能:万能操作,可删除、插入、替换元素,返回被删除元素组成的数组
- 语法:
splice(起始索引, 删除个数, 插入的元素1, 插入的元素2...)
- 语法:
- 原生用法:
const arr = [1, 2, 3, 4]; // 删除:从索引1开始删2个,返回 [2,3] const removed = arr.splice(1, 2); // arr 变为 [1,4]// 插入:从索引1开始删0个,插入5、6 arr.splice(1, 0, 5, 6); // arr 变为 [1,5,6,4]// 替换:从索引1开始删1个(5),插入7 arr.splice(1, 1, 7); // arr 变为 [1,7,6,4]
- Vue 重写后特点:
无论删除、插入还是替换,Vue 都会:- 处理新增元素的响应式(插入/替换场景)
- 触发视图更新,保证页面和数据同步
6. sort
:排序
- 功能:对数组元素排序(默认按字符串 Unicode 排序,可传自定义比较函数)
- 原生用法:
const arr = [3, 1, 2]; // 默认排序:转为字符串比较,结果 [1,2,3] arr.sort(); // 自定义排序(数字升序) arr.sort((a, b) => a - b);
- Vue 重写后特点:
排序后触发视图更新,比如this.list.sort()
执行后,页面里依赖list
的列表会重新渲染排序后的结果。
7. reverse
:反转
- 功能:反转数组元素顺序
- 原生用法:
const arr = [1, 2, 3]; arr.reverse(); // arr 变为 [3,2,1]
- Vue 重写后特点:
反转后触发视图更新,例:this.list.reverse()
执行后,页面里的列表会同步显示反转后的数组内容。
- 将重写后的方法应用到数组:当Vue实例化时,如果
data
中的属性是数组,会将重写后的数组方法赋值给该数组。
function observe(data) {if (!data || typeof data!== 'object') {return;}let ob;if (Array.isArray(data)) {data.__proto__ = arrayMethods;ob = new Observer(data);} else {// 对象的响应式处理逻辑}return ob;
}
这样,当用户调用数组的变异方法时,实际上调用的是Vue重写后的方法,进而触发了数据变化的监测和视图更新。
变异方法的重写
Vue 2.x通过重写数组的7种变异方法(push
、pop
、shift
、unshift
、splice
、sort
、reverse
)来实现对数组变化的监测。具体过程如下:
- 获取原始数组方法:Vue首先获取这7种原生数组方法的引用。
const arrayProto = Array.prototype;
export const arrayMethods = Object.create(arrayProto);
- 重写方法:对这7种方法进行重写,在方法执行前后添加通知视图更新的逻辑。以
push
方法为例:
['push', 'pop','shift', 'unshift','splice','sort','reverse'].forEach(method => {arrayMethods[method] = function mutator(...args) {// 调用原始数组方法,获取返回值const result = arrayProto[method].apply(this, args); const ob = this.__ob__;let inserted;switch (method) {case 'push':case 'unshift':inserted = args;break;case'splice':inserted = args.slice(2);break;}if (inserted) {// 对新插入的元素进行响应式处理(如果是对象或数组)ob.observeArray(inserted); }// 通知依赖该数组的Watcher进行更新ob.dep.notify(); return result;};
});
非变异方法的处理
对于非变异方法(如 filter
、map
、slice
等),它们不会直接修改原数组,而是返回一个新数组。Vue无法自动监测到这种情况。如果要让视图更新,需要将返回的新数组重新赋值给原来的数组变量。例如:
export default {data() {return {myArray: [1, 2, 3]};},methods: {updateArray() {// 使用非变异方法获取新数组const newArray = this.myArray.filter(item => item > 1); // 重新赋值,触发视图更新this.myArray = newArray; }}
};
1.15Vue 表单数据收集规则
1. 文本框(text)
- 收集内容:
value
值 - 用户输入:直接作为
value
- 示例:
<input type="text" v-model="message"> <!-- 用户输入 "hello" → message = "hello" -->
2. 单选框(radio)
- 收集内容:
value
值 - 必选配置:必须为每个
radio
标签设置value
- 示例:
<input type="radio" id="male" value="male" v-model="gender"> <input type="radio" id="female" value="female" v-model="gender"> <!-- 选中男 → gender = "male" -->
3. 复选框(checkbox)
-
情况1:未配置
value
属性- 收集内容:
checked
状态(布尔值) - 示例:
<input type="checkbox" v-model="isAgree"> <!-- 勾选 → isAgree = true -->
- 收集内容:
-
情况2:配置
value
属性-
子情况1:
v-model
初始值为 非数组- 收集内容:
checked
状态(布尔值) - 示例:
<input type="checkbox" value="apple" v-model="selected"> <!-- 勾选 → selected = true -->
- 收集内容:
-
子情况2:
v-model
初始值为 数组- 收集内容:所有勾选项的
value
组成的数组 - 示例:
<input type="checkbox" value="apple" v-model="selectedFruits"> <input type="checkbox" value="banana" v-model="selectedFruits"> <!-- 勾选苹果和香蕉 → selectedFruits = ["apple", "banana"] -->
- 收集内容:所有勾选项的
-
4. v-model 修饰符
修饰符 | 作用 | 示例 |
---|---|---|
lazy | 失去焦点后再收集数据 | <input v-model.lazy="msg"> |
number | 将输入的字符串转为有效数字 | <input v-model.number="age"> |
trim | 过滤输入内容首尾的空格 | <input v-model.trim="name"> |
注意事项
- 单选框:同一组
radio
需设置相同的v-model
绑定变量,且每个选项的value
必须唯一。 - 复选框数组:初始值必须显式定义为数组(如
data()
中selectedFruits: []
),否则无法正确收集多选值。 - 修饰符组合:可同时使用多个修饰符,如
v-model.lazy.number="price"
。
收集表单数据:
若: < input type=“text”/>, 则v-model收集的是value值, 用户输入的就是value值。
若: < input type=“radio”/> 则v-model收集的是value值, 且要给标签配置value值。
若: < input type=“checkbox”/>
1.没有配置input的value属性, 那么收集的就是checked(勾选 or 未勾选, 是布尔值)
2.配置input的value属性:
(1)v-model的初始值是非数组, 那么收集的就是checked(勾选 or 未勾选, 是布尔值)
(2)v-model的初始值是数组, 那么收集的的就是value组成的数组
备注: v-model的三个修饰符:
lazy: 失去焦点再收集数据
number: 输入字符串转为有效的数字
trim: 输入首尾空格过滤
演示 v-model 收集各类表单数据 的完整流程,可直接复制运行:
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>Vue 表单数据收集示例</title><!-- 引入 Vue(可根据实际环境替换为本地文件或 CDN 地址) --><script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script><style>/* 简单美化表单样式 */form {width: 400px;margin: 20px auto;padding: 20px;border: 1px solid #eee;border-radius: 8px;box-shadow: 0 0 8px rgba(0,0,0,0.1);}label {display: block;margin: 10px 0 4px;font-weight: bold;}input, select, textarea {width: 100%;padding: 8px;margin-bottom: 16px;box-sizing: border-box;border: 1px solid #ddd;border-radius: 4px;}input[type="radio"],input[type="checkbox"] {width: auto;margin-right: 6px;}button {padding: 10px 20px;background: #42b983;color: #fff;border: none;border-radius: 4px;cursor: pointer;}button:hover {background: #359e6b;}</style>
</head>
<body><div id="root"><form @submit.prevent="demo"><!-- 文本框:收集 value,自动同步到 userInfo.account --><label>账号:</label><input type="text" v-model="userInfo.account"><!-- 密码框:收集 value,自动同步到 userInfo.password --><label>密码:</label><input type="password" v-model="userInfo.password"><!-- 数字框 + number 修饰符:输入转为数字,同步到 userInfo.age --><label>年龄:</label><input type="number" v-model.number="userInfo.age"><!-- 单选框:选中项的 value 同步到 userInfo.sex --><label>性别:</label>男<input type="radio" name="sex" v-model="userInfo.sex" value="male">女<input type="radio" name="sex" v-model="userInfo.sex" value="female"><!-- 复选框:- 若 v-model 绑定非数组,收集 checked(布尔值);- 这里绑定数组 userInfo.hobby,收集选中项的 value 组成数组 --><label>爱好:</label>学习<input type="checkbox" v-model="userInfo.hobby" value="study">打游戏<input type="checkbox" v-model="userInfo.hobby" value="game">吃饭<input type="checkbox" v-model="userInfo.hobby" value="eat"><!-- 下拉选择框:选中项的 value 同步到 userInfo.city --><label>所属校区:</label><select v-model="userInfo.city"><option value="">请选择校区</option><option value="beijing">北京</option><option value="shanghai">上海</option><option value="shenzhen">深圳</option><option value="wuhan">武汉</option></select><!-- 文本域 + lazy 修饰符:失去焦点后,才同步内容到 userInfo.other --><label>其他信息:</label><textarea v-model.lazy="userInfo.other"></textarea><!-- 提交按钮:触发 demo 方法,打印收集的表单数据 --><button type="submit">提交</button></form></div><script>// 创建 Vue 实例new Vue({el: '#root',data: {// 表单数据对象,用于绑定 v-modeluserInfo: {account: '',password: '',age: '',sex: '', // 单选框:存 value(male/female)hobby: [], // 复选框:存选中的 value 数组city: '', // 下拉框:存选中的 valueother: '' // 文本域:存内容(lazy 修饰符,失去焦点同步)}},methods: {demo() {// 提交时,打印收集的表单数据console.log('表单数据:', this.userInfo);// 可扩展:调用接口,将 this.userInfo 发送到后端}}})</script>
</body>
</html>
** 代码说明**
表单元素与
v-model
绑定规则:
- 文本框/密码框:直接绑定
v-model
,实时同步输入内容。- 数字框 +
.number
:输入自动转为数字类型,适合年龄、数量等场景。- 单选框:通过
value
区分选项,选中项的value
会同步到绑定变量(如userInfo.sex
)。- 复选框:
- 绑定数组时(如
userInfo.hobby
),选中项的value
会被收集到数组中;- 若绑定非数组,收集的是
checked
状态(布尔值)。- 下拉框:选中项的
value
同步到绑定变量(如userInfo.city
)。- 文本域 +
.lazy
:输入时不实时同步,失去焦点后才更新绑定变量,适合大文本输入场景。提交逻辑:
点击“提交”按钮,触发@submit.prevent
(阻止默认表单提交行为),执行demo
方法,打印收集的表单数据(可扩展为调用接口,将数据发往后端)。样式说明:
简单写了 CSS 美化表单,让示例更贴近真实项目;核心逻辑还是聚焦 v-model 数据收集,可根据需求调整样式或交互。
直接在浏览器打开该 HTML 文件,即可输入测试,控制台会输出完整的表单数据对象,清晰看到各类表单元素如何通过 v-model
收集值 。
1.16Vue 过滤器(Filter)
Vue 过滤器核心知识点
-
定义:
对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理),不改变原数据,生成新格式化结果。 -
语法:
- 注册过滤器:
- 全局:
Vue.filter('过滤器名name', 处理函数callback)
- 局部:
new Vue({ filters: { 过滤器名: 处理函数 } })
- 全局:
- 使用过滤器:
- 插值:
{{ 数据 | 过滤器名 }}
- 绑定属性:
v-bind:属性="数据 | 过滤器名"
- 插值:
- 注册过滤器:
-
扩展特性:
- 支持传参:
{{ 数据 | 过滤器名(参数) }}
(参数传给处理函数) - 支持串联:
{{ 数据 | 过滤器1 | 过滤器2 }}
(前一个结果作为后一个输入)
- 支持传参:
**备注 **
- 过滤器也可以接收额外参数、多个过滤器也可以串联
- 并没有改变原本的数据,是产生新的对应的数据
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><!-- 引入 dayjs 库,用于时间格式化 --><script src="https://cdn.jsdelivr.net/npm/dayjs@1.11.9/dayjs.min.js"></script> <title>Vue 时间格式化示例</title>
</head><body><div id="root"><h2>显示格式化后的时间</h2><!-- 计算属性实现 --><h3>现在是:{{fmtTime}}</h3><!-- methods 实现 --><h3>现在是:{{getFmtTime()}}</h3><!-- 过滤器实现 --><h3>现在是:{{time | timeFormater}}</h3><!-- 过滤器实现(传参) --><h3>现在是:{{time | timeFormater('YYYY_MM_DD')}}</h3><!-- 全局过滤器示例 --><h3>截取前4位:{{message | myslice}}</h3></div><script type="text/javascript">Vue.config.productionTip = false//全局过滤器注册(注意大小写和语法)vue.filter('myslice' function(value){return value.slice(0,4)}),new Vue({el: '#root',data: {time: 1621561377603 ,// 时间戳message: 'Hello Vue' // 用于全局过滤器测试},computed: {fmtTime() {// 利用 dayjs 格式化时间戳,返回格式化后的时间字符串return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')}},methods: {getFmtTime() {// 同样用 dayjs 格式化时间戳,返回格式化后的时间字符串return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')}},filters: {timeFormater(value) {// 过滤器接收时间戳参数,格式化后返回//return dayjs(value).format('YYYY年MM月DD日 HH:mm:ss')timeFormater(value, formatStr = 'YYYY年MM月DD日 HH:mm:ss') {return dayjs(value).format(formatStr)}}}})</script>
</body></html>
代码说明
依赖引入:通过
script
标签引入dayjs
库,它是一个轻量级的处理时间和日期的 JavaScript 库,用于便捷地对时间戳进行格式化操作。
computed
(计算属性):fmtTime
计算属性内部调用dayjs
对data
中的time
时间戳进行格式化,计算属性会基于依赖(这里是time
)进行缓存,依赖不变时直接返回缓存结果,提升性能。
methods
(方法):getFmtTime
方法也使用dayjs
格式化时间戳,不过方法调用每次都会执行函数体逻辑,没有计算属性的缓存特性,适合需要主动触发或有复杂逻辑的场景。
filters
(过滤器):timeFormater
过滤器接收time
时间戳作为参数,使用dayjs
格式化后返回结果,在模板中通过|
管道符使用,可对数据进行格式化展示,常用于文本、时间等简单格式化场景。这样代码就完整可运行了,打开对应的 HTML 文件,在浏览器中就能看到三种不同方式(计算属性、方法、过滤器)格式化后的时间展示效果。
过程:
1.17 内置指令与自定义指令
我们学过的指令:v-bind → : 单向绑定解析表达式,可简写为 :xxxv-model → : 双向数据绑定v-for → : 遍历数组/对象/字符串v-on → : 绑定事件监听,可简写为@v-if → : 条件渲染(动态控制节点是否存在)v-else → : 条件渲染(动态控制节点是否存在)v-show → : 条件渲染(动态控制节点是否展示)
v-text
v-text指令:
1.作用:向其所在的节点中渲染文本内容。
2.与插值语法的区别:v-text会替换掉节点中的内容,{{xx}}则不会。
v-text
是 Vue.js 中用于 渲染纯文本内容 的指令,功能类似于插值语法 {{ }}
,但有细微差异。以下是其核心特性和用法:
1. 基本作用
将表达式的值作为纯文本插入到元素中,覆盖元素内原有的内容。
<!-- 等同于 <div>{{ message }}</div> -->
<div v-text="message"></div>
2. 与插值语法的区别
特性 | v-text | {{ }} (插值) |
---|---|---|
覆盖内容 | 会完全替换元素内的原有内容 | 仅替换自身占位符 {{ }} |
HTML 转义 | 始终将内容作为纯文本处理 | 可通过 v-html 渲染 HTML |
初始闪烁 | 初始渲染时可能显示 v-text | 可能显示未编译的 {{ message }} |
优先级 | 高于插值语法 | 普通渲染优先级 |
3. 适用场景
- 纯文本渲染:无需 HTML 解析的场景(如用户名称、数值等)。
- 避免闪烁:当需要确保元素内容始终存在时(相比插值语法,
v-text
初始渲染时不会显示表达式本身)。
<!-- 插值语法可能出现闪烁 -->
<p>{{ username }}</p><!-- v-text 初始值为空字符串时也不会显示占位符 -->
<p v-text="username"></p>
4. 示例代码
<div id="app"><!-- 基础用法 --><p v-text="message"></p> <!-- 等同于 <p>{{ message }}</p> --><!-- 对比插值语法 --><p>使用插值:{{ htmlContent }}</p> <!-- 显示原始 HTML 标签 --><p v-text="htmlContent"></p> <!-- 显示原始 HTML 标签(不会解析) --><!-- 特殊场景:避免初始闪烁 --><p v-text="loading ? '加载中...' : data"></p>
</div><script>new Vue({el: '#app',data: {message: 'Hello Vue!',htmlContent: '<b>粗体文本</b>',loading: true,data: null},mounted() {// 模拟异步加载setTimeout(() => {this.loading = false;this.data = '加载完成';}, 2000);}});
</script>
5. 注意事项
-
HTML 转义:
v-text
会将 HTML 标签作为纯文本显示,不会解析为 DOM 结构。若需渲染 HTML,使用v-html
(但需注意 XSS 风险)。 -
性能考虑:
相比插值语法,v-text
可能在某些场景下略高效(如大量文本更新时),但差异通常可忽略不计。 -
指令参数:
v-text
不支持参数,只能直接绑定表达式。
v-html
v-html指令:1.作用:向指定节点中渲染包含html结构的内容。2.与插值语法的区别:(1).v-html会替换掉节点中所有的内容,{{xx}}则不会。(2).v-html可以识别html结构。3.严重注意:v-html有安全性问题!!!!(1).在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。(2).一定要在可信的内容上使用v-html,永不要用在用户提交的内容上!
v-html
是Vue.js中的一个指令,它用于向指定的DOM元素中渲染包含HTML结构的内容,以下是对v-html
指令的详细介绍:
功能作用
v-html
指令会将绑定的字符串当作HTML代码进行解析,并渲染到对应的DOM元素中, 类似原生JavaScript中的element.innerHTML
。例如:
<div id="app"><div v-html="htmlContent"></div>
</div>
<script>
new Vue({el: '#app',data: {htmlContent: '<p>这是一段通过v-html渲染的<strong>HTML</strong>内容</p>'}
});
</script>
在上述代码中,htmlContent
里的HTML结构会被解析并渲染到页面中,用户看到的是带有加粗效果的段落文本。
与插值语法的区别
- 内容替换:
v-html
会完全替换掉指令所在DOM元素内部原有的所有内容;而插值语法{{ }}
只是将自身占位符替换为对应的数据,不会影响元素内其他的文本和节点 。 - HTML识别:插值语法会将数据当作普通文本进行渲染,不会解析其中的HTML标签;
v-html
则会将绑定的字符串解析为HTML结构进行展示。例如:
<div id="app"><div>{{ htmlText }}</div><div v-html="htmlText"></div>
</div>
<script>
new Vue({el: '#app',data: {htmlText: '<p>这是一段包含<a href="#">链接</a>的文本</p>'}
});
</script>
第一个div
会将htmlText
原样输出,显示为包含标签的文本;第二个div
则会将其中的标签解析,呈现出带有链接的段落。
安全性问题
v-html
存在严重的安全风险,容易导致跨站脚本攻击(XSS)。如果渲染的内容是用户可控的(比如用户提交的评论、输入框内容等),攻击者可能会注入恶意的JavaScript代码,当其他用户访问页面时,恶意代码就会被执行。因此,绝不要在不可信的内容上使用v-html
。
为了应对安全风险,在使用v-html
渲染内容前,需要对内容进行严格的过滤和验证,或者使用一些安全的富文本解析库,确保只渲染安全的HTML结构。
v-cloak
v-cloak 是 Vue.js 提供的一个特殊指令,其核心作用是防止在 Vue 实例完成编译前,页面显示未编译的插值表达式(如 {{ message }}。
v-cloak指令(没有值):1.本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。2.使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题。
它的工作原理基于以下两点:
1. CSS 选择器与样式控制
[v-cloak] {display: none;
}
- 作用:通过 CSS 选择器匹配所有带有
v-cloak
属性的元素,并将其隐藏(display: none
)。 - 关键点:
- 这个 CSS 规则必须在 外部 CSS 文件 或 内联
<style>
标签 中定义,且需在 Vue 实例初始化前加载完成。 - 若 CSS 未正确加载(如网络延迟),
v-cloak
可能失效。
- 这个 CSS 规则必须在 外部 CSS 文件 或 内联
2. Vue 实例编译过程
当 Vue 实例接管 DOM 时:
-
初始状态:
- DOM 元素上存在
v-cloak
属性(如<div v-cloak>{{ message }}</div>
)。 - 由于 CSS 规则,该元素此时被隐藏(
display: none
)。
- DOM 元素上存在
-
编译完成后:
- Vue 会自动移除所有
v-cloak
属性。 - 元素不再匹配
[v-cloak]
选择器,因此恢复显示。
- Vue 会自动移除所有
执行流程示例
<style>[v-cloak] {display: none; /* 关键样式:隐藏未编译的元素 */}
</style><div id="app" v-cloak><h1>{{ message }}</h1> <!-- 初始状态:隐藏 -->
</div><script>// 模拟网络延迟(500ms后才创建Vue实例)setTimeout(() => {new Vue({el: '#app',data: {message: 'Hello Vue!'}});// Vue实例创建后:// 1. 编译DOM,将 {{ message }} 替换为 "Hello Vue!"// 2. 移除 v-cloak 属性// 3. 元素恢复显示(不再匹配 [v-cloak] 选择器)}, 500);
</script>
以下是一个使用 v-cloak
指令解决插值表达式闪烁问题的完整示例:
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>v-cloak 指令示例</title><script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script><style>/* 关键CSS:未渲染前隐藏带 v-cloak 的元素 */[v-cloak] {display: none;}</style>
</head>
<body><div id="app" v-cloak><!-- 插值表达式在Vue实例初始化前不会显示 --><h1>{{ message }}</h1><p>{{ username }},欢迎使用Vue!</p></div><script>// 模拟网络延迟(500ms后才创建Vue实例)setTimeout(() => {new Vue({el: '#app',data: {message: 'Vue加载完成!',username: '访客'}});}, 500);</script>
</body>
</html>
CSS 样式:
[v-cloak] {display: none; }
- 所有带
v-cloak
属性的元素默认隐藏,防止插值表达式{{ }}
未编译时显示在页面上。HTML 结构:
<div id="app" v-cloak><h1>{{ message }}</h1><p>{{ username }}</p> </div>
v-cloak
直接添加到根容器,整个内容区域在 Vue 实例初始化完成前保持隐藏。JavaScript 逻辑:
// 模拟网络延迟 setTimeout(() => {new Vue({ /* ... */ }); }, 500);
- 通过
setTimeout
延迟创建 Vue 实例,凸显v-cloak
的作用(若无v-cloak
,页面会先显示{{ message }}
再替换为真实内容)。
v-once
v-once指令:1.v-once所在节点在初次动态渲染后,就视为静态内容了。2.以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。
v-once
是 Vue.js 中的一个特殊指令,用于 一次性渲染元素或组件。一旦渲染完成,后续数据更新时,该元素或组件的内容将被视为静态内容,不再重新渲染。
1. 核心作用
- 只渲染一次:指令所在的 DOM 节点在初次渲染后,会被标记为静态内容,后续数据变化不会触发其更新。
- 优化性能:适用于不需要响应式更新的场景(如展示固定文本、初始值等),减少不必要的 DOM 操作。
2. 基础语法
<!-- 简单用法 -->
<span v-once>{{ message }}</span><!-- 复杂结构也适用 -->
<div v-once><h1>{{ title }}</h1><p>{{ description }}</p>
</div>
3. 示例对比
场景1:无 v-once(默认响应式更新)
<div id="app"><p>{{ count }}</p> <!-- 每次 count 变化都会重新渲染 --><button @click="count++">增加</button>
</div>
场景2:使用 v-once(仅渲染一次)
<div id="app"><p v-once>{{ count }}</p> <!-- 初始渲染后不再更新 --><button @click="count++">增加</button>
</div>
执行效果
- 无 v-once:点击按钮时,
p
标签内的数字会实时更新。 - 有 v-once:无论点击多少次,
p
标签始终显示初始值(如0
)。
4. 进阶用法
结合组件使用
<!-- 组件只初始化一次,后续不响应数据变化 -->
<MyComponent v-once :data="initialData" />
优化复杂计算
<div v-once><!-- 复杂计算只执行一次 -->{{ expensiveComputation() }}
</div>
5. 注意事项
-
仅影响直接子节点:
v-once
只会让当前元素及其所有子元素变为静态内容,不会影响外部元素。 -
与其他指令共存:
若同时使用v-once
和其他指令(如v-if
、v-for
),需注意执行顺序。例如:<!-- 当 show 为 true 时渲染,且只渲染一次 --> <p v-once v-if="show">{{ message }}</p>
v-pre
v-pre指令:1.跳过其所在节点的编译过程。2.可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。
v-pre
是 Vue.js 的一个指令,核心作用是 跳过所在节点及其子节点的编译过程,直接按原始内容渲染。
核心特性
- 跳过编译:Vue 解析模板时,会忽略带有
v-pre
的节点,不处理其中的插值语法({{ }}
)、指令(如v-bind
、v-on
等)。 - 提升性能:对于纯静态内容(无任何 Vue 语法),使用
v-pre
可减少编译时间,优化初始渲染效率。
示例
<div id="app"><!-- 会被编译:显示 "Hello Vue" --><p>{{ message }}</p><!-- 不会被编译:直接显示 "{{ message }}" --><p v-pre>{{ message }}</p><!-- 子节点也不会被编译 --><div v-pre><p v-bind:title="title">这是静态文本</p> <!-- 直接显示标签及属性 --></div>
</div><script>
new Vue({el: '#app',data: {message: 'Hello Vue',title: '提示'}
});
</script>
适用场景
- 展示包含
{{ }}
等特殊符号的文本(如代码片段、Vue 语法示例)。 - 纯静态内容(无数据绑定、无指令),通过跳过编译提升性能。
自定义指令
需求1:定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍。
需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点。
自定义指令总结:一、定义语法:(1).局部指令:new Vue({directives:{指令名:配置对象} 或 directives{指令名:回调函数}})(2).全局指令:Vue.directive(指令名,配置对象) 或 Vue.directive(指令名,回调函数)二、配置对象中常用的3个回调:(1).bind:指令与元素成功绑定时调用。(2).inserted:指令所在元素被插入页面时调用。(3).update:指令所在模板结构被重新解析时调用。三、备注:1.指令定义时不加v-,但使用时要加v-;2.指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名。
自定义指令是 Vue 中用于扩展 DOM 操作的工具,可封装常用行为(如表单聚焦、元素样式调整等)。以下是核心要点:
1. 定义方式
全局指令(所有组件可用)
Vue.directive('指令名', {// 钩子函数(指令生命周期)bind(el, binding) {}, // 指令与元素绑定成功时调用inserted(el, binding) {}, // 元素插入页面时调用update(el, binding) {} // 元素所在模板更新时调用
});
局部指令(仅当前组件可用)
new Vue({directives: {'指令名': {// 钩子函数bind(el, binding) { /* ... */ }}}
});
2. 钩子函数参数
el
:指令绑定的 DOM 元素(可直接操作)。binding
:包含指令信息的对象,如:binding.value
:指令绑定的值(如v-color="red"
中 value 为red
)。binding.arg
:指令参数(如v-color:bg="red"
中 arg 为bg
)。
这是关于 Vue 自定义指令中 配置对象常用回调函数 的详细说明,结合示例代码帮助理解:
1. bind
钩子
- 触发时机:指令首次绑定到元素时调用,只执行一次。
- 适用场景:初始化设置(如添加样式、事件监听)。
- 参数:
el
(DOM 元素)、binding
(指令信息)。
示例:添加基础样式
Vue.directive('highlight', {bind(el, binding) {// 初始化样式el.style.backgroundColor = binding.value || 'yellow';}
});// 使用
<div v-highlight="'#f0f0f0'">背景色被设置</div>
2. inserted
钩子
- 触发时机:元素插入 DOM 后调用(保证父元素存在)。
- 适用场景:需要访问 DOM 布局的操作(如获取尺寸、聚焦)。
- 参数:同
bind
。
示例:自动聚焦输入框
Vue.directive('focus', {inserted(el) {el.focus(); // 此时元素已插入页面,可安全聚焦}
});// 使用
<input v-focus> <!-- 页面加载后自动聚焦 -->
3. update
钩子
- 触发时机:元素所在模板更新时调用(无论数据是否变化)。
- 适用场景:响应数据变化的更新操作。
- 参数:新增
vnode
(当前虚拟节点)和oldVnode
(旧虚拟节点)。
示例:动态更新文本颜色
Vue.directive('color', {update(el, binding) {el.style.color = binding.value; // 数据更新时重新设置颜色}
});// 使用
<p v-color="text_color">动态颜色</p>
4. 钩子参数详解
参数 | 说明 |
---|---|
el | 指令绑定的 DOM 元素,可直接操作(如 el.style )。 |
binding | 对象,包含: - value :指令绑定的值(如 v-color="red" 的 red )- arg :指令参数(如 v-on:click 的 click )- modifiers :修饰符对象(如 v-my-directive.foo.bar ) |
vnode | Vue 编译生成的虚拟节点。 |
oldVnode | 上一次的虚拟节点(仅 update 和 componentUpdated 可用)。 |
5. 执行顺序对比
假设有指令 v-example
:
- 初始渲染:
bind
→inserted
- 后续更新:
update
→componentUpdated
6. 典型应用场景
钩子 | 常见用途 |
---|---|
bind | 设置初始样式、添加事件监听器(仅一次)。 |
inserted | 第三方插件初始化(如 Chart.js )、DOM 布局操作(如计算高度)。 |
update | 根据数据变化更新 DOM(如防抖、节流处理)。 |
总结
bind
:一次性初始化,不依赖 DOM 位置。inserted
:需要访问完整 DOM 树结构时使用。update
:响应数据变化,避免不必要的操作(可对比binding.value
和oldValue
)。
3. 示例
需求1:实现 v-focus
指令(自动聚焦输入框)
// 全局指令
Vue.directive('focus', {inserted(el) {el.focus(); // 元素插入页面时自动聚焦}
});
<input v-focus> <!-- 页面加载时自动聚焦 -->
需求2:实现 v-color
指令(动态设置颜色)
// 局部指令
new Vue({directives: {color(el, binding) {el.style.color = binding.value; // 绑定值作为颜色}}
});
<p v-color=" 'blue' ">蓝色文本</p>
在 Vue 中,自定义指令有两种定义方式:函数式 和 对象式。下面通过具体示例说明它们的区别和用法:
1.函数式
适用于 只需要 bind
和 update
钩子相同行为 的场景,语法更简洁。
示例:实现 v-color
指令(动态设置文本颜色)
// 全局注册函数式指令
Vue.directive('color', (el, binding) => {// 此函数会在 bind 和 update 时都被调用el.style.color = binding.value; // binding.value 是指令绑定的值
});// 使用
<p v-color="'red'">红色文本</p>
等价于对象式写法
Vue.directive('color', {bind(el, binding) {el.style.color = binding.value;},update(el, binding) {el.style.color = binding.value;}
});
**示例:实现 v-big
指令:
<!-- 准备好一个容器-->
<div id="root"><h2>{{name}}</h2><h2>当前的n值是:<span v-text="n"></span> </h2><h2>放大10倍后的n值是:<span v-big="n"></span> </h2><button @click="n++">点我n+1</button>
</div>
</body><script type="text/javascript">
Vue.config.productionTip = false
new Vue({el:'#root',data:{name:'天山',n:1},directives:{//big函数何时会被调用?1.指令与元素成功绑定时(一上来)。2.指令所在的模板被重新解析时。big(element,binding){console.log('big')element.innerText = binding.value * 10}}
})
</script>
2. 对象式(完整写法)
适用于 需要多个生命周期钩子 的复杂场景,可精细化控制指令行为。
示例:实现 v-draggable
指令(元素可拖拽)
Vue.directive('draggable', {bind(el) {// 初始化样式el.style.cursor = 'move';},inserted(el) {// 元素插入 DOM 后,添加拖拽功能let offsetX, offsetY;el.addEventListener('mousedown', (e) => {offsetX = e.clientX - el.getBoundingClientRect().left;offsetY = e.clientY - el.getBoundingClientRect().top;// 监听移动和释放事件document.addEventListener('mousemove', onDrag);document.addEventListener('mouseup', onDragEnd);});function onDrag(e) {el.style.position = 'fixed';el.style.left = `${e.clientX - offsetX}px`;el.style.top = `${e.clientY - offsetY}px`;}function onDragEnd() {document.removeEventListener('mousemove', onDrag);document.removeEventListener('mouseup', onDragEnd);}},unbind(el) {// 指令解绑时,移除事件监听,避免内存泄漏el.removeEventListener('mousedown', /* ... */);document.removeEventListener('mousemove', /* ... */);document.removeEventListener('mouseup', /* ... */);}
});// 使用
<div v-draggable>可拖拽元素</div>
示例
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8"><title>Vue 自定义指令示例</title><script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head><body><!-- 准备好一个容器 --><div id="root"><h2>{{name}}</h2><h2>当前的n值是:<span v-text="n"></span></h2><h2>放大10倍后的n值是:<span v-big="n"></span></h2><button @click="n++">点我n+1</button><hr /><input type="text" v-fbind:value="n"></div><script type="text/javascript">Vue.config.productionTip = falsenew Vue({el: '#root',data: {name: 'wuwu',n: 1},directives: {// 定义 big 指令(函数式写法 )big(element, binding) {console.log('big', this) // 此处 this 指向 window element.innerText = binding.value * 10},// fbind 指令(对象式写法,这里仅写了部分钩子,可按需补充 )fbind: {// 指令与元素成功绑定时(一上来)bind(element, binding) {// 可在这里做初始化操作,比如给元素设置初始值等element.value = binding.value},// 元素插入页面时调用 inserted(element) {element.focus()},// 指令所在模板更新时调用 update(element, binding) {element.value = binding.value}}}})</script>
</body></html>
代码说明
big
指令:采用函数式自定义指令写法,会在指令与元素绑定、模板重新解析时触发,执行时内部this
指向window
,功能是把绑定值放大 10 倍并渲染到页面。fbind
指令:用对象式自定义指令写法,示例写了bind
(绑定)、inserted
(插入 DOM )、update
(更新)钩子,可用于类似v-bind
结合聚焦等复杂逻辑,比如给输入框赋值、让输入框自动聚焦,数据更新时同步值 。- 整体结合了 Vue 实例的基本配置(
el
、data
等 )以及自定义指令在模板中的使用(v-big
、v-fbind
),能体现不同自定义指令写法和执行时机特点 。
3. 对比总结
特性 | 函数式 | 对象式 |
---|---|---|
语法 | 单个函数 | 包含多个钩子的对象 |
生命周期 | 仅 bind 和 update | 支持 bind 、inserted 、update 等所有钩子 |
适用场景 | 简单的一次性操作(如样式设置) | 需要复杂 DOM 操作或事件监听 |
示例 | v-color 、v-text-truncate | v-draggable 、v-lazyload |
1.18 Vue实例生命周期
- 牢记生命周期钩子(
beforeCreate
、created
、beforeMount
、mounted
、beforeUpdate
、updated
、beforeDestroy
、destroyed
)
生命周期:1.又名:生命周期回调函数、生命周期函数、生命周期钩子。2.是什么:Vue在关键时刻帮我们调用的一些特殊名称的函数。3.生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的。4.生命周期函数中的this指向是vm 或 组件实例对象。
Vue 生命周期描述了一个 Vue 实例从创建到销毁的全过程,可分为 8 个主要阶段(以 Vue2 为例),各阶段特点和关键钩子如下:
1. 创建前(beforeCreate
)
- 特点:
- 实例刚被初始化(
this
已指向实例),但数据观测(data
、props
)和事件机制未完成。 - 无法访问
data
、methods
等内容。
- 实例刚被初始化(
- 典型场景:无实际操作(因无可用数据)。
beforeCreate() {console.log('beforeCreate')
}
2. 创建后(created
)
- 特点:
- 实例已完成 数据观测(
data
已代理、methods
已绑定),但 DOM 未渲染。 - 可访问
data
、methods
,适合发起异步请求(如接口调用)。
- 实例已完成 数据观测(
- 典型场景:初始化数据、调用接口获取数据。
created() {console.log('created')
}
挂在流程:
3. 挂载前(beforeMount
)
- 特点:
- 模板已编译为虚拟 DOM,但未渲染到真实 DOM。
$el
已存在(虚拟 DOM 容器),但内容为旧的(如插值表达式未替换)。
- 典型场景:最后一次修改数据(不会触发额外渲染)。
beforeMount() {console.log('beforeMount')
},
4. 挂载后(mounted
)
- 特点:
- 虚拟 DOM 渲染为真实 DOM,并挂载到页面。
- 可访问真实 DOM(通过
$el
或document
查询),适合操作 DOM(如初始化第三方库)。
- 典型场景:初始化图表、地图等依赖 DOM 的插件。
mounted() {console.log('mounted')
},
更新流程:
5. 更新前(beforeUpdate
)
- 特点:
- 数据已更新,但真实 DOM 未更新(虚拟 DOM 准备对比更新)。
- 可获取更新前的 DOM 状态,用于对比数据。
- 典型场景:记录 DOM 状态(如滚动位置)。
beforeUpdate() {console.log('beforeUpdate')
}
6. 更新后(updated
)
- 特点:
- 虚拟 DOM 对比后,真实 DOM 已更新。
- 可访问更新后的 DOM,适合根据新 DOM 调整逻辑。
- 典型场景:更新第三方库(如重新渲染图表)。
updated() {console.log('updated')console.log(this.n)debugger
}
销毁流程:
- 方法名:
vm.$destroy()
- 作用:完全销毁一个实例,清理它与其它实例的连接,解绑它的全部指令及事件监听器,触发
beforeDestroy
和destroyed
钩子 。
7. 销毁前(beforeDestroy
)
- 特点:
- 实例即将销毁,所有数据、方法仍可访问。
- 适合清理定时器、移除事件监听,防止内存泄漏。
- 典型场景:清除
setInterval
、解绑window
事件。
8. 销毁后(destroyed
)
- 特点:
- 实例已销毁,所有数据、方法不可访问。
- 真实 DOM 可能仍存在(需手动移除),但不再受 Vue 控制。
- 典型场景:无(实例生命周期结束)。
下面是一个完整的 Vue 生命周期演示代码,包含各阶段钩子函数的执行顺序和典型操作示例:
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>Vue 生命周期示例</title><script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body><div id="app"><h1>{{ message }}</h1><button @click="updateMessage">更新消息</button><button @click="destroyInstance">销毁实例</button><div ref="domElement">这是一个 DOM 元素</div><ul><li v-for="item in list" :key="item">{{ item }}</li></ul></div><script>const app = new Vue({el: '#app',data: {message: 'Vue 生命周期演示',list: ['初始化数据'],intervalId: null},// ===== 创建阶段 =====beforeCreate() {console.log('1. beforeCreate: 数据观测和 event/watcher 未初始化');console.log(' - this.message:', this.message); // undefinedconsole.log(' - this.$el:', this.$el); // undefined},created() {console.log('2. created: 数据观测和 event/watcher 已初始化');console.log(' - this.message:', this.message); // "Vue 生命周期演示"console.log(' - this.$el:', this.$el); // undefined (DOM 未渲染)// 典型操作:初始化数据、发起异步请求setTimeout(() => {this.list.push('异步获取的数据');}, 1000);// 启动定时器(后续需在销毁前清除)this.intervalId = setInterval(() => {console.log('定时器运行中...');}, 2000);},beforeMount() {console.log('3. beforeMount: 模板编译完成,但未挂载到 DOM');console.log(' - this.$el:', this.$el); // 虚拟 DOM 容器(未渲染真实内容)},mounted() {console.log('4. mounted: 已挂载到真实 DOM');console.log(' - this.$el:', this.$el); // 真实 DOM 元素console.log(' - DOM 内容:', this.$el.querySelector('h1').textContent); // "Vue 生命周期演示"// 典型操作:操作 DOM、初始化第三方库const domElement = this.$refs.domElement;domElement.style.color = 'red';// 模拟初始化第三方库(如 Chart.js)console.log('第三方库初始化完成');},// ===== 更新阶段 =====beforeUpdate() {console.log('5. beforeUpdate: 数据更新,但 DOM 未更新');console.log(' - 数据:', this.message); // 新值console.log(' - DOM 内容:', this.$el.querySelector('h1').textContent); // 旧值},updated() {console.log('6. updated: 数据和 DOM 都已更新');console.log(' - 数据:', this.message); // 新值console.log(' - DOM 内容:', this.$el.querySelector('h1').textContent); // 新值// 典型操作:根据 DOM 更新调整第三方库console.log('第三方库已同步更新');},// ===== 销毁阶段 =====beforeDestroy() {console.log('7. beforeDestroy: 实例即将销毁');// 典型操作:清理定时器、移除事件监听clearInterval(this.intervalId);window.removeEventListener('resize', this.handleResize);console.log('定时器已清除,事件已解绑');},destroyed() {console.log('8. destroyed: 实例已销毁');console.log(' - this.message:', this.message); // 仍可访问(但已无意义)console.log(' - this.$el:', this.$el); // DOM 元素仍存在,但不再响应数据变化},methods: {updateMessage() {this.message = '数据已更新';},destroyInstance() {this.$destroy();},handleResize() {console.log('窗口大小变化');}},// 监听窗口大小变化(需在 beforeDestroy 中解绑)mounted() {window.addEventListener('resize', this.handleResize);}});</script>
</body>
</html>
执行流程说明:
-
创建阶段:
beforeCreate()
:实例初始化,但无数据和 DOM。created()
:数据和方法可用,发起异步请求、启动定时器。beforeMount()
:模板编译完成,但未渲染到 DOM。mounted()
:真实 DOM 已挂载,可操作 DOM 和初始化第三方库。
-
更新阶段(点击"更新消息"按钮时触发):
beforeUpdate()
:数据已更新,但 DOM 尚未更新。updated()
:数据和 DOM 均已更新,可同步第三方库。
-
销毁阶段(点击"销毁实例"按钮时触发):
beforeDestroy()
:实例即将销毁,清理定时器和事件监听。destroyed()
:实例已销毁,所有 Vue 功能失效。
关键观察点:
- 在
created()
中访问this.$el
会得到undefined
,因为 DOM 尚未渲染。 - 在
mounted()
中可以安全操作真实 DOM。 - 在
beforeDestroy()
中必须清理自定义定时器和事件监听,否则会导致内存泄漏。
这个示例展示了 Vue 生命周期各阶段的执行顺序和典型操作场景,帮助理解何时使用哪个钩子函数。
关键对比表
阶段 | 能否访问 data | 真实 DOM 状态 | 典型操作 |
---|---|---|---|
created | ✔️ | ❌(虚拟 DOM 未渲染) | 初始化数据、调用接口 |
mounted | ✔️ | ✔️(真实 DOM 已渲染) | 操作 DOM、初始化第三方库 |
beforeUpdate | ✔️ | ❌(未更新) | 记录更新前状态 |
updated | ✔️ | ✔️(已更新) | 同步第三方库 |
beforeDestroy | ✔️ | ✔️(存在) | 清理定时器、事件监听 |
自定义事件监视器
// 自定义事件监视器(事件总线/发布订阅模式)
const eventBus = {events: {},// 注册事件监听on(eventName, callback) {if (!this.events[eventName]) {this.events[eventName] = [];}this.events[eventName].push(callback);},// 触发事件emit(eventName, data) {if (this.events[eventName]) {this.events[eventName].forEach(callback => callback(data));}},// 取消事件监听off(eventName, callback) {if (this.events[eventName]) {this.events[eventName] = this.events[eventName].filter(cb => cb !== callback);}}
};// 使用示例
// 监听事件
eventBus.on('message', (data) => {console.log('收到消息:', data);
});// 触发事件
eventBus.emit('message', 'Hello World!');// 取消监听
const callback = (data) => console.log('另一个监听器:', data);
eventBus.on('message', callback);
eventBus.off('message', callback);
- on(eventName, callback):注册事件监听器
- emit(eventName, data):触发事件并传递数据
- off(eventName, callback):取消特定监听器
下面是一个完整的 Vue 自定义事件监视器(Event Bus)实现,包含完整的代码和使用示例:
// event-bus.js
class EventBus {constructor() {this.events = {}; // 存储事件和监听器的映射}// 注册事件监听器on(eventName, callback) {if (!this.events[eventName]) {this.events[eventName] = [];}this.events[eventName].push(callback);return this; // 支持链式调用}// 触发事件emit(eventName, payload) {if (this.events[eventName]) {this.events[eventName].forEach(callback => callback(payload));}return this;}// 取消事件监听器off(eventName, callback) {if (this.events[eventName]) {this.events[eventName] = this.events[eventName].filter(cb => cb !== callback);}return this;}// 只触发一次的事件once(eventName, callback) {const wrapper = (payload) => {callback(payload);this.off(eventName, wrapper);};this.on(eventName, wrapper);return this;}// 清除所有事件或特定事件clear(eventName) {if (eventName) {delete this.events[eventName];} else {this.events = {};}return this;}
}// 创建单例实例
export const eventBus = new EventBus();// 可选:在Vue中全局使用
export default {install(Vue) {Vue.prototype.$eventBus = eventBus;}
};
// main.js (Vue项目入口)
import Vue from 'vue';
import App from './App.vue';
import eventBusPlugin from './event-bus';// 全局注册事件总线插件
Vue.use(eventBusPlugin);new Vue({render: h => h(App),
}).$mount('#app');
// 使用示例// 1. 组件A:发布事件
export default {methods: {sendMessage() {// 通过全局实例访问this.$eventBus.emit('message', {type: 'greeting',content: 'Hello from Component A'});// 或直接导入使用// import { eventBus } from './event-bus';// eventBus.emit('message', ...);}}
};// 2. 组件B:监听事件
export default {created() {// 监听事件this.$eventBus.on('message', (payload) => {console.log('收到消息:', payload);// 处理消息...});// 监听一次的事件this.$eventBus.once('init', () => {console.log('初始化操作只执行一次');});},beforeDestroy() {// 组件销毁前清除监听器,避免内存泄漏this.$eventBus.off('message');}
};
核心功能说明
- 事件注册:
on(eventName, callback)
- 事件触发:
emit(eventName, payload)
- 事件取消:
off(eventName, callback)
- 单次触发:
once(eventName, callback)
- 事件清除:
clear(eventName)
优势:
- 跨组件通信:无需依赖父子关系
- 解耦逻辑:发布者和订阅者无需直接引用
- 灵活扩展:支持多种事件管理方式
- 类型安全:可通过 TypeScript 进一步增强
使用场景:
- 组件间消息传递
- 全局状态变更通知
- 插件系统事件机制
- 异步操作回调管理
注意事项:
- 确保在组件销毁前取消监听器(使用
beforeDestroy
钩子) - 避免滥用事件总线,复杂场景建议使用 Vuex/Pinia
- 大型应用可考虑按模块划分多个事件总线实例
- 注意事件命名冲突,建议使用命名空间(如
user:login
)
总结
- 创建阶段:
created
(初始化数据)→mounted
(渲染 DOM)。 - 更新阶段:
beforeUpdate
(数据更新)→updated
(DOM 更新)。 - 销毁阶段:
beforeDestroy
(清理资源)→destroyed
(实例结束)。
常用的生命周期钩子:1.mounted:发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。2.beforeDestroy:清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。关于销毁Vue实例 1.销毁后借助Vue开发者工具看不到任何信息。2.销毁后自定义事件会失效,但原生DOM事件依然有效。3.一般不会再beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了。
二:Vue组件化编程
一.模块与组件,模块化与组件化
1.模块
1.理解:向外提供特定功能的js程序,一般就是一个js文件
2.为什么:js文件很多很复杂
3. 作用:复用js,简化js的编写,提高js运行效率
1.1非单文件组件:
一个文件中包含有n个组件。
1.2单文件组件:
一个文件中只包含有1个组件。
2.组件
1.理解:用来实现局部(特定)功能效果的代码集合(html/css/js/image…)
2.为什么一个界面的功能很复杂
3. 作用:复用编码,简化项目编码,提高运行效率
组件的定义 :实现应用中局部功能代码和资源的集合
3.模块化
当应用中的js都以模块来编写的,那这个应用就是一个模块化的应用。
4.组件化
当应用中的功能都是多组件的方式来编写的,那这个应用就是一个组件化的应用。
二.非单文件组件
data与el的2种写法
data与el的2种写法
1. el有2种写法(1). new Vue时候配置el属性。(2). 先创建Vue实例,随后再通过vm.$mount('#root')指定el的值。
2. data有2种写法(1). 对象式(2). 函数式如何选择:目前哪种写法都可以,以后学习到组件时,data必须使用函数式,否则会报错。
3. 一个重要的原则:由Vue管理的函数,一定不要写箭头函数,一旦写了箭头函数,this就不再是Vue实例了。
核心要点:
el
两种指定方式:实例化时配置 / 后续通过$mount
挂载data
两种写法:对象式(非组件场景)/ 函数式(组件场景必选)- 关键原则:Vue 管理的函数(如生命周期、方法等)不能用箭头函数,否则破坏
this
指向
Vue中使用组件
Vue中使用组件的三大步骤:
一、定义组件(创建组件)
二、注册组件
三、使用组件(写组件标签)一、如何定义一个组件?
使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别;
区别如下:1.el不要写,为什么? —— 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。2.data必须写成函数,为什么? —— 避免组件被复用时,数据存在引用关系。
备注:使用template可以配置组件结构。二、如何注册组件?
1.局部注册:靠new Vue的时候传入components选项
2.全局注册:靠Vue.component('组件名',组件)三、编写组件标签:
<school></school>
几个注意点:
几个注意点:
1.关于组件名:一个单词组成:第一种写法(首字母小写):school第二种写法(首字母大写):School多个单词组成:第一种写法(kebab-case命名):my-school第二种写法(CamelCase命名):MySchool(需要Vue脚手架支持)备注:(1).组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。(2).可以使用name配置项指定组件在开发者工具中呈现的名字。2.关于组件标签:第一种写法:<school></school>第二种写法:<school/>备注:不用使用脚手架时,<school/>会导致后续组件不能渲染。3.一个简写方式:const school = Vue.extend(options) 可简写为:const school = options
示例:
// 定义组件
const s = Vue.extend({name: 'atg',template: `...`,data() { ... }
})new Vue({el: '#root',data: {msg: '欢迎学习Vue!'},components: {xuexiao: s}
})
代码主要做了这些事:
- 用
Vue.extend
定义了一个名为atg
的组件,指定了template
和data
等配置(template
和data
具体内容因截图省略)。 - 创建 Vue 实例,挂载到
#root
元素上,data
里有msg
数据,在components
里局部注册了xuexiao
组件(关联前面定义的s
组件构造器 ) 。
组件的嵌套
vuecomponent
关于VueComponent:
1.school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。2.我们只需要写<school/>或<school></school>,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的:new VueComponent(options)。3.特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!!!!4.关于this指向:(1).组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】。(2).new Vue()配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】。5.VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)。Vue的实例对象,以后简称vm。
一个重要的内置关系
1. 一个重要的内置关系:VueComponent.prototype.__proto__ === Vue.prototype
2. 为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue 原型上的属性、方法。
Vue.config.productionTip = false // 阻止 vue 在启动时生成生产提示。// 定义一个构造函数
function Demo() {this.a = 1this.b = 2
}
// 创建一个 Demo 的实例对象
const d = new Demo()console.log(Demo.prototype) // 显示原型属性
console.log(d.__proto__) // 隐式原型属性
console.log(Demo.prototype === d.__proto__)// 程序员通过显示原型属性操作原型对象,追加一个 x 属性,值为 99
Demo.prototype.x = 99console.log('@', d.x)
代码主要演示了 JavaScript 构造函数、原型(prototype
)和实例隐式原型(__proto__
)的关系:
- 构造函数
Demo
定义属性a
、b
,通过new
创建实例d
。 - 打印对比
Demo.prototype
(构造函数显式原型)和d.__proto__
(实例隐式原型),二者指向同一原型对象。 - 给原型对象追加
x = 99
,实例d
可通过原型链访问到d.x
,最终输出@ 99
。
三.单文件组件
<template>// 组件的结构
</template><script>// 组件交互相关的代码(数据、方法等等)
</script><style>// 组件的样式
</style>
这是 Vue 单文件组件(SFC)的基础结构,各部分作用:
<template>
:编写组件的 HTML 结构<script>
:编写组件的逻辑(数据、方法、生命周期等)<style>
:编写组件的 CSS 样式
特点:
- 一个文件管理组件的结构、逻辑、样式,符合 Vue 组件化开发规范
- 支持添加
scoped
(样式作用域)、lang="ts"
(TypeScript 支持)等扩展配置
三:使用Vue脚手架
3.1脚手架搭建
1.初始化脚手架
1.1 说明
- vue脚手架是vue官方提供的标准化开发工具 (开发平台).
- 文档:https://cli.vuejs.org/zh/
1.2具体步骤
- 安装命令
第一步(仅第一次执行):全局安装@vue/cli
.
# 使用 npm 全局安装
npm install -g @vue/cli
- 创建项目命令
第二步:切换到你要创建项目的目录,然后使用命令创建项目
# 命令行创建项目
vue create my-project
第三步:启动项目
`npm run serve`
备注:
- 如出现下载缓慢请配置npm 淘宝镜像:
npm config set registry https://registry.npm.taobao.org
-
vue脚手架隐藏了所有webpack相关的配置,若想查看具体的webpakc配置,请执
行:vue inspect > output.js
-
过程展示
4. 文件解析
main.js
html
render函数
关于不同版本的Vue:
1.vue.js与vue.runtime.xxx.js的区别:(1).vue.js是完整版的Vue,包含:核心功能+模板解析器。(2).vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。2.因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement函数去指定具体内容。
// 创建Vue实例对象---vm
new Vue({el: '#app',// 下面这行代码一会解释,完成了这个功能:将App组件放入容器中render: h => h(App),// render:q=> q('h1','你好啊')// template:`<h1>你好啊</h1>`,// components:{App},
})
代码核心逻辑是创建 Vue 实例并挂载到 #app
容器:
el: '#app'
:指定 Vue 实例挂载的 DOM 元素render: h => h(App)
:通过render
函数渲染App
组件(替代template
+ 编译器的场景)- 注释的代码演示了
render
函数的其他用法(直接渲染h1
标签)和template
写法,以及组件注册的示例
在 Vue 中,render
函数和 template
都是用于定义组件渲染内容的方式,但它们在实现原理、使用场景、灵活性等方面存在明显区别,具体如下:
- 实现原理
-
template
是 Vue 提供的声明式语法,类似 HTML 结构,需要 Vue 的模板编译器(vue-template-compiler
)将其编译为render
函数,最终在浏览器中执行的是编译后的render
函数。- 完整版 Vue(
vue.js
)内置了模板编译器,可直接使用template
; - 运行时版 Vue(
vue.runtime.js
)需借助打包工具(如vue-loader
)在构建时编译template
。
- 完整版 Vue(
-
render
函数
是编程式语法,直接返回虚拟 DOM(通过createElement
或 JSX 构建),无需编译过程,可直接在浏览器中执行。- 本质是 Vue 渲染逻辑的底层入口,
template
最终也会被编译为render
函数运行。
- 本质是 Vue 渲染逻辑的底层入口,
- 使用场景
-
template
适合常规业务开发,尤其是 UI 结构清晰、与 HTML 语法接近的场景(如页面布局、表单等)。
示例:<template><div class="hello"><h1>{{ msg }}</h1></div> </template>
-
render
函数
适合复杂逻辑或动态渲染场景:- 需要高度动态生成 DOM(如根据条件渲染不同组件、循环嵌套复杂);
- 开发 Vue 插件、库(避免依赖模板编译器,减小体积);
- 使用运行时版 Vue(
vue.runtime.js
)时,必须用render
函数替代template
。
示例(动态渲染组件):
render(h) {// 根据条件动态渲染组件const componentToRender = this.condition ? ComponentA : ComponentB;return h(componentToRender, { props: { msg: this.msg } }); }
- 灵活性与复杂度
-
template
语法简单、直观,贴近 HTML,学习成本低,但动态逻辑受限于 Vue 指令(v-if
、v-for
等),复杂逻辑需结合 JavaScript 表达式。 -
render
函数
完全通过 JavaScript 编程控制渲染,灵活性极高,但需手动处理虚拟 DOM 构建(如嵌套、属性传递),代码复杂度更高,对开发者要求也更高。
- 性能差异
-
template
依赖编译过程(开发时由vue-loader
编译,或运行时由完整版 Vue 编译),若运行时编译(如直接在浏览器中使用template
)会有一定性能开销。 -
render
函数
无需编译,直接执行,运行时性能更优;但编写复杂逻辑时,若代码不够高效(如大量循环创建虚拟 DOM),反而可能降低性能。
- 核心区别总结
对比项 | template | render 函数 |
---|---|---|
语法风格 | 声明式(接近 HTML) | 编程式(纯 JavaScript) |
依赖 | 需要模板编译器(或运行时编译) | 无需编译,直接运行 |
灵活性 | 受限于 Vue 指令,适合常规场景 | 完全编程控制,适合复杂动态逻辑 |
性能(运行时) | 需编译(或提前编译) | 无需编译,直接执行 |
适用场景 | 常规业务、UI 结构清晰的页面 | 动态渲染、插件/库开发、运行时版 Vue |
一句话总结:
template
是更易用的“语法糖”,适合常规开发;render
函数是底层渲染入口,适合复杂动态场景或追求极致灵活与性能的需求。
关于不同版本的 Vue:
- vue.js 与 vue.runtime.xxx.js 的区别:
- vue.js 是完整版的 Vue,包含:核心功能 + 模板解析器。
- vue.runtime.xxx.js 是运行版的 Vue,只包含:核心功能;没有模板解析器。
- 因为 vue.runtime.xxx.js 没有模板解析器,所以不能使用 template 配置项,需要使用 render 函数接收到的 createElement 函数去指定具体内容。
这些内容主要围绕 Vue 不同构建版本(完整版和运行时版 )在功能组成(是否包含模板解析器 )及使用差异(能否用 template 配置 )展开说明,帮助理解 Vue 不同版本的适用场景和用法限制 。
vue.config.js配置文件
vue.config.js配置文件
> 使用vue inspect > output.js可以查看到Vue脚手架的默认配置。
> 使用vue.config.js可以对脚手架进行个性化定制,详情见:https://cli.vuejs.org/zh
ref属性
## ref属性
1. 被用来给元素或子组件注册引用信息(id的替代者)
2. 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
3. 使用方式:打标识:<h1 ref="xxx">.....</h1> 或 <School ref="xxx"></School>获取:this.$refs.xxx
这些内容是 Vue 中 ref
属性的核心说明,包括作用(给元素/子组件注册引用 )、不同应用场景的返回值(DOM 元素或组件实例 ),以及使用方式(打标识和获取的语法 )。
配置项 props
功能:让组件接收外部传过来的数据
1. 传递数据
<Demo name="xxx"/>
2. 接收数据
-
第一种方式(只接收):
props:['name']
-
第二种方式(限制类型):
props:{name:Number }
-
第三种方式(限制类型、限制必要性、指定默认值):
props:{name:{type:String, // 类型required:true, // 必要性default:'老王' // 默认值} }
备注
props
是只读的,Vue 底层会监测对 props
的修改,若修改会警告。
若需修改,需复制 props
内容到 data
中一份,然后再去修改 data
中的数据。
这些内容完整覆盖了 Vue 中 props
配置的核心用法:传递语法、接收方式(三种)、只读特性及修改方案。
mixin(混入)
- 功能:把多个组件共用的配置提取成一个混入对象
- 使用方式:
- 定义混合,示例:
{data(){...},methods:{...}// 其他组件配置项 }
- 使用混入:
- 全局混入:
Vue.mixin(xxx)
- 局部混入:
mixins:['xxx']
(组件内配置,xxx
为定义好的混入对象 )
- 全局混入:
- 定义混合,示例:
这些内容介绍了 Vue 中 mixin
(混入)的核心作用(提取组件共用配置 )和基本使用流程(定义、全局/局部使用 )。
插件
- 功能:用于增强 Vue
- 本质:包含
install
方法的一个对象,install
的第一个参数是Vue
,第二个以后的参数是插件使用者传递的数据。
定义插件
对象.install = function (Vue, options) {// 1. 添加全局过滤器Vue.filter(...) // 2. 添加全局指令Vue.directive(...) // 3. 配置全局混入(合)Vue.mixin(...) // 4. 添加实例方法Vue.prototype.$myMethod = function () { ... } Vue.prototype.$myProperty = xxxx
}
使用插件
Vue.use()
这些内容完整覆盖了 Vue 插件的核心概念:作用(增强 Vue)、本质(含 install
方法的对象 )、定义方式(通过 install
扩展功能 )和使用方式(Vue.use
)。
scoped样式
- 作用:让样式在局部生效,防止冲突
- 写法:
<style scoped>
这是 Vue 单文件组件中 scoped
样式的核心说明,用于限定样式作用域,避免不同组件间样式污染 。
npm view less-loader versions
Todo-listanli案例
1.组件化编码流程:
(1),拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
(2).实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
1). 一个组件在用:放在组件自身即可。
2). 一些组件在用:放在他们共同的父组件上(`<span style="color:red">状态提升</span>`)
(3).实现交互:从绑定事件开始。
2.props适用于:
(1).父组件:>子组件通信
(2).子组件:>父组件通信(要求父先给子一个函数)
3.使用v-model时要切记: v-model绑定的值不能是props传过来的值,因为props是不可以修改的!
浏览器本地存储
webStorage
- 存储内容大小一般支持5MB左右(不同浏览器可能还不一样)
- 浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。
- 相关API:
xxxxxxStorage.setItem('key', 'value');
该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。xxxxxxStorage.getItem('person');
该方法接受一个键名作为参数,返回键名对应的值。xxxxxxStorage.removeItem('key');
该方法接受一个键名作为参数,并把该键名从存储中删除。xxxxxxStorage.clear()
该方法会清空存储中的所有数据。
- 备注:
- SessionStorage存储的内容会随着浏览器窗口关闭而消失。
- LocalStorage存储的内容,需要手动清除才会消失。
xxxxxxStorage.getItem(xxx)
如果xxx对应的value获取不到,那么getItem的返回值是null。
4.JSON.parse(null)
的结果依然是null。
5.props传过来的若是对象类型的值,修改对象中的属性时vue不会报错,但不推荐这样做。
在浏览器中储存
JSON.parse() 是一个 JavaScript 方法,用于将符合 JSON 格式的字符串解析为 JavaScript 对象。
组件自定义事件
组件的自定义事件
一种组件间通信的方式,适用于:子组件 ===> 父组件
使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。
绑定自定义事件:
- 第一种方式,在父组件中:<Demo @atguigu=“test”/> 或
- 第二种方式,在父组件中: ```
… mounted(){ this.refs.xxx.refs.xxx.refs.xxx.on(‘atguigu’,this.test) } ```
3. 若想让自定义事件只能触发一次,可以使用 once 修饰符,或 $once 方法。
触发自定义事件:this.$emit(‘atguigu’,数据)
解绑自定义事件 this.$off(‘atguigu’)
组件上也可以绑定原生DOM事件,需要使用 native 修饰符。
注意:通过 this.refs.xxx.refs.xxx.refs.xxx.on(‘atguigu’,回调) 绑定自定义事件时,回调要么配置在
methods中,要么用箭头函数,否则this指向会出问题!
以下从绑定和解绑两个维度,详细梳理 Vue 组件自定义事件的核心用法:
一、绑定自定义事件(子传父通信)
1. 模板语法绑定(最常用)
<!-- 父组件模板 -->
<ChildComponent @custom-event="handleCustomEvent" @update-data="onUpdateData"
/>
- 作用:在父组件中,通过
@事件名
监听子组件触发的自定义事件 - 特点:
- 简洁直观,与原生事件绑定语法一致
- 支持绑定多个事件,自动关联父组件的 methods
2. 动态绑定(v-on
或 $on
)
// 父组件逻辑
export default {mounted() {// 方式1:通过 $on 动态绑定this.$refs.child.$on('custom-event', this.handleCustomEvent)// 方式2:结合 v-on 动态绑定(需配合模板)<ChildComponent v-on:[eventName]="handleCustomEvent" />},methods: {handleCustomEvent(data) {console.log('子组件传值:', data)}}
}
- 适用场景:
- 事件名需要动态生成(如根据变量决定监听的事件)
- 需在组件挂载后延迟绑定事件
3. 绑定一次性事件($once
)
// 父组件逻辑
mounted() {this.$refs.child.$once('custom-event', (data) => {console.log('仅触发一次:', data)})
}
- 特点:事件触发一次后自动解绑,适合初始化、一次性通知场景
二、解绑自定义事件(清理事件监听)
1. 单个事件解绑($off
单事件)
// 子组件/父组件逻辑
export default {beforeDestroy() {// 方式1:解绑指定事件this.$off('custom-event')// 方式2:解绑指定事件并清除特定回调this.$off('custom-event', this.handleCustomEvent)}
}
- 作用:精确清理某个事件的监听,避免内存泄漏
2. 批量解绑($off
多事件)
// 解绑多个事件(传入事件名数组)
this.$off(['custom-event', 'update-data'])// 解绑所有事件(不传参数)
this.$off()
- 适用场景:
- 组件销毁前清理所有自定义事件
- 批量移除不再需要的事件监听
3. 自动解绑(组件销毁时)
// 子组件触发事件后,主动清理自身事件(可选)
export default {methods: {emitEvent() {this.$emit('custom-event', '数据')this.$off('custom-event') // 触发后立即解绑}}
}
- 特点:
- 结合业务逻辑主动清理,避免父组件重复监听
- Vue 组件销毁时会自动清理所有未手动解绑的事件
完整流程示例(父 ← 子通信)
子组件(触发事件)
<template><button @click="emitCustomEvent">触发事件</button>
</template><script>
export default {methods: {emitCustomEvent() {this.$emit('custom-event', { msg: '子组件数据' })}}
}
</script>
父组件(绑定 + 解绑)
<template><ChildComponent ref="childRef"@custom-event="handleEvent" />
</template><script>
import ChildComponent from './ChildComponent.vue'export default {components: { ChildComponent },mounted() {// 动态绑定(可选)this.$refs.childRef.$on('custom-event', this.handleEvent)},beforeDestroy() {// 手动解绑(可选,Vue 销毁时会自动清理)this.$refs.childRef.$off('custom-event')},methods: {handleEvent(data) {console.log('父组件接收:', data)}}
}
</script>
关键区别总结
操作 | 语法示例 | 特点 |
---|---|---|
绑定事件 | @event="handler" | 模板语法,自动关联组件实例 |
动态绑定 | this.$on('event', handler) | 灵活,支持延迟/条件绑定 |
一次性绑定 | this.$once('event', ...) | 触发一次后自动解绑 |
单个解绑 | this.$off('event') | 精确清理,避免内存泄漏 |
批量解绑 | this.$off(['event1','event2']) | 高效清理多个事件 |
通过绑定实现子组件向父组件的通信,通过解绑确保组件销毁时清理资源,两者结合是 Vue 组件自定义事件的核心实践。
全局事件总线(GlobalEventBus)
- 概念:一种组件间通信方式,适用于任意组件间通信。
- 安装:在
new Vue
实例的beforeCreate
钩子中,通过Vue.prototype.$bus = this
,将当前Vue实例作为全局事件总线($bus
),代码示例:
new Vue({beforeCreate() {Vue.prototype.$bus = this // 安装全局事件总线,$bus就是当前应用的vm},// ...其他配置
})
示例:
// 在 Vue 项目中,通常在创建 Vue 实例时安装全局事件总线,将当前的 Vue 实例挂载到Vue.prototype上,使其在所有组件中都能通过this访问。
import Vue from 'vue'
import App from './App.vue'Vue.config.productionTip = false// 在beforeCreate钩子函数中安装全局事件总线
new Vue({beforeCreate() {Vue.prototype.$bus = this // $bus 就是全局事件总线,它指向当前应用的 Vue 实例},render: h => h(App)
}).$mount('#app')
- 使用:
- 接收数据:组件(如A组件 )要接收数据,在自身
methods
定义回调,mounted
钩子通过this.$bus.$on('事件名', 回调)
绑定事件,示例:
- 接收数据:组件(如A组件 )要接收数据,在自身
methods: {demo(data) { /* 处理数据逻辑 */ }
},
mounted() {this.$bus.$on('xxxx', this.demo)
}
示例:
//在需要接收数据的组件中,通过$on方法来绑定自定义事件,并定义事件的回调函数。例如,在组件的mounted生命周期钩子中进行绑定:
export default {data() {return {}},mounted() {this.$bus.$on('eventName', (data) => {// data 是发布事件时传递的数据console.log('接收到的数据:', data)// 在这里可以编写处理数据的逻辑})},beforeDestroy() {// 在组件销毁前,解绑事件,避免内存泄漏this.$bus.$off('eventName') }
}
- 提供数据:通过
this.$bus.$emit('事件名', 数据)
触发事件传数据。
示例:
//在需要发送数据的组件中,使用$emit方法触发自定义事件,并传递数据。
export default {methods: {sendData() {const dataToSend = { message: '这是要传递的数据' }// 触发名为 'eventName' 的事件,并传递 dataToSend 数据this.$bus.$emit('eventName', dataToSend) }}
}
- 优化:建议在组件
beforeDestroy
钩子用$off
解绑事件,避免内存泄漏,如this.$bus.$off('事件名')
。 - 适用场景:适用于任意组件间的通信,尤其是在组件层级关系复杂,使用 props 和 $emit 等常规父子组件通信方式难以实现时,比如兄弟组件、祖孙组件,甚至是跨层级、没有直接关系的组件之间的数据传递和事件通知。
消息订阅与发布(pubsub)
- 一种组件间通信的方式,适用于任意组件间通信。
- 使用步骤:
- 安装pubsub:
npm i pubsub-js
- 引入:
import pubsub from 'pubsub-js'
- 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。
methods(){ demo(data){......} } mounted() { this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息 }
- 提供数据:
pubsub.publish('xxx',数据)
- 最好在beforeDestroy钩子中,用
PubSub.unsubscribe(pid)
去取消订阅。
这是 Vue 中 消息订阅与发布(pubsub) 机制的知识点,用于实现任意组件间通信,核心流程拆解如下:
1. 是什么?
一种组件通信方案,突破父子、祖孙等固定关系限制,任意组件 都能通过 “订阅 - 发布” 模式收发数据,解耦组件依赖。
2. 怎么用?(5 步流程)
① 安装依赖
npm i pubsub-js
通过包管理工具安装 pubsub-js
库,让项目支持订阅发布功能。
② 引入库
import pubsub from 'pubsub-js'
在需要的组件中引入 pubsub-js
,拿到操作订阅 / 发布的 API。
③ 订阅消息(收数据)
以 “A 组件想收数据” 为例:
export default { methods: { demo(data) { // data 是发布方传来的数据,这里写处理逻辑 } }, mounted() { // 订阅名为 'xxx' 的消息,触发时调用 this.demo this.pid = pubsub.subscribe('xxx', this.demo) }
}
subscribe
是订阅方法,第一个参数'xxx'
是 消息名(类似事件名),第二个是 回调函数(收到消息时执行)。this.pid
记录订阅 “ID”,用于后续取消订阅。
④ 发布消息(发数据)
任意组件里,通过以下代码发数据:
pubsub.publish('xxx', 要传递的数据)
'xxx'
是之前订阅的消息名,保证和订阅方一致才能触发。要传递的数据
可以是任意类型(对象、字符串等 ),会传给订阅方的回调。
⑤ 取消订阅(优化)
为避免组件销毁后仍监听消息(内存泄漏),在 beforeDestroy
钩子取消订阅:
beforeDestroy() { pubsub.unsubscribe(this.pid)
}
用 unsubscribe
+ 之前记录的 this.pid
,关闭当前组件的订阅。
3. 核心逻辑
- 解耦通信:不限制组件层级 / 关系,只要知道 “消息名”,就能跨组件收发数据。
- 订阅者模式:发布方
publish
发消息 → 订阅方subscribe
收消息,中间由pubsub-js
做 “消息中转站”。
简单说,就是用 pubsub-js
库提供的 subscribe
(订阅)、publish
(发布)、unsubscribe
(取消订阅)方法,实现 任意组件间的数据传递,适合复杂场景下的跨组件通信。
完整示例
- App.vue
<template><div id="app"><button @click="sendMessage">发布消息</button><ChildComponent /></div>
</template><script>
import pubsub from 'pubsub-js';
import ChildComponent from './ChildComponent.vue';export default {components: {ChildComponent},methods: {sendMessage() {const data = { content: '来自父组件的消息' };pubsub.publish('testMessage', data);}}
};
</script>
- ChildComponent.vue
<template><div><!-- 这里可根据接收到的数据进行展示,示例中仅在控制台打印 --></div>
</template><script>
import pubsub from 'pubsub-js';export default {mounted() {this.subId = pubsub.subscribe('testMessage', (msg, data) => {console.log('子组件接收到消息:', data);});},beforeDestroy() {pubsub.unsubscribe(this.subId);}
};
</script>
述代码中,在App.vue组件中点击按钮发布消息,ChildComponent.vue组件订阅消息并在控制台打印接收到的数据,同时在组件销毁前取消订阅,以保证资源的合理释放。
$nextTick
这是 Vue 中 nextTick
的知识点,核心信息:
1. 语法
this.$nextTick(回调函数)
2. 作用
让回调函数在下一次 DOM 更新完成后执行,保证能拿到最新 DOM 状态。
<template><div ref="box">{{ message }}</div><button @click="changeMessage">改变数据</button>
</template><script>
export default {data() {return {message: '初始内容'};},methods: {changeMessage() {this.message = '更新后的内容';this.$nextTick(() => {// 这里获取的DOM内容是'更新后的内容',因为DOM已经更新console.log(this.$refs.box.textContent); });}}
};
</script>
3. 适用场景
当你修改数据后,需要基于更新后的新 DOM 做操作(比如获取 DOM 高度、操作新渲染的元素),就把逻辑写在 nextTick
的回调里。
简单说,nextTick
是 Vue 解决 “数据更新后,DOM 没及时更新” 问题的工具,确保 DOM 操作拿到最新结果 。
vue封装的过渡与动画
作用: 在插入,更新,或移除DOM元素时,在合适的时候给元素添加样式类名
图示:
写法
- 准备好样式:
- 元素进入的样式:
- v-enter:进入的起点
- v-enter-active:进入过程中
- v-enter-to:进入的终点
- 元素离开的样式:
- v-leave:离开的起点
- v-leave-active:离开过程中
- v-leave-to:离开的终点
- 元素进入的样式:
- 使用
<transition>
包裹要过渡的元素,并配置 name 属性:
<transition name="hello"><h1 v-show="isShow">你好啊!</h1>
</transition>
- 备注:若有多个元素需要过渡,则需要使用:
<transition-group>
,且每个元素都要指定 key 值。
1.效果
2.动画
1.示例
<template><div><button @click="isShow = !isShow">显示/隐藏</button><transition appear name="hello"><h1 v-show="isShow">显示内容</h1></transition></div>
</template>
<script lang="ts">
export default {name: 'TestSty',data() {return {isShow: true}}
}
</script>
<style scoped>
h1 {background-color: rgb(235, 168, 121);
}.hello-enter-active {animation: fadeIn 1s ease-in-out;
}.hello-leave-active {animation: fadeIn 1s reverse;
}@keyframes fadeIn {from {transform: translateX(100%);}to {transform: translate(0px);}
}
</style>
2.集成第三方动画
npm上的animate.css
库
安装:
npm install animate.css
引入
import `animate.css`
3.过渡
1.示例
<template><div><!-- 用 Transition 包裹要过渡的元素 --><transition name="vt" appear><h1 v-show="isShow">显示内容</h1></transition><button @click="isShow = !isShow">显示/隐藏</button></div>
</template><script>
export default {name: 'TestTwo',data() {return { isShow: true }}
}
</script><style scoped>
/* 过渡动画逻辑写这里,作用于当前组件内的 h1 */
/*进入的起点*/
.vt-enter {transform: translateX(-100%);
}/*进入的终点*/
.vt-enter-to {transform: translateX(0);
}
/*或者*/
.vt-enter-active,.vt-leave-active{
transition: 0.5s linear;
}/*离开的起点*/
.vt-leave {transform: translateX(0);
}/*离开的终点*/
.vt-leave-to {transform: translateX(-100%);
}h1 {/* transition: all 0.5s linear;*/background-color: rgb(11, 185, 127);
}
</style>
2.多个元素过渡
<template><div><!-- 用 Transition 包裹要过渡的元素 --><transition-group appear name="vt"><h1 v-show="isShow" key="1">你好,世界</h1><h1 v-show="!isShow" key="2">不好意思,世界</h1></transition-group></div>
</template>
四:配置代理服务器
网络请求方案
- 常用
axios
(基于Promise,支持浏览器和Node,可配置拦截器、请求/响应.转换 ),在Vue中通过import axios from 'axios'
使用,结合async/await
或Promise处理异步
在前端开发中,配置代理(Proxy)是解决跨域问题的常见方式,以下是不同场景下的代理配置方法:
一、Vue CLI 项目(Webpack Dev Server)
如果使用 Vue CLI 创建的项目,可直接在 vue.config.js
中配置代理。
开启代理服务器(方式一)
//开启代理服务器:基础代理配置(简单写法)devServer: {proxy: 'http://localhost:5000'}//此时代理服务器已开启
1.作用:把前端(默认 8080 端口)的请求,转发到 http://localhost:5000(后端服务)。
2. 关键说明
- 优点:配置简单,前端请求直接发 8080 端口,代理自动转发。
- 缺点:只能配 1 个代理,无法灵活控制哪些请求走代理。
- 工作逻辑:前端请求资源时,先找前端项目里的文件;如果找不到,才转发给代理的后端(5000 端口)。
3. 适用场景
适合简单项目(只有 1 个后端域名,不需要复杂代理规则),比如本地开发时,把前端请求统一转发到后端 API。
本质是 Vue CLI 借助 webpack-dev-server 的代理功能,让前端请求 “绕开浏览器跨域限制”,开发时常用
开启代理服务器(方式二)
// 高级代理配置(路径匹配写法)
module.exports = {devServer: {proxy: {'/api': {target: '<url>',ws: true,changeOrigin: true},'/foo': {target: '<other_url>'}}}
}
-
关键配置:
'/api'
:路径匹配规则,只有请求路径以 /api 开头的会走这个代理。target
: ‘’:代理的目标后端地址(如 http://localhost:3000)。ws: true
:是否代理 WebSocket 请求(如果有实时通信需求,需开启)。changeOrigin: true
:是否修改请求头的 Origin,让后端认为请求来自目标地址(解决跨域的核心)
-·pathRewrite
:重写请求路径(比如 /api1/xxx 会变成 /xxx,避免后端接口路径带 /api1)。
示例
module.exports = {devServer: {proxy: {// 规则1:匹配 /api1 开头的请求'/api1': {target: 'http://localhost:5000', // 转发到 5000 端口changeOrigin: true, // 让后端认为请求来自 5000 端口(伪装 Host)pathRewrite: {'^/api1': ''} // 去掉请求路径里的 /api1 前缀},// 规则2:匹配 /api2 开头的请求'/api2': {target: 'http://localhost:5001', // 转发到 5001 端口changeOrigin: true,pathRewrite: {'^/api2': ''}}}}
}
1. 基础配置(单域名代理)
// vue.config.js
module.exports = {devServer: {proxy: {// 将所有以 /api 开头的请求代理到目标服务器'/api': {target: 'http://localhost:3000', // 后端API地址changeOrigin: true, // 允许跨域pathRewrite: {'^/api': '' // 重写路径,去掉 /api 前缀}}}}
}
使用示例:
// 前端请求:http://localhost:8080/api/users
// 实际会被代理到:http://localhost:3000/users
this.$axios.get('/api/users').then(res => {console.log(res.data);
});
2. 多域名代理
如果需要同时代理多个后端服务,可配置多个路径规则:
// vue.config.js
module.exports = {devServer: {proxy: {// 代理A服务'/api': {target: 'http://service-a.com',changeOrigin: true},// 代理B服务'/auth': {target: 'http://service-b.com',changeOrigin: true,pathRewrite: { '^/auth': '' }}}}
}
二、Vite 项目(开发服务器)(了解)
如果使用 Vite 构建工具,在 vite.config.js
中配置代理。
1. 基础配置
// vite.config.js
import { defineConfig } from 'vite';export default defineConfig({server: {proxy: {'/api': {target: 'http://localhost:3000',changeOrigin: true,rewrite: (path) => path.replace(/^\/api/, '')}}}
});
2. 高级配置(使用正则匹配)
server: {proxy: {// 使用正则匹配更灵活的路径'^/api/.*': {target: 'http://localhost:3000',changeOrigin: true,rewrite: (path) => path.replace(/^\/api/, '')},// 代理WebSocket'/ws': {target: 'ws://localhost:3000',ws: true}}
}
三、手动配置 Webpack Dev Server(了解)
如果项目未使用 Vue CLI 或 Vite,可直接在 Webpack 配置中添加代理:
// webpack.config.js
const { DefinePlugin } = require('webpack');module.exports = {devServer: {proxy: {'/api': {target: 'http://localhost:3000',changeOrigin: true,pathRewrite: { '^/api': '' }}}}
};
四、生产环境代理配置(了解)
开发环境的代理仅在本地有效,生产环境需要在 后端服务器 或 网关层 处理跨域:
- Nginx 反向代理(最常见):
server {listen 80;server_name example.com;# 前端静态资源location / {root /path/to/your/dist;index index.html;}# API请求代理到后端location /api/ {proxy_pass http://backend-server:3000/;} }
- Node.js 中间件(如 Express):
const express = require('express'); const { createProxyMiddleware } = require('http-proxy-middleware');const app = express();// 代理API请求 app.use('/api', createProxyMiddleware({target: 'http://backend-server:3000',changeOrigin: true }));// 静态资源 app.use(express.static('dist'));app.listen(80);
五、关键配置参数说明
target
:目标服务器地址,如http://localhost:3000
。changeOrigin
:是否修改请求头中的Origin
,通常设为true
以欺骗服务器。pathRewrite
/rewrite
:重写请求路径,常用于移除前缀(如/api
)。secure
:是否验证 HTTPS 证书,开发环境可设为false
。ws
:是否代理 WebSocket 请求。
六、注意事项
- 代理仅在开发环境有效:生产环境需通过后端服务器或 Nginx 处理跨域。
- 路径匹配规则:确保代理路径(如
/api
)与实际请求路径一致。 - 调试技巧:可在代理配置中添加
logLevel: 'debug'
查看详细代理日志。
根据项目架构选择合适的代理方式,开发环境优先使用工具内置的代理配置,生产环境则需在服务器层处理。
github案例
1.静态组件
2.列表展示
3.完善案例
vue-resource(不流行了(* ^ ▽ ^ *))
vue-resource
是一个曾经在 Vue.js 项目中广泛使用的 HTTP 请求库,用于在 Vue 组件中方便地发起 AJAX 请求。不过随着 Vue 生态的发展,现在它已逐渐被 axios
等库替代,以下是关于它的详细介绍:
1. 安装
可以通过 npm
或 yarn
进行安装:
- 使用
npm
:
npm install vue-resource --save
- 使用
yarn
:
yarn add vue-resource
2. 引入与使用
在项目中使用 vue-resource
,一般需要在入口文件(如 main.js
)中引入并安装它,然后在组件中使用:
// main.js
import Vue from 'vue';
import VueResource from 'vue-resource';Vue.use(VueResource);new Vue({// ...其他配置
}).$mount('#app');
在 Vue 组件中,vue-resource
为 Vue 的原型添加了 $http
属性,你可以使用它发起不同类型的请求,例如:
GET 请求
export default {data() {return {};},methods: {fetchData() {this.$http.get('https://api.example.com/data').then(response => {console.log('请求成功', response.body);}).catch(error => {console.error('请求失败', error);});}},mounted() {this.fetchData();}
};
POST 请求
export default {data() {return {};},methods: {submitData() {const dataToSend = { name: 'John', age: 30 };this.$http.post('https://api.example.com/submit', dataToSend).then(response => {console.log('请求成功', response.body);}).catch(error => {console.error('请求失败', error);});}}
};
3. 特性
- 简洁的 API:
vue-resource
提供了简洁直观的 API,使得在 Vue 组件中发起 GET、POST、PUT、DELETE 等常见的 HTTP 请求变得简单易懂,降低了开发门槛。 - 与 Vue 集成紧密:作为 Vue 的插件,它能很好地与 Vue 组件集成,开发者可以方便地在组件的生命周期钩子函数或方法中使用它来进行数据获取和交互。
- 支持 Promise:
vue-resource
的请求方法返回 Promise 对象,这使得开发者可以使用 Promise 的链式调用和async/await
语法来处理异步操作,编写更优雅的异步代码。
4. 逐渐被替代的原因
- 维护频率降低:
vue-resource
的维护者减少了对该库的更新和维护,这使得它在面对新的浏览器特性、安全问题等时,可能无法及时得到支持和修复。 - 功能相对单一:相比后来兴起的
axios
等库,vue-resource
在功能丰富度上存在一定差距。例如,axios
提供了更强大的拦截器功能、更灵活的请求配置选项、支持请求取消等,能更好地满足复杂项目的需求。 - 生态发展趋势:随着 Vue 生态的不断发展,社区更倾向于使用那些更活跃、功能更强大且被广泛认可的库,
axios
逐渐成为了大多数开发者在 Vue 项目中进行 HTTP 请求的首选,导致vue-resource
的使用越来越少 。
虽然 vue-resource
现在已不那么流行,但了解它有助于理解 Vue 项目中 HTTP 请求库的发展历程。在新的 Vue 项目中,通常更推荐使用 axios
或浏览器原生的 fetch
API 来进行网络请求。
插槽
Vue 中的插槽(Slot)是实现组件内容分发的核心机制,让父组件能灵活向子组件传递 HTML 结构。下面用最简示例 + 核心对比,清晰区分 默认插槽、具名插槽、作用域插槽:
一、默认插槽(Default Slot)
核心:子组件留一个“通用坑位”,父组件填内容。
子组件(Child.vue)
<template><div class="child"><!-- 留一个默认插槽,父组件没传具名内容时,这里显示 --><slot><!-- 插槽默认内容(父组件没传内容时显示) --><p>父组件没传内容时,显示我</p></slot></div>
</template>
父组件(Parent.vue)
<template><div class="parent"><!-- 向子组件的默认插槽传内容 --><Child><p>我是父组件传给默认插槽的内容</p></Child></div>
</template>
效果:子组件的 <slot>
会被父组件传入的 <p>
替换,默认内容不显示(传了内容则覆盖)。
二、具名插槽(Named Slot)
核心:子组件留多个“命名坑位”,父组件用 slot="name"
精准填充。
子组件(Child.vue)
<template><div class="child"><!-- 具名插槽:header --><slot name="header">默认头部</slot><!-- 具名插槽:footer --><slot name="footer">默认底部</slot><!-- 默认插槽(没写 name 就是默认) --><slot>默认内容</slot></div>
</template>
父组件(Parent.vue)
<template><div class="parent"><Child><!-- 填充 header 插槽 --><template v-slot:header><h2>我是头部</h2></template><!-- 填充 footer 插槽(简写 #footer) --><template #footer><p>我是底部</p></template><!-- 填充默认插槽(没写 v-slot 就是默认) --><p>我是默认内容</p></Child></div>
</template>
效果:父组件用 v-slot:name
(或 #name
)对应子组件的 name
,精准填充多个插槽。
三、作用域插槽(Scoped Slot)
核心:子组件给插槽传数据,父组件接收并自定义渲染。
子组件(Child.vue)
<template><div class="child"><!-- 作用域插槽:把数据 student 传给父组件 --><slot name="student" :student="student"></slot></div>
</template><script>
export default {data() {return {student: { name: 'Tom', age: 18 }}}
}
</script>
父组件(Parent.vue)
<template><div class="parent"><Child><!-- 用 v-slot:xxx="obj" 接收子组件传的数据 --><template #student="slotProps"><!-- slotProps.student 就是子组件传的 { name: 'Tom', age: 18 } --><p>姓名:{{ slotProps.student.name }}</p><p>年龄:{{ slotProps.student.age }}</p></template></Child></div>
</template>
效果:子组件通过 :student
传数据,父组件用 v-slot="slotProps"
接收,实现“数据在子组件,渲染在父组件”的灵活控制。
四、核心对比表格
类型 | 特点 | 子组件传数据 | 父组件使用方式 |
---|---|---|---|
默认插槽 | 单个通用坑位,无命名 | 不能传 | 直接写内容 |
具名插槽 | 多个命名坑位,精准填充 | 不能传 | v-slot:name 或 #name |
作用域插槽 | 子组件传数据,父组件自定义渲染 | 能传 | v-slot="obj" 接收数据 |
五、一句话总结
- 默认插槽:子组件留 1 个“坑”,父组件填内容。
- 具名插槽:子组件留 N 个“命名坑”,父组件精准填。
- 作用域插槽:子组件边“挖坑”边“传数据”,父组件拿数据自定义填。
插槽让组件更灵活,是 Vue 组件化开发必备技能,建议结合示例多实操~
五:vuex
理解 vuex
1. vuex 是什么
- 概念:专门在 Vue 中实现集中式状态(数据)管理的一个 Vue 插件,对 vue 应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。
- Github 地址: https://github.com/vuejs/vuex
2. 什么时候使用 Vuex
- 多个组件依赖于同一状态
- 来自不同组件的行为需要变更同一状态
- vuex 是什么:
- 从功能上看,它是 Vue 生态里用于集中管理状态(数据)的插件 。在 Vue 项目里,不同组件可能会用到相同的数据,或者不同组件的操作需要修改同一份数据,vuex就把这些共享数据收纳起来,统一进行读写管理,让数据的变化可追溯、更规范,同时也能作为组件间>通信的一种高效方式,不管组件层级多深、关系多复杂,都能借助 vuex 传递和共享数据。
- 从生态角度,它是 Vue 官方生态体系里的一部分,有专门的文档和社区支持,项目地址在 https://github.com/vuejs/vuex ,开发者能在上面了解更新、提交 issues 等。
- 什么时候使用 Vuex:
- 当多个组件都要用到同一份状态数据时,比如电商项目里,购物车的商品列表数据, header 组件要显示购物车商品数量,购物车页面组件要展示商品详情、进行增删操作,用 vuex 集中管理这份购物车数据,能让数据更新后各组件及时响应。
- 当不同组件的操作都要修改同一状态时,例如社交应用中,点赞功能,列表组件里的点赞按钮、详情页组件里的点赞按钮,点击后都要修改点赞数这个状态,借助
vuex 统一处理点赞数的变更逻辑,能避免各组件各自维护数据导致的混乱,让状态变化清晰、可维护 。
(一).状态管理模式
- 理解Vuex用于集中管理组件共享状态,解决跨组件通信复杂问题,核心概念:
state
(存储数据,组件通过mapState
或this.$store.state
访问 )mutations
(唯一修改state的地方,同步操作,组件commit
触发 )actions
(处理异步操作,提交mutations
,组件dispatch
触发 )getters
(类似计算属性,对state做派生,mapGetters
使用 )modules
(拆分模块,解决状态过多时的命名冲突、代码臃肿 )
(二)、vuex(状态管理)
1.核心概念
- State:存储应用的状态,是响应式的。
// store/index.js
const state = {count: 0
};
- Getter:从State中派生出一些数据,类似于计算属性。
const getters = {doubleCount: state => state.count * 2
};
- Mutation:唯一可以修改State的方法,且必须是同步函数。
const mutations = {increment(state) {state.count++;}
};
- Action:可以包含异步操作,通过提交Mutation来修改State。
const actions = {incrementAsync({ commit }) {setTimeout(() => {commit('increment');}, 1000);}
};
- Module:将Vuex Store分割成模块,每个模块都有自己的State、Mutation、Action、Getter等。
2.在组件中使用
import { mapState, mapGetters, mapActions } from 'vuex';export default {computed: {// 映射state中的count到组件的count属性...mapState(['count']), // 映射getters中的doubleCount到组件的doubleCount属性...mapGetters(['doubleCount']) },methods: {// 映射actions中的incrementAsync到组件的incrementAsync方法...mapActions(['incrementAsync']) }
};
(三)搭建vuex环境
在 Vue 2 项目中搭建 Vuex 环境的详细步骤如下(基于 Vue CLI 创建的项目):
1. 安装 Vuex(适用于 Vue 2)
npm install vuex --save
//npm i vuex@3 用vuex的3版本
Vue3:npm install vuex@next --save
2. 创建 Store 文件夹与配置文件
在 src
目录下创建 store
文件夹,并在其中创建 index.js
,用于配置 Vuex 的 store:
// src/store/index.js
import Vue from 'vue';
import Vuex from 'vuex';// 安装 Vuex 插件***************************
Vue.use(Vuex);// 定义状态
const state = {count: 0,user: null,todos: []
};// 定义 Getters(获取状态)
const getters = {evenOrOdd: state => state.count % 2 === 0 ? 'even' : 'odd',completedTodos: state => state.todos.filter(todo => todo.completed)
};// 定义 Mutations(修改状态,必须同步)
const mutations = {INCREMENT(state) {state.count++;},SET_USER(state, user) {state.user = user;},ADD_TODO(state, todo) {state.todos.push(todo);}
};// 定义 Actions(处理异步,最终调用 Mutations)
const actions = {incrementAsync({ commit }) {setTimeout(() => {commit('INCREMENT');}, 1000);},fetchUser({ commit }) {return axios.get('/api/user').then(res => commit('SET_USER', res.data)).catch(err => console.error(err));}
};// 创建并导出 Store
export default new Vuex.Store({state,getters,mutations,actions,strict: process.env.NODE_ENV !== 'production' // 开发环境启用严格模式
});
3. 在入口文件(main.js
)中注册 Store
// src/main.js
import Vue from 'vue';
import App from './App.vue';
import store from './store'; // 导入 Storenew Vue({el: '#app',store, // 注册 Storerender: h => h(App)
});
4. 在组件中使用 Vuex
以下是一个使用 Vuex 的组件示例:
<!-- src/components/Counter.vue -->
<template><div><p>Count: {{ count }} ({{ evenOrOdd }})</p><button @click="increment">同步增加</button><button @click="incrementAsync">异步增加</button><button @click="setUser">设置用户</button><div v-if="user">当前用户: {{ user.name }}</div></div>
</template><script>
import { mapState, mapGetters, mapActions } from 'vuex';export default {computed: {// 映射 state 中的 count...mapState(['count', 'user']),// 映射 getters 中的 evenOrOdd...mapGetters(['evenOrOdd'])},methods: {// 映射 mutations 中的 INCREMENTincrement() {this.$store.commit('INCREMENT');},// 映射 actions 中的 incrementAsync...mapActions(['incrementAsync']),// 自定义 actionsetUser() {this.$store.dispatch('fetchUser');}}
};
</script>
5. 项目结构优化(可选)
对于大型项目,建议按模块拆分 store:
src/store/index.js # 入口文件modules/ # 模块文件夹auth.js # 认证模块todos.js # 待办事项模块getters.js # 全局 gettersmutations.js # 全局 mutationsactions.js # 全局 actions
关键概念总结
- State:存储应用状态(如
count
,user
)。 - Getters:获取派生状态(如
evenOrOdd
)。 - Mutations:修改状态(必须同步,如
INCREMENT
)。 - Actions:处理异步逻辑(如
incrementAsync
)。 - Modules:模块化管理复杂应用的状态。
通过这种方式,你可以在 Vue 2 项目中实现集中式状态管理,解决组件间通信问题。
基本使用
以下是对图片内容的提取与整理,清晰呈现 Vuex 基本使用的核心代码与逻辑:
1. 依赖引入与插件使用
// 引入Vue核心库
import Vue from 'vue'
// 引入Vuex
import Vuex from 'vuex'
// 引用Vuex(Vue2 中需通过 Vue.use 注册插件)
Vue.use(Vuex)
2. actions 配置(处理异步/复杂逻辑,提交 mutation)
const actions = {// 响应组件中“加”的动作,context 包含 store 核心方法(commit/dispatch 等),value 为组件传递的参数jia(context, value) {// 触发 mutations 中的 JIA 方法,传递 valuecontext.commit('JIA', value)}
}
3. mutations 配置(直接修改 state,需为同步操作)
const mutations = {// 执行“加”逻辑,state 为当前 store 状态,value 为传递的参数JIA(state, value) {// 修改 state 中的 sum,执行累加state.sum += value}
}
4. state 初始化(存储应用状态数据)
// 初始化数据,定义 sum 初始值为 0
const state = {sum: 0
}
5. store 创建与暴露
// 创建并暴露 store 实例,整合 actions、mutations、state
export default new Vuex.Store({actions,mutations,state
})
6. Vuex 在组件中读写状态的核心用法,清晰说明如何在组件里操作 Vuex 的数据,提取关键信息:
6.1. 读取 Vuex 数据
// 直接通过 $store 访问 state 中的 sum
this.$store.state.sum
6.2. 修改 Vuex 数据(两种方式)
方式 1:通过 actions(适合异步/复杂逻辑)
// dispatch 触发 actions 中的方法,传递数据
this.$store.dispatch('action中的方法名', 数据)
方式 2:通过 mutations(直接修改,适合同步逻辑)
// commit 触发 mutations 中的方法,传递数据
this.$store.commit('mutations中的方法名', 数据)
7. 备注(简化逻辑)
- 如果没有网络请求/复杂业务,可以跳过 actions,直接用
commit
触发 mutations。
总结
组件操作 Vuex 的核心:
- 读数据 →
this.$store.state.xxx
- 改数据 →
dispatch
(走 actions)或commit
(直接走 mutations),简单场景可跳过 actions 直接commit
~
核心流程说明
- 组件触发:组件通过
this.$store.dispatch('jia', value)
触发 actions 中的jia
方法(或简化用mapActions
辅助函数)。 - actions 转发:
jia
方法通过context.commit
调用 mutations 中的JIA
。 - mutations 修改状态:
JIA
直接操作state.sum
,完成累加逻辑。 - 状态响应更新:state 变化后,依赖该状态的组件会自动重新渲染。
这是 Vuex 最基础的“动作触发 → 异步/逻辑处理 → 状态修改”流程,体现了 Vuex 集中式状态管理的核心思想(actions 处理流程,mutations 负责同步修改 state )。
getters
的使用:
1. getters 的核心概念
当 state 中的数据需要“加工后再使用”(如过滤、计算、格式化)时,用 getters
统一处理,避免重复写逻辑。
2. 配置步骤(在 store.js
中)
(1)定义 getters
const getters = {// state 是 store 的状态对象,可直接访问 state 中的数据bigSum(state) {// 对 state.sum 加工:乘以 10return state.sum * 10 }
}
(2)整合到 store
export default new Vuex.Store({// ...其他配置(state、mutations 等)getters // 挂载 getters
})
3. 组件中使用 getters
通过 $store.getters.xxx
读取加工后的数据:
// 组件中
const bigSum = this.$store.getters.bigSum
核心流程
- 需求:state 数据(如
sum
)需要加工(如sum * 10
)。 - 配置:在
store.js
的getters
中写加工逻辑(bigSum
)。 - 使用:组件通过
$store.getters.bigSum
拿到加工后的数据。
类比理解
state
是“原材料仓库”,getters
是“加工厂”,组件用的是“加工后的成品”。- 类似组件的
computed
,但作用域是全局 store,可跨组件复用加工逻辑。
这是 Vuex 中 “数据加工与复用” 的基础方案,适合处理需要全局复用的计算逻辑~
map的四个方法: mapState
和 mapGetters
,mapActions
和 mapMutations
mapState
和 mapGetters
mapState
和 mapGetters
是 Vuex 提供的两个辅助函数,用于简化在组件中引用 store 数据的写法,让代码更简洁、易读。以下是它们的核心用法与对比:
一、mapState
:映射 state 到组件计算属性
1. 基本用法(数组形式)
// 在组件中
import { mapState } from 'vuex';export default {computed: {// 将 store.state 中的 `count` 和 `user` 映射为组件的计算属性...mapState(['count', 'user']),// 现在可以直接在模板中使用 `this.count` 和 `this.user`}
};
2. 对象形式(自定义名称)
computed: {...mapState({myCount: state => state.count, // 自定义名称 `myCount`isAdmin: state => state.user?.role === 'admin' // 复杂映射})
}
3. 混合使用(与本地计算属性共存)
computed: {...mapState(['count']),doubleCount() { // 本地计算属性return this.count * 2;}
}
二、mapGetters
:映射 getters 到组件计算属性
1. 基本用法(数组形式)
// 在组件中
import { mapGetters } from 'vuex';export default {computed: {// 将 store.getters 中的 `completedTodos` 和 `userInfo` 映射为组件的计算属性...mapGetters(['completedTodos', 'userInfo']),// 现在可以直接在模板中使用 `this.completedTodos` 和 `this.userInfo`}
};
2. 对象形式(自定义名称)
computed: {...mapGetters({doneTodos: 'completedTodos', // 自定义名称 `doneTodos`userData: 'userInfo' // 重命名})
}
三、对比与适用场景
特性 | mapState | mapGetters |
---|---|---|
映射目标 | store.state 中的数据 | store.getters 中的计算属性 |
是否缓存 | 否(随 state 实时更新) | 是(类似 computed,依赖变化才更新) |
典型场景 | 直接获取原始数据 | 获取派生数据(过滤、计算后的数据) |
语法示例 | ...mapState(['count']) | ...mapGetters(['doubleCount']) |
四、高级技巧
1. 与本地计算属性结合
<template><div><p>Count: {{ count }}</p><p>Double: {{ doubleCount }}</p> <!-- 本地计算属性 --></div>
</template><script>
export default {computed: {...mapState(['count']),doubleCount() {return this.count * 2; // 复用 state 数据}}
};
</script>
2. 模块命名空间(Module)
如果使用了模块命名空间,需指定模块路径:
computed: {...mapState('user', ['name', 'age']), // 映射 user 模块的 state...mapGetters('cart', ['totalPrice']) // 映射 cart 模块的 getters
}
五、注意事项
-
必须在 computed 中使用:
因为它们返回的是计算属性配置对象,只能放在组件的computed
选项中。 -
避免命名冲突:
如果组件已有同名属性,建议用对象形式自定义名称(如myCount: 'count'
)。 -
与直接访问的对比:
- 直接访问:
this.$store.state.count
(代码冗余,但直观)。 - 使用辅助函数:
this.count
(简洁,但需熟悉语法)。
- 直接访问:
总结
mapState
:把 store 中的 state 变成组件的计算属性,减少this.$store.state.xxx
的重复。mapGetters
:把 store 中的 getters 变成组件的计算属性,减少this.$store.getters.xxx
的重复。
它们的核心作用是简化组件对 store 数据的引用,让代码更符合 Vue 的风格~
mapActions
和 mapMutations
mapActions
和 mapMutations
是 Vuex 提供的两个辅助函数,用于简化在组件中触发 actions 和 mutations 的写法。它们的核心作用是将 Vuex 的方法映射为组件的 methods,让代码更简洁、易读。
一、mapActions
:映射 actions 到组件 methods
1. 基本用法(数组形式)
// 在组件中
import { mapActions } from 'vuex';export default {methods: {// 将 store.actions 中的 `increment` 和 `fetchUser` 映射为组件的 methods...mapActions(['increment', 'fetchUser']),// 现在可以直接在模板中使用 `this.increment()` 和 `this.fetchUser()`}
};
2. 对象形式(自定义名称)
methods: {...mapActions({add: 'increment', // 将 action 'increment' 映射为组件的 'add' 方法loadUser: 'fetchUser' // 自定义名称})
}
3. 传递参数
<template>
******************************zhongyao******<button @click="increment(5)">增加 5</button> <!-- 传递参数 -->
</template><script>
export default {methods: {...mapActions(['increment'])}
};
</script>
二、mapMutations
:映射 mutations 到组件 methods
1. 基本用法(数组形式)
// 在组件中
import { mapMutations } from 'vuex';export default {methods: {// 将 store.mutations 中的 `SET_USER` 和 `ADD_TODO` 映射为组件的 methods...mapMutations(['SET_USER', 'ADD_TODO']),// 现在可以直接在模板中使用 `this.SET_USER()` 和 `this.ADD_TODO()`}
};
2. 对象形式(自定义名称)
methods: {...mapMutations({setUser: 'SET_USER', // 将 mutation 'SET_USER' 映射为组件的 'setUser' 方法addTask: 'ADD_TODO' // 自定义名称})
}
3. 传递参数
<template><button @click="addTask({ text: '学习 Vuex', completed: false })">添加任务</button>
</template><script>
export default {methods: {...mapMutations(['ADD_TODO'])}
};
</script>
三、对比与适用场景
特性 | mapActions | mapMutations |
---|---|---|
触发目标 | store.actions 中的方法(异步逻辑) | store.mutations 中的方法(同步修改 state) |
调用方式 | 通过 dispatch 触发 | 通过 commit 触发 |
典型场景 | 网络请求、复杂业务逻辑 | 直接修改 state |
语法示例 | ...mapActions(['fetchUser']) | ...mapMutations(['SET_USER']) |
四、高级技巧
1. 与本地 methods 混合使用
<template><button @click="submitForm">提交表单</button>
</template><script>
export default {methods: {...mapActions(['saveData']),submitForm() {// 本地逻辑this.validateForm();// 调用 actionthis.saveData(this.formData);}}
};
</script>
2. 模块命名空间(Module)
如果使用了模块命名空间,需指定模块路径:
methods: {...mapActions('user', ['login', 'logout']), // 映射 user 模块的 actions...mapMutations('cart', ['ADD_ITEM']) // 映射 cart 模块的 mutations
}
五、注意事项
-
异步 vs 同步:
- actions:处理异步操作(如 API 请求),最终通过
commit
触发 mutation。 - mutations:只能包含同步代码,用于直接修改 state。
- actions:处理异步操作(如 API 请求),最终通过
-
选择原则:
- 简单场景:直接用
mapMutations
触发同步修改。 - 复杂场景(如需要异步):用
mapActions
处理业务逻辑。
- 简单场景:直接用
-
与直接调用的对比:
- 直接调用:
this.$store.dispatch('increment')
或this.$store.commit('SET_USER')
。 - 使用辅助函数:
this.increment()
或this.SET_USER()
(代码更简洁)。
- 直接调用:
总结
mapActions
:把 store 中的 actions 变成组件的 methods,减少this.$store.dispatch
的重复。mapMutations
:把 store 中的 mutations 变成组件的 methods,减少this.$store.commit
的重复。
它们的核心作用是简化组件触发 Vuex 方法的方式,让代码更符合 Vue 的风格~
多组件共享数据
在 Vue 中,使用 Vuex 实现多组件共享数据的核心逻辑是:将共享数据集中存储在 Store 中,所有组件通过统一接口读写数据,确保数据变更可追踪、状态一致。以下是具体实现步骤(以 Vue 2 + Vuex 3 为例):
1. 定义共享数据(State)
在 store/index.js
中集中管理共享数据:
// src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({state: {// 共享数据(如用户信息、购物车、全局设置等)userInfo: null, // 多组件共享的用户信息cart: [], // 多组件共享的购物车数据theme: 'light' // 多组件共享的主题设置},// ...其他配置(mutations/actions/getters)
})
2. 定义修改数据的方法(Mutations + Actions)
- Mutations:同步修改共享数据(唯一修改 state 的方式)。
- Actions:处理异步逻辑(如 API 请求),最终通过 mutations 修改数据。
// 续 store/index.js
mutations: {// 修改用户信息SET_USER_INFO(state, user) {state.userInfo = user},// 添加商品到购物车ADD_TO_CART(state, product) {state.cart.push(product)},// 切换主题TOGGLE_THEME(state) {state.theme = state.theme === 'light'? 'dark' : 'light'}
},
actions: {// 异步获取用户信息(如从接口)fetchUserInfo({ commit }) {return new Promise(resolve => {// 模拟 API 请求setTimeout(() => {const user = { id: 1, name: '张三' }commit('SET_USER_INFO', user) // 调用 mutation 修改数据resolve(user)}, 1000)})}
}
3. 组件中读写共享数据
所有组件通过 this.$store
访问 Store,实现数据共享。
(1)读取共享数据
<!-- 组件 A:读取用户信息和主题 -->
<template><div><p>当前用户:{{ $store.state.userInfo?.name || '未登录' }}</p><p>主题:{{ $store.state.theme }}</p></div>
</template>
(2)修改共享数据
<!-- 组件 B:添加商品到购物车 -->
<template><button @click="addProduct">添加商品到购物车</button>
</template><script>
export default {methods: {addProduct() {const product = { id: 101, name: '手机' }// 调用 mutation 修改购物车this.$store.commit('ADD_TO_CART', product)}}
}
</script>
(3)调用异步操作
<!-- 组件 C:获取用户信息 -->
<template><button @click="loadUser">加载用户信息</button>
</template><script>
export default {methods: {loadUser() {// 调用 action 执行异步操作this.$store.dispatch('fetchUserInfo')}}
}
</script>
4. 简化代码:使用辅助函数
通过 mapState
、mapMutations
等辅助函数,简化组件中对 Vuex 的使用:
<template><div><p>购物车商品数:{{ cartLength }}</p><button @click="toggleTheme">切换主题</button></div>
</template><script>
import { mapState, mapMutations } from 'vuex'export default {computed: {// 映射 state 中的 cart 并计算长度...mapState({cartLength: state => state.cart.length})},methods: {// 映射 mutation:TOGGLE_THEME...mapMutations(['TOGGLE_THEME']),toggleTheme() {this.TOGGLE_THEME() // 直接调用映射后的方法}}
}
</script>
核心优势
- 单一数据源:所有共享数据集中在 Store,避免数据分散导致的不一致。
- 可追踪:所有数据修改通过 mutations 完成,配合 Vue Devtools 可追踪每一次状态变化。
- 跨组件通信:无论组件层级、关系(父子/兄弟/跨路由),都能直接访问共享数据。
总结
使用 Vuex 实现多组件共享数据的流程:
- Store 集中存储数据(state)。
- 通过 mutations 同步修改、actions 处理异步。
- 组件通过 $store 读写数据,辅助函数简化代码。
这种模式特别适合中大型项目,解决多组件数据共享和通信复杂的问题。
vuex模块化与namespace
在 Vuex 中,随着应用程序的规模不断扩大,store 中的代码会变得越来越复杂,管理起来也会更加困难。Vuex 提供了模块化(Module)和命名空间(namespace)机制来解决这些问题,使代码结构更加清晰、可维护。
模块化
Vuex 的模块化允许将 store 分割成多个模块(module),每个模块都有自己独立的 state、mutation、action、getter 等,就像把一个大仓库分成了多个小仓库,每个小仓库负责管理一类特定的物品。
Vuex 模块化 + 命名空间(namespace) 的使用方式:
一、核心目标
模块化 + 命名空间 的作用:
- 解耦代码:按功能拆分模块(如
countAbout
处理计数、personAbout
处理人员),避免 Store 臃肿。 - 避免命名冲突:不同模块的
state/mutations/actions
用命名空间隔离(如personAbout/ADD_PERSON
)。
二、模块定义(store.js
关键代码)
// 定义模块(开启命名空间:namespaced: true)
const countAbout = {namespaced: true, // 开启命名空间state: { x: 1 },mutations: { ... },actions: { ... },getters: {bigSum(state) {return state.sum * 10}}
}const personAbout = {namespaced: true, // 开启命名空间state: { ... },mutations: { ... },actions: { ... }
}// 注册模块到 Store
const store = new Vuex.Store({modules: {countAbout,personAbout}
})
三、组件中使用(命名空间模块的 6 种操作)
1.组件中读取 state
数据
// 方式 1:直接读取(模块名 + state 字段)
this.$store.state.personAbout.list // 方式 2:mapState 映射(指定模块名)
...mapState('countAbout', ['sum', 'school', 'subject'])
2. 组件中读取 getters
数据
// 方式 1:直接读取(模块名 + getters 名)
this.$store.getters['personAbout/firstPersonName'] // 方式 2:mapGetters 映射(指定模块名)
...mapGetters('countAbout', ['bigSum'])
3. 组件中调用 dispatch
(触发 actions)
// 方式 1:直接 dispatch(模块名 + action 名)
this.$store.dispatch('personAbout/addPersonWang', person) // 方式 2:mapActions 映射(指定模块名)
...mapActions('countAbout', {incrementOdd: 'jiaOdd', incrementWait: 'jiaWait'
})
4. 组件中调用 commit
(触发 mutations)
// 方式 1:直接 commit(模块名 + mutation 名)
this.$store.commit('personAbout/ADD_PERSON', person) // 方式 2:mapMutations 映射(指定模块名)
...mapMutations('countAbout', {increment: 'JIA', decrement: 'JIAN'
})
四、核心规则
- 命名空间标识:所有操作需带模块名前缀(如
personAbout/ADD_PERSON
)。 - 辅助函数用法:
mapState/mapGetters/mapActions/mapMutations
第一个参数传入模块名,指定操作的模块。
五、类比理解
把 Vuex 模块比作“文件夹”,命名空间是“文件夹路径”。
- 不开启命名空间:所有模块的
state/mutations
混在一起,容易重名冲突。 - 开启命名空间:每个模块是独立“文件夹”,操作需带“路径”(模块名),清晰且安全。
基本使用
假设我们有一个应用,包含用户模块(user)和商品模块(product),可以如下定义模块:
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)// 用户模块
const userModule = {state: {userInfo: null,token: ''},mutations: {SET_USER_INFO(state, payload) {state.userInfo = payload},SET_TOKEN(state, payload) {state.token = payload}},actions: {async fetchUserInfo({ commit }) {// 模拟异步请求获取用户信息setTimeout(() => {const user = { name: 'John', age: 30 }commit('SET_USER_INFO', user)}, 1000)}},getters: {isLoggedIn: state => state.userInfo!== null}
}// 商品模块
const productModule = {state: {products: []},mutations: {ADD_PRODUCT(state, payload) {state.products.push(payload)}},actions: {async fetchProducts({ commit }) {// 模拟异步请求获取商品列表setTimeout(() => {const products = [{ id: 1, name: 'Product A', price: 100 },{ id: 2, name: 'Product B', price: 200 }]commit('ADD_PRODUCT', products)}, 1500)}},getters: {productCount: state => state.products.length}
}// 创建并导出store
export default new Vuex.Store({modules: {user: userModule,product: productModule}
})
命名空间(namespace)
当使用模块化后,不同模块可能会出现命名冲突(比如两个模块都有一个名为 increment
的 mutation 或 action ),这时就需要使用命名空间来区分。
命名空间可以理解为给模块添加了一个专属的前缀,使得模块内的方法和属性在全局中具有唯一的标识。
启用命名空间
默认情况下,模块没有启用命名空间。要启用命名空间,需要在模块定义中添加 namespaced: true
选项。
修改后的模块定义
// 用户模块,启用命名空间
const userModule = {namespaced: true,state: {userInfo: null,token: ''},mutations: {SET_USER_INFO(state, payload) {state.userInfo = payload},SET_TOKEN(state, payload) {state.token = payload}},actions: {async fetchUserInfo({ commit }) {setTimeout(() => {const user = { name: 'John', age: 30 }commit('SET_USER_INFO', user)}, 1000)}},getters: {isLoggedIn: state => state.userInfo!== null}
}// 商品模块,启用命名空间
const productModule = {namespaced: true,state: {products: []},mutations: {ADD_PRODUCT(state, payload) {state.products.push(payload)}},actions: {async fetchProducts({ commit }) {setTimeout(() => {const products = [{ id: 1, name: 'Product A', price: 100 },{ id: 2, name: 'Product B', price: 200 }]commit('ADD_PRODUCT', products)}, 1500)}},getters: {productCount: state => state.products.length}
}
在组件中使用带有命名空间的模块
在组件中使用带有命名空间的模块时,需要在调用方法和访问属性时加上模块的命名空间前缀。
组件示例
<template><div><button @click="fetchUserInfo">获取用户信息</button><button @click="fetchProducts">获取商品列表</button></div>
</template><script>
import { mapActions } from 'vuex'export default {methods: {...mapActions('user', ['fetchUserInfo']),...mapActions('product', ['fetchProducts'])}
}
</script>
关键点总结
- 模块化:将大的 store 拆分成多个小模块,每个模块负责管理特定的功能或业务逻辑,提高代码的可读性和可维护性。
- 命名空间:为模块添加唯一标识,避免命名冲突,在使用辅助函数(如
mapState
、mapActions
等)时,需要指定命名空间来正确访问模块内的属性和方法。
通过模块化和命名空间机制,Vuex 能够更好地适应大型项目的开发需求,让状态管理变得更加有序和高效。