一.快速上手Vue3
1.使用Vue3的CDN
<script src="https://lib.baomitu.com/vue/3.0.2/vue.global.js"></script>
2.vue-cli
升级到vue-cli v4.5 (删除后再安装)
npm i -g @vue/cli@next
3.使用Vite
利用ES6的import会发送请求去加载文件的特性,拦截这些请求,做一些预编译,省区webpack冗长的打包时间
// npm
$ npm init vite-app <project-name>
$ cd <project-name>
$ npm install
$ npm run dev
// yarn
$ yarn create vite-app <project-name>
$ cd <project-name>
$ yarn
$ yarn dev
Vue3的亮点:
1、性能比Vue2快(diff算法优化,)
2、按需编译,体积更小
3、Composition API 组合API
4、更好的支持TS
5、暴露了自定义渲染API
6、更多新的先进的组件
Diff算法优化:
Vue2中的虚拟DOM是进行全量的对比
Vue3的diff算法在创建虚拟DOM的时候会根据DOM中的内容是否会发生变化,添加静态标记(静态标记不同值会有不同的比较方式)
Vue Template Explorer (vue-next-template-explorer.netlify.app)
静态提升:
Vue2中无论元素是否参加更新,每次都会重新创建,然后再渲染
Vue3中对于不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用即可
<div>
<p>1</p>
<p>2</p>
<p>{{msg}}</p>
</div>
import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
const _hoisted_1 = /*#__PURE__*/_createElementVNode("p", null, "1", -1 /* HOISTED */)
const _hoisted_2 = /*#__PURE__*/_createElementVNode("p", null, "2", -1 /* HOISTED */)
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("div", null, [
_hoisted_1,
_hoisted_2,
_createElementVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
]))
}
cacheHandlers事件监听器缓存:
默认情况下onClick会被视为动态绑定,所以每次都会去追踪他的变化
但是因为是同一个函数,所以没有追踪变化,直接缓存起来复用即可
//未使用cacheHandlers
import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
const _hoisted_1 = ["onClick"]
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("div", null, [
_createElementVNode("button", { onClick: _ctx.click }, "123", 8 /* PROPS */, _hoisted_1)
]))
}
//使用cacheHandlers 不存在静态标记变量,不需要再次比较
import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("div", null, [
_createElementVNode("button", {
onClick: _cache[0] || (_cache[0] = (...args) => (_ctx.click && _ctx.click(...args)))
}, "123")
]))
}
SSR渲染:
当有大量静态内容时,这些内容会被当做纯字符串推进一个buffer里面,即使存在动态的绑定,会通过模板插值嵌入进去。这样会比通过虚拟dom来渲染的快上很多很多
当静态内容大到一定量级时候,会用_createStaticVNode方法再客户端去省城一个static node,这些静态node会被直接innerHtml,不需要创建对象然后根据对象渲染
Vue3中的Composition API
一,Setup函数
1.setup参数
接收两个参数:props
和context
props:响应式的,当传入新的 prop 时,它将被更新。(不能使用 ES6 解构)
export default {
props: {
title: String
},
setup(props) {
console.log(props.title)
}
}
context:普通 JavaScript 对象,暴露了其它可能在 setup
中有用的值
export default {
setup(props, context) {
// Attribute (非响应式对象,等同于 $attrs)
console.log(context.attrs)
// 插槽 (非响应式对象,等同于 $slots)
console.log(context.slots)
// 触发事件 (方法,等同于 $emit)
console.log(context.emit)
// 暴露公共 property (函数)
console.log(context.expose)
}
}
2.setup执行时机
setup执行先于beforecreate
选项式 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 |
3.setup注意点
3.1 由于在执行setup()
的时候,还没有执行声明周期函数,所以在setup中无法使用data和methods
3.2 在 setup()
内部,this
不是该活跃实例的引用,解析其它组件选项之前被调用的,所以 setup()
内部的 this
的行为与其它选项中的 this
完全不同
3.3 setup()
只能是同步的不能是异步的
二,Vue3中的响应式数据处理
1.reactive,ref
Vue3中响应式数据通过ES6的Proxy来实现,reactive和ref作用是创建响应式数据
reactive
参数必须是对象
ref
从性能角度适用于监听简单类型数据。对于复杂类型objet也可以实现响应式(在vue官网中对ref频频出现对arr,object的响应包装,只是在使用时需要.value
形式,导致他响应式创建复杂,访问复杂)
ES6
结构赋值通常会使数据响应性丢失,使用toRefs
保留与源对象的响应式关联
reactive 和 ref 都是用来定义响应式数据的 reactive更推荐去定义复杂的数据类型 ref 更推荐定义基本类型,ref
和 reactive
本质我们可以简单的理解为ref是对reactive的二次包装, ref定义的数据访问的时候要多一个.value,使用ref定义基本数据类型,ref也可以定义数组和对象。
<template>
<div ref="box">
我是div
</div>
</template>
<script>
//ref获取dom
import {ref,onMounted} from 'vue'
export default{
name:'App',
setup(){
let box = ref(null)
onMounted(()=>{
console.log('onMounted',box.value)
})
return {box}
}
}
</script>
2.响应式数据实现
Vue2中响应式有很多局限性在Vue3中使用proxy解决:1.对属性的添加,删除动作的检测 2.对数组基于下标的修改,对于.length修改的检测 3.对接MapSet等的支持
a.Proxy
Proxy 是一个对象,它包装了另一个对象,并允许你拦截对该对象的任何交互。使用 Proxy 的一个难点是 this
绑定。我们希望任何方法都绑定到这个 Proxy,而不是目标对象,这样我们也可以拦截它们。使用ES6中的Reflect
新特性可以实现
let obj = {name:'lnj',age:18}
let state = new Proxy(obj,handler:{
get(target, property, receiver) {
return target[property]
}
set(target,property){
}
})
b.Reflect(不会阻塞程序进行)
反射机制:反射机制指的是程序在运行时能够获取自身的信息
Reflect是一个内建的对象,用来提供方法去拦截JavaScript操作,他不是一个函数对象,不可构造。(与porxy handlers的方法相同)
let obj = {name:'lnj',age:18}
let state = new Proxy(obj,handler:{
get(target, property, receiver) {
return Reflect.get(target, property, receiver)
}
set(target,property,value,receiver){
return Reflect.set(target, property,value,receiver)
}
})
3.递归和非递归监听
3.1 递归监听
默认情况下,无论通过ref还是reactive都是递归监听
存在问题(数据量过大非常消耗性能)
let state = reactive({
a:'a',
gf:{
b:'b',
f:{
c:'c',
s:{
d:'d'
}
}
}
})
function myFn(){
console.log(state)
console.log(state.gf)
console.log(state.gf.f)
console.log(state.gf.f.s)
}
return {state,myFn}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PuvHXM1p-1639649691948)(C:\Users\xupt\AppData\Roaming\Typora\typora-user-images\image-20211117174755249.png)]
3.2 非递归监听(shallowReactive,shallowRef)
shallowRef创建数据,Vue监听的是.value的变化,并不是第一层的变化
let state = shallowReactive({
a:'a',
gf:{
b:'b',
f:{
c:'c',
s:{
d:'d'
}
}
}
})
function myFn(){
console.log(state)
console.log(state.gf)
console.log(state.gf.f)
console.log(state.gf.f.s)
}
return {state,myFn}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SvsXSKe2-1639649691950)(C:\Users\xupt\AppData\Roaming\Typora\typora-user-images\image-20211117174852825.png)]
3.3 triggerRef方法
根据传入的数据主动去更新界面(没有triggerReactive方法)
let state = shallowRef({
a:'a',
gf:{
b:'b',
f:{
c:'c',
s:{
d:'d'
}
}
}
})
function myFn(){
state.value.gf.f.s.d = '4'
triggerRef(state)
}
return {state,myFn}
3.4 一般情况下我们使用ref
和reactive
即可,如果需要监听数据量比较大的时候,使用shallowRef
和shallowReactive
4. toRaw
proxy代理生成一个proxy对象,但是和原对象的引用指向是一样的
setup(){
let obj = {name:'l',age:18}
let state = reactive(obj)
function myFn(){
obj.name = 'zs'
console.log(obj)
}
return {state,myFn}
}
//如果直接修改OBJ无法触发响应式,只有修改state才能更新界面
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fme0FZz4-1639649691951)(C:\Users\xupt\AppData\Roaming\Typora\typora-user-images\image-20211117182038478.png)]
setup(){
let obj = {name:'l',age:18}
let state = reactive(obj)
let obj2 = toRaw(state)
console.log(obj === obj2) //true
function myFn(){
obj2.name = 'zs'
console.log(obj)
}
return {state,myFn}
}
//toRaw方法可以获取原始数据
ref和reactive数据类型每次修改都会被追踪,都会更新UI界面,非常损耗性能
如果我们操作不需要追踪更新UI界面,可以使用toRaw方法拿到他的原始数据进行操作,节省性能
setup(){
let obj = {name:'l',age:18}
let state = ref(obj)
let obj2 = toRaw(state)
let obj3 = toRaw(state.value)
//ref(obj) —> reactive({value:obj})
console.log(obj === obj2) //false
console.log(obj === obj3) //true
function myFn(){
obj2.name = 'zs'
console.log(obj)
}
return {state,myFn}
}
//toRaw方法可以获取原始数据
5.markRaw
setup(){
let obj = {name:'l',age:18}
obj = markRaw(obj)
let state = reactive(obj)
function myFn(){
obj2.name = 'zs'
console.log(obj)
}
return {state,myFn}
}
//永远不被响应式
6.toRef和toRefs
6.1 toRef
setup(){
let obj = {name:'l',age:18}
//ref(obj.name)—>ref('l')->reactive({value:'l'})
//let state = ref(obj.name)
let state = toRef(obj,'name')
function myFn(){
state.value = 'zs'
}
return {state,myFn}
}
//如果利用ref将某个对象中的属性变成响应式的数据,我们修改响应式的数据不会影响原始数据
//toRef将一个对象中的属性变成响应式的数据,修改响应式数据会影响原始数据,但是不会触发UI界面更新
ref:赋值,修改响应式不会影响以前数据,界面会更新
toRef:引用,修改响应式数据会影响以前的数据,界面不会更新
6.2 toRefs
setup(){
let obj = {name:'l',age:18}
//let state = toRef(obj,'name','age') //报错
//let name = toRef(obj,'name')
//let age = toRef(obj,'age')
let {name,age} = ...toRefs(obj)
function myFn(){
state.value = 'zs'
}
return {name,age,myFn}
}
7.readonly
readonly:创建只读数据,并且是递归只读
shallowReadonly:非递归只读
isReadonly:判断是否是只读
Vue3进阶
一.setup语法糖
1.组件自动注册
<template>
<Child />
</template>
<script setup>
import Child from './Child.vue'
</script>
2.组件核心API的使用
使用props
通过defineProps指定当前 props 类型,获得上下文的props对象
<script setup>
import { defineProps } from 'vue'
const props = defineProps({
title: String,
})
</script>
使用emits
使用defineEmit定义当前组件含有的事件,并通过返回的上下文去执行 emit
<script setup>
import { defineEmits } from 'vue'
const emit = defineEmits(['change', 'delete'])
</script>
获取 slots 和 attrs
可以通过useContext
从上下文中获取 slots 和 attrs。不过提案在正式通过后,废除了这个语法,被拆分成了useAttrs
和useSlots
<script setup>
import { useContext } from 'vue'
const { slots, attrs } = useContext()
import { useAttrs, useSlots } from 'vue'
const attrs = useAttrs()
const slots = useSlots()
</script>
3.defineExpose API
传统的写法,我们可以在父组件中,通过 ref 实例的方式去访问子组件的内容,但在 script setup 中,该方法就不能用了,setup 相当于是一个闭包,除了内部的 template
模板,谁都不能访问内部的数据和方法。
如果需要对外暴露 setup 中的数据和方法,需要使用 defineExpose AP
<script setup>
import { defineExpose } from 'vue'
const a = 1
const b = 2
defineExpose({
a
})
</script>
4.属性和方法无需返回,直接使用
在以往的写法中,定义数据和方法,都需要在结尾 return 出去,才能在模板中使用。在 script setup 中,定义的属性和方法无需返回,可以直接使用
<template>
<div>
<p>My name is {{name}}</p>
</div>
</template>
<script setup>
import { ref } from 'vue';
const name = ref('Sam')
</script>