vue基础知识总结
# 基础知识
# 模板
vue.js的模板使用了基于html
的模板语法,在模板中数据的绑定是使用"Mustache"语法(双括号)。双括号会将数据解析成普通文本,而非html代码,如果想输出真正的html需要使用v-html
,双括号语法不能作用于Html Attribute上,这种情况需要借助v-bind指令。
<div v-bind:id="dynamicId"></div>
如果 dynamicId 的值是 null、undefined 或 false,则 disabled attribute 甚至不会被包含在渲染出来的 <div>
元素中。
在模板提供了完全的表达式支持,但每个绑定只能包含单个表达式,所以下面不会生效
<!-- 这是语句,不是表达式 -->
{{ var a = 1 }}
<!-- 流控制也不会生效,请使用三元表达式 -->
{{ if (ok) { return message } }}
2
3
4
5
提示
模板表达式都放在沙盒中,只能访问全局变量的一个白名单,不要在模板中试图访问用户自定义的全局变量
# 指令
指令 (Directives) 是带有 v- 前缀的特殊 attribute。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。
常用指令
- v-if 条件渲染指令,代表存在和销毁
- v-bind绑定指令,用来绑定属性(简写:)
- v-on事件绑定指令(简写@)
- v-for 循环指令
- v-text 内容显示为文本相当于{{}}
- v-html内容按普通 HTML 插入
- v-once只渲染一次
# 常用指令缩写
<!-- 完整语法 -->
<a v-bind:href="url">...</a>
<!-- 缩写 -->
<a :href="url">...</a>
<!-- 动态参数的缩写 (2.6.0+) -->
<a :[key]="url"> ... </a>
2
3
4
5
6
7
8
<!-- 完整语法 -->
<a v-on:click="doSomething">...</a>
<!-- 缩写 -->
<a @click="doSomething">...</a>
<!-- 动态参数的缩写 (2.6.0+) -->
<a @[event]="doSomething"> ... </a>
2
3
4
5
6
7
8
# 自定义指令
在 Vue 2,自定义指令是通过使用下面列出的钩子来创建的,这些钩子都是可选的
- bind - 指令绑定到元素后发生。只发生一次。
- inserted - 元素插入父 DOM 后发生。
- update - 当元素更新,但子元素尚未更新时,将调用此钩子。
- componentUpdated - 一旦组件和子级被更新,就会调用这个钩子。
- unbind - 一旦指令被移除,就会调用这个钩子。也只调用一次。
<p v-highlight="yellow">高亮显示此文本亮黄色</p>
2
Vue.directive('highlight', {
bind(el, binding, vnode) {
el.style.background = binding.value
}
})
2
3
4
5
# computed 和watch
computed:🎉:
- 计算属性的结果会被缓存,除非依赖的响应式 property 变化才会重新计算。
- 不支持异步,当computed内有异步操作时无效,无法监听数据的变化
- 如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个多对一或者一对一,一般用computed
模板内的表达式逻辑复杂时应当使用computed计算属性。
它为什么需要缓存:因为一个开销比较大的计算属性A,它需要遍历一个巨大的数组做大量计算,然后我们有其它属性依赖于A。如果它没有做缓存,我们将不可避免的多次执行A的getter。
watch:🎉:
- 不支持缓存,数据变,直接会触发相应的操作
- watch支持异步
- 当一个属性发生变化时,需要执行对应的操作;一对多
- 监听数据必须是data中声明过或者父组件传递过来的props中的数据,当数据变化时,触发其他操作,函数有两个参数:
- watch加immediate才能触发立即执行
immediate:组件加载立即触发回调函数执行,
deep: 深度监听,为了发现对象内部值的变化,复杂类型的数据时使用,例如数组中的对象内容的改变,注意监听数组的变动不需要这么做。注意:deep无法监听到数组的变动和对象的新增,参考vue数组变异,只有以响应式的方式触发才会被监听到。
# v-for循环
v-for指令循环要携带key,因为dom做diff对比时,会把开始和结束做一系列对比,命中则拿新节点的key对比旧节点的key,未对应上会new element,如果key对应上再用旧节点的tag和新节点的tag是同样的,会认为这是一个旧节点。减少对比次数。
# 生命周期
beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、activated、deactivated、beforeDestroy、destroyed、errorCaptured
一般将ajax请求放到mounted中,放到create中会被重复执行
生命周期函数
在父子组件中,在某个阶段,会先执行父组件生命周期,再执行子组件生命周期,子组件先完成生命周期然后父组件完成生命周期如:
父beforeCreate 父created
子beforeCreate 子created
beforeCreate 数据检测事件配置之前调用
create 数据监测、事件(watch、event)生效后调用
beforeMount 相关的render首次被调用后执行
mounted 实例挂载后调用
beforeUpdate 数据更新时调用,在虚拟dom打补丁之前,适合更新访问现有dom,比如移除已添加的事件监听
update 数据已经更新,避免此期间更改状态。update不保证所有子组件的更新,如果想在整个视图绘制完毕可以在updated中用vm.$nextTick中执行
activated 被keep-alive缓存的组件激活时调用
deactivate 被keep-alive缓存的组件停用时调用
beforeDestroy 实例销毁之前调用,在这一步实例仍然完全可用。解绑自定义事件event.$off、清除定时器、解绑自定义的dom事件,如window.scroll等
destroyed 实例销毁后调用对应的vue指令解绑、事件监听移除、子实例也被销毁。
errorCaptured 捕获一个来自子孙组件的错误时调用。此钩子会收到三个参数:错误对象、发生错误的组件实例、错误来源信息字符串。此钩子可以返回false以阻止该错误继续向上传播。
# 组件通信
- 父组件A向子组件B传递数据可以通过props向下传递给子组件。不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。
提示
注意在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变变更这个对象或数组本身将会影响到父组件的状态。
- 子组件向父组件传递值通过事件的形式。子组件通过$emit向父组件调用时携带的事件发送数据
- 通过sync修饰符对一个prop进行双向数据绑定 相当于第1,2的简写方式
// 子组件
this.$emit('update:title', newTitle)
// 父组件
<text-document
v-bind:title="doc.title"
v-on:update:title="doc.title = $event"
></text-document>
// 简写
<text-document v-bind:title.sync="doc.title"></text-document>
4. 自定义事件
5. slot 作用域插槽
6. vuex
//修改方式: 1)可以直接使用 this.$store.state.变量 = xxx;
2)this.$store.dispatch(actionType, payload)
或者:this.$store.commit(commitType, payload)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 自定义组件v-model
一个组件v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value attribute 用于不同的目的。model 选项可以用来避免这样的冲突:
// 子组件
Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`
})
// 父组件
<base-checkbox v-model="lovingVue"></base-checkbox>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# nextTick
vue是异步渲染的,data修改后dom不会李可渲染。$nextTick会在dom渲染之后被触发。以获取最新的dom节点。
Vue 会根据当前浏览器环境优先使用原生的 Promise.then 和 MutationObserver,如果都不支持,就会采用 setTimeout 代替,目的是 延迟函数到 DOM 更新后再使用。
Vue $nextTick 原理 (opens new window)
# 动态组件&异步组件
- 动态组件
<!-- 失活的组件将会被缓存!-->
<keep-alive>
<!-- 动态组件 -->
<component v-bind:is="currentTabComponent"></component>
</keep-alive>
2
3
4
5
- 异步组件 在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。
Vue.component('async-example', function (resolve, reject) {
setTimeout(function () {
// 向 `resolve` 回调传递组件定义
resolve({
template: '<div>I am async!</div>'
})
}, 1000)
})
Vue.component('async-webpack-example', function (resolve) {
// 这个特殊的 `require` 语法将会告诉 webpack
// 自动将你的构建代码切割成多个包,这些包
// 会通过 Ajax 请求加载
require(['./my-async-component'], resolve)
})
Vue.component(
'async-webpack-example',
// 这个动态导入会返回一个 `Promise` 对象。
() => import('./my-async-component')
)
new Vue({
// ...
components: {
'my-component': () => import('./my-async-component')
}
})
const AsyncComponent = () => ({
// 需要加载的组件 (应该是一个 `Promise` 对象)
component: import('./MyComponent.vue'),
// 异步组件加载时使用的组件
loading: LoadingComponent,
// 加载失败时使用的组件
error: ErrorComponent,
// 展示加载时组件的延时时间。默认值是 200 (毫秒)
delay: 200,
// 如果提供了超时时间且组件加载也超时了,
// 则使用加载失败时使用的组件。默认值是:`Infinity`
timeout: 3000
})
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
提示
注意如果你希望在 Vue Router 的路由组件中使用上述语法的话,你必须使用 Vue Router 2.4.0+ 版本。
# keep-alive
- 缓存组件
- 频繁切换不需要重复渲染
- 与v-show的区别是,v-show是css样式来实现,keep-alive是vue层的控制,简单的可用v-show,像tab切换这种复杂的可以keep-alive
Props:
- include - 字符串或正则表达式。只有名称匹配的组件会被缓存。
- exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
- max - 数字。最多可以缓存多少组件实例。
include 和 exclude prop 允许组件有条件地缓存。二者都可以用逗号分隔字符串、正则表达式或一个数组来表示:
<!-- 逗号分隔字符串 -->
<keep-alive include="a,b"> // 这里的a,b是注册的component名
<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>
2
3
4
5
6
7
8
9
10
11
12
13
14
有keep-alive时生命周期:
加载时:parent-beforeCreate ==> parent-created ==> parent-beforeMount ==> child-beforeCreate ==> child-created ==> child-beforeMount ==> child-Mounted ==> child-actived ==> parent-mounted
销毁时:parent-beforeDestroy==> child-deactived ==> child-beforeDestroy ==> child-Destroyed ==> parent-Destroyed
# mixin
将多个组件相同的逻辑抽离出来放到一起,引用的钩子,按照传入顺序依次调用,并在调用组件钩子之前会被调用。
问题:
- 变量来源不明确
- 多个可能会有变量冲突
- 和组件可能出现多对多关系,复杂度较高
# vue-router
vue-router的模式分三种:"hash" | "history" | "abstract"
默认值是: "hash" (浏览器环境) | "abstract" (Node.js 环境)
- hash: 使用 URL hash 值来作路由。支持所有浏览器,包括不支持 HTML5 History Api 的浏览器。
- history: 依赖 HTML5 History API 和服务器配置。查看 HTML5 History 模式 (opens new window)。
- abstract: 支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式。
创建路由常用参数
- path 路径
- name 命名路由
- components 命名视图组件
- meta
- alias
- children 嵌套路由
- redirect 重定向
- props // 2.6.0+
- caseSensitive?: boolean, // 匹配规则是否大小写敏感?(默认值:false)
- pathToRegexpOptions?: Object // 编译正则的选项
# router实例方法
全局钩子函数
- router.beforeEach 全局前置守卫,在路由切换开始时调用,必须调用
next
。当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。
next方法
next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true、name: 'home' 之类的选项以及任何用在 router-link 的 to prop 或 router.push 中的选项。
next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。
- router.beforeResolve 全局解析守卫,必须调用
next
在 2.5.0+ 你可以用 router.beforeResolve 注册一个全局守卫。这和 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后
,解析守卫就被调用。
- router.afterEach 路由切换离开时调用,钩子不会接受 next 函数也不会改变导航本身
- router.beforeEnter 路由独享的守卫。这些守卫与全局前置守卫的方法参数是一样的。
- 组件内的守卫
- beforeRouteEnter
- beforeRouteUpdate (2.2 新增)
- beforeRouteLeave
const Foo = { template: `...`, beforeRouteEnter(to, from, next) { // 在渲染该组件的对应路由被 confirm 前调用 // 不!能!获取组件实例 `this` // 因为当守卫执行前,组件实例还没被创建 // 你可以通过传一个回调给 next来访问组件实例。 }, beforeRouteUpdate(to, from, next) { // 在当前路由改变,但是该组件被复用时调用 // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候, // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。 // 可以访问组件实例 `this` }, beforeRouteLeave(to, from, next) { // 导航离开该组件的对应路由时调用 // 可以访问组件实例 `this` } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 - 编程式导航
- router.push
- router.replace
- router.go
- router.back
局部到单个路由
- beforeEnter
组件的钩子路由
- beforeRouterEnter
- beforeRouterUpdate
- beforeRouterLeave
🎉 全局路由的使用
router.beforeEach((to, from, next) =>{
// to 表示即将进入的目标对象
// from 当前要离开的路由对象
// next 是一个函数 调用resolve 执行下一步
})
2
3
4
5
# 完整的导航解析流程
钩子被触发顺序: beforeEach全局路由守卫 => beforeEnter 路由独享守卫 => beforeRouteEnter 组件内的守卫 => beforeResolve全局解析守卫 => afterEach全局后置钩子
提示
如果是根路径的话beforeEnter路由独享守卫不会执行。
如果是一个组件路由切换到另外一个组个,会先触发组件内beforeRouteLeave再触发beforeEach全局路由守卫
- 导航被触发。
- 在失活的组件里调用 beforeRouteLeave 守卫。
- 调用全局的 beforeEach 守卫。
- 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
- 在路由配置里调用 beforeEnter。
- 解析异步路由组件。
- 在被激活的组件里调用 beforeRouteEnter。
- 调用全局的 beforeResolve 守卫 (2.5+)。
- 导航被确认。
- 调用全局的 afterEach 钩子。
- 触发 DOM 更新。
- 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。
# vue3的差异
- 去掉了filters
- 以前用prototype,现在不建议用
- ref不能直接使用
- 添加了setup
- 添加组合式api
- 生命周期
- 定义props的方式不同
# 父子组件传参
Vue 3中使用 emit 和 update: 来实现父子组件之间的数据传递。
update: 用于在父组件中通过 v-model 绑定子组件的数据,并在子组件中触发一个 update:modelValue 事件来更新数据。例如,在子组件中:
<!-- 子组件 -->
<template>
<input :value="modelValue" @input="updateValue">
</template>
<script>
export default {
props: ['modelValue'],
methods: {
updateValue(event) {
this.$emit('update:modelValue', event.target.value);
}
}
}
</script>
<!-- 父组件 -->
<template>
<child-component v-model="data"></child-component>
</template>
<script>
export default {
data() {
return {
data: 'initial data'
}
}
}
</script>
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
27
28
29
30
# 定义props
<!-- 子组件声明了的 props ,若父组件未传,则该值为 undefined -->
<script lang='ts' setup>
const props = defineProps({
child: {
type:String, // 参数类型
default: 1, //默认值
required: true, //是否必传
validator: value => {
return value >= 0 // 除了验证是否符合type的类型,此处再判断该值结果是否符合验证
}
},
sda: String, //undefined
strData: String,
arrFor: Array
})
</script>
<!-- 类型声明方式 -->
<script lang='ts' setup>
const props = defineProps<{
either: '必传且限定'|'其中一个'|'值', // 利用TS:限定父组件传 either 的值
child?: string|number,
strData?: string,
arrFor: any[]
}>();
console.log(props);
</script>
<!-- 类型声明添加默认值 :默认值为引用类型的,需要包装一个函数 return 出去。-->
<script lang='ts' setup>
interface Props {
either: '必传且限定'|'其中一个'|'值', // 利用TS:限定父组件传 either 的值
child: string|number,
sda?: string, // 未设置默认值,为 undefined
strData: string,
msg?: string
labels?: string[],
obj?:{a:number}
}
const props = withDefaults(defineProps<Props>(), {
msg: 'hello',
labels: () => ['one', 'two'],
obj: () => { return {a:2} }
})
</script>
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 其它
- 从elementUi中
el-dialog
转到elementPlus后,visible.sync修改为v-model="visible"