day-113-one-hundred-and-thirteen-20230715-vue3新API-Vue3和Vue2对比-vue3语法-Vue3编码
vue3新API
虚拟DOM
- 说明:看01视频。
- 虚拟DOM是用于让vue核心代码脱离浏览器的限制,在微信小程序、手机端、canvas之类也有能使用vue语法的可能,而不必改动vue源码。
createRenderer
-
提供⾃定义渲染器,可以在⾮DOM环境中使⽤Vue的运⾏时。
-
虚拟dom的优点?
- 有了虚拟dom 可以使用diff算法。
- 跨平台:vue是针对虚拟dom创建了真实的dom,其它的框架可以基于虚拟dom做其他的事情,如:canvas。
-
平时我们一般并不需要使用这个api,只是为了扩展渲染能力才会使用,如用vue写微信小程序之类的需求。
-
这个api主要就是可以自定义渲染方式。
-
说明:
-
vue2中没createRenderer的写法。
import { createRenderer, h, render } from "vue" const vnode = h('div', 'hello-vue2的render') render(vnode, app); // 这个render方法,createElemet() el.textContent = 'hello' // // appendChild
-
vue3中有createRenderer自定义浏览器的写法。
import { createRenderer, h } from "vue" const { render } = createRenderer({ // 创建一个元素 createElement(type) { if (type === "div") { return document.createElement("p") } }, setElementText(el, text) { el.textContent = text }, insert(el, parent) { parent.appendChild(el) } // 给元素设置属性,方法 // 给元素设置内容 }) const vnode = h("div", "vue3-hello-自定义render") render(vnode, app)
-
v-bind绑定样式CSS变量
- 在css中使⽤v-bind绑定样式
<script>
export default {
mounted() {
setTimeout(() => {
this.color = "blue"
}, 1000)
},
data() {
return { color: "red" }
}
}
</script>
<template>
<div class="custorm">v-bind绑定样式CSS变量-绑定样式</div>
</template>
<style scoped>
.custorm {
background: v-bind(color);
}
</style>
具有scoped属性的style标签中新加的选择器
-
在scoped中自定义深度选择、插槽、全局样式的可以采用:
:deep()
、:slotted()
、:global()
这些选择器函数。 -
修改子组件中的样式:
- 注:vue3中在子组件上直接设置的类名及样式及事件会追踪到子组件的根节点上。
- 示例:
-
vue3-7-15/src/App.vue
<script> import Scoped from "./components/scoped.vue" export default { componets: { Scoped } } </script> <template> <div class="parent"> <Scoped></Scoped> </div> </template> <style scoped> .parent :deep(h1) { color: yellow; } </style>
-
vue3-7-15/src/components/scoped.vue
<template> <div> <h1>子组件的h1标签----hello</h1> </div> </template>
-
样式已经生效。
-
- 示例:
-
vue3-7-15/src/App.vue
<script> import Scoped from "./components/scoped.vue" export default { componets: { Scoped, }, } </script> <template> <div class="parent"> <Scoped></Scoped> </div> </template> <style scoped> .parent h1 { color: yellow; } </style>
-
vue3-7-15/src/components/scoped.vue
<template> <div> <h1>子组件的h1标签----hello</h1> </div> </template>
-
样式没有生效。
-
-
:slotted()
在子组件中捕获修改来自于父组件插槽中的类名,并可根据预定的类让插槽的css样式有预设:-
示例:
-
vue3-7-15/src/App.vue
<script> import Scoped from "./components/scoped.vue" export default { componets: { Scoped, }, } </script> <template> <div class="parent"> <Scoped> <h2 class="inner">这是来自于父组件的插槽内容</h2> </Scoped> </div> </template>
-
vue3-7-15/src/components/scoped.vue
<template> <slot></slot> </template> <style scoped> :slotted(.inner) { background: red; } </style>
-
样式生效。
-
-
示例:
-
vue3-7-15/src/App.vue
<script> import Scoped from "./components/scoped.vue" export default { componets: { Scoped, }, } </script> <template> <div class="parent"> <Scoped> <h2 class="inner">这是来自于父组件的插槽内容</h2> </Scoped> </div> </template>
-
vue3-7-15/src/components/scoped.vue
<template> <slot></slot> </template> <style scoped> .inner{ background: red; } </style>
-
样式不生效。
-
-
示例:
-
vue3-7-15/src/App.vue
<script> import Scoped from "./components/scoped.vue" export default { componets: { Scoped, }, } </script> <template> <div class="parent"> <Scoped> <h2 class="inner">这是来自于父组件的插槽内容</h2> </Scoped> </div> </template> <style scoped> h2 { color: blue; } </style>
-
vue3-7-15/src/components/scoped.vue
<template> <slot></slot> </template>
-
直接在父组件中设置插槽的样式,生效。
-
-
-
:global()
在组件的scoped中设置全局样式。-
示例:
-
vue3-7-15/index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <link rel="icon" href="/favicon.ico"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Vite App</title> </head> <body> <div id="app"></div> <div id="root">root</div> <script type="module" src="/src/main.js"></script> </body> </html>
-
vue3-7-15/src/main.js
import { createApp } from 'vue' import App from './App.vue' import router from './router' const app = createApp(App) app.use(router) app.mount('#app')
-
vue3-7-15/src/App.vue
<script> export default {} </script> <style scoped> :global(#root) { color: red; } </style>
-
-
在组件的scoped中设置的全局样式生效了。
-
vue3于父组件中直接上子组件上设置的事件直接会绑定到子组件的根节点上
-
vue3于父组件中直接上子组件上设置的事件直接会绑定到子组件的根节点上。
- 示例:
-
vue3-7-15/src/App.vue
<script> import Emit from "./components/emit.vue" export default { componets: { Emit, }, methods: { handleClick(type) { alert(`父组件中-${type}`) } } } </script> <template> <Emit @click="handleClick"></Emit> </template>
-
vue3-7-15/src/components/emit.vue
<template> <div>子组件上的根节点-我是一个按钮</div> <!-- <div @click="handleClick">我是一个按钮</div> --> </template>
-
vue3中父组件上绑定的事件会触发。
-
- 示例:
-
vue3设置上emits可以设置在当前组件中那些组件是自定义事件,需要基于$emit(“自定义事件名”)才能向让父组件在其自身组件实例上绑定的该同名方法被触发。
- 示例:
-
vue3-7-15/src/App.vue
<script> import Emit from "./components/emit.vue" export default { componets: { Emit, }, methods: { handleClick(type) { alert(`父组件中-${type}`) } } } </script> <template> <Emit @click="handleClick"></Emit> </template>
-
vue3-7-15/src/components/emit.vue
<template> <div @click="handleClick">子组件上的根节点-我是一个按钮</div> </template> <script> export default { emits: ["click"], // 标识哪些事件是自定义的 methods: { handleClick() { console.log(`子组件中点击事件@click`) // this.$emit("click", "hello") } } } </script>
-
此时如果子组件不使用this.$emit(“click”)触发自定义的click事件,在父组件上监听不到它的执行。
-
- 示例:
-
vue3-7-15/src/App.vue
<script> import Emit from "./components/emit.vue" export default { componets: { Emit, }, methods: { handleClick1(type) { alert(`父组件中-${type}`) } } } </script> <template> <Emit @click="handleClick1"></Emit> </template>
-
vue3-7-15/src/components/emit.vue
<template> <div @click="handleClick">子组件上的根节点-我是一个按钮</div> </template> <script> export default { emits: ["click"], // 标识哪些事件是自定义的 methods: { handleClick() { console.log(`子组件中点击事件@click`) this.$emit("click", "hello") } } } </script>
-
此时如果子组件先执行handleClick(),之后使用this.$emit(“click”)触发自定义的click事件,在父组件上监听到才执行handleClick1()。
-
- 示例:
-
vue3-7-15/src/App.vue
<script> import Emit from "./components/emit.vue" export default { componets: { Emit, }, methods: { handleClick1(type) { alert(`父组件中-${type}`) } } } </script> <template> <Emit @click="handleClick1"></Emit> </template>
-
vue3-7-15/src/components/emit.vue
<template> <div @click="handleClick">子组件上的根节点-我是一个按钮</div> </template> <script> export default { methods: { handleClick() { console.log(`子组件中点击事件@click`) this.$emit("click", "hello") } } } </script>
-
由于没基于emits标识哪些事件是自定义的,此时如果子组件先执行handleClick(),之后使用this.$emit(“click”)触发自定义的click事件,在父组件上监听到才执行handleClick1(),此时handleClick1()的入参是子组件传递的值"hello"。而由于没基于emits标识哪些事件是自定义的,父组件依旧会监听到子组件根节点上执行了click事件,于是父组件的handleClick1(),此时handleClick1()的入参是子组件根节点的事件对象。
-
- 示例:
Suspense组件可以支持渲染异步组件
-
Suspense组件主要的作⽤优雅地处理异步组件的加载状态。
-
可以支持渲染异步组件 并且添加loading,我们需要先有一个异步组件。异步组件可以是懒加载的组件,也可以在setup被异步执行的组件。我们也可以自己写一个异步组件。
-
异步组件示例:
<template> <div>这是一个异步加载到的的组件---哈哈</div> </template> <script setup> await new Promise((resolve, reject) => { setTimeout(() => { resolve() }, 1000) }) </script>
等价于
<template> <div>这是一个异步加载到的的组件---哈哈</div> </template> <script> export default { async setup() { await new Promise((resolve, reject) => { setTimeout(() => { resolve() }, 1000) }) } } </script>
-
Suspense组件的示例:
-
vue3-7-15/src/App.vue
<script> import AsyncDemo from "./components/async-demo.vue" import { Suspense } from "vue" export default { componets: { AsyncDemo, Suspense } } </script> <template> <Suspense> <template #default> <AsyncDemo></AsyncDemo> </template> <template #fallback>正在加载异步组件的loading文字 </template> </Suspense> </template>
-
vue3-7-15/src/components/async-demo.vue
<template> <div>这是一个异步加载到的的组件---哈哈</div> </template> <script setup> await new Promise((resolve, reject) => { setTimeout(() => { resolve() }, 1000) }) </script>
-
Vue3和Vue2对比
-
整个性能比以前高了
- 代理实现得更优越了。
- 为什么性能高了?
- Object.defineProperty()性能低。要递归增加getter和setter,不存在的属性不能事件进行getter与setter劫持,数组由于性能问题不能用它,需要使用
s
e
t
(
)
与
set()与
set()与delete()给它上补丁。
- 无法支持set或者map的属性劫持
- 优化层级不能嵌套过深,属性使用的时候一定要缓存。
- 有些属性不需要响应式 Object.freeze()
- Proxy(完美解决以上的问题)
- Object.defineProperty()性能低。要递归增加getter和setter,不存在的属性不能事件进行getter与setter劫持,数组由于性能问题不能用它,需要使用
s
e
t
(
)
与
set()与
set()与delete()给它上补丁。
- 为什么性能高了?
- diff算法的缺点是:要一层层比对。
- vue3在模版编译的时候做了很多优化。
- Block(收集当前组件的动态节点) Tree (靶向更新)更新的过程中会进行动态属性的描述(标记哪些属性会变化,不变的不用更新)。
- vue3 建议是尽量模板语法,jsx 无法得到这些靶向更新的优化支持。
- Block(收集当前组件的动态节点) Tree (靶向更新)更新的过程中会进行动态属性的描述(标记哪些属性会变化,不变的不用更新)。
- vue3在模版编译的时候做了很多优化。
- 代理实现得更优越了。
-
整个体积小了。
- 用的compositionAPI打包小。
- 删除vue2大量api
$set
、$delete
、.native
、$listeners
、eventBus
、$on
、$emit
、$off
、$once
都移除了;- 不再支持
keyCode修饰符
。 过滤器
干掉。inline-template
直接删除。Vue.component
、Vue.directive
等全局静态方法,全部移除。.sync
移除,如@xxx.sync="xxx"
不再支持。provide
、inject
实例上的api也进行了转移。需要使用import {provide,inject} from 'vue'
这类写法。
-
vue3 采用ts来编写提示好, vue2.7 也是用ts来编写的
-
以前vue2把代码都放在一起管理,想扩展或者单独使用不方便, vue3在一个仓库下管理了多个项目,每个模块可以单独使用
Vue3对⽐Vue2的变化
- Vue3对⽐Vue2的变化?
- 性能优化(更快):
- 使⽤了Proxy替代Object.defineProperty实现响应式。(为什么?defineProperty需要对属性进⾏递归重写添加getter及setter 性能差,同时新增属性和删除属性时⽆法监控变化,需要 s e t 、 set、 set、delete⽅法。此⽅法对数组劫持性能差,同时不⽀持map和set的数据结构。)
- 模板编译优化。给动态节点增添PatchFlag标记;对静态节点进⾏静态提升;对事件进⾏缓存处理等。
- Diff算法优化,全量diff算法中采⽤最⻓递增⼦序列减少节点的移动。在⾮全量diff算法中只⽐较动态节点,通过PatchFlag标记更新动态的部分。
- 体积优化(更⼩):
- Vue3移除了不常⽤的API:
- 移除inline-template (Vue2中就不推荐使⽤)
- o n 、 on、 on、off、$once (如果有需要可以采⽤mitt库来实现)
- 删除过滤器 (可以通过计算属性或者⽅法来实现)
- 移除.sync .native)修饰符 (.sync通过 v-model:xxx实现,.native为Vue3中的默认⾏为) 以及不在⽀持keycode作为v-on修饰符(@keyup.13不在⽀持)
- 移除全局API。Vue.component、Vue.use、Vue.directive (将这些api挂载到实例上)
- 通过构建⼯具Tree-shaking机制实现按需引⼊,减少⽤户打包后体积。
- Vue3移除了不常⽤的API:
- ⽀持⾃定义渲染器:
- ⽤户可以⾃定义渲染API达到跨平台的⽬的。扩展能⼒更强,⽆需改造Vue源码就可以支持类似于微信小程序这类平台。
- TypeScript⽀持:
- Vue3源码采⽤Typescript来进⾏重写 , 对Ts的⽀持更加友好。
- 源码结构变化:
- Vue3源码采⽤ monorepo ⽅式进⾏管理,将模块拆分到package⽬录中,解耦后可单独使⽤。
- 性能优化(更快):
vue3源码的核心组成
- vue3的源码组成结构 : vue
@vue/compiler-dom
-@compiler-core
(将我们的模版变成render函数)。@vue/runtime-dom
-@vue/runtime-core
-@vue/reactivity
方便管理可以单独使用。
Vue3 响应式数据原理
const isObject = (val) => val !== null && typeof val === "object"
const proxyMap = new WeakMap()
function createReactiveObject(target) {
if (!isObject(target)) {
console.warn(`value cannot be madereactive: ${String(target)}`)
return target
}
// 经过劫持处理过的,就不在重复处理了
const existingProxy = proxyMap.get(target)
if (existingProxy) return existingProxy
// 进⾏数据劫持
const proxy = new Proxy(target, {
get: function get(target, key, receiver) {
const res = Reflect.get(target, key)
if (isObject(res)) {
return reactive(res)
}
return res
},
set: function set(target, key, value, receiver) {
let oldValue = target[key]
if (oldValue === value) return
const result = Reflect.set(target, key, value, receiver)
console.log("渲染")
return result
}
})
proxyMap.set(target, proxy)
return proxy
}
function reactive(target) {
return createReactiveObject(target)
}
const state = reactive({ name: "jw", arr: [1, 2, 3] })
state.name = "哈哈"
state.arr[0] = 100
Proxy与Object.definePrototype()
- 以前劫持的是属性-重写set和get,新增的不行。
- proxy劫持的是对象-代理,并没有增添客户的属性。代理的范围变大了。
Proxy
const 以函数作为属性的代理配置对象 = {
get: function(obj, prop) {
return prop in obj ? obj[prop] : 37;
}
};
const 源对象 = {}
const 代理对象 = new Proxy(源对象, 以函数作为属性的代理配置对象);
代理对象.a = 1;
代理对象.b = undefined;
console.log(代理对象.a, 代理对象.b); // 1, undefined
console.log('c' in 代理对象, 代理对象.c); // false, 37
- Proxy
- Proxy()的特点:
- Proxy()返回的是一个
代理对象
,对该返回的代理对象
执行操作才会触发以函数作为属性的代理配置对象
中的配置属性。 - 对源对象直接进行的操作,并不会与代理对象有什么直接联系,
以函数作为属性的代理配置对象
也监听不到源对象上的直接修改。 源对象
一般是常被作为代理对象
的存储后端。代理对象
一般根据目标验证关于源对象
不可扩展性或不可配置属性的不变量。- 也就是说,一般一旦一个对象被作为代理对象后,就不直接改动它了,只用于存储数据,以便
代理对象
对应的以函数作为属性的代理配置对象
中的函数里去修改它。
- Proxy()返回的是一个
Reflect
- Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与 proxy handler (en-US) 的方法相同。Reflect 不是一个函数对象,因此它是不可构造的。
- 与大多数全局对象不同 Reflect 并非一个构造函数,所以不能通过 new 运算符对其进行调用,或者将 Reflect 对象作为一个函数来调用。Reflect 的所有属性和方法都是静态的(就像 Math 对象)。
- 虽然Reflect常与Proxy来一起使用,但先有的Reflect才有的Proxy。两者不是包含的关系,而是同为js的基础API。不过,Proxy的入参值一般与Reflect入参值保持一致。
Proxy懒代理
// 以前我们劫持的是属性(重写set和get)(新增的不行) $set $delete
// proxy 劫持的是对象 (代理,并没有增添额外的属性)(劫持的范围变大了)
function isObject(value) {
return value !== null && typeof value === "object"
}
const state = { name: "zf", age: { n: { n: 100 } } }
// 最终返回的是代理对象,后续我们使用代理对象访问属性
function reactive(state) {
const proxy = new Proxy(state, {
get(target, key, receiver) {
// target指代的是被代理源对象
// key 是取值的属性
// receiver 代理对象
// 依赖收集
// return target[key]
let res = Reflect.get(target, key, receiver)
if (isObject(res)) {
// 懒代理
return reactive(res)
}
return res
},
set(target, key, value, receiver) {
console.log("用户设置值了")
return Reflect.set(target, key, value, receiver)
}
})
return proxy
}
// proxy支持数组和对象的新增以及删除,而且没有给属性重新定义。 性能好,是懒代理的
const proxy = reactive(state)
// 当我取到proxy.age 的时候发现他是一个对象,那我就把这个对象在进行代理
// Reflect 以后会将所有的Object的api全部放到reflect中
// Object.defineProperty Reflect.defineProperty
// Object.setPrototypeof Reflect.setPrototypeof
靶向更新
- 就是在模板语法如果一个是静态内容,如不绑定响应式数据的DPM,则比较时直接跳过它。
- Vue3的模板经过编译后,可以把从模板中提取到这个信息附着到对应的vnode上,可以看到编译后的vnode中p标签多了一个patchFlag属性。
vue3语法
- new Vue不再使用了,而是使用createApp()来创建应用。
- Vue3整个的入口都是基于组件 createApp(组件)
入口文件的更改
全局组件
- Vue2中的全局扩展
Vue.component
,Vue.directive
Vue.use
Vue.prototype
->app.config.globalProperties
,app.use
app.mixin
;
注释全局组件
访问全局组件
选项式
组合式
Vue3 composition Api
:reactive
、computed
、watch
、watchEffect
、ref
、toRef
、toRefs
。
vue3生命周期
- 可以写多次。
- 名称修改了。
- 生命周期: setup 替代了 (beforeCreate created) onXxxx (beforeDestroy destroyed) 做了修改
与vue2生命周期的对比
全局捕获错误示例
- app.config.errorHandler 如果面试中问到如何监控vue中的错误;
组件的组成
- 组件的组成
- 模板
- 实例:生命周期、属性、状态、事件、通信方式、插槽
- 样式
组件中配置组件名
- 开发中如果有对应的语法糖,一般就使用对应的语法糖写法。
- setup语法声明的组件 要配置name属性
defineOption()
;
- setup语法声明的组件 要配置name属性
Vue3编码
- Vue2中的全局扩展
- app.config.errorHandler
- Vue3整个的计算器老师基于组件
setup(){}与setup模式
- Vue组件我们期望采用vue3 compositionAPI的方式来进行编写
setup(){}
,setup语法糖
(新的语法);
with
- 模板中this可以省略也可以不省略。内部可以认为是用了with(){}来达到类似的效果。
let proxy = { name: "zf" }
with (proxy) {
console.log(proxy.name) //zf
}
计算属性computed
计算属性与watch
- 什么时候用watch,什么时候用computed?
watch
watchEffect
ref
ref的实现原理
// Object.defineProperty({getter})
const total = {
// ref的实现原理
_value: null,
get value() {
console.log("依赖收集")
return this._value
},
set value(val) {
this._val = val
}
}
console.log(total.value)
total.value = "abc"
console.log(total.value)
toRef
toRefs
props与attrs与emits与slots与expose
数据传递
属性通信
事件通信
插槽通信
setup语法糖的内容
- Setup语法的使用
const props = defineProps()
const emit = defineEmits()
defineExpose()
const attrs = useAttrs()
const slots = useSlots()