1. Vue 3 为什么使用 Proxy?
- 弥补 Object.defineProperty 的两个不足
- 动态创建的 data 属性需要用 Vue.set 来赋值,Vue 3 用了 Proxy 就不需要了
- 基于性能考虑,Vue 2 篡改了数组的 7 个 API,Vue 3 用了 Proxy 就不需要了
- defineProperty 需要提前递归地遍历 data 做到响应式,而 Proxy 可以在真正用到深层数据的时候再做响应式
2. Vue 3 为什么使用 Composition API?
参考尤雨溪的博客:传送门
- Composition API 比 mixins、高阶组件、extends、Renderless Components 等更好,原因有三:
- 模版中的数据来源不清晰。
- 命名空间冲突。
- 性能。
- 更适合 TypeScript
3. Vue 3 对比 Vue 2 做了哪些改动?
官方文档写了(中文在这),这里列出几个容易被考的:
- createApp() 代替了 new Vue()
- v-model 代替了以前的 v-model 和 .sync
- 根元素可以有不止一个元素了
- 新增 Teleport 传送门
- destroyed 被改名为 unmounted 了(before 当然也改了)
- ref 属性支持函数了
4.Vue3 Diff算法和 Vue2 的区别
编译阶段的优化:
- 事件缓存:将事件缓存(如: @click),可以理解为变成静态的了
- 静态提升:第一次创建静态节点时保存,后续直接复用
- 添加静态标记:给节点添加静态标记,以优化 Diff 过程
由于编译阶段的优化,除了能更快的生成虚拟 DOM 以外,还使得 Diff 时可以跳过"永远不会变化的节点",
Diff 优化如下
- Vue2 是全量 Diff,Vue3 是静态标记 + 非全量 Diff
- 使用最长递增子序列优化了对比流程
根据尤大公布的数据就是 Vue3 update 性能提升了 1.3~2 倍
5.composition API 与 options API的区别
-
vue2 采用的就是
optionsAPI(1) 优点:
易于学习和使用, 每个代码有着明确的位置 (例如: 数据放 data 中, 方法放 methods中)(2) 缺点: 相似的逻辑, 不容易复用, 在大项目中尤为明显
(3) 虽然 optionsAPI 可以通过mixins 提取相同的逻辑, 但是也并不是特别好维护
-
vue3 新增的就是
compositionAPI(1) compositionAPI 是基于 逻辑功能 组织代码的, 一个功能 api 相关放到一起
(2) 即使项目大了, 功能多了, 也能快速定位功能相关的 api
(3) 大大的提升了
代码可读性和可维护性 -
vue3 推荐使用 composition API, 也保留了options API
即就算不用composition API, 用 vue2 的写法也完全兼容!!
6.step函数
step()函数是vue3中,专门为组件提供的新属性。为composition API新特性提供了统一的入口,step函数在beforeCreate、created之前执行,vue3取消了这两个钩子,用step代替,该函数相当于一个生命周期钩子,vue中过去的data,methods,watch等全部都用新增api写在函数当中
step接受两个参数props和context,里面不能用this,用contest对象来替代当前执行的上下文,context对象有四个属性:attrs、slots、emit、expose
通过ref和reactive代替以前的data语法,return出去的内容(变量、方法等)可以在模版直接使用
<template>
<div class="container">
<h1 @click="say()">{{msg}}</h1>
</div>
</template>
<script>
export default {
setup (props,context) {
console.log('setup执行了')
console.log(this) // undefined
// 定义数据和函数
const msg = 'hi vue3'
const say = () => {
console.log(msg)
}
// Attribute (非响应式对象,等同于 $attrs)
context.attrs
// 插槽 (非响应式对象,等同于 $slots)
context.slots
// 触发事件 (方法,等同于 $emit)
context.emit
// 暴露公共 property (函数)
context.expose
return { msg , say}
},
beforeCreate() {
console.log('beforeCreate执行了')
console.log(this)
}
}
</script>
7.setup语法糖 (script setup语法)
script step是在单文件组件(SFC)中使用组合式API的语法糖。相比于普通script语法更简洁
<template>
<div>
<h3>根组件</h3>
<div>点击次数:{{ count }}</div>
<button @click="add">点击修改</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
const add = () => {
count.value++
}
</script>
使用setup语法糖,不用eaport default{},,自组建不用注册,直接倒入使用,属性,方法也不用return
并且里面不需要async可以直接使用await,因为会默认变为async step 用语法糖时,props、attrs、solts、emit、expose的获取方式也不一样
3.0~3.2版本变成了通过 import 引入的 API:defineProps、defineEmit、useContext(在3.2版本已废弃),useContext 的属性 { emit, attrs, slots, expose }
3.2+版本不需要引入,而直接调用:defineProps、defineEmits、defineExpose、useSlots、useAttrs
8.# 7个 Vue 3 中的组件通信方式
本文采用
- props
- emit
- v-model
- refs
- provide/inject
- eventBus
- vuex/pinia
1、Props
<template>
<!-- child component -->
<child-components :list="list"></child-components>
<!-- parent component -->
<div class="child-wrap input-group">
<input
v-model="value"
type="text"
class="form-control"
placeholder="Please enter"
/>
<div class="input-group-append">
<button @click="handleAdd" class="btn btn-primary" type="button">
add
</button>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import ChildComponents from './child.vue'
const list = ref(['JavaScript', 'HTML', 'CSS'])
const value = ref('')
// event handling function triggered by add
const handleAdd = () => {
list.value.push(value.value)
value.value = ''
}
</script>
子组件只需要渲染父组件传递的值。
<template>
<ul class="parent list-group">
<li class="list-group-item" v-for="i in props.list" :key="i">{{ i }}</li>
</ul>
</template>
<script setup>
import { defineProps } from 'vue'
const props = defineProps({
list: {
type: Array,
default: () => [],
},
})
</script>
2、Emit
Emit也是Vue中最常见的组件通信方式,用于子组件向父组件传递消息。
我们在父组件中定义列表,子组件只需要传递添加的值。
子组件代码如下:
<template>
<div class="child-wrap input-group">
<input
v-model="value"
type="text"
class="form-control"
placeholder="Please enter"
/>
<div class="input-group-append">
<button @click="handleSubmit" class="btn btn-primary" type="button">
add
</button>
</div>
</div>
</template>
<script setup>
import { ref, defineEmits } from 'vue'
const value = ref('')
const emits = defineEmits(['add'])
const handleSubmit = () => {
emits('add', value.value)
value.value = ''
}
</script>
点击子组件中的【添加】按钮后,我们会发出一个自定义事件,并将添加的值作为参数传递给父组件。
<template>
<!-- parent component -->
<ul class="parent list-group">
<li class="list-group-item" v-for="i in list" :key="i">{{ i }}</li>
</ul>
<!-- child component -->
<child-components @add="handleAdd"></child-components>
</template>
<script setup>
import { ref } from 'vue'
import ChildComponents from './child.vue'
const list = ref(['JavaScript', 'HTML', 'CSS'])
// event handling function triggered by add
const handleAdd = value => {
list.value.push(value)
}
</script>
在父组件中,只需要监听子组件的自定义事件,然后执行相应的添加逻辑即可。
3、v-model
v-model 是 Vue 中一个优秀的语法糖,比如下面的代码。
<ChildComponent v-model:title="pageTitle" />
这是以下代码的简写形式
<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />
这确实容易了很多。现在我们将使用 v-model 来实现上面的示例。
子组件
<template>
<div class="child-wrap input-group">
<input
v-model="value"
type="text"
class="form-control"
placeholder="Please enter"
/>
<div class="input-group-append">
<button @click="handleAdd" class="btn btn-primary" type="button">
add
</button>
</div>
</div>
</template>
<script setup>
import { ref, defineEmits, defineProps } from 'vue'
const value = ref('')
const props = defineProps({
list: {
type: Array,
default: () => [],
},
})
const emits = defineEmits(['update:list'])
// Add action
const handleAdd = () => {
const arr = props.list
arr.push(value.value)
emits('update:list', arr)
value.value = ''
}
</script>
在子组件中,我们先定义props和emits,添加完成后再发出指定的事件。
注意:update:*是Vue中固定的写法,*代表props中的一个属性名。
在父组件中使用比较简单,代码如下:
<template>
<!-- parent component -->
<ul class="parent list-group">
<li class="list-group-item" v-for="i in list" :key="i">{{ i }}</li>
</ul>
<!-- child component -->
<child-components v-model:list="list"></child-components>
</template>
<script setup>
import { ref } from 'vue'
import ChildComponents from './child.vue'
const list = ref(['JavaScript', 'HTML', 'CSS'])
</script>
4、Refs
使用API选项时,我们可以通过this.$refs.name获取指定的元素或组件,但在组合API中不行。如果我们想通过ref获取,需要定义一个同名的Ref对象,在组件挂载后可以访问。
示例代码如下:
<template>
<ul class="parent list-group">
<li class="list-group-item" v-for="i in childRefs?.list" :key="i">
{{ i }}
</li>
</ul>
<!-- The value of the child component ref is the same as that in the <script> -->
<child-components ref="childRefs"></child-components>
<!-- parent component -->
</template>
<script setup>
import { ref } from 'vue'
import ChildComponents from './child.vue'
const childRefs = ref(null)
</script>
子组件代码如下:
<template>
<div class="child-wrap input-group">
<input
v-model="value"
type="text"
class="form-control"
placeholder="Please enter"
/>
<div class="input-group-append">
<button @click="handleAdd" class="btn btn-primary" type="button">
add
</button>
</div>
</div>
</template>
<script setup>
import { ref, defineExpose } from 'vue'
const list = ref(['JavaScript', 'HTML', 'CSS'])
const value = ref('')
// event handling function triggered by add
const handleAdd = () => {
list.value.push(value.value)
value.value = ''
}
defineExpose({ list })
</script>
注意:默认情况下,setup 组件是关闭的,通过模板 ref 获取组件的公共实例。如果需要公开,需要通过defineExpose API 公开。
5、provide/inject
provide/inject是 Vue 中提供的一对 API。无论层级多深,API 都可以实现父组件到子组件的数据传递。
示例代码如下所示:
父组件
<template>
<!-- child component -->
<child-components></child-components>
<!-- parent component -->
<div class="child-wrap input-group">
<input
v-model="value"
type="text"
class="form-control"
placeholder="Please enter"
/>
<div class="input-group-append">
<button @click="handleAdd" class="btn btn-primary" type="button">
add
</button>
</div>
</div>
</template>
<script setup>
import { ref, provide ,readonly} from 'vue'
import ChildComponents from './child.vue'
const list = ref(['JavaScript', 'HTML', 'CSS'])
const value = ref('')
// Provide data to child components.
provide('list', readonly(list.value))
// event handling function triggered by add
const handleAdd = () => {
list.value.push(value.value)
value.value = ''
}
</script>
子组件
<template>
<ul class="parent list-group">
<li class="list-group-item" v-for="i in list" :key="i">{{ i }}</li>
</ul>
</template>
<script setup>
import { inject } from 'vue'
// Accept data provided by parent component
const list = inject('list')
</script>
注意:使用provide进行数据传输时,尽量使用readonly封装数据;避免子修改父数据
6、eventBus
Vue 3 中移除了 eventBus,但可以借助第三方工具来完成。Vue 官方推荐使用 mitt 或 tiny-emitter。在大多数情况下,不建议使用全局事件总线来实现组件通信。虽然比较简单粗暴,但是维护事件总线从长远来看是个大问题,这里就不解释了。有关详细信息,您可以阅读特定工具的文档。
7、vuex/pinia
Vuex 和 Pinia 是 Vue 3 中的状态管理工具,使用这两个工具可以轻松实现组件通信。由于这两个工具都比较强大,这里就不一一展示了。有关详细信息,请参阅文档。
9. vue3:兄弟组件,跨组件传值,事件总线的通信方式(mitt / tiny-emitter)
在vue2中的跨组件通信中,我们如果不用状态管理vuex的话,我们就会采用事件总线的通信的方式,通常做法就是新建一个js文件,例如bus.js,在里面new Vue(),然后export default导出,但是在vue3中移除了事件总线,我们不可以再这么用了,,,但是官方给我们推荐了外部第三方的库来帮我们完成事件总线,官方推荐了两个: mitt 或者 tiny-emitter
npm地址:mitt - npm 或者 tiny-emitter - npm
用法也很简单:我这里以mitt举例
1.找到vue项目中的utils文件夹,新建一个bus.js
bus.js
import mitt from "mitt";
const emitter = mitt()
export default emitter
2.使用,我现在需要使用mitt进行兄弟组件之间的通信实现
父组件
<template>
<child1></child1>
<child2></child2>
</template>
<script setup>
import Child1 from "./components/Child1";
import Child2 from "./components/Child2";
</script>
子组件-child1
<template>
<div>child1
<button @click="click">给child2 传值</button>
</div>
</template>
<script setup>
import emitter from "@/utils/bus"
function click() {
emitter.emit('child2Data', {name: '小米'})
}
</script>
子组件-child2
<template>
<div>child1
<div>{{str}}</div>
</div>
</template>
<script setup>
import emitter from "@/utils/bus"
import ref onbeforeUnmount from "vue"
let str = ref()
emitter.on("child2data",data=>{
str.value = data.name
})
onbeforeUnmount(()=>{
emitter.off('child2data')
})
</script>