Vue2基础入门
Vue概况
Vue概念
Vue 是一个用于 构建用户界面 的 渐进式 JavaScript框架
- 构建用户界面:基于数据渲染出用户看到的页面
- 渐进式:根据需求循序渐进的学习和使用Vue
- 框架:一套完整的项目解决方案,具有自己的语法规则(本质上还是使用JavaScript语法)
Vue 的特点:
- 采用组件化模式,提高代码复用率,且让代码更好维护
- 声明式编码,让编程人员无序直接操作 DOM,提高开发效率
- 使用虚拟 DOM + 优秀的 Diff 算法,尽量复用 DOM 节点
MVVM概念
MVVM 是 Vue 实现数据驱动视图和双向数据绑定的核心原理。MVVM 指的是 Model、View 和 ViewModel,它把每个 HTML 页面都拆分成了这三个部分,如图所示:
在 MVVM 概念中:
Model 表示当前页面渲染时所依赖的数据源
View 表示当前页面所渲染的 DOM 结构
ViewModel 表示 vue 的实例,它是 MVVM 的核心
ViewModel 作为 MVVM 的核心,是它把当前页面的数据源(Model)和页面的结构(View)连接在了一起
当数据源发生变化时,会被 ViewModel 监听到,VM 会根据最新的数据源自动更新页面的结构
当表单元素的值发生变化时,也会被 VM 监听到,VM 会把变化过后最新的值自动同步到 Model 数据源中
创建Vue实例
国内CDN:BootCDN
国外CND:JSDELIVR
<!-- Vue所管理的范围 -->
<div id="app">
<!-- 用于渲染的代码逻辑 -->
<h1>{{ msg }}</h1>
<a href="#">{{ count }}</a>
</div>
<!-- 引入的是开发版本包 - 包含完整的注释和警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script>
// 一旦引入 VueJS核心包,在全局环境,就有了 Vue 构造函数
new Vue({
// 通过 el 配置选择器,指定 Vue 管理的是哪个容器,值通常为css选择器字符串
el: '#app',
// 通过 data 提供数据
data: {
msg: 'Hello World',
count: 666
}
})
</script>
想让 Vue 工作,就必须创建一个 Vue 实例,且要传入一个配置对象
app 容器里面的代码依旧符合 html 规范,只不过混入了一些特殊的 Vue 语法。app 容器里面的代码被称为 Vue 模板
此外,Vue 还有先来先生效的原则:多个容器对应一个 Vue 实例,则 Vue 实例控制第一个容器;多个 Vue 实例对应一个容器,则只有第一个 Vue 实例才能控制该容器。总的来说,容器与 Vue 实例之间是一一对应的关系
el与data拓展
el的两种写法
<div id="app">
<h1>{{msg}}</h1>
</div>
<script>
const app = new Vue({
el:'#app' //第一种写法,直接绑定
data: {
msg:'hello world'
}
})
app.$mount('#app') //第二种写法
</script>
data两种写法
<div id="app">
<h1>{{msg}}</h1>
</div>
<script>
const app = new Vue({
el:'#app'
//第一种写法:对象式
data: {
msg:'hello world'
}
//第二种写法:函数式
data:function(){
return {
msg:'hello world'
}
}
//由 Vue 管理的函数,一定不要写箭头函数,一旦写了箭头函数,this就不再是 Vue 实例了
})
</script>
插值表达式
-
插值表达式是一种 Vue 的模板语法
-
利用表达式进行插值,渲染到页面中
-
表达式:是可以被求值的代码,JavaScript 引擎会将其计算出一个结果
-
语法:
{{表达式}} //小胡子语法
使用的数据必须存在
data
中;支持的是表达式,而非语句;不能在标签属性上使用{{}}
插值
Vue响应式特性
- 除了基本的模板渲染,Vue 背后还做了大量工作,最主要的就是实现了响应式特性
- 响应式:数据变化,视图自动更新
- data中的数据,最终会被添加到实例上
- 访问数据:
实例.属性名
- 修改数据:
实例.属性名 = 值
使用示例:
<div id="app">
{{ msg }}
{{ count }}
</div>
<script>
const app = new Vue({
el: '#app',
// data中的数据,是会被添加到实例上
data: {
// 响应式数据 → 数据变化了,视图自动更新
msg: 'Hello World',
count: 666
}
})
</script>
- 数据改变,视图会自动更新
数据驱动视图,可以让程序员聚焦于数据,关注业务的核心逻辑,根据业务修改数据即可。此外我们需要注意的是,数据驱动视图是单向的数据绑定
Vue开发者工具
Vue Devtools
- 通过谷歌应用商店安装(科学上网VPN)
- 通过极简插件商店安装
- 下载 → 开发者模式 → 拖拽安装 → 插件详情允许访问文件
- 设置完成后,打开 Vue 运行的页面,调试工具中 Vue 栏,即可查看修改数据,进行调试
Vue指令
Vue 指令是 Vue.js 框架中的特殊属性,用于向 HTML 元素添加交互式行为和动态功能。指令以 v-
前缀作为识别符,紧跟在指令名称后面的是其表达式或参数,Vue 会根据不同的指令,针对标签实现不同的功能
v-html
- 作用:设置元素的
innerHTML
- 语法:
v-html = 表达式
- v-html 后解析,因此标签内不能继续加其他内容,如插值表达式或文本,会被直接覆盖掉
- 此外,v-text 为设置元素的
innerText
,用于将数据渲染到元素的文本内容中,多数情况下使用 v-html
使用示例:
<div id="app">
<div v-html="msg"></div>
</div>
<script>
const app = new Vue({
el: '#app',
data: {
msg: `<h1>Hello World!</h1>`
}
})
</script>
v-if与v-show
v-show
- 作用: 控制元素显示隐藏
- 语法:
v-show = 表达式
,表达式值 true 显示,false 隐藏 - 原理: 切换
display:none
控制显示隐藏 - 场景: 频繁切换显示隐藏的场景
v-if
- 作用: 控制元素显示隐藏(条件渲染)
- 语法:
v-if = 表达式
,表达式值 true 显示, false 隐藏 - 原理: 基于条件判断,是否创建或移除元素节点
- 场景: 要么显示,要么隐藏,不频繁切换的场景
使用示例:
<div id="app">
<div v-show="flag" class="box">我是v-show控制的盒子</div>
<div v-if="flag" class="box">我是v-if控制的盒子</div>
</div>
<script>
const app = new Vue({
el: '#app',
data: {
flag: false
}
})
</script>
v-else与v-else-if
- 辅助 v-if 进行判断渲染
- 语法:
v-else v-else-if = "表达式"
- 注意:需要紧挨着 v-if 一起使用
使用示例:
<div id="app">
<p v-if="gender === 1">性别:男</p>
<p v-else>性别:女</p>
<hr>
<p v-if="score >= 90">成绩评定A</p>
<p v-else-if="score >= 80">成绩评定B</p>
<p v-else-if="score >= 70">成绩评定C</p>
<p v-else>成绩评定D</p>
</div>
<script>
const app = new Vue({
el: '#app',
data: {
gender: 2,
score: 95
}
})
</script>
v-on
事件绑定
-
注册事件 = 添加监听 + 提供处理逻辑
-
语法1:
v-on:事件名 = "内联语句"
<button v-on:click='count++'>按钮</button>
-
语法2:
v-on:事件名 = "methods中的函数名"
<button v-on:click='add'>按钮</button>
-
语法简写:
@事件名
<button @click='add'>按钮</button>
-
传参:
<button @click='add(参数1, 参数2)'>按钮</button>
使用示例:
<div id="app">
<p>num值为:{{ num }}</p>
<button @click="add(5)">+5</button>
</div>
<script>
const app = new Vue({
el: '#app',
data: {
num: 100
},
methods: {
add(num) {
this.num += num
//此处的 this 指向的就是 app 实例对象
//与 js 不相同,因为 Vue 内部已经把实例对象里面的 this 指向修改了,不会指向 Window
}
}
})
</script>
事件参数对象
在原生的 DOM 事件绑定中,可以在事件处理函数的形参处,接收事件参数对象 event。同理,在 v-on 指令所绑定的事件处理函数中,同样可以接收到事件参数对象 event。Vue 在出发事件回调时,会主动给我们传入 event 事件对象参数
事件传参有以下四种情况:
- 不传参数:
@click="show"
,show 方法会收到一个 event(事件对象) - 传一个参数:
@click="show(6)"
,show 方法只会收到:6 - 传多个参数:
@click="show(6, 7, 8)"
,show 方法会收到:6、7、8 - 传参 + 事件对象:
@click="show($event, 6)"
,show 方法会收到:event、6
$event
是 Vue 提供的特殊变量,用来表示原生的事件参数对象 event。$event
可以解决事件参数对象 event 被覆盖的问题,示例用法如下:
<!-- ... -->
<button v-on:click="addCount(1, $event)">+1</button>
<!-- ... -->
<script>
const app = new Vue({
//...
methods: {
addCount(step, e) { //接收事件参数对象 event
const nowBgColor = e.target.style.backgroundColor
e.target.style.backgroundColor = nowBgColor === 'red' ? '' : 'red'
this.count += step
}
}
})
</script>
$event
有两种使用场景:
- 在 DOM 事件的回调函数中传入参数
$event
,可以获取到该事件的事件对象- 在子组件中通过
$emit
注册事件,将数据作为参数传入,在父组件中通过$event
接收(传过来的第一个参数)
v-bind
基本使用
- 作用:动态的设置 html 的标签属性
- 语法:
v-bind:属性名="表达式"
- 简写:
:属性名="表达式"
使用示例:
<div id="app">
<img v-bind:src="imgUrl" v-bind:title="msg" alt="">
<img :src="imgUrl" :title="msg" alt="">
</div>
<script>
const app = new Vue({
el: '#app',
data: {
imgUrl: './imgs/1.png',
msg: 'hello'
}
})
</script>
class与style绑定
Vue 扩展了 v-bind 的语法,可以针对 class 类名 和 style 行内样式 进行控制
操作class
语法::class = "对象/数组"
-
对象:键就是类名,值是布尔值。如果值为 true,有这个类,否则没有这个类。适用于一个类名来回切换
<div class="box" :class="{ 类名1: 布尔值, 类名2: 布尔值 }"></div>
-
数组:数组中所有的类,都会添加到盒子上,本质就是一个 class 列表。适用于批量添加或删除类
<div class="box" :class="[ '类名1', '类名2', '类名3' ]"></div>
-
在数组语法中也可以使用对象语法:
<div :class="[{ 类名1: 布尔值 }, 类名2]"></div>
当在一个自定义组件上使用
class
属性时,这些 class 将被添加到该组件的根元素上面。这个元素上已经存在的 class 不会被覆盖,语法与用在标签上类似
操作style
语法::style = "样式对象/数组"
-
v-bind:style
的对象语法十分直观,看着非常像 CSS,但其实是一个 JavaScript 对象。CSS 属性名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case) 来命名:<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
-
v-bind:style
的数组语法可以将多个样式对象应用到同一个元素上:<div :style="[baseStyles, overridingStyles]"></div>
-
从 Vue2.3.0 起你可以为
style
绑定中的属性提供一个包含多个值的数组,常用于提供多个带前缀的值,例如:<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>
这样写只会渲染数组中最后一个被浏览器支持的值。在本例中,如果浏览器支持不带浏览器前缀的 flexbox,那么就只会渲染
display: flex
v-for
基于数据循环,多次渲染整个元素(数组、对象、字符串、数字)
遍历数组
语法:v-for = "(item, index) in 数组"
item
每一项,index
索引index
可省略:v-for = "item in 数组"
遍历对象
语法:v-for="(value, key, index) in 对象" :key="index"
value
值,key
键,index
索引key
与index
可省略:v-for = "value in 数组"
遍历字符串
语法:v-for="(char, index) in 字符串"
char
单个字符,index
索引index
可省略:v-for="char in 字符串"
遍历指定次数
语法:v-for="(number, index) in 数字"
number
一个数字,index
索引index
可省略:v-for="(number, index) in 数字"
- 遍历的
number
从1开始,index
索引从0开始
v-for遍历中的key
在 Vue.js 中,当使用v-for
指令进行循环渲染时,每个生成的元素都需要具有一个唯一的标识符,以便 Vue 可以跟踪每个元素的状态和重新排序。Vue通过比较新旧节点的key来最小化DOM操作,提高性能,这就是key
属性的作用
key
属性是 Vue 用来追踪列表渲染的特殊属性。它的主要作用有两个方面:
- 识别每个节点的身份:Vue 会根据每个节点的
key
属性来判断它们的身份,以便高效地复用和更新已有的元素,而不是销毁并重新创建。如果没有使用key
或者key
不唯一,Vue将无法正确追踪每个元素的状态变化,可能导致不必要的渲染错误。 - 提高性能:使用合适的
key
值可以帮助 Vue 优化列表渲染的性能。Vue 会通过比较新旧节点的key
属性来确定是否需要重新渲染,从而减少不必要的 DOM 操作。
通常,key
属性的值(只能是字符串或数字类型)应该是每个元素在循环中的唯一标识符,例如在使用数组进行循环时,可以使用数组项的唯一标识符作为key
属性。此外,key
的值
v-model
v-model
是Vue.js中用于双向数据绑定的指令,它可以在表单元素(如输入框、选择框等)和组件之间建立起数据的双向绑定关系。双向绑定意味着当绑定的数据发生变化时,表单元素或组件的值也会自动更新,反之亦然
v-model
语法如下:
<!-- 在表单元素上 -->
<input v-model="dataProperty">
<!-- 在自定义组件上 -->
<custom-component v-model="dataProperty"></custom-component>
v-model
会忽略所有表单元素的 value
、checked
、selected
属性的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data
选项中声明初始值。常见的表单元素都可以用 v-model
绑定关联,用于快速获取或设置表单元素的值,它会根据控件类型自动选取正确的方法来更新元素
v-model
在内部为不同的输入元素使用不同的 property 并抛出不同的事件:
- text 和 textarea 元素使用
value
property 和input
事件 - checkbox 和 radio 使用
checked
property 和change
事件 - select 字段将
value
作为 property 并将change
作为事件
使用示例:
<div id="app">
<!-- 绑定的是 value 属性 -->
姓名:
<input v-model="username" type="text">
<br><br>
<!-- 绑定的是 checked 属性 -->
<!-- <input :checked="toggle" @input="toggle = $event.target.checked" type="checkbox"> -->
是否单身:
<input v-model="isSingle" type="checkbox">
<br><br>
<!-- 复选框特殊点 -->
<!-- 1.绑定的数据如果是数组类型, 会将 input 的 value 添加到数组中, 当数组中存在对应复选框的值时,对应的复选框 checked 属性为 true,否则为 false -->
<!-- 2.绑定的数据如果是非数组型, 默认都当布尔值处理, 绑定的是 checked 属性 -->
兴趣爱好:
<input v-model="hobby" value="smoke" type="checkbox">抽烟
<input v-model="hobby" value="drink" type="checkbox">喝酒
<input v-model="hobby" value="hothead" type="checkbox">烫头
<br><br>
<!-- 1.name: 给单选框加上 name 属性可以分组,同一组互相会互斥 -->
<!-- 2.value: 给单选框加上 value 属性,用于提交给后台的数据 -->
性别:
<input v-model="gender" name="gender" value="male" type="radio">男
<input v-model="gender" name="gender" value="female" type="radio">女
<br><br>
<!-- 1. option 需要设置 value 值,提交给后台 -->
<!-- 2. select 的 value 值,关联了选中的 option 的 value 值 -->
<!-- 注意: 下拉选择框使用 v-model 要绑定在 select 上 -->
所在城市:
<select v-model="city">
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
</select>
自我描述:
<textarea v-model="introduce"></textarea>
<button>立即注册</button>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.7.14/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
username: 'Seven',
isSingle: true,
hobby: [],
gender: 'female',
city: 'beijing',
introduce: ''
}
})
</script>
指令修饰符
指令修饰符是 Vue.js 中用于修改指令行为的特殊后缀,可以添加在指令后面,用以改变指令的行为方式。通过 .
指明一些指令后缀,不同后缀封装了不同的处理操作,这么做的好处是可以简化代码
按键修饰符
在监听键盘事件时,我们经常需要判断详细的按键。此时,可以为键盘相关的事件添加按键修饰符,例如:
按键修饰符 | 说明 |
---|---|
enter | 回车 |
delete | 删除 (捕获“删除”和“退格”键) |
esc | 退出 |
space | 空格 |
up | 上 |
down | 下 |
left | 左 |
right | 右 |
Vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为kebab-case(短横线命名)
<input v-on:keyup.page-down="onPageDown">
Vue.config.keyCodes.自定义键名 = 键码
,可以去定制按键别名
// 可以使用 `v-on:keyup.f1`
Vue.config.keyCodes.f1 = 112
事件修饰符
在事件处理函数中调用 event.preventDefault()
或 event.stopPropagation()
是非常常见的需求。因此,vue 提供了事件修饰符的概念,来辅助程序员更方便的对事件的触发进行控制。常用的 6 个事件修饰符如下:
事件修饰符 | 说明 |
---|---|
.prevent | 阻止默认行为 (例如:阻止a连接的跳转、阻止表单的提交等) |
.stop | 阻止事件冒泡 |
.capture | 以捕获模式触发当前的事件处理函数 |
.once | 绑定的事件只触发1次 |
.self | 只有在 event.target 是当前元素自身时触发事件处理函数 |
.passive | 事件的默认行为立即执行,无需等待事件回调执行完毕 |
使用示例:
<!-- 此处也可以不用添加点击事件 -->
<!-- 修饰符可以连续写 -->
<a href="https://www.baidu.com" @click.prevent.stop="onLinkClick">百度首页</a>
<!-- 使用事件的捕获模式 -->
<div class="box1" @click.capture="console.log(1)">
<div class="box2" @click="console.log(2)"></div>
</div>
<!-- 事件只触发一次(常用) -->
<button @click.once="showInfo">点我提示信息</button>
<!-- 只有event.target是当前操作的元素时才触发事件 -->
<div class="demo1" @click.self="showInfo">
<button>点我提示信息</button>
</div>
v-model修饰符
为了方便对用户输入的内容进行处理,vue 为 v-model 指令提供了3个修饰符,分别是:
修饰符 | 作用 | 示例 |
---|---|---|
.number | 自动将用户的输入值转为数值类型 | <input v-model.numbe r="age"/> |
.trim | 自动过滤用户输入的首尾空白字符 | <input v-model.trim="msg"/> |
.lazy | 在“change”时而非“input”时更新 | <input v-model.lazy="msg"/> |
computed计算属性
概念
基于现有的数据,计算出来的新属性。 依赖的数据变化,自动重新计算
语法:
-
声明在 computed 配置项中,一个计算属性对应一个函数,必须要有返回值
-
使用起来和普通属性一样使用
{{ 计算属性名 }}
-
计算属性可以将一段求值的代码进行封装
computed: { 计算属性名 () { //基于现有数据,编写求值逻辑 return 结果 } },
-
计算属性默认的简写,只能读取访问,不能修改,如果要修改,需要写计算属性的完整写法
- 完整写法:计算属性是一个对象,对象中拥有两个方法(get / set),get 相当于以前的简写函数
- 完整写法可以被赋值,当计算属性被赋值时,计算属性的 set 会执行
computed: { 计算属性名: { get() { //一段代码逻辑(计算逻辑) //提供值,不叫修改 return 结果 }, set(修改的值) { //一段代码逻辑(修改逻辑) } } }
computed与methods
computed计算属性
-
作用:封装了一段对于数据的处理,求得一个结果
-
语法:
- 写在
computed
配置项中 - 本质是函数,可作为属性,直接使用:
this.计算属性、{{ 计算属性 }}
- 写在
computed缓存特性:计算属性会对计算出来的结果缓存,再次使用直接读取缓存,依赖项变化了,会自动重新计算并再次缓存
methods方法
-
作用:给实例提供一个方法,调用以处理业务逻辑
-
语法:
- 写在
methods
配置项中 - 作为方法,需要调用:
this.方法名()、{{ 方法名() }}、@事件名="方法名"
- 写在
两者的主要区别在于computed
具有缓存特性、自动依赖追踪,适用于衍生数据的计算;而methods
没有缓存和自动追踪依赖,适用于执行函数操作和处理事件
watch侦听器
watch 侦听器用于监视数据变化,执行一些业务逻辑或异步操作
语法:
-
简单写法:简单类型数据,直接监视
- 监听器的名字需要和数据名完全相同,当数据变化时自动执行该函数
- 如果数据是一个对象的属性,可以把函数名定义成:
对象.属性
watch: { //侦听器有两个参数, 参数1:新值, 参数2:旧值 数据属性名(newValue, oldValue) { //一些业务逻辑 或 异步操作 }, '对象.属性名'(newValue, oldValue) { //一些业务逻辑 或 异步操作 } }
-
完整写法:添加额外配置项
deep: true
对复杂类型深度监视immediate: true
初始化立刻执行一次handler方法
watch: {// watch 完整写法 数据属性名: { deep: true, // 深度监视(针对复杂类型) immediate: true, // 是否立刻执行一次handler handler (newValue) { console.log(newValue) } } }
生命周期
概念
Vue生命周期:一个Vue实例从创建到销毁的整个过程
生命周期的四个阶段:① 创建 ② 挂载 ③ 更新 ④ 销毁
钩子函数
Vue生命周期过程中,会自动运行一些函数,被称为生命周期钩子让开发者可以在特定阶段运行自己的代码
这些生命周期钩子函数提供了在组件不同阶段执行代码的机会,我们可以根据需求在特定的生命周期函数中处理数据、发送请求、监听事件等操作。生命周期函数的执行顺序是固定的,遵循前后依赖关系
Vue2 组件的生命周期分为8个阶段,分别是:
生命周期阶段 | 调用函数 |
---|---|
将要创建(在实例创建之前,此时对象还未被初始化,无法访问data等属性) | beforeCreate |
创建完毕(实例创建完成,可以访问data等属性,但DOM节点还未被挂载) | created |
将要挂载(在Vue实例挂载到DOM节点之前调用,此时模板编译已完成) | beforeMount |
挂载完毕(Vue实例已经挂载到DOM节点上,可以进行DOM操作) | mounted |
将要更新(在数据更新之前调用,DOM还未更新) | beforeUpdate |
更新完毕(数据已经更新,DOM已经重新渲染) | updated |
将要摧毁(在实例销毁之前调用,此时实例仍可用) | beforeDestroy |
摧毁完毕(实例已经销毁,清理工作已完成) | destroyed |
vue2 生命周期各阶段详细过程如下:
工程化开发
工程化开发是指使用各种现代化工具、技术和最佳实践来提高软件开发过程的效率、可靠性和可维护性的一种开发方法。它旨在解决传统开发中出现的一些问题,如复杂的依赖管理、低效的构建过程、缺乏一致性的代码质量等
通过工程化开发,开发团队可以更高效地协作、迭代和交付软件项目,减少人为错误,提高代码质量和项目的可维护性,同时使开发过程更加标准化和可重复。工程化开发是现代化软件开发的重要实践,被广泛应用于各种类型的项目和开发环境中
前端工程化方案包括构建工具、自动化测试、代码规范、模块化开发、版本控制等,下面主要介绍 Vue 构建工具 Vue CLI 以及组件化开发的概念
脚手架 Vue CLI
基本概念
Vue CLI 构建 Vue 项目的过程本质上就是通过 Webpack 进行构建,Vue CLI 是将 Webpack 集成在其中,简化了配置和操作的过程,提供了更高层次的抽象和命令工具
使用步骤:
-
全局安装:
yarn global add @vue/cli
或npm i @vue/cli -g
-
查看 Vue 版本:
vue --version
或vue -V
-
创建项目架子:
vue create project-name
(项目名不能用中文,字母也不能大写) -
启动项目:
yarn serve
或npm run serve
(找package.json)
在工程化的项目中,vue 要做的事情很单纯:通过 main.js 把 App.vue(用来编写待渲染的模板结构) 渲染到 index.html 的指定区域中(index.html 中需要预留一个 el 区域)
main.js 文件代码详解:
// 引入 Vue 构造函数
import Vue from 'vue'
// 引入 App.vue 组件
import App from './App.vue'
// 配置 Vue 生产版本的提示信息是否显示, false 表示不显示
Vue.config.productionTip = false
// 创建一个 Vue 实例
new Vue({
el: '#app', // 指定挂载点, 告诉 Vue 在哪里渲染
// render: h => h(App), // 挂载什么东西, 把什么东西渲染上去
render(createElement) {
// createElement 是一个函数, 它的作用是根据指定的组件创建「元素」
return createElement(App)
}
})
// $mount() 和 指定 el 效果和原理一样
易混淆点
在使用 Webpack(Vue CLI构建项目本质)构建 Vue.js 项目时,Webpack 的执行环境通常是 Node.js。Webpack 是一个用于打包、构建和管理前端资源的工具,它是基于 Node.js 构建的,其配置文件通常是使用 JavaScript 编写,并通过 Node.js 环境来解析和执行
当你在 Vue.js 项目中使用 Webpack 时,你会使用 Webpack 的命令行工具或者在配置文件中定义的脚本来运行 Webpack。例如,你可以使用webpack
命令或者在package.json
文件中配置一个脚本来执行 Webpack 构建。在构建过程中,Webpack 会根据配置文件的设置,解析入口文件及其依赖关系,处理各种类型的资源文件(如JavaScript、CSS、图片等),并生成最终的打包文件
需要注意的是,尽管 Webpack 的执行环境是 Node.js,Webpack 最终生成的打包文件是用于在浏览器环境中运行的。Webpack会根据配置文件的设置将各个模块合并、转换和压缩,最终生成浏览器可执行的JavaScript、CSS和其他静态资源文件
总的来说,尽管 Webpack 的执行环境是 Node.js,但 Webpack 的目标是生成适用于浏览器环境的资源文件,以供 Vue.js 应用程序在浏览器中运行。这点解释了:当你在 Vue 源码中看到 import
关键字时,它其实是在 Node.js 环境中执行的,因此,执行 import
就相当于执行了 require
,会按照其算法来查找包路径
组件化开发
基本概念
组件:Vue.js 应用程序的可复用、自包含的一部分,用于封装一段特定功能的 HTML 模板、CSS 样式和 JavaScript 逻辑
组件化:一个页面可以拆分成一个个组件,每个组件有着自己独立的结构、样式、行为。这样做的好处是便于维护,利于复用 从而能够提升开发效率
组件化开发:根据封装的思想,把页面上可重用的 UI 结构封装为组件,从而方便项目的开发和维护
组件分为普通组件和根组件,其中根组件是整个应用最上层的组件,包裹所有普通小组件
组件组成
Vue 组件由三个主要部分组成:
- template 结构 (只能有一个根节点)
- style 样式 (支持 less 和 sass )
- script 行为
每个组件中必须包含 template 模板结构,而 script 行为和 style 样式是可选的组成部分
template
vue 规定:每个组件对应的模板结构,需要定义到 <template>
节点中
<template>
节点的基本结构如下:
<template>
<!-- 当前组件的 DOM 结构,需要定义到 template 标签内部-->
</template>
注意:
- template 是 vue 提供的容器标签,只起到包裹性质的作用,它不会被渲染为真正的 DOM 元素
- template 中只能包含唯一的根节点
script
vue 规定:开发者可以在 <script>
节点中封装组件的 JavaScript 业务逻辑
<script>
节点的基本结构如下:
<script>
// 组件相关的 data 数据、methods 方法等都需要定义到 export default 所导出的对象中
export default {
data() {
return {
//数据
}
},
methods: {...}
}
</script>
vue 组件中的 data 必须是一个函数,不能直接指向一个数据对象,以保证每个组件实例维护独立的一份数据对象。每次创建新的组件实例,都会新执行一次 data 函数,得到一个新对象
style
vue 规定:组件内的 <style>
节点是可选的,开发者可以在 <style>
节点中编写样式美化当前组件的 UI 结构
<style>
节点的基本结构如下:
<style>
h1{
font-weight: nomal;
}
</style>
组件注册
关于组件名:
- 单个单词组成:
- 第一种写法:
layout
(首字母小写) - 第二种写法:
Layout
(首字母大写、推荐)
- 第一种写法:
- 多个单词组成:
- 第一种写法:
ProjectLayout
(大)驼峰命名(Camel-case)多个单词首字母大写 - 第二种写法:
project-layout
烤串命名(Kebab-case)多个单词之间用 - 连接
- 第一种写法:
局部注册
-
只能在注册的组件内使用
-
当成 html 标签使用:
<组件名></组件名>
// 导入需要注册的组件 // import 组件对象 from '.vue文件路径' import ProjectHeader from './components/ProjectHeader' export default { // 局部注册 components: { //'组件名': 组件对象, ProjectHeader: ProjectHeader } }
全局注册
-
所有组件内都能使用
-
当成 html 标签使用:
<组件名></组件名>
-
在
main.js
内导入,并全局注册Vue.component(组件名, 组件对象)
-
一般都用局部注册,如果发现确实是通用组件,再定义到全局
// 在 main.js 中导入需要全局注册的组件 import ProjectButton from './components/ProjectButton' // 调用 Vue.component 进行全局注册 // Vue.component('组件名', 组件对象) Vue.component('ProjectButton', ProjectButton)
组件样式冲突
默认情况: 写在组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题
- 全局样式:默认组件中的样式会作用到全局
- 局部样式:可以给组件加上
scoped
属性,可以让样式只作用于当前组件
scoped原理:
- 当前组件内标签都被添加
data-v-hash值
的自定义属性,且当前组件内的元素 hash 值都一样,但与其他组件的 hash 不同 - Vue 在添加样式时,会自动使用属性交集选择器,都被添加
元素[data-v-hash值]
的属性选择器
最终效果:必须是当前组件的元素,才会有这个自定义属性,才会被这个样式作用到。此外,当使用scoped
修饰符时,样式的选择器是动态生成的,因此在编译时无法直接访问这些选择器
需要注意的是,scoped
样式修饰符仅在单文件组件中生效(当前组件的样式对其子组件是不生效的)。如果你在 Vue 的普通模板或外部样式表中使用scoped
,它不会起到任何作用。如果想让某些样式对子组件生效,可以使用 /deep/
(less)或::v-deep
(sass、less)深度选择器
以/deep/
为例:
<style lang="less" scoped>
.title {
color: blue /* 不加/deep/时,生效的选择器格式为:.title[data-v-052342ad] 交集选择器 */
}
/deep/ .title {
color: blue /* 加上/deep/时,生效的选择器格式为:[data-v-052342ad] .title 后代选择器*/
}
</style>
组件通信
概念
组件通信,就是指组件与组件之间的数据传递或消息的传递和交流的过程
- 组件的数据是独立的,无法直接访问其他组件的数据,想用其他组件的数据就要用到组件通信
- 组件关系分类如下:
父子通信
- 父组件通过属性传递数据给子组件,在子组件中使用
props
接收并使用这些数据。这是一种单向数据流,只能从父组件向子组件传递数据 - 子组件可以通过
$emit
触发自定义事件并向父组件发送消息。父组件在子组件上通过监听事件来接收消息,并作出响应
父传子props:
①在父组件中给子组件标签添加自定义属性;②子组件使用 props 接收;③子组件使用数据,可以直接访问
子传父$emit:
①子组件使用 $emit 发送消息;②父组件中给子组件绑定自定义事件,添加消息监听;③在父组件中的事件处理函数中获取数据
非父子通信(不常用)
event bus 事件总线
非父子组件之间,进行简易消息传递(复杂场景 → Vuex)
-
创建一个都能访问到的事件总线 (空 Vue 实例) → utils/EventBus.js
import Vue from 'vue' const Bus = new Vue() export default Bus
-
A 组件(接收方),监听 Bus 实例的事件
created () { Bus.$on('sendMsg', (msg) => { this.msg = msg }) }
-
B 组件(发送方),触发 Bus 实例的事件
Bus.$emit('sendMsg', '这是一个消息')
provide & inject
provide
和inject
是一对高级特性,可以在组件树中上层组件提供数据,然后在下层组件中注入并使用这些数据,用于跨层级共享数据
-
父组件 provide 提供数据
export default { provide () { return { // 普通类型[非响应式] color: this.color, // 复杂类型[响应式] userInfo: this.userInfo, } } }
-
子/孙组件 inject 取值使用
export default { inject: ['color','userInfo'], created () { console.log(this.color, this.userInfo) } }
Prop
在Vue中,prop
是一种用于父组件向子组件传递数据的机制。通过在子组件中定义props
属性,可以声明需要从父组件接收的数据,并在子组件中使用这些数据
特点:
- 可以传递任意数量的 prop
- 可以传递任意类型的 prop
Props 校验
作用:为组件的 prop 指定验证要求,不符合要求,控制台就会有错误提示,能够帮助开发者快速发现错误
语法:
-
基础写法(类型校验)
props: { 校验的属性名: 类型 // Number String Boolean ... }
-
完整写法(类型、非空校验、默认值、自定义校验)
props: { 校验的属性名: { type: 类型, // Number String Boolean ... required: true, // 是否必填 default: 默认值, // 默认值 validator (value) { // 自定义校验逻辑,通过返回true,不通过返回false并报错 return 是否通过校验 } } }
Prop & data、单向数据流
共同点:都可以给组件提供数据
区别:
- data 的数据是自己的可以随意更改
- prop 的数据是外部的不能直接改,要遵循单向数据流,子组件通过
this.$emit
将数据传给父组件,让父组件来修改
单向数据流:父级 prop 的数据更新,会向下流动,影响子组件,这个数据流动是单向的
总之,
prop
用于接收来自父组件的数据,实现父子组件之间的数据传递;data
用于存储组件内部的私有数据,并且具有响应式能力
Vue进阶语法
v-model 原理
原理
v-model 本质上是一个语法糖。例如应用在输入框上,就是 value属性 和 input事件 的合写
作用: 提供数据的双向绑定
- 数据变,视图跟着变
:value
- 视图变,数据跟着变
@input
注意: $event 用于在模板中,获取事件的形参
<template>
<div id="app" >
<input v-model="msg" type="text">
<input :value="msg" @input="msg = $event.target.value" type="text">
</div>
</template>
v-model 除了可以使用在表单元素上,还可以使用在组件上,这也是需要理解
v-model
这个语法糖原理的根本原因v-model 本质上是希望一个数据传过去,如果要修改就直接传回来,主要适用于数据双向绑定的场景
组件中的v-model
当v-model
指令用于自定义组件时,它实际上会将value
属性和input
事件与组件的对应属性和方法进行绑定。这样,在组件内部对属性进行赋值时,会触发input
事件,从而更新外部的属性值;反过来,外部修改属性的值时,会通过value
属性将新值传递给组件内部
需要注意的是,不同的表单元素和自定义组件可能对v-model
的使用有所不同。对于大多数的表单元素,v-model
会监听input
事件和change
事件来更新值。对于自定义组件,可以通过在组件内部使用$emit('input', newValue)
来手动触发更新
以表单类组件封装实现为例:
使用属性绑定和事件绑定的组合方法,并实现父与子的双向数据绑定
- 父传子:数据是父组件 props 传递过来的,拆解 v-model 绑定数据
- 子传父:监听输入,通过
$emit
传值给父组件进行修改
父组件:
<BaseSelect :cityId="selectId" @changeHandle="selecteId = $event" />
子组件:
<select :value="cityId" @change="handleChange">...</select>
<script>
...
props: {
cityId: String
},
methods: {
handleChange (e) {
this.$emit('changeHandle', e.target.value)
}
}
</script>
由于 v-model
本质上是一个:value="属性"
和@input="事件"
的合写语法糖,于是我们可以把父组件中自己命名的属性名cityId
改为value
,并且把子组件传的事件名changeHandle
改为input
,这样就可以使用v-model
直接替换:cityId="selectId" @changeHandle="selecteId = $event"
,达到代码简写的效果
简写代码示意如下:
<BaseSelect v-model="selectId"></BaseSelect>
<!-- 等同于 -->
<BaseSelect :value="selectId" @input="selecteId = $event" />
注:在自定义事件中,
$event
是模板中事件触发时传入的第一个参数,而浏览器触发事件时传入的第一个参数正好是事件对象
.sync 修饰符
用于实现子组件与父组件数据的双向绑定,简化代码。本质也是一个语法糖,是 :属性名
和 @update:属性名
的合写
特点: prop 属性名,可以自定义,非固定为 value
以封装弹框类的基础组件为例:
- visible 属性:true 显示、false 隐藏
父组件使用:
<!-- 简写 -->
<BaseDialog :visible.sync="isShow" />
<!-- 全写 -->
<BaseDialog :visible="isShow" @update:visible="isShow = $event" />
子组件使用:
props: {
visible: Boolean
},
this.$emit('update:visible', false)
ref 和 $refs
ref
是一种在模板中给元素或组件添加引用的方式,通过使用ref
可以给元素或组件取一个名称,然后通过this.$refs
来访问引用对象。ref
用于在 Vue 实例中获取 DOM 元素的属性或方法,或调用组件实例的方法,其查找范围在当前组件内,更加精确与稳定
获取 DOM:
-
目标标签添加
ref
属性<div ref="divRef">我是被标记的容器</div>
-
恰当时机,通过
this.$refs.xxx
获取目标标签mounted () { console.log(this.$refs.divRef) }
获取组件:
-
目标组件添加
ref
属性<BaseForm ref="baseForm"></BaseForm>
-
恰当时机,通过
this.$refs.xxx
,获取目标组件,就可以调用组件对象里面的方法this.$refs.baseForm.组件方法()
Vue异步更新、$nextTick
在 Vue 中,DOM 的更新是异步执行的,这是因为 Vue 实际上进行了优化以提高性能和效率
当你修改 Vue 实例的数据时,Vue 会触发一个 DOM 更新队列。而 Vue 为了尽可能地减少对真实 DOM 的直接操作,会先将需要更新的 DOM 操作进行缓存,并通过异步方式在合适的时机一次性进行批处理
例如实现编辑标题,编辑框自动聚焦:
- 点击编辑,显示编辑框
- 让编辑框,立刻获取焦点
this.isShowEdit = true // 显示输入框
this.$refs.inp.focus() // 获取焦点
此处,显示编辑框之后,立刻获取焦点是不能成功的。原因是因为 Vue 是异步更新 DOM 的(提升性能)。如果你需要确保在 DOM 更新完成后执行某些操作,可以使用$nextTick
方法来处理
$nextTick
:等 DOM 更新后,才会触发执行此方法里的函数体。在支持 Promise 的环境中,返回一个 Promise 对象
语法: this.$nextTick(函数体)
使用$nextTick
既可实现点击编辑框后立刻获取焦点:
this.$nextTick(() => {
this.$refs.inp.focus()
})
自定义指令
基本语法
除了核心功能默认内置的指令 (v-model
和 v-show
),Vue 也允许注册自定义指令。当需要对普通 DOM 元素进行底层操作时,就会用到自定义指令(自己定义的指令, 可以封装一些 DOM 操作, 扩展额外功能)
全局注册:
Vue.directive('指令名', {
inserted(el) {
//可以对 el 标签拓展额外的功能
el.focus()
}
})
局部注册:
directives: {
"指令名": {
inserted(el) {
//可以对 el 标签拓展额外的功能
el.focus()
}
}
}
指令的值
在绑定指令时,可以通过“等号”的形式为指令绑定具体的参数值
<div v-color="color">我是内容</div>
通过 binding.value
可以拿到指令值,指令值修改会触发 update
函数
directives: {
color: {
// 参数1: el 指令绑定的元素对象
// 参数2: binding 指令绑定的参数, 对象
// 被绑定元素插入父节点时调用, 只执行一次
inserted(el, binding) {
el.style.color = binding.value
},
// 指令值修改会 触发 update 函数
update(el, binding) {
el.style.color = binding.value
}
}
}
插槽
默认插槽
默认插槽的主要作用是让组件内部的一些结构支持自定义
- 组件内需要定制的结构部分,改用
<slot></slot>
占位 - 使用组件时,
<MyDialog></MyDialog>
标签内部传入结构替换slot
插槽后备内容(默认值):
- 封装组件时,可以为预留的
<slot>
插槽提供后备内容(默认内容) - 语法:在
<slot>
标签内,放置内容, 作为默认显示内容 - 外部使用组件时,不传内容,则
slot
会显示后备内容;外部使用组件时,传入内容,则slot
整体会被替换
具名插槽
具名插槽,顾名思义就是具有名字的插槽,可以将一个组件中的多处结构进行定制
具名插槽语法:
-
多个 slot 使用 name 属性区分名字(子组件中)
<div class="dialog-header"> <slot name="head"></slot> </div> <div class="dialog-content"> <slot name="content"></slot> </div> <div class="dialog-footer"> <slot name="footer"></slot> </div>
-
template 配合
v-slot:插槽名
来分发对应标签(父组件中)<MyDialog> <template v-slot:head> 大标题 </template> <template v-slot:content> 内容文本 </template> <template v-slot:footer> <button>按钮</button> </template> </MyDialog>
v-slot:插槽名
可以简化成#插槽名
,此外插槽默认名为default
作用域插槽
定义 slot 插槽的同时,是可以传值的。插槽上可以绑定数据,将来使用组件时可以使用
作用域插槽: 将数据从子组件通过插槽传递给父组件
- 在子组件的
slot
上绑定数据 - 在父组件中使用
template
配合v-slot
取值
为了让 user
在父级的插槽内容中可用,我们可以将 user
作为 <slot>
元素的一个属性绑定上去:
<span>
<!-- 注意: :user 相当于往插槽上加了一个属性, 最终以对象包裹属性的形式传递过去{ user: user} -->
<slot v-bind:user="user">
{{ user.lastName }}
</slot>
</span>
绑定在 <slot>
元素上的属性被称为插槽 prop。现在在父级作用域中,我们可以使用带值的 v-slot
来定义我们提供的插槽 prop 的名字:
<current-user>
<!-- <template #default="slotProps"> -->
<!-- 如果不用 # 也可以不指定名字, 直接写为 v-slot="变量名",不过使用多个插槽时不能这么做 -->
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
</current-user>
在这个例子中,我们选择将包含所有插槽 prop 的对象命名为
slotProps
,也可以使用任意命名。此外,插槽也是可以解构的
插槽新老版本写法
<template>
<div id="app">
<slotA>
<!-- 具名插槽 -->
<!-- vue2.6.0以下版本的书写方式 -->
<div slot="header">
<div>
header头部
</div>
</div>
<!-- vue2.6.0以上版本书写方式 -->
<template v-slot:content>
<div>
content内容区位
</div>
</template>
<!-- vue2.6.0以上版本的书写语法糖 -->
<template #footer>
<div>
footer尾部
</div>
</template>
<!-- 作用域插槽 -->
<!-- vue2.6.0以下版本的书写方式 -->
<template slot="params" slot-scope="{names}">
姓名 {{names}}
</template>
<!-- vue2.6.0以上版本书写方式 -->
<template v-slot:gender="{gender}">
性别 {{gender}}
</template>
<!-- vue2.6.0以上版本的书写语法糖 -->
<template #age="{age}">
年龄 {{age}}
</template>
</slotA>
</div>
</template>
<script>
import slotA from './components/slot/slotA.vue'
export default {
components: { slotA }
}
</script>
了解新老版本写法,有助于理解 element ui 组件库中的部分插槽写法,element ui 使用的是旧版 vue 语法~
路由
SPA
SPA (Single Page Application) 单页面应用程序:所有功能在 一个 HTML 页面上实现
- 优点:按需更新性能高,开发效率高,用户体验好
- 缺点:学习成本高,首屏加载慢,不利于SEO
开发分类 | 实现方式 | 页面性能 | 开发效率 | 用户体验 | 学习成本 | 首屏加载 | SEO |
---|---|---|---|---|---|---|---|
单页 | 一个HTML页面 | 按需更新、性能高 | 高 | 非常好 | 高 | 慢 | 差 |
多页 | 多个HTML页面 | 整页更新、性能低 | 中等 | 一般 | 中等 | 快 | 优 |
单页面应用程序,之所以开发效率高,性能高,用户体验好,最大的原因就是按需更新
路由与VueRouter
路由是一种映射关系,在 Vue 中,路由是路径和组件的映射关系。根据路由就能知道不同的路径应该匹配渲染哪个组件
Vue Router 是 Vue 官方提供的路由管理器,它作为 Vue 的插件(第三方包),可以使你构建单页应用程序(SPA)时实现页面之间的导航和路由控制
Vue Router 允许你通过定义路由配置来映射 URL 路径到 Vue 组件,根据 URL 的变化来动态地加载和渲染相应的组件。使用Vue Router,你可以实现跳转到不同路由,而不需要刷新页面,从而实现更流畅和更快速的用户体验
VueRouter 基本使用步骤:
-
下载 VueRouter 模块到当前工程
npm i vue-router@3
-
引入
import VueRouter from 'vue-router'
-
安装注册
Vue.use(VueRouter)
-
创建路由对象
const router = new VueRouter()
-
注入,将路由对象注入到 new Vue 实例中,建立关联
new Vue({ render: h => h(App), router }).$mount('#app')
VueRouter 核心使用步骤:
-
创建需要的组件 (views目录),配置路由规则
import Find from './views/Find.vue' import My from './views/My.vue' import Friend from './views/Friend.vue' const router = new VueRouter({ routes: [ { path: '/find', component: Find }, { path: '/my', component: My }, { path: '/friend', component: Friend }, ] })
-
配置导航,配置路由出口 (路径匹配的组件显示的位置)
<div class="footer_wrap"> <!-- a 标签的 href 要以 # 开头, 不然就会变成页面跳转了! #为锚点--> <a href="#/find">发现音乐</a> <a href="#/my">我的音乐</a> <a href="#/friend">朋友</a> </div> <div class="top"> <!-- 配置路由出口 --> <router-view></router-view> </div>
组件分类与路由抽离
Vue 组件分为两类:
-
页面组件:
也称视图组件(View Components),用于表示应用程序中的不同页面或视图。这些组件通常与路由关联,根据路由路径呈现不同的页面内容。页面组件通常具有页面级别的功能和布局,并且可能包含其他复用组件
-
复用组件:
也称功能组件(Functional Components)或UI组件(UI Components),是可以在应用程序中多次使用的独立组件。这些组件通常是独立的、可组合的,用于表示应用程序中的特定功能或UI元素。复用组件是构建应用程序的基本构建块,它们在整个应用程序中可以重复使用,以实现代码重用和模块化
将组件分类更易维护:
src/views
文件夹或scr/pages
文件夹:页面组件,用于页面展示,配合路由用src/components
文件夹:复用组件,用于展示数据,常用于组件复用
将路由模块抽离出来,利于维护:
在 src
中新建 router
文件夹,文件夹内部新建 index.js
文件,将 main.js
中的路由相关代码移入 index.js
文件中,随后将路由对象导出并在 main.js
文件中引入即可
-
router
文件夹中的index.js
文件import VueRouter from 'vue-router' import Vue from 'vue' // Vue 的脚手架为我们配置了路径别名: @ 表示 src 的绝对路径 import Find from '@/views/Find.vue' import My from '@/views/My.vue' import Friend from '@/views/Friend.vue' Vue.use(VueRouter) // 帮我们自动注册两个组件: router-view router-link const router = new VueRouter({ routes: [ // 一个对象表示一条路由规则 // 路由规则就是路径和组件的映射关系 { path: '/find', component: Find }, { path: '/my', component: My }, { path: '/friend', component: Friend }, ] }) export default router
-
main.js
import Vue from 'vue' import App from './App.vue' import router from './router' Vue.config.productionTip = false new Vue({ render: h => h(App), router //挂载路由 }).$mount('#app')
路由懒加载
路由懒加载是一种优化技术,用于延迟加载网页中的路由组件。在 Vue 中,当应用初始化时,会将所有的路由组件一起打包到一个单独的 JavaScript 文件中。这意味着,无论用户实际需要访问哪个路由,都会在初始加载时一次性下载所有的路由组件,导致初始页面加载时间变长
而路由懒加载则可以使得页面初始加载时只下载当前所需的路由组件,而不是一次性下载全部的路由组件。当用户访问某个路由时,才会异步加载对应的路由组件。这样可以减少初始加载时间,提升网页性能和用户体验
在 Vue 中,可以通过使用动态导入(dynamic import)来实现路由懒加载。在路由配置中,使用import()
函数来导入路由组件,将其作为路由的component
选项,例如:
// import Home from '@/views/Home.vue'
// 替换成:() => import('@/views/Home.vue') 直接作为component选项
const routes = [
{
path: '/',
name: 'Home',
component: () => import('@/views/Home.vue')
},
{
path: '/about',
name: 'About',
component: () => import('@/views/About.vue')
},
];
const router = new VueRouter({
routes
});
在上述示例中,每个路由的component
选项使用了import()
函数,它会返回一个 Promise,用于异步加载对应的路由组件。这样,在初始加载页面时,只会下载首页所需的Home.vue
组件,而不会加载其他路由组件。当用户访问 About 页面时,才会异步加载并渲染About.vue
组件
声明式导航
导航链接
vue-router 提供了一个全局组件 router-link
(取代 a
标签)
-
能跳转,配置
to
属性指定路径(必须),本质还是a
标签 ,to
无需#
<router-link to="/路径值" ></router-link>
-
能高亮,默认就会提供高亮类名,可以直接设置高亮样式
-
router-link-active
模糊匹配 (用的多)to="/my"
可以匹配/my
、/my/a
、/my/b
... -
router-link-exact-active
精确匹配to="/my"
仅可以匹配/my
-
-
此外,类名还可以自定义 (在 VueRouter 配置对象中设置)
const router = new VueRouter({ routes: [...], linkActiveClass: "类名1", linkExactActiveClass: "类名2" })
跳转传参
查询参数(适合传多个参数)
-
跳转:
/path?参数名1=参数值1&参数名2=参数值2
<router-link to="/search?words=参数">传参</router-link>
-
接收:
$route.query.参数名
动态路径参数(适合传单个参数)
-
定义参数:在路由中配置(必须)
/path/:参数名
const router = new VueRouter({ routes: [ { path: '/home', component: () => import('@/views/Home.vue') }, // 谁接受参数谁配置 { path: '/search/:words', component: () => import('@/views/search.vue') } ] })
-
跳转:
/path/参数值
<router-link to="/search/参数">传参</router-link>
-
接收:
$route.params.参数名
-
动态路由参数可选符:如果不传参数也希望匹配,可以加个可选符
?
Vue路由重定向
重定向:匹配 path
后,强制跳转 path路径
语法: { path: 匹配路径, redirect: 重定向到的路径 }
示例如下:
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter);
const routes = [
{
path: '/',
redirect: '/home' // 将根路径重定向到/home
},
{
path: '/home',
component: () => import('@/components/Home.vue')
},
{
path: '/about',
component: () => import('@/components/About.vue')
}
];
const router = new VueRouter({
mode: 'history',
routes
});
export default router;
Vue路由404
当路径找不到匹配时,给个提示页面,配在路由最后
语法:path: '*' (任意路径)
(前面不匹配就命中最后这个)
示例如下:
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
component: () => import('@/components/Homepage'),
},
{
path:'*',
component: () => import('@/views/404.vue')
}
]
})
Vue路由模式设置
路由的路径带有 #
,看起来不自然,可以设置 history 路由模式,让 URL 看起来更像 URL
最常见的路由模式有两种:
- hash 路由(默认) 例如: http://localhost:8080/#/home
- history 路由(常用) 例如: http://localhost:8080/home(以后上线需要服务器端支持)
只需要在VueRouter构造函数中设置 mode 属性即可:
const router = new VueRouter({
mode: 'history',
routes: [...]
})
编程式导航
基本跳转
编程式导航顾名思义就是使用 JS 代码来进行跳转
两种跳转方式:
-
path 路径跳转
//方式一 this.$router.push('路由路径') //方式二 this.$router.push({ path: '路由路径' })
-
name 命名路由跳转
this.$router.push({ name: '路由名' }) //routes配置中: routes: [ ... { name: '路由名', path: '/path/xxx', component: XXX }, ]
跳转传参
两种传参方式:查询参数传参 + 动态路由传参
两种跳转方式,对于两种传参方式都支持:
-
path 路径跳转传参 (query传参)
//方式一 this.$router.push('/路径?参数名1=参数值1&参数名2=参数值2') //方式二 this.$router.push({ path: '/路径', query: { 参数名1: '参数值1', 参数名2: '参数值2' } })
-
path 路径跳转传参 (动态路由传参)
//方式一 this.$router.push('/路径/参数值') //方式二 this.$router.push({ path: '/路径/参数值' })
-
name 命名路由跳转传参 (query传参)
this.$router.push({ name: '路由名字', query: { 参数名1: '参数值1', 参数名2: '参数值2' } })
-
name 命名路由跳转传参 (动态路由传参)
this.$router.push({ name: '路由名字', params: { 参数名: '参数值', } })
路由嵌套
使用 children属性
即可实现路由嵌套,示例如下:
const router = new VueRouter({
routes: [
{
path: '/',
component: Layout,
redirect: '/article',
children: [
{ path: 'article', component: Article },
{ path: 'collect', component: Collect },
{ path: 'like', component: Like },
{ path: 'user', component: User },
]
},
{ path: '/detail/:id?', component: ArticleDetail},
],
})
不带 /
- 子路由不带
/
,是以相对路径进行访问的 - 例如嵌套路由中的父级路由路径是
path:'/questions'
,子路由的路径是path:'new'
,那么进行访问的时候,路由地址会拼接上父级路由的路径:http://localhost:8080/#/questions/new
带 /
- 默认以绝对路径进行访问
- 如果是子路由前面加
/
,就不会拼接上父级路由的path
路径,地址则为:http://localhost:8080/#/new
组件缓存
<keep-alive>
是 Vue 的内置组件,当它包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。<keep-alive>
是一个抽象组件,它自身不会渲染成一个 DOM 元素,也不会出现在父组件链中
此外, <keep-alive>
要求被切换到的组件都有自己的名字,不论是通过组件的 name
选项还是局部 / 全局注册
<keep-alive>
优点:
在组件切换过程中把切换出去的组件保留在内存中,防止重复渲染 DOM,减少加载时间及性能消耗,提高用户体验性
<keep-alive>
的三个属性:
-
include
:组件名数组,只有匹配的组件会被缓存 -
exclude
:组件名数组,任何匹配的组件都不会被缓存,二者都可以用逗号分隔的字符串、正则表达式或一个数组来表示<!-- 逗号分隔字符串 --> <keep-alive include="a,b"> <component :is="view"></component> </keep-alive> <!-- 正则表达式 (使用 v-bind) --> <keep-alive :include="/a|b/"> <component :is="view"></component> </keep-alive> <!-- 数组 (使用 v-bind) --> <keep-alive :include="['a', 'b']"> <component :is="view"></component> </keep-alive>
-
max
:数字,最多可以缓存多少组件实例<keep-alive :max="10"> <component :is="view"></component> </keep-alive>
<keep-alive>
的使用会触发两个生命周期函数
-
activated
:当组件被激活(使用)的时候触发(进入这个页面的时候触发)activated () { console.log('actived 激活 → 进入页面'); },
-
deactivated
:当组件不被使用的时候触发(离开这个页面的时候触发)deactivated() { console.log('deactived 失活 → 离开页面'); }
组件缓存后就不会执行组件的 created、mounted、destroyed 等钩子了。所以其提供了 actived 和 deactived 钩子,帮我们实现业务需求
导航守卫(路由守卫)
正如其名,vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。这里有很多方式植入路由导航中:全局的,单个路由独享的,或者组件级的
路由守卫有三种:
- 全局前置守卫: beforeEach、全局后置钩子:afterEach
- 独享守卫: beforeEnter、 beforeLeave
- 组件内守卫:beforeRouteEnter、 beforeRouteUpdate、 beforeRouteLeave
全局前置守卫
使用 router.beforeEach
注册一个全局前置守卫:
const router = createRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
// 返回 false 以取消导航
return false
})
当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于等待中
守卫有三个参数:
to
往哪里去, 到哪去的路由信息对象from
从哪里来, 从哪来的路由信息对象next()
是否放行(可选)- 如果
next()
调用,就是放行 next(路径)
拦截到某个路径页面
- 如果
可以返回的值如下:
false
: 取消当前的导航。如果浏览器的 URL 改变了(可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到from
路由对应的地址- 一个路由地址:通过一个路由地址跳转到一个不同的地址,就像你调用
router.push()
一样,你可以设置诸如replace: true
或name: 'home'
之类的配置。当前的导航被中断,然后进行一个新的导航,就和from
一样
如果遇到了意料之外的情况,可能会抛出一个 Error
。这会取消导航并且调用 router.onError()
注册过的回调
如果什么都没有,undefined
或返回 true
,则导航是有效的,并调用下一个导航守卫
全局后置钩子
全局后置钩子和守卫不同的是,这些钩子不会接受 next
函数也不会改变导航本身
router.afterEach((to, from) => {
sendToAnalytics(to.fullPath)
})
它们对于分析、更改页面标题、声明页面等辅助功能以及许多其他事情都很有用
独享守卫
单路由独享守卫,独享守卫只有前置没有后置
const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: (to, from, next) => {
// reject the navigation
return false
},
},
]
组件内守卫
你可以为路由组件添加以下配置:
beforeRouteEnter
beforeRouteUpdate
beforeRouteLeave
const UserDetails = {
template: ...,
beforeRouteEnter(to, from) {
// 在渲染该组件的对应路由被验证前调用
// 不能获取组件实例 `this` !
// 因为当守卫执行时,组件实例还没被创建!
},
beforeRouteUpdate(to, from) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
// 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
},
beforeRouteLeave(to, from) {
// 在导航离开渲染该组件的对应路由时调用
// 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
},
}
Vuex
概念
Vuex 是一个专门为 Vue.js 应用程序开发的状态管理模式。它通过提供一个集中存储管理应用的所有组件的状态,并提供了一些操作和方法来修改和访问这些状态。通过使用 Vuex,可以更好地组织和管理 Vue.js 应用程序的状态,提高代码的可维护性和可测试性。它适用于中大型的应用程序或需要共享状态的多个组件之间进行通信的场景
一般情况下,只有多个组件均需要共享的数据 ,才有必要存储在 Vuex 中,对于某个组件中的私有数据,依旧存储在组件自身的 data 中
使用 Vuex 优势主要有三点:
- 共同维护一份数据,数据集中化管理
- 响应式变化
- 操作简洁 (Vuex 提供了一些辅助函数)
Vuex使用
安装 Vuex 插件,初始化一个空仓库
-
安装 vuex,与 vue-router 类似,vuex 是一个独立存在的插件,如果脚手架初始化没有选 vuex,就需要额外安装。
npm i vuex@3
-
新建
store/index.js
专门存放 vuex。为了维护项目目录的整洁,在 src 目录下新建一个 store 目录,其下放置一个index.js
文件 (和router/index.js
类似)
-
创建仓库
store/index.js
// 导入 vue import Vue from 'vue' // 导入 vuex import Vuex from 'vuex' // vuex 也是 vue 的插件, 需要 use 一下, 进行插件的安装初始化 Vue.use(Vuex) // 创建仓库 store const store = new Vuex.Store() // 导出仓库 export default store
-
在
main.js
中导入挂载到 Vue 实例上import Vue from 'vue' import App from './App.vue' import store from './store' Vue.config.productionTip = false new Vue({ render: h => h(App), store }).$mount('#app')
此刻起, 就成功创建了一个 空仓库!!
Vuex 的核心概念
Vuex 的核心概念包括:State(状态)、Mutation(变化)、Action(动作)、Getter(获取器)、Module(模块)
State
State(状态): Vuex 使用单一状态树来管理应用的状态,也就是一个包含了所有状态的对象。通过将状态放在一个集中的地方,可以方便地跟踪和管理应用的状态
State 提供唯一的公共数据源,所有共享的数据都要统一放到 Store 中的 State 中存储。在 state 对象中可以添加我们要共享的数据:
// 创建仓库 store
const store = new Vuex.Store({
// state 状态, 即数据, 类似于 vue 组件中的 data,
// 区别在于 data 是组件自己的数据, 而 state 中的数据整个 vue 项目的组件都能访问到
state: {
count: 10
}
})
State数据访问
通过仓库直接访问
-
组件中可以使用 this.$store 获取到 vuex 中的 store 对象实例,可通过 state 属性属性获取 count:
<h1>state的数据---{{ $store.state.count }}</h1>
-
或者将 state 属性定义在计算属性中:
// 把 state 中数据,定义在组件内的计算属性中 computed: { count () { return this.$store.state.count } }
使用计算属性简化了代码,但是每次都需要依次提供计算属性,比较繁琐,vuex 辅助函数帮我们解决了这个问题
使用辅助函数 mapState
mapState 是辅助函数,帮助我们把 store 中的数据映射到组件的计算属性中
使用步骤:
-
导入 mapState ( mapState 是 vuex 中的一个函数 )
import { mapState } from 'vuex'
-
采用数组形式引入 state 属性
mapState(['count'])
-
利用展开运算符将导出的状态映射给计算属性
computed: { ...mapState(['count']) }
mutations
Mutation(变化): 变化是修改状态的唯一方式。它类似于事件,每个变化都有一个类型和一个处理函数。通过提交一个变化,可以触发状态的修改
事实上确实是可以直接修改 store 中的数据,无需通过 mutation,并且不会报错。但直接修改可能会造成很严重的后果, Vue 官方也不希望我们这么做,如若担心自己可能无意间直接修改了 store 中的数据,可以开启严格模式 strict: true
,让 Vue 来监督我们:
const store = new Vuex.Store({
strict: true
state: {
count: 0
},
// 定义mutations
mutations: {
}
// ...
})
访问mutations
state 数据的修改只能通过 mutations,并且 mutations 必须是同步的。mutations 是一个对象,对象中存放修改 state 的方法
mutations: {
// 第一个参数是当前 store 的 state 属性
// payload载荷 运输参数,调用mutaiions时,可以传递参数,传递载荷
addCount (state) {
state.count += 1
}
}
组件中提交 mutations(其实就是执行对应函数)
this.$store.commit('addCount')
带参数的mutations
提交 mutations 是可以传递参数的 this.$store.commit('xxx', 参数)
-
提供 mutations 函数
mutations: { ... addCount (state, count) { state.count = count } },
-
提交 mutations
handle ( ) { this.$store.commit('addCount', 10) }
提交的参数只能是一个, 如果有多个参数要传, 可以传递一个对象
辅助函数
mapMutations 和 mapState 很像,它把位于 mutations 中的方法提取了出来
import { mapMutations } from 'vuex'
methods: {
...mapMutations(['addCount'])
}
此时,就可以直接通过 this.addCount()
调用
<button @click="addCount">+1</button>
mutations 中不能写异步代码,如果有异步的 ajax 请求,应该放置在 actions 中
actions
Action(动作): action 类似于变化,它可以包含任意异步操作。它们是通过提交一个 action 来触发的,然后在 actions 中可以执行异步操作并通过提交变化来修改状态
state 用于存放数据,mutations 用于同步更新数据(便于监测数据的变化,更新视图等,方便于调试工具查看变化),actions 则负责进行异步操作
定义actions
-
actions 的函数第一个参数是 context (上下文对象,参数名可修改),可以理解为是一个简化版本的 store 对象,第二个参数同 mutations 一样,只能接受一个参数,多个参数用对象包裹后传递
actions: { setAsyncCount (context, num) { // 一秒后, 给一个数, 去修改 num setTimeout(() => { context.commit('changeCount', num) }, 1000) } },
-
原始调用
$store
(支持传参)setAsyncCount () { this.$store.dispatch('setAsyncCount', 666) }
辅助函数
actions 也有辅助函数(mapActions),可以将 actions 导入到组件中
import { mapActions } from 'vuex'
methods: {
...mapActions(['setAsyncCount'])
}
直接通过 this.方法
就可以调用
<button @click="setAsyncCount(200)">异步修改数值</button>
getters
Getter(获取器): 获取器用于从状态中派生出新的状态。它可以将现有的状态进行计算和转换,并提供一个类似于计算属性的方式来访问这些派生状态
-
例如,state 中定义了 list 数组,而在组件中,需要显示所有大于5的数据,正常的方式,是需要 list 在组件中进行再一步的处理,但是 getters 可以帮助我们实现它:
... state: { list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] } getters: { // getters函数的第一个参数是 state // 必须要有返回值 filterList: state => state.list.filter(item => item > 5) }
-
原始方式
$store
<div>{{ $store.getters.filterList }}</div>
Getter 辅助函数与上文类似,均是用于用于获取对象属性值的函数,简化了代码
-
mapGetters
computed: { ...mapGetters(['filterList']) }
-
使用
<div>{{ filterList }}</div>
modules
概念
Module(模块): 模块可以将整个状态树分割成更小的模块,每个模块都有自己的状态(state)、变化(mutation)、动作(action) 和获取器(getter)。这样可以将复杂的应用程序拆分成可维护和可测试的模块。通过模块化的方式,可以更好地组织和维护大型的状态管理
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿,于是就有了 Vuex 的模块化。模块化是使用 Vuex 的一种重要的模式,开发中基本上都是基于模块化思想来开发
理解 Vuex 中的模块,可以从以下几个方面来思考:
- 分割状态:一个大型的应用程序可能包含多个功能模块,每个模块都有自己的一组相关状态(state),例如用户信息、商品列表、购物车等。通过将这些状态划分成不同的模块,可以更好地组织和管理状态,使代码更清晰和可维护
- 调用命名空间:每个模块可以通过命名空间(namespace)来调用自己的状态、mutations、actions 和 getters,避免了全局命名冲突的问题。调用模块中的状态和操作时,需要在相应的模块名前加上模块的命名空间
- 可嵌套的模块:在 Vuex 中,模块可以嵌套定义,形成树状结构。这种嵌套关系可以反映实际应用程序中的结构关系,使代码更符合应用程序的逻辑结构
- 共享状态和模块间通信:通过模块,可以实现在不同模块间共享状态和进行模块间的通信。例如,一个模块可以通过getters 获取另一个模块的状态,或者通过 actions 调用其他模块的 mutations 来修改状态
- 代码重用和组合性:模块化的设计让我们可以更好地重用和组合代码。一个模块可以独立开发和测试,然后可以在其他地方多次使用,减少了代码的冗余和重复
综上所述,模块是 Vuex 中用于组织和管理状态的一种机制,通过将状态、mutations、actions 和 getters 划分为不同的模块,可以更好地组织和维护代码,提高代码的可维护性和扩展性
模块化示例
定义模块 user
user 中管理用户的信息状态:userInfo,位于modules/user.js
const state = {
userInfo: {
name: 'zs',
age: 18
}
}
const mutations = {}
const actions = {}
const getters = {}
export default {
state,
mutations,
actions,
getters
}
使用模块中的数据, 可以直接通过模块名访问
$store.state.模块名.xxx
,也可以通过 mapState 映射
命名空间 namespaced
默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的。这意味着,刚才的 user 模块,它的 action、mutation 和 getter 其实并没有区分,都可以直接通过全局的方式调用,如下图所示:
如果我们想保证内部模块的高封闭性,我们可以通过 namespaced 来设置
modules/user.js
const state = {
userInfo: {
name: 'zs',
age: 18
},
myMsg: '我的数据'
}
const mutations = {
updateMsg (state, msg) {
state.myMsg = msg
}
}
const actions = {}
const getters = {}
export default {
namespaced: true,
state,
mutations,
actions,
getters
}
提交模块中的 mutation
//全局的
this.$store.commit('mutation函数名', 参数)
//模块中的
this.$store.commit('模块名/mutation函数名', 参数)
namespaced: true
后要添加映射,可以加上模块名,找对应模块的 state/mutations/actions/getters
computed: {
// 全局的
...mapState(['count']),
// 模块中的
...mapState('user', ['myMsg']),
},
methods: {
// 全局的
...mapMutations(['addCount'])
// 模块中的
...mapMutations('user', ['updateMsg'])
}
关系图
转载自:https://juejin.cn/post/7255955134131093564