Vue2到Vue3之进阶篇
巩固一些概念
自定义v-model
我们都知道v-model
一般用于表单组件中,比如一个input标签中,要和data中的响应式属性text绑定input中的value,第一步是需要把text=value
,然后在input输入是value改变时进行第二次绑定,即value=text
,看完我们不难想到v-model
实质结合了v-bind
和v-on
的语法糖。下面我们来看看怎么实现:
<input type="text" :value="text" @input="$emit('change', $event.target.value)" />
不难看出,其实就是把text绑定到value中,在input修改时去触发了一个change事件,由change事件分发value值赋给text。
nextTick
为什么会有nextTick?首先我们来看看这个例子:
<template>
<div>
<ul ref="ul">
<li v-for="(item, index) in list" :key="index">{{item}}</li>
</ul>
<button @click="addItem">添加</button>
</div>
</template>
<script>
export default {
data() {
return {
list: ['a', 'b', 'c'],
}
},
methods: {
addItem() {
this.list.push(`${new Date()}`)
this.list.push(`${new Date()}`)
this.list.push(`${new Date()}`)
const ulElem = this.$refs.ul
console.log(ulElem.childNodes.length)
},
},
}
</script>
我们想要在button
点击后更新list,接着去打印了ul
的子节点个数,一开始已经有了3个,加完3个应该是6个,但是我们看到的打印结果却是3,再次点击才是6。
由此我们可以知道:
- data改变不会立刻渲染更新,是异步渲染的,并且页面渲染会将data修改做整合在一起修改
那么如果我们需要在修改完后获取页面信息,这时我们就需要用到$nextTick
,它会等待DOM渲染完后在回调。上面的例子,我们可以修改看看结果
this.$nextTick(() => {
const ulElem = this.$refs.ul
console.log(ulElem.childNodes.length)
})
这时的结果就是正常的了。
动态组件和异步组件
<!-- 用于组件名还未确定的情况 -->
<component :is="componentId"></component>
<!-- 可以异步加载, 用到才加载 -->
() => import('component.vue')
mixin(Vue3解决)
- 抽离公共逻辑
- 但是容易造成来源不明确,不利于阅读
- 多个mixin会有覆盖,造成命名冲突
- 容易出现多对多关系,复杂度高
Vue2生命周期
放一张官网的图
Vue3更新功能
Vue3升级了哪些重要功能
- createApp
- emits属性
- 生命周期
- 多事件
- 比如click执行多个函数
- Fragment
- 移除.sync
- 异步组件的写法
- defineAsyncComponent
- 移除filter管道
- Teleport
- Suspense异步组件
- Suspence两个插槽 #fallback可以写loading...
- Composition API
- reactive
- ref
- readonly
- watch和watchEffect
- setup
Vue3比Vue2有什么优势
- 性能更好
- 体积更小
- 更好的ts支持
- 更好的代码组织
- 更好的逻辑抽离
- 更多新功能
描述Vue3生命周期
这里放一下官网列出的Options API
和Composition API
的生命周期函数比较
选项式 API | Hook inside setup |
---|---|
beforeCreate | Not needed* |
created | Not needed* |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
errorCaptured | onErrorCaptured |
renderTracked | onRenderTracked |
renderTriggered | onRenderTriggered |
activated | onActivated |
deactivated | onDeactivated |
如何理解ref、toRef和toRefs
ref
- 生成值类型的响应式数据
- 可用于模板和reactive
- 通过.value修改数据
toRef
- 针对一个响应式对象(reactive封装)的prop
- 具有响应式
- 两者保持响应关系
toRefs
- 将响应式对象(reactive对象)转换为普通对象
- 对象的每个prop都是对应的ref
- 两者保持引用关系
- 不丢失响应式,可以解构
为何需要ref?
返回值类型,会丢失响应式
如果vue不定义ref,用户会自定义ref,造成混乱
为何需要.value?
ref是一个对象(不丢失响应性),通过.value存储值
通过.value属性的get和set实现响应性
当用于模板、reactive时,不需要.value取值,其他情况都需要
为何需要toRef、toRefs?
初衷:不丢失响应性的情况下,把对象数据分解/扩散
前提:针对的是响应式对象(reactive封装的)
注意:不创造响应式,而是延续响应式
- 用reactive做对象响应式,用refs值响应式
- setup中返回toRefs
- 合成函数返回响应式对象时,使用toRefs
- 变量命名尽量使用xxxRef
用法:
<script setup>
import { ref, toRef, toRefs, reactive, onMounted } from 'vue'
// ref用法
const nameRef = ref('AirHua')
const obj = reactive({
name: 'air',
age: 20,
})
// toRef用法
const name1 = toRef(obj, 'name')
// toRefs
const { name, age } = toRefs(obj)
</script>
<template>
<div>
<h1>{{ nameRef }}</h1>
<h1>{{ name1 }}</h1>
<h1>{{ name }}</h1>
<h1>{{ age }}</h1>
</div>
</template>
Composition API代码复用
这里的代码复用可以理解为当一个功能多个页面可能都会用到,我们可以抽离逻辑代码到一个函数
,
如果了解多React
的同学可以把这个和React hooks结合理解一下。
举个例子,我们写了一个监听鼠标移动的函数,可以把它抽离出来,命名为useMousePosition.js
,代码如下:
import {
reactive,
toRefs,
onMounted,
onUnmounted
} from 'vue'
function useMousePosition() {
const mousePosition = reactive({
x: 0,
y: 0
})
const {
x,
y
} = toRefs(mousePosition)
function update(e) {
mousePosition.x = e.pageX
mousePosition.y = e.pageY
}
onMounted(() => {
window.addEventListener('mousemove', update)
})
onUnmounted(() => {
window.removeEventListener('mousemove', update)
})
return {
x,
y
}
}
export default useMousePosition
接下来我们在组件中使用它就可以了。
<script setup>
import useMousePosition from '../hook/useMousePosition'
const { x, y } = useMousePosition()
</script>
<template>
<h1>坐标: {{ x }}, {{ y }}</h1>
</template>
<style scoped>
</style>
watch和watchEffect的区别是什么
- watch需要明确监听哪个属性(
immediate
初始化之前就监听,deep
深度监听) - watchEffect会根据其中的属性,自动监听其变化
// watch source can only be a getter/effect function
watch(
() => state.age,
(newVal, oldVal) => {
console.log(newVal, oldVal)
},
{
// deep: true,
}
)
// 自动监听state.age和numberRef
watchEffect(() => {
console.log(state.age)
console.log(numberRef.value)
})
setup中如何获取组件实例
getCurrentInstance
支持访问内部组件实例。
官方建议:强烈反对在应用的代码中使用 getCurrentInstance
。请不要把它当作在组合式 API 中获取 this
的替代方案来使用。
Vue3为何比Vue2快
- Proxy响应式
- 在原理篇会详细讲到
- PatchFlag
- 编译模板时,AST中会给动态节点做标记
- 标记分为不同的类型,如TEXT、PROPS CLASS
- 用于在diff算法中,区分静态节点,以及不同类型动态节点的对应处理(diff算法优化)
可以来看一下,一个dom树,传入render函数被标记PatchFlag:
-
hoistStatic
- 将静态节点的定义,提升到父级作用域,缓存起来
- 多个相邻的静态节点,会被合并
- chcheHandler
- 缓存事件
- SSR优化
- 静态节点直接输出,绕过vdom
- 动态节点,还是需要动态渲染
- tree-shaking
- 按需引入不同的API
Vite打包
- 开发环境使用ES6 Module无需打包
Composition API和React Hooks对比
- 前者setup在生命周期中只会调用一次,后者函数会被多次调用
- 前者无需useMemo、useCallback,因为setup只会调用一次
- 前者无需顾虑调用顺序,而后者需要保证hoks顺序一致
script setup(vue3.2.0+)
-
顶级变量可直接用于template
-
defineProps
定义属性返回props -
defineEmits
定义事件返回emits -
defineExpose
(父组件获取子组件信息)