vue3新特性
1、重写双向绑定
vue2基于Object.defineProperty()实现
vue3 基于Proxy
好处:
1、可监听到对象新增、删除属性
2、可监听到数组变化 可以监听数组的索引和 length 属性
2、优化虚拟dom
vue2 每次更新diff 都是全量对比
vue3值对比带有标记的 ,vue3在创建dom的时候 给他们添加了标记 (patch flag) 当diff算法 对比时 会忽略所有静态节点 只对有标记的动态节点进行对比
3、组合式API
4、支持多个根节点
<template>
<div>12</div>
<div>23</div>
</template>
5、 Tree shaking
在Vue2中,无论我们使用什么功能,它们最终都会出现在生产代码中。主要原因是Vue实例在项目中是单例的,捆绑程序无法检测到该对象的哪些属性在代码中被使用到
而Vue3源码引入tree shaking特性,将全局 API 进行分块。如果你不使用其某些功能,它们将不会包含在你的基础包中
就是比如你要用watch 就是import {watch} from 'vue' 其他的computed 没用到就不会给你打包减少体积
响应式API
ref
创建可以使用任何值类型的响应式,不过一般用来创建基本类型的响应式对象
<template>
<div>
<button @click="changeMsg">change</button>
<div>{{ message }}</div>
</div>
</template>
<script setup lang="ts">
let message: string = "我是message"
const changeMsg = () => {
message = "change msg"
}
</script>
<style>
</style>
reactive
创建一个响应式对象或数组,仅对对象、数组、Map、Set有用
注意: 1、Vue 的响应式系统是通过属性访问进行追踪的,因此我们必须始终保持对该响应式对象的相同引用。这意味着我们不可以随意地“替换”一个响应式对象,因为这将导致对初始引用的响应性连接丢失:
所以对于数组或对象直接赋值更改会使其失去响应性
let person = reactive<number[]>([])
setTimeout(() => {
person = [1, 2, 3]
console.log(person);
},1000)
替换为 使用 push 或者splice 或包裹一层对象
push splice:
let person = reactive<number[]>([]);
setTimeout(() => {
//splice
person.splice(0, 0, 1, 2, 3);
//push
const arr = [1, 2, 3]
person.push(...arr)
console.log(person);
}, 10);
包裹对象
type Person = {
list?:Array<number>
}
let person = reactive<Person>({
list:[]
})
setTimeout(() => {
const arr = [1, 2, 3]
person.list = arr;
console.log(person);
},1000)
2、当我们将响应式对象的属性赋值或解构至本地变量时,或是将该属性传入一个函数时,我们会失去响应性:
const state = reactive({ count: 0 })
// n 是一个局部变量,同 state.count
// 失去响应性连接
let n = state.count
// 不影响原始的 state
n++
// count 也和 state.count 失去了响应性连接
let { count } = state
// 不会影响原始的 state
count++
// 该函数接收一个普通数字,并且
// 将无法跟踪 state.count 的变化
callSomeFunction(state.count)
isRef
判断是不是一个ref对象
import { ref, Ref,isRef } from 'vue'
let message: Ref<string | number> = ref("我是message")
let notRef:number = 123
const changeMsg = () => {
message.value = "change msg"
console.log(isRef(message)); //true
console.log(isRef(notRef)); //false
}
shallowRef
创建一个跟踪自身 .value 变化的 ref,但不会使其值也变成响应式的
shallowReactive
只处理对象最外层属性的响应式(浅响应式)
组件
注册组件
在单文件中使用:直接引入 使用即可,无需注册;
import Tree from '@/components/Tree/index.vue';
<Tree></Tree>
批量注册全局组件
//注册文件 registerGlobComp.js
import type { App } from 'vue';
import { Button } from './Button';
import { Input, Layout } from 'ant-design-vue';
export function registerGlobComp(app: App) {
app.use(Input).use(Button).use(Layout);
}
main.ts
import { registerGlobComp } from '/@/components/registerGlobComp';
// 注册全局组件
registerGlobComp(app);
自动批量注册全局组件
**全局注册组件
** 放在components/global文件夹下
*/
import { defineAsyncComponent } from 'vue';
const components = import.meta.glob('./global/*.vue'); // 异步方式
export default function install(app) {
for (const [key, value] of Object.entries(components)) {
const name = key.slice(key.lastIndexOf('/') + 1, key.lastIndexOf('.'));
app.component(name, defineAsyncComponent(value));
}
}
组件间传值
父传子
defineProps withDefaults(不需要引入直接使用)
传递字符串类型是不需要v-bind 传递非字符串类型需要加v-bind 简写 冒号
//父组件
<template>
<div class="layout">
<Menu v-bind:data="data" title="我是标题"></Menu>
<div class="layout-right">
<Header></Header>
<Content></Content>
</div>
</div>
</template>
<script setup lang="ts">
import Menu from './Menu/index.vue'
import Header from './Header/index.vue'
import Content from './Content/index.vue'
import { reactive } from 'vue';
const data = reactive<number[]>([1, 2, 3])
</script>
//子组件
<template>
<div class="menu">
菜单区域 {{ title }}
<div>{{ data }}</div>
</div>
</template>
<script setup lang="ts">
// 没默认值
defineProps<{
title:string,
data:number[]
}>()
//有默认值:
type Props = {
title?: string,
data?: number[]
}
withDefaults(defineProps<Props>(), {
title: "张三",
data: () => [1, 2, 3]
})
</script>
子传父
1、defineEmits
//子组件
<template>
<div class="menu">
<button @click="clickTap">派发给父组件</button>
</div>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
const list = reactive<number[]>([4, 5, 6])
//如果用了ts可以这样两种方式
const emit = defineEmits(['on-click'])
const emit = defineEmits<{
(e: "on-click", name: []): void
}>()
const clickTap = () => {
emit('on-click', list)
}
</script>
父组件:
<template>
<div class="layout">
<Menu @on-click="getList"></Menu>
<div class="layout-right">
<Header></Header>
<Content></Content>
</div>
</div>
</template>
<script setup lang="ts">
import Menu from './Menu/index.vue'
import Header from './Header/index.vue'
import Content from './Content/index.vue'
import { reactive } from 'vue';
const data = reactive<number[]>([1, 2, 3])
const getList = (list: number[]) => {
console.log(list,'父组件接受子组件');
}
</script>
2、defineExpose 将子组件属性暴露给父组件
//子组件
const list = reactive<number[]>([4, 5, 6])
defineExpose({
list
})
//父组件
<Menu ref="menus"></Menu>
//这样获取是有代码提示的
const menus = ref<InstanceType<typeof menus>>()
menus.value?.list
祖孙传值
provide inject
1、应用场景:对于深度嵌套的组件 想要传值 需要Provide Inject
2、修改值: 尽量在供给组件修改值 如果在孙子组件更改inject中的值 在供给方组件内声明
<!-- 在供给方组件内 -->
<script setup>
import { provide, ref } from 'vue'
const location = ref('North Pole')
function updateLocation() {
location.value = 'South Pole'
}
provide('location', {
location,
updateLocation
})
</script>
<!-- 在注入方组件 -->
<script setup>
import { inject } from 'vue'
const { location, updateLocation } = inject('location')
</script>
<template>
<button @click="updateLocation">{{ location }}</button>
</template>
兄弟组件传值
1、设一个共有的父组件 通过父组件传值
2、Mitt
使用方法:xiaoman.blog.csdn.net/article/det…
插槽
常用的为作用域插槽,使用方法:
//子组件
<slot name="header" message="hello"></slot>
//父组件
<MyComponent>
<template #header="headerProps">
{{ headerProps }}//headerProps = {message:hello}
</template>
<template #default="defaultProps">
{{ defaultProps }}
</template>
<template #footer="footerProps">
{{ footerProps }}
</template>
</MyComponent>
内置组件
异步组件
懒加载组件 只有在用到该组件时 才会加载对应组件 提升性能
script setup>
import { defineAsyncComponent } from'vue
const AdminPage = defineAsyncComponent(() =>
import('./components/AdminPageComponent.vue')
</script>
<template>
<AdminPage />
</template>
<!-- 处理加载错误状态 -->
const AsyncComp = defineAsyncComponent({
// 加载函数
loader: () => import('./Foo.vue'),
// 加载异步组件时使用的组件
loadingComponent: LoadingComponent,
// 展示加载组件前的延迟时间,默认为 200ms
delay: 200,
// 加载失败后展示的组件
errorComponent: ErrorComponent,
// 如果提供了一个 timeout 时间限制,并超时了
// 也会显示这里配置的报错组件,默认值是:Infinity
timeout: 3000
})
Suspense
Suspense是一个内置组件,用来在组件树中协调对异步依赖的处理,在加载前的当对应深层组件没有加载完之前 显示fallback中内容 当加载完后展示深层
<Suspense>
<template #default>
<Dialog>
<template #default>
<div>我在哪儿</div>
</template>
</Dialog>
</template>
<template #fallback>
<div>loading...</div>
</template>
</Suspense>
keep-alive
<!-- a,b 是组件名字 -->
<!-- 以英文逗号分隔的字符串 -->
<KeepAlive include="a,b">
<component :is="view"/>
</KeepAlive>
<!-- 正则表达式(需使用`v-bind`)-->
<KeepAlive :include="/a|b/">
<component :is="view"/>
</KeepAlive>
<!-- 数组(需使用`v-bind`) -->
<KeepAlive :include="['a', 'b']">
<component :is="view" />
</KeepAlive>
max: 来限制可被缓存的最大组件实例数。 的行为在指定了 max 后类似一个 LRU 缓存:如果缓存的实例数量即将的缓存实例将被销毁,以便为新的实例腾出空间。
<KeepAlive :max="10">
<component :is="activeComponent"/>
</KeepAlive>
触发钩子函数:
onActivated 在组件挂载时也会调用,并且 onDeactivated 在组件卸载时也会调用。
onActivated(() => {// 调用时机为首次挂载
// 以及每次从缓存中被重新插入时
})
```
onDeactivated(() => {
// 在从 DOM 上移除、进入缓存// 以及组件卸载时调用})
Teleport传送组件
能够将我们的模板渲染至指定DOM节点,不受父级style、v-show等属性影响,但data、prop数据依旧能够共用的技术
主要解决的问题 因为Teleport节点挂载在其他指定的DOM节点下,完全不受父级style样式影响
computed
计算属性,只有依赖更改的时候返回值才会改变
注意:
1、计算属性的 getter 应只做计算而没有任何其他的副作用,getter 的职责应该仅为计算和返回该值,不要在 getter 中做异步请求或者更改 DOM!
2、计算属性的返回值应该被视为只读的,并且永远不应该被更改
let price = ref(0); //$0
let m = computed<string>(() => {
return `$` + price.value;
});
watch
第一个参数可以是不同形式的“数据源”:它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组
第二个参数:回调函数
第三个参数:
opyions:{
immediate:true //是否立即调用一次
deep:true //是否开启深度监听
深度侦听需要遍历被侦听对象中的所有嵌套的属性,
当用于大型数据结构时,开销很大。因此请只在必要时才使用它,并且要留意性能
}
const x = ref(0);
const y = ref(0);
// 单个 ref
watch(x, (newX) => {
console.log(`x is ${newX}`);
});
// 提供一个 getter 函数 当监听对象中某个属性的时候
const obj = reactive({ count: 0 })
watch(
() => obj.count,
(count) => {
console.log(`count is: ${count}`);
}
);
//一个reactive对象
let message = reactive({
nav:{
bar:{
name:""
}
}
})
watch(message, (newVal, oldVal) => {
console.log('新的值----', newVal);
console.log('旧的值----', oldVal);
})
// 多个来源组成的数组
watch([x, y], ([newX, newY]) => {
console.log(`x is ${newX} and y is ${newY}`);
});
watchEffect
懒执行的:仅当数据源变化时,才会执行回调,并立即执行一遍回调函数,在副作用发生期间追踪依赖。它会在同步执行过程中,自动追踪所有能访问到的响应式属性
回调触发时机:
默认情况下,用户创建的侦听器回调,都会在 Vue 组件更新之前被调用。这意味着你在侦听器回调中访问的 DOM 将是被 Vue 更新之前的状态。
停止侦听器
1、同步语句创建的侦听器,会自动绑定到宿主组件实例上,并且会在宿主组件卸载时自动停止
2、异步语句创建的侦听器,它不会绑定到当前组件上,你必须手动停止它,以防内存泄漏。
//停止侦听 调用`watch` 或 `watchEffect` 返回的函数
const unwatch = watchEffect(()=>{ /*...*/ })
unwatch()
逻辑复用
自定义指令
想要复用 操作dom函数
使用场景:只有当所需功能只能通过直接的 DOM 操作来实现时,才应该使用
用法:
const myDirective = {
// 在绑定元素的 attribute 前
// 或事件监听器应用前调用
created(el, binding, vnode, prevVnode) {
// 下面会介绍各个参数的细节
},
// 在元素被插入到 DOM 前调用
beforeMount(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都挂载完成后调用
mounted(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件更新前调用
beforeUpdate(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都更新后调用
updated(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载前调用
beforeUnmount(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载后调用
unmounted(el, binding, vnode, prevVnode) {}
}
具体参数在vue官方文档中查看
简化版 只在mounted 和updated中应用
app.directive('color', (el, binding) => {
// 这会在 `mounted` 和 `updated` 时都调用
el.style.color = binding.value
})
在组件上使用:
应用于组件的根节点,和透传 attributes 类似。
组件可能含有多个根节点。当应用到一个多根组件时,指令将会被忽略且抛出
HOOK 组合式函数
相当于vue2中的Mixins 将多个组件重复的函数 提取出来 里边可以使用周期函数