vue3学习

129 阅读8分钟

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节点,不受父级stylev-show等属性影响,但dataprop数据依旧能够共用的技术

主要解决的问题 因为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 将多个组件重复的函数 提取出来 里边可以使用周期函数