Vue2
脚手架安装和使用 - VueCli
1 | npm install -g @vue/vli |
项目创建
1 | vue create <my-project> |
项目目录结构
vue MVVM架构模式
- M : model 数据模型 模型代表应用程序的数据层。在 Vue 中,模型通常是通过 Vue 实例的数据属性(data)来表示。
- V: view 视图 视图是用户界面的结构和外观。在 Vue 中,视图是由模板(template)编写的
- VM: viewModel 视图模型 视图模型是连接视图和模型的桥梁,负责处理视图的逻辑和状态。在 Vue 中,视图模型主要由 Vue 实例的选项和方法组成,包括计算属性、监听属性变化、方法等。
在 Vue 中,数据流动的方向是单向的
- 视图 -> 模型:用户通过视图操作,例如输入框输入文字,会更新模型中的数据。
- 模型 -> 视图:模型中的数据变化会自动更新到视图中,实现了响应式的视图更新。
Vue 的 MVVM 架构模式使得前端开发者可以更加高效地管理和维护应用程序的状态和逻辑,同时提升了代码的可读性和可维护性。
vue.config.js 配置
1 | const {defineConfig} = require('@vue/cli-service') |
vue 组件 <style>
的 scoped和template 标签
在vue单文件里面可以给样式添加scoped,可以将选择器变成属性选择器,解决css样式冲突问题,保证唯一性。
vue2 <template>
标签只能允许存在一个根节点
vue 插值表达式
{{ number + 1 }}
{{ number > 10 ? '数值大于10' : '数值小于10' }}
使用双花括号,这些表达式会在所属的vue实例的数据作用下作为javascript被解析
指令
v-bind 简写为 :
v-bing 可以灵活的给标签属性通过vue变量去定义,可以动态地绑定多个 attribute
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22<template>
<img v-bing:src="imgSrc">
<img :src="imgSrc">
</template>
<script>
export default{
name: 'App',
data(){
return {
imgSrc: require('../images/logo.png'),
/*
!!! 在require使用变量赋值图片时需要注意事项
当直接在src属性使用路径,webpack会将小图片处理成base64格式,以减少http请求
当使用变量赋值时,会导致图片存放在public,并不是base64格式,导致图片渲染失败,可以使用require语法可以避免
如果大图片webpack打包就不会经过base64编码,会打包编码成后缀名带8位哈希值的图片路径,放在img/根目录下
如果不用require,则根目录是在public/ 静态资源下
*/
}
}
}
</script>
1 | <template> |
v-on 简写 @
点击事件 @click
- 参数event
- 修饰符
- .stop 调用 event.stopPropagation() 阻止事件冒泡
- .prevent 调用event.preventDefault() 阻止事件默认行为
- .capture 添加事件侦听器时使用capture模式
- .self 只当事件是从侦听器绑定的元素本身触发时才触发回调
- .{keyCode| keyAlias} 只当事件是从特定键触发时才触发回调
- .native 监听组件根元素的原生事件
- .once 只触发一次回调
- .left (2.2.0) 只点击鼠标左键时触发
- .right (2.2.0) 只点击鼠标右键时触发
- .middle (2.2.0) 只点击鼠标中键时触发
- .passive (2.3.0) 以 {passive: true} 模式添加侦听器
鼠标事件 @
键盘事件 @keyup
- .enter
- .tab
- .delete ( 删除和退格)
- .esc
- .space
- .up
- .down
- .left
- right
页面滚动
动态事件绑定 (2.6.0+)
v-on:[event] = “fn”
通过vue变量event ,绑定事件;可以通过切换event,从而切换不同事件类型
v-model 双向数据绑定
- 修饰符
- .lazy v-model 是在每次input 事件触发后将值与数据进行同步,lazy则是在change事件之后进行同步(失焦)
- .unmber 将用户输入的值转为数值类型
- .trim 过滤首尾空白字符串
- 修饰符
v-show 元素显示隐藏
v-text
v-html
v-if & v-else & v-else-if
v-for 建议绑定的key为数据的id而不是数组下标index,数组下标为key会导致插入新数据,数据对不上的问题;原理:就地更新-默认原地址修改元素内容而不是移动它们,
数组的更新检测
变更方法 - 会改变原数组 ( 会触发页面更新)
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
非变更方法 - 不会改变原数组,返回新数组 ( 不会触发页面更新)
filter()
concat()
slice()
修改数组数据
1
2
3
4update(){
// 解决方法使用this.$set()
this.$set(this.list, 0, '新数据')
}
过滤器
可用于一些常见的文本格式化
双花括号形式
1
{{ message | filterFn }
在v-bind中绑定
1
<div v-bind:id="message | filterFn"></div>
定义过滤器
- vue单文件组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<script>
export defalut {
name: 'filterModel',
data() {
return {}
},
filters: {
filterFn: function(value){
if(!value) return '';
value = value,toString();
return value.charAt(0).tiUpperCase()
}
}
}
</script>- 全局中定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTop = false
// 定义全局过滤器
Vue.filter( 'filterFn', function(value){
if(!value) return '';
value = value,toString();
return value.charAt(0).tiUpperCase()
})
// 初始化Vue实例
new Vue({
// 在id为app的div盒子上渲染App.vue组件
render:h => h(App),
}).$mount('#app') // 挂载容器,将vue实例插入到id为app的div盒子
计算属性
一个数据需要依赖另外一些数据计算得到的结果;
定义计算属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21<template>
<div>
{{ reversedMessage }}
</div>
</template>
<script>
export defalut {
name: 'filterModel',
data() {
return {
message: "Hello",
mes: "word"
}
},
computed: {
reversedMessage: function (){
return this.message + ' ' + this.mes
}
}
}
</script>计算属性 & 方法
计算属性是有缓存的;基于它们的响应式依赖进行缓存
计算属性细节
计算属性默认只有getter, 不过在需要时也可以提供setter
1 | <template> |
监听器
vue通过watch来响应数据变化
1 | <template> |
组件基础
一个页面可以拆分成多个组件组成一个完整页面,将组件封装可以实现组件的复用,代码冗余问题,易维护
组件& 模块化;
模块化
模块化封装的是js功能
组件
组件除了js功能外还含有页面结构,样式,交互
组件使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// son.vue
<template>
<div>
{{Son组件}}
</div>
</template>
<script>
export default {
name : 'Son',
data() {
return
},
}
</script>
<style></style>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26// parent.vue
<template>
<div>
{{parent}}
</div>
<Son></Son>
</template>
<script>
// 引入组件
import Son from './Son.vue'
export default {
name: 'Parent',
data() {
return {
}
},
// 组件使用
components:{
// Son : Son 简写
Son
}
}
</script>全局注册组件
1
2
3
4
5
6
7
8
9
10
11
12
13// main.js
import Vue from 'vue'
import App from './App.vue'
import Son from './Son.vue'
Vue.Config.productionTip = false
// 全局注册组件
Vue.component('Son', Son)
new Vue({
render: h=>h(App),
}).$mount('#app')
props 传值 ( 单向数据流)
props可以将数据从父组件传递给子组件
在子组件,props的数据是只读的不能修改的;如果要修改可以使用emit 通知父组件去修改,或者用计算属性来中转
1 | // parent.vue |
1 | // Son.vue |
props 定义 默认值,数据类型,是否一定要传该属性
1 | // Son.vue |
$emit()
在子组件中触发自定义事件,将参数回传给父组件
1 | // parent.vue |
1 | // Son.vue |
发布订阅者模式 - $on $emit
- 在Vue实例对象中,prototype原型对象中,存在$emit (发布者[ 触发 ]) 和 $on (订阅者[ 监听 ]) 方法 ( Vue 3弃用了$emit 和 $on 方法,引入了Composition API ,使用
provide
和inject
来实现基于组合的发布订阅模式。)
eventBus 事件总线( 兄弟组件通信)
通过订阅发布者模式,通过同一个vue实例的$emit 和$on 就可以获取到信息;兄弟两个组件分别为两个不同的vue实例;为了保证实例的一致性。需要额外新建一个文件 eventBus 创建vue实例
1 | // ./EventBus/index.js |
全局引入 (或局部引入)
1
2
3
4
5
6
7
8
9
10
11
12// main.js
import Vue from 'vue';
import App from './App.vue';
import EventBus from './EventBus/index'
Vue.config.productionTip = false;
// 全局引入eventBus-事件总线
Vue.prototype.$eventBus = EventBus;
new Vue({
render: h=>h(App),
}).$mount('#app')brother1 组件
1
2
3
4
5
6
7
8
9
10
11
12
13<templage></templage>
<script>
export default {
name: 'brother1',
// 在created生命周期钩子函数中监听(订阅),监听到后执行回调
created(){
this.$eventBus.$on('send',(value)=>{
console.log('$on-send:'+value)
})
}
}
</script>brother2 组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<templage></templage>
<script>
export default {
name: 'brother2',
methods: {
subFn(){
// 触发(发布)
this.$eventBus.$emit('send','触发事件')
}
}
}
</script>
vue 生命周期
创建
- beforeCreate
- 访问不到实例数据,还没有被创建出来data和methods中的数据还没有初始化
- created
- 数据初始化完成,可以访问实例数据;
- 发起请求,或者准备数据等 —
created
是在组件实例一旦创建完成的时候立刻调用,这时候页面dom
节点并未生成;mounted
是在页面dom
节点渲染完毕之后就立刻执行的;mounted
中的请求有可能导致页面闪动; 因为页面dom
结构已经生成,所以放在created中更合适
挂载
- beforeMount
- 在 Vue 实例挂载开始之前被调用的生命周期钩子函数。
- 获取不到真实Dom;在
beforeMount
钩子函数中,可以访问到 Vue 实例的$el
属性,它是虚拟 DOM 的根节点。此时,虚拟 DOM 已经被 Vue 编译器处理过,但尚未挂载到真实的 DOM 上 - 该钩子函数中执行一些需要在挂载前操作的任务,例如访问和修改虚拟 DOM 的结构
- mounted
- 可以获取到真实Dom,在
mounted
钩子函数中,Vue 实例已经被挂载到 DOM 上,此时可以访问和操作真实的 DOM 元素。 - 需要依赖于已渲染的真实 DOM 元素进行操作,通常建议在
mounted
钩子函数中进行
- 可以获取到真实Dom,在
更新
不要更新钩子函数中修改数据,会触发死循环
- beforeUpdate
- 获取的值还是旧的值 — 数据已经更新,但还没有渲染
- updated
- 能够获取到更新后的值
Keepalive
- activated
- deactivated
销毁
- beforeDestroy
- 实例销毁前调用,实例还完全可用
- destoryed
- 实例已经被销毁,所有指令被解绑,所有事件监听器被移除,子实例也被移除
组件异常捕获
- errorCaptured
- 捕获后代组件的错误
- 钩子会接受3各参数;错误对象,发生错误的组件实例,包含错误来源信息的祖父穿;可以return 一个boolean来是否将该错误向上传递
嵌套组件生命周期
- 初始化 — 创建 挂载阶段
父组件 beforeCreated — 父组件 created — 父组件 beforeMount — 子组件beforeCreate — 子组件 created —子组件 beforeMount — 子组件 Mounted — 父组件 Mounted
更新
父组件更新只执行父组件的更新钩子函数
父组件 beforeUpdate — 父组件 Updated
子组件更新只执行子组件的更新钩子函数
子组件 beforeUpdate — 子组件 Updated
销毁子组件
父组件 beforeUpdate — 子组件 beforeDestroy — 子组件 destroyed — 父组件 updated
全局错误捕获 — config.errorHandler
- 使用方法与生命周期异常捕获钩子函数一致
1 | // main.js |
ref
- 可以获取真实Dom元素; 如果是一个vue组件,则获取的是vue组件实例
1 | <templage> |
nextTick([callback])
vue中更新Dom是异步的;会导致 数据更新延迟
nextTick 可以等待Dom更新之后再执行
1 | <template> |
内置组件Component
在 Vue 应用中实现动态组件渲染、按需加载组件、条件渲染等功能,从而使应用更加灵活和高效
props
- is
- inline-template
用法
渲染一个元组件为动态组件。通过is,来决定渲染那个组件
1 | <template> |
keep-alive内置组件
可以缓存组件,每次切换组件的时候不会重复创建挂载,只会出发首次的挂载生命周期,后续只会触发keep-alive的activated和deactivated的生命周期
1 | <template> |
插槽
默认插槽
通过调用组件的方式,使用双标签写法,将包裹的元素传递给组件,组件使用
1 | // Son.vue |
1 | // parent.vue |
具名插槽
将多个元素利用命名来分别占位到子组件的不同的
子组件命名使用 name属性; 父组件使用v-slot:
1 | // Son.vue |
1 | //parent.vue |
作用域插槽
让父组件使用插槽时可以访问子组件内部的数据 , 子组件使用 :row 属性将数据暴露,row并非固定命名;父组件使用v-slot=”scope”,接收 使用scope. 就能使用,scope并非固定命名
#title=”scope” 是具名插槽加作用域的写法,同一标签中不能使用多个v-slot指令
1 | // Son.vue |
1 | // Son.vue |
自定义指令
除了可以用封装组件的形式来代码复用和抽象,也可以使用自定义指令。
- 全局指令创建
1 | // main.js |
- 局部指令创建
1 | <template> |
- 案例- 访问当前页面时,input自动聚焦 - v-focus
1 | <template> |
案例 - 按钮级权限( 显示或隐藏判断) - v-has
pass
自定义指令传参
pass
路由VueRouter 3
vue-router使用
- 封装路由规则
1 | // /router/index.js |
- 挂载封装好的router
1 | // main.js |
- App.vue中使用内置路由占位组件 - RouteView (router-view)
1 | // ./src/App.vue |
RouteLink 内置组件
页面导航,最终会被翻译为a标签,to属性翻译成herf
1 | // ./src/App.vue |
- 与a标签区别
动态路由匹配 - 路由传参
params
模式 | 匹配路径 | $route.params |
---|---|---|
/user/:username | /user/evan | { username: 'evan' } |
/user/:username/post/:post_id | /user/evan/post/123 | { username: 'evan', post_id: '123' } |
路由规则为
1
2
3
4
5
6
7import User from '@/view/userList.vue'
const router = new VueRouter({
routes: [
// 动态路径参数 以冒号开头
{ path: '/user/:id', component: User }
]
})在模板中匹配 ( 使用 $route.params 获取参数)
1
2
3
4
5
6
7// App.vue
<template>
<div>
<router-link to="/user/100">用户</router-link>
</div>
<RouterView />
</template>1
2
3
4// userList.vue
<template>
<div> 传递参数 {{ $route.params.id}}</div>
</template>
query
路由规则为
1
2
3
4
5
6
7import User from '@/view/userList.vue'
const router = new VueRouter({
routes: [
// 动态路径参数 以冒号开头
{ path: '/user', component: User }
]
})在模板中匹配 ( 使用 $route.query 获取参数)
1
2
3
4
5
6
7// App.vue
<template>
<div>
<router-link to="/user?username='张三'&age='23'">用户</router-link>
</div>
<RouterView />
</template>1
2
3
4// userList.vue
<template>
<div> 传递参数 {{ $route.query.username}} - {{ $route.query.age}}</div>
</template>
路由重定向
路由规则
1
2
3
4
5
6
7
8
9
10
11import User from '@/view/userList.vue'
const router = new VueRouter({
routes: [
// 动态路径参数 以冒号开头
{ path: '/user', component: User, name: 'user' },
// 利用path
{ path: '/userList', redirect: '/user'},
// 利用命名
{ path: '/userList', redirect: {name: 'user'}
]
})
404 页面
路由规则是从上到下检索的,所以404需要放在最下面。
路由规则
1
2
3
4
5
6
7
8
9
10
11
12
13import User from '@/view/userList.vue'
const router = new VueRouter({
routes: [
// 动态路径参数 以冒号开头
{ path: '/user', component: User, name: 'user' },
// 利用path
{ path: '/userList', redirect: '/user'},
// 或利用命名
{ path: '/userList', redirect: {name: 'user'},
// 上面的路由没有匹配到,剩下的所有 * 多会进入404页面
{ path: '*', component: () => import('./views/404.vue')}
]
})
编程式导航
router.push()
声明式 | 编程式 |
---|---|
<router-link :to="..."> |
router.push(...) |
使用方法
在 Vue 2,你可以通过
$router
访问路由实例。因此你可以调用this.$router.push
。1
2
3
4
5
6
7
8
9
10
11// 字符串
router.push('home')
// 对象
router.push({ path: 'home' })
// 命名的路由
router.push({ name: 'user', params: { userId: '123' }})
// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})
router.replace
跟 router.push
很像,唯一的不同就是,它不会向 history 添加新记录, 而是会替换掉当前的 history 记录。
声明式 | 编程式 |
---|---|
<router-link :to="..." replace> |
router.replace(...) |
router.go(n)
这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似 window.history.go(n)
。
使用方法
1
2
3
4
5
6
7
8// 在浏览器记录中前进一步,等同于 history.forward()
router.go(1)
// 后退一步记录,等同于 history.back()
router.go(-1)
// 前进 3 步记录
router.go(3)
嵌套路由
在一个路由里面嵌套其他子路由,在路由规则中使用children定义子路由
路由规则
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22const router = new VueRouter({
routes: [
{
path: '/user',
component: User,
children: [
{
// 当 /user/profile 匹配成功,
// UserProfile 会被渲染在 User 的 <router-view> 中
path: 'profile',
component: UserProfile
},
{
// 当 /user/posts 匹配成功
// UserPosts 会被渲染在 User 的 <router-view> 中
path: 'posts',
component: UserPosts
}
]
}
]
})
Router实例属性
- 导航守卫 - router.beforeEach()