生命周期
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<script>
export default {
// 实例初始化之前
beforeCreate() {
console.log('home', 'beforeCreate')
},
// 初始化实例(DOM还未挂载)
created() {
console.log('home', 'created')
},
// 组件挂载之前,render函数首次被调用
beforeMount() {
console.log('home', 'beforeMount')
},
// 组件挂载,DOM已经存在
mounted() {
console.log('home', 'mounted')
},
// 数据更新时(虚拟DOM打补丁之前)
beforeUpdate() {
console.log('home', 'beforeUpdate')
},
// 虚拟DOM重新渲染和打补丁之后
updated() {
console.log('home', 'updated')
},
// 组件实例销毁之前, vue3 中 使用 `beforeUnmount`
beforeDestroy() {
console.log('home', 'beforeDestroy')
},
// 实例销毁之后, vue3 中 使用 `unmounted`
destroyed() {
console.log('home', 'destroyed')
},
}
</script>
composition API 生命周期钩子
onBeforeMount()
在组件被挂载之前被调用onMounted()
在组件挂载完成后执行onBeforeUpdate()
在组件即将因为响应式状态变更而更新其 DOM 树之前调用。onUpdated()
在组件因为响应式状态变更而更新其 DOM 树之后调用onBeforeUnmount()
在组件实例被卸载之前调用onUnmounted()
在组件实例被卸载之后调用onErrorCaptured()
在捕获了后代组件传递的错误时调用onRenderTracked()
当组件渲染过程中追踪到响应式依赖时调用onRenderTriggered()
当响应式依赖的变更触发了组件渲染时调用onActivated()
若组件实例是<KeepAlive>
缓存树的一部分,当组件被插入到 DOM 中时调用onDeactivated()
若组件实例是<KeepAlive>
缓存树的一部分,当组件从 DOM 中被移除时调用onServerPrefetch()
在组件实例在服务器上被渲染之前调用
computed和watch
- computed 有缓存,data不变时不会重新计算
- watch 是浅监听,如何深度监听?
- watch 监听引用类型,拿不到oldval 他们的区别: 1、
computed
用于计算产生新的数据,有缓存 2、watch
用于监听现有数据
watch进行深比较:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script>
export default {
data() {
return {
obj: {
name: "vue",
age: 18,
},
};
},
watch: {
obj: {
// 使用 deep 选项进行深比较
deep: true,
handler(val, oldVal) {
console.log("val:", val);
console.log("oldVal:", oldVal);
},
},
},
};
</script>
当我们修改 obj
的属性时,watch
的回调函数将会被调用,并传入新的值和旧值。
1
2
3
4
5
6
7
8
val: {
name: "vue",
age: 19,
}
oldVal: {
name: "vue",
age: 18,
}
vue事件挂载
- vue事件中
event
是原生的,事件被挂载到当前元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<button @click="increment1">+1</button>
<button @click="increment2($event, 2)">+2</button>
</template>
<script>
exprot default {
data() {
return {
}
},
methdos: {
increment1(event) {
// 打印出一个原生的 event对象
console.log('event', event, event.__proto__.constructor)
// 事件挂载的目标元素button按钮
console.log(event.target)
// 事件被注册到当前元素
cnsole.log(event.currentTarget)
}
}
}
</script>
修饰符
1
2
3
4
5
6
7
8
9
10
11
12
13
<!--阻止单击事件继续传播-->
<a v-on:click.stop="doThis"></a>
<!-- 提交事件不再重载页面-->
<form v-on:submit.prevent="onSubmit"></form>
<!--A-I1修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>
<!-- 只有修饰符-->
<form v-on:submit.prevent></form>
<!--添加事件监听器时使用事件捕获模式-->
<!--即内部元素触发的事件先在此处理,然后才交由内部元素进行处理-->
<div V-on:click.capture="doThis">...</div>
<!--只当在 event.target 是当前元素自身时触发处理函数A--I即事件不是从内部元素触发的-->
<div v-on:click.self="doThat">...</div>
v-for/v-if
官方不推荐一起使用,会影响性能,每次循环时都会进行一次v-if的判断
自定义事件
兄弟组件自定义事件通讯 注意:event
在vue3
中已经弃用了(vue3
创建实例使用的是工厂函数 createApp()
创建的实例)
vue2.x
使用自定义事件先创建一个 event
文件, event
就是vue
的实例
1
2
3
// event.js 文件
import Vue from 'vue'
export default new Vue()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 兄弟组件compents-A.vue
<script>
import event from './event.js'
export default {
mounted() {
// 绑定自定义事件
event.$on('onAddTile', this.addTitleHandler)
},
methdos: {
addTitleHandler(title) {
console.log(title)
}
}
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 兄弟组件compents-B.vue
<script>
import event from './event.js'
export default {
data() {
return {
title: ''
}
}
methdos: {
addTitle() {
// 调用自定义事件
event.$emit('oAddTitle', this.title)
}
}
}
</script>
组件A通过$on
监听onAddTile
,组件B通过event.$emit
触发onAddTile
- 绑定事件后要,不用时要及时销毁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script>
export default {
mounted() {
// 绑定自定义事件
event.$on('onAddTile', this.addTitleHandler)
},
methdos: {
addTitleHandler(title) {
console.log(title)
}
},
// 生命周期-组件销毁之前
beforeDestroy() {
// 组件销毁之前进行事件解绑
event.$off('oAddTitle', this.addTitleHandler)
}
}
</script>
vue3中的自定义绑定事件
在Vue 3中, event.$on
已经被废弃了,因为Vue 3中的事件系统已经发生了一些变化。
Vue 3中的事件系统使用了新的API,包括 on
、 off
、 once
和 emit
。这些方法可以通过 app.config.globalProperties
来设置全局属性,或者在组件中通过 setup
函数来访问。
下面是一个示例,展示了如何在Vue 3中使用新的事件API
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import { createApp } from 'vue';
const app = createApp({});
// 设置全局事件
app.config.globalProperties.$eventBus = {
listeners: {},
on(event, callback) {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event].push(callback);
},
off(event, callback) {
if (this.listeners[event]) {
const index = this.listeners[event].indexOf(callback);
if (index !== -1) {
this.listeners[event].splice(index, 1);
}
}
},
once(event, callback) {
const onceCallback = (...args) => {
callback(...args);
this.off(event, onceCallback);
};
this.on(event, onceCallback);
},
emit(event, ...args) {
if (this.listeners[event]) {
this.listeners[event].forEach(callback => {
callback(...args);
});
}
}
};
// 在组件中使用事件
app.component('my-component', {
setup() {
const eventBus = app.config.globalProperties.$eventBus;
const handleClick = () => {
eventBus.emit('my-event', 'Hello, world!');
};
eventBus.on('my-event', message => {
console.log(message);
});
return {
handleClick
};
},
template: `
<button @click="handleClick">Click me</button>
`
});
app.mount(‘#app’); 在这个示例中,我们创建了一个名为 $eventBus
的全局事件对象,并定义了 on
、 off
、 once
和 emit
方法来实现事件的注册、注销、触发和一次性触发功能。然后,在组件中使用 setup
函数来访问 $eventBus
对象,并使用 on
方法来注册事件监听器,使用 emit
方法来触发事件。
父子组件生命周期的执行顺序
1、首先父组件先创建实例 ceated 2、子组件进行create -> mounted 等生命周期的执行 3、父组件中的子组件生命周期渲染完后,父组件开始mounted,执行自己的生命周期
异步组件
在大型项目中,我们可能需要拆分应用为更小的块,并仅在需要时再从服务器加载相关组件。Vue 提供了 defineAsyncComponent
方法来实现此功能:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script>
import { defineAsyncComponent } from 'vue'
export default {
components: {
AdminPage: defineAsyncComponent(() =>
import('./components/AdminPageComponent.vue')
)
}
}
</script>
<template>
<AdminPage />
</template>
异步组件两种写法的区别
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export default {
components: {
AdminPage: defineAsyncComponent(() =>
import('./components/AdminPageComponent.vue')
)
}
}
// or
export default {
components: {
AdminPage: () => import('./components/AdminPageComponent.vue')
}
}
上面两种写法的主要区别在于,第一种写法使用了 Vue 提供的 defineAsyncComponent()
方法来定义异步组件,而第二种写法直接使用了一个返回 Promise 的函数来定义异步组件。
defineAsyncComponent()
方法可以将一个返回 Promise 的函数转换为一个异步组件。该方法会在 Promise 成功 resolve 时返回组件定义,在 Promise 失败 reject 时返回报错组件。
直接使用返回 Promise 的函数来定义异步组件,需要在组件渲染时手动调用 resolve()
方法来 resolve Promise。如果 Promise 失败 reject,则需要在组件中处理错误。
因此,两种写法的区别主要体现在异步组件定义的灵活性和易用性上。defineAsyncComponent()
方法提供了更高的灵活性,可以根据需要自定义异步组件的加载行为。而直接使用返回 Promise 的函数来定义异步组件更加简单易用,但灵活性较差。
具体来说,两种写法的区别如下:
特性 | defineAsyncComponent() | 返回 Promise 的函数 |
---|---|---|
灵活性 | 高 | 低 |
易用性 | 低 | 高 |
自定义性 | 高 | 低 |
错误处理 | 自动处理 | 手动处理 |
在实际使用中,可以根据具体的需求来选择合适的写法。如果需要对异步组件加载行为进行自定义,则可以使用 defineAsyncComponent()
方法。如果只需要简单地异步加载组件,则可以直接使用返回 Promise 的函数来定义异步组件。
总结:
defineAsyncComponent()
提供了组件加载错误的自动处理错误功能errorComponent
,加载中的提示loadingComponent
, 可以定义这是延迟加载的时间delay
展示加载组件前的延迟时间,默认为 200ms.
defineComponent()
- 在定义 Vue 组件时提供类型推导的辅助函数。 类型推导、参数类型定义、正确推断
setup()
组件参数类型、支持泛型
Object.defineProperty
vue2响应式使用的是Object.defineProperty
,但它是有缺点:
- 深度监听,需要递归,一次性计算太大
- 无法监听新增属性/删除属性(Vue.set / Vue.delete)
- 无法原生监听数组,需要特殊处理
watch/watchEffect
1、两者都可以监听data属性变化
2、watch需要明确监听哪个属性
3、watchEffect会根据其中的属性,自动监听其变化
- watch 示例代码
需要指定监听的属性
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
27
28
29
30
31
32
33
34
<script>
export default {
setup() {
const numberRef = ref(100)
const state = reactive({
name: '饼干',
age: 18
})
// 第一种监听方式 监听值类型
watch(numberRef, (newNumber, oldNumber) => {
console.log('ref watch', newNumber, oldNumber)
}
// , {
// immdiate: true // 初始化之前就监听,可选
// }
)
// 第二种监听方式
watch(
// 第一个参数,确定要监听那个属性
() => state.age,
// 第二个参数,回调函数
(newState, oldState) => {
console.log('state watch', newState, oldState)
},
// 第三个参数,配置项
{
immediate: true, // 初始化之前就监听,可选
deep: true // 深度监听
}
)
}
}
</script>
watchEffect
初始化时会执行一次,在方法内使用哪个属性就监听那个属性,不需要提前指定。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script>
export default {
setup() {
const numberRef = ref(100)
const state = reactive({
name: '饼干',
age: 18
})
wathcEffect(() => {
// 初始化时执行,这里使用了属性name所以就直接监听了name
console.log('state.name', state.name)
})
}
}
</script>
directives
- 自定义指令,可以全局自定义,也可以局部自定义指令
局部自定义指令示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<input v-focus v-model="val"/>
</template>
<script>
export default {
data() {
return {
val:''
}
}
// 自定义指令
directives: {
'focus': {
mounted(el) {
el.focus()
el.value = '6666'
},
}
},
}
</script>
defineExpose
可以通过 defineExpose
编译器宏来显式指定在 <script setup>
组件中要暴露出去的属性:
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
27
28
// Child.vue
<script setup>
import { ref } from 'vue'
const a = 1
const b = ref(2)
// 将属性暴露给外部
defineExpose({
a,
b
})
</script>
// Parent.vue
<script setup>
import { ref, onMounted } from 'vue'
import Child from 'Child.vue'
const childRef = ref(null)
onMounted(() => {
console.log(childRef.value.a) // 输出 1
console.log(childRef.value.b) // 输出 2
})
</script>
<template>
<Child ref="childRef"></Child>
</template>
template 模板
template
模版编译产出的是一个render
函数,render函数返回vnode
, 最后通过patch
对虚拟DOM
进行处理。createElement
方法返回vnode
1 2 3 4 5 6 7 8 9 10 11
const compileer = require('vue-template-compiler') const template =` <div id="div1" class="container"> <img :src="imgUrl"/> </div> ` // 返回的 createElement 函数, _c是简写 // with(this){return _c('div', // {staticClass: "container", attrs: {"id": "div1"}}, // [_c('img', fattrs: {"src" :imgUrl}})] // )}
setup 中如何获取组件实例
- 在
setup
和其他Composition API
中没有this
- 可通过
getCurrentInstance
获取当前实例
setup script
中操作 DOM
在 Vue 3 的 script setup
语法中,可以使用 ref
函数创建一个响应式引用,并将其与 DOM 元素相关联,以便在 setup
中获取和操作 DOM 结构。
以下是一个简单的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div ref="myElement">This is a DOM element</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
// 使用 ref 创建一个响应式引用,并关联到 DOM 元素
const myElement = ref(null);
// 在组件挂载后,通过 ref 获取 DOM 元素并进行操作
onMounted(() => {
console.log('DOM element:', myElement.value);
myElement.value.innerText = 'Updated text';
});
</script>
在这个例子中,myElement
是一个通过 ref
创建的响应式引用,它与 <div>
元素关联。在 onMounted
生命周期钩子中,我们通过 myElement.value
获取到实际的 DOM 元素,然后进行操作。
请注意,ref
在 script setup
中的使用方式和在传统的 script
区块中略有不同。在 script setup
中,直接使用 ref
而不是 this.$refs
来访问引用。
hash路由实现原理
说到路由就要先了解一下网页url组成部分
1
2
3
4
5
6
7
8
// http://127.0.0.1:8080/hash.html?a=1&b=2#/aaa/bbb
location.protocol // http:
location.hostname // 127.0.0.1
location.host // 127.0.0.1:8080
location.port // 8080
location.pathname // '/hash.html'
location.search // '?a=1&b=2'
location.hash // '#/aaa/bbb'
了解了url组成部分后我们就可以通过js去实现一个hash路由功能了 hash的特点:
- hash 变化会触发网页跳转,即浏览器的前进、后退
- hash 变化不会刷新页面,SPA必需的特点
- hash 永远不会提交到server端(前端自生自灭)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// hash 变化:js修改url、手动修改url的hash、浏览器前进/后退
// 监听hash变化事件
window.onhashchange = (event) {
console.log('old-ulr', event.oldURL) // 旧的url地址
console.log('new-url', event.newURL) // 新的url地址
console.log('hash', location.hash) // hash地址
}
// 页面初次加载, 获取 hash
document.addEventListener('DOMContentLoaded', () => {
console.log('hash', location.hash)
})
H5 history
需要server端配合
- 用 url 规范的路由,但跳转时不刷新页面
- history.pushState
- window.onpopstate
1
2
3
4
5
6
7
8
9
10
11
// 使用 pushState 改变url地址
function routerPush() {
console.log('切换路由到 page1')
history.pushState({name: 'page1'}, '', 'page1')
}
// 监听浏览器前进、后退
window.onpopstate = (event) => {
console.log('onpopstate', event.state, location.pathname)
}
.snyc
修饰符
- vue 2.3版本新增, vue3.0版本弃用改为
v-model
toRef与toRefs区别
toRef
针对一个响应式对象(reactive封装
)的prop
,toRef
引用对象中一个属性,直接读写其引用的属性,让其依然保持响应式状态
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
27
<template>
<div>{{ ageRef }} 等同于 {{ state.age }}</div>
</template>
<script>
import {ref, toRef, reactive} from 'vue'
export default {
setup() {
// 创建一个响应式对象
const state = reactive({
name: '饼干',
age: '18'
})
// 引用单个对象属性,简化了`state.age`这种调用,在模板中直接`{{ageRef}}`直接渲染
const ageRef = toRef(state, "age")
return {
// 注意state不可以使用解构方式 ...state 这种将会丧失响应式,reactive创建的响应式不可以被解构直接使用对象属性,`toRef`等同于解构了单个属性直接使用,保持着响应状态
state
ageRef
}
}
}
</script>
toRefs
针对一个响应式对象(reactive封装)的prop,转化为一个普通对象(保持响应式)
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
<template>
<!--toRefs 方式直接可以在模板中使用属性渲染在模板上,省去了对象.属性这中访问方式-->
<div>名字:{{ name }} 年龄:{{ age }}</div>
</template>
<script>
import {ref, toRef, toRefs, reactive} from 'vue'
export default {
setup() {
// 创建一个响应式对象
const state = reactive({
name: '饼干',
age: '18'
})
// 将响应式对象转化为普通对象(使用解构方式依然保持响应)
const stateRefs = toRefs(state)
return stateRefs
}
}
</script>
为什么需要ref
- 返回值类型,会丢失响应式 vue3的响应式是
proxy
它只能拦截对象引用类型,针对值类型
无法拦截所以需要ref()
方法存在去使用值类型 setup
、computed
、合成函数,都有可能返回值类型。 值类型如:number
,string
,boolean
,null
,undefined
, 和symbol
。
ref为何需要.value
- ref 是一个对象(不丢失响应式),value存储值
- 通过
.value
属性的get和set实现响应式 - 用于模版、reactive时,不需要
.value
,其他情况都需要
ref与reactive
ref
可以很好的处理值类型,多个ref组合成一个对象时,可以使用解构方式,省去了对象引用属性调用链条过长,简化属性值的调用。唯一麻烦的是在修改值是需要使用.value
如:state.value = 1
,模板渲染调用时不需要加value,直接使用声明的变了即可 “{ {sate} }”reactive
将普通对象转化为响应式,它封装的对象不可以使用解构方法,否则将会失去响应式。
vue中diff算法
vue和react的diff不一样,vue是双端diff,头头尾尾双指针。它不像react是单端的它在效率上会比react高一个层级。vue3在双端diff上又加了一个最长递增子序列
这个算法,它整个函数的流程通过child NOODE
不断去递归查找和上一个diff不一样的地方,再做头尾的一个比较和替换。
diff算法key的作用
在比对时必须标记出这次和上次的同一个dom元素替换的位置,如果不加key
那么位置扰乱了,不加key它会根据索引进行比较,索引比较有很大一个缺陷,当push一个元素时,后面都会向后错位,重新进行计算,浪费性能及时间。
vue与react区别
:vue是MVVM
,数据驱动视图,视图驱动数据。react是MVC
数据和视图之间需要一个Controller
(控制器)它不断去dispath
通过事件去驱动数据的改变更新视图。
proxy
vue3中响应式使用的是proxy做代理拦截
- 深度监听,性能更好
- 可监听 新增/删除 属性
- 可监听数组变化
Proxy能规避Object.defineProperty的问题
Proxy无法兼容所有浏览器,无法polyfill
下面是proxy基本使用示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const data = {
name: '饼干',
age: 20
}
const proxyData = new Proxy(data, {
get(target, key, receiver) {
const result = Reflect.get(targe, key, receiver)
console.log('get', key)
// 返回结果
return result
},
set(target, key, val, receiver) {
const result = Reflect.set(target, key, val, receiver)
console.log('set', key, val)
return result
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key)
console.log('delete property', key)
return result
}
})
Vue3 (script setup)
- 基本使用,
<script>
写在<template>
前面 - 定义属性
defineProps
, 定义事件defineEmits
defineExpose
暴漏数据给父组件
vue3比vue2优势?
- 性能更好
- 体积更小哦
- 更好的ts支持
- 更好的代码组织
- 更多新功能
Composition API和 React Hooks
- vue中
setup
只会被调用一次,Hooks
函数会被多次调用 - vue无需
useMemo
useCallback
,因为setup只调用一次 - vue无需顾虑调用顺序,而react需要保证
hooks
的顺序一致
Composition API带来了什么
- 更好的代码组织
- 更好的逻辑复用
- 更好的类型推导