vue3学习

小白学习多多指教~

简介

vue3 官网

Vite 官网

一个 Vue 3 UI 框架 | Element Plus

//查看vue版本  必须在4.5.0以上
vue --version

//升级vue 
npm install -g @vue/cli
yarn global add @vue/cli

//创建项目
vue create my-project

回顾vue2 对比 vue3 

  • vue2 逻辑比较分散 可读性差 可维护性差 对typescript支持有限
  • vue3 逻辑分明 可维护性高

 Vue3 新特性介绍

  • 重写向数据绑定
    • //vue2
      //defineProperty只有知道key,才能读取拦截,所以新增属性v2表示无能为力
      Object.defineProperty(data,'count',{
      	get(){},
      	set(){},
      })
      //vue3 Proxy
      //知道对象名就可以读取或拦截data上任意的key
      new Proxy(data,{
      	get(key){},
      	set(key,value){}
      })
      
      相比有以下优势:
      丢掉麻烦的备份数据
      省去for in 循环
      可以监听数组变化
      代码更简化
      可以监听动态新增的属性;
      可以监听删除的属性 ;
      可以监听数组的索引和 length 属性;

  • VDOM性能瓶颈,优化diff算法
    • vue2中diff算法是整体全量比对,在vue3中加入了静态标签(PatchFlag)的概念
      PatchFlag给元素增加标记。
      • 大于1的,diff运算时会与old vdom进行比对更新。
      • 小于1的,diff运算时会忽略这些元素,只整体递归遍历vdom tree时进行比对更新
      • vue3学习_第1张图片
  • Fragments
    • vue3 允许我们支持多个根节点
    • 支持render JSX 写法
  • 按需加载
    • 代码中没有用到的部分代码中不会打包到dist目录中

  • Composition API
    • Setup 语法糖式编程 

setup

  1. setup函数处于 生命周期函数 beforeCreate 和 Created 两个钩子函数之间的函数 (无法使用 data数据、 methods方法的)
  2. setup函数 Composition API(组合API)的入口
  3. setup函数 变量和方法,要return出去的 ,不然无法使用
  4. setup函数 this为undefined
  5. setup函数 只能同步,不能异步

模板语法


 
 

 

内置指令

v- 开头都是vue 的指令

  • v-text         展示文本
  • v-html        展示富文本
  • v-if             控制元素的显示隐藏(切换真假DOM)
  • v-else-if      v-if 的“else if 块”。可以链式调用
  • v-else         v-if条件收尾语句
  • v-show       控制元素的显示隐藏(display none block Css切换)
  • v-on           简写@ 用来给元素添加事件
  • v-bind        简写:  用来绑定元素的属性Attr
  • v-model     双向绑定
  • v-for           用来遍历元素
  • v-once       性能优化只渲染一次
  • v-memo     性能优化会有缓存

v-model

  • 支持组件
    • Input
    • Select
    • 表单元素
    • 自定义组件

在自定义组件中,使用v-model,可以达到父子双向传值的效果

  1. 将内部原生  元素的 value attribute 绑定到 modelValue prop
  2. 当原生的 input 事件触发时,触发一个携带了新值的 update:modelValue 自定义事件
//父组件





//子组件


自定义修饰符

//父组件



//子组件

自定义指令

虚拟dom

通过JS来生成一个AST节点树。

为什么不直接操作真实dom?  真实dom上面的属性是非常多的,直接操作DOM非常浪费性能。

如何解决?   通过js计算性能来换取操作dom所消耗的性能,当然我们不可避免要操作DOM,但是我们尽可能少的操作真实dom,而操作js是非常快的

diff算法

diff算法可以看作是一种对比算法,对比的对象是新旧虚拟Dom

十分详细的diff算法原理解析_Dddusty的博客-CSDN博客

ref和reactive

相同点:都是实现响应式数据

ref

  •  创建响应式变量,主要是一些 基本数据类型
  • 可以直接返回创建的值。
  • ref( ) 创建的是对象类型,在js使用 .value 形式获取
  •  在 template模板中直接读取,自动展开渲染内部的值。 

reactive

  • 创建响应式对象,主要是 object array 类型。
  • 可以直接返回创建的值,以链式调用读取,也可以直接读取变量里的值(需要用到 toRefs )
  • 在js中,不需要.value,可以 直接修改
  • 在template模板中,需要用 x.y 的形式获取值;
const test = reactive({
    id: 1,
    name: 'neeko',
    
})
console.log(test.id) // 1

注意:

  • 清空reactive定义的数组时必须将length设为0,直接赋值一个空数组是没有作用的
  • 对象直接赋值一个空对象也没有作用,只能遍历对象一项一项删

computed

计算属性, 当依赖项发生改变时,值也会发生改变。

  • 函数形式   返回只读响应式ref对象。ref.value暴露getter函数返回值
  • 对象形式   接受一个带有get和set函数的对象来创建一个可读写 ref 对象。

watch和watchEffect

watch函数与watchEffect函数都是监听器,在写法和用法上有一定区别,属于是同一功能的两种不同形态,底层都是一样的。

watch

  • watch显式指定依赖数据(第一个参数),依赖数据更新时执行回调函数
  • 具有一定的惰性lazy ,第一次页面展示的时候不会执行,数据变化时才会执行(设置immediate: true时可以变为非惰性,页面首次加载就会执行)
  • 三个参数,第一个是监视的对象,第二个是监视的回调函数,第三个是监视的配置
const mesage1 = ref({
  nav: {
    tit: {
      name: "neeko",
    },
  },
});

//监听单个ref对象
watch(
  mesage1,
  (newV, oldV) => {
    console.log(newV, oldV);
  },
  {
    immediate: true, //是否立即调用一次
    deep: true, //是否开启深度监听  只有深度监听才能收听到ref对象类型
  }
);

//多个监听ref对象
watch(
  [mesage1,mesage],
  (newV, oldV) => {
    console.log(newV, oldV);
  },
  {
    immediate: true, //是否立即调用一次
    deep: true, //是否开启深度监听  只有深度监听才能收听到ref对象
  }
);

reactive与deep深度监听

//reactive
const mesage1 = reactive({
  nav: {
    tit: {
      name: "neeko",
    },
  },
});

watch(
  mesage1,
  (newV, oldV) => {
    //若监视的是reactive定义的响应式数据,则无法正确获得oldValue
    console.log(newV, oldV);
  },
  {
    immediate: true, //是否立即调用一次
    deep: false, //是否开启深度监听 即时关闭深度监听,reactive作为监听深层对象也能监听到
  }
);

watchEffect

  • watchEffect自动收集函数中依赖数据,依赖项更新时重新执行,不用像watch前面指定依赖项
  • 非惰性,进入页面会立即加载一次
  • 参数为一个函数,在可以在监听之前做操作
watchEffect((oninvalidate) => {
  oninvalidate(() => {
    //触发监听之前会调用这个函数
    //可以进行一些逻辑,例如防抖
  });
  console.log(mesage);
  console.log(mesage1);
});

停止更新

const stop = watchEffect(
  () => {
    console.log(mesage);
    console.log(mesage1);
  }
);
//调用函数名,停止更新
stop()

配置项

const stop = watchEffect(
  () => {
    const btn: HTMLElement = document.getElementById("btn") as HTMLElement;
    console.log(btn);
  },
  {
    flush:"post",
    onTrigger () {
        debugger
    }
  }
);

flush属性 刷新时机

  • pre 组件更新前执行
  • aync 强制效果始终同步触发
  • post 组件更新后执行

onTrigger 调试watchEffect

生命周期

  1. beforeCreate -> use setup()
  2. created -> use setup()
  3. beforeMount -> onBeforeMount  DOM实际渲染安装之前调用,根元素还不存在
  4. mounted -> onMounted  组件第一次渲染后调用,可以访问DOM
  5. beforeUpdate -> onBeforeUpdate 数据更新时调用,发生在虚拟 DOM 打补丁之前
  6. updated -> onUpdated DOM更新后,updated的方法即会调用
  7. beforeDestroy -> onBeforeUnmount 卸载组件实例之前调用,这个阶段实例是完全正常的
  8. destroyed -> onUnmounted 卸载组件实例后调用。调用此钩子时,组件实例的所有指令都被解除绑定,所有事件侦听器都被移除,所有子组件实例被卸载。
  9. activated -> onActivated
  10. deactivated -> onDeactivated
  11. errorCaptured -> onErrorCaptured

//added
onRenderTracked 
onRenderTriggered //注册一个调试钩子,当响应式依赖的变更触发了组件渲染时调用。

组件



组件传值

父传子

//父组件

let title: string = "关于 Watch";


//子组件
const props=defineProps({
  title: { type: String, default: "默认值" },
});

 接收props ts方式

//无默认值
const props = defineProps<{
  title:string;
}>();

console.log(props.title); 

//有默认值
type Props = {
    title?: string,
    data?: number[]
}
withDefaults(defineProps(), {
    title: "张三",
    data: () => [1, 2, 3]
})

子传父

//子组件

const emit=defineEmits(['value']) //注册 
const send=()=>{ //发送
  emit('value','neeko')
}

//父组件

const getChildValue=(v:string)=>{
  console.log(v);
  
}
//子组件

const props=defineProps({
  flag: { type: boolean, default: "false" },
});
const emit=defineEmits(['update:flag']) //注册 
const send=()=>{ //发送
  emit('update:modelValue',!flag)
}

//父组件

const flag=ref(true)
const getChildValue=(v:string)=>{
  console.log(v);
}

 EventBus 实现兄弟组件传参

Vue3中使用 EventBus 实现兄弟组件传参_⁡⁢⁡⁢⁠Ac的博客-CSDN博客

小满Vue3(Mitt)_小满zs的博客-CSDN博客

全局组件

//mian.ts
import Index from "./views/index.vue";
export const app=createApp(App)
app.component('Index',Index)

//.vue 页面直接使用

批量注册 

通过循环的方式注册组件

// require.context()   1.指定目录 2.是否加载子目录 3.加载的文件名(正则匹配)
const components= require.context('./', false, /\.vue$/)

export default {
  install (app) {
    // 根据keys批量注册
    components.keys().forEach(path => {
      // 导入组件
      const component = importFn(path).default
      // 组件全局注册
      app.component(component.name, component)
    })
  }
}

//引入main.js
import components  from './components/myComponents'
app.use(components)

递归组件

//父组件传入数据


import Tree from "./views/Tree.vue";
import { reactive } from "vue";
interface Tree {
  name: string;
  checked: boolean;
  children?: Tree[];
}
const data = reactive([
  {
    name: "1",
    checked: false,
    children: [
      {
        name: "1.1",
        checked: false,
      },
      { name: "1.2", checked: false },
      {
        name: "1.3",
        checked: false,
        children: [
          { name: "1.3.1", checked: false },
          {
            name: "1.3.2",
            checked: false,
            children: [{ name: "1.3.3.1", checked: false }],
          },
          { name: "1.3.3", checked: false },
        ],
      },
    ],
  },
  { name: "2", checked: false },
  { name: "3", checked: false },
]);
//Tree 组件





?. 可选链操作符

var a={}
a.children //undefined
a.children.length //报错
a.children?.length //undefined 防止报错

?? 双问号表达式

a.children?.length ?? []
??的作用:当左边为null或undefined(不包括0,false哦!!!),才会执行右边

动态组件

多个组件使用一个挂载点,并且可以做到动态切换

第一种方法

{{ v.name }}
import A from "./components/tab/A.vue"; import B from "./components/tab/B.vue"; import C from "./components/tab/C.vue"; const coms = ref(A); const com = reactive([ { name: "组件A", con: A, }, { name: "组件B", con: B, }, { name: "组件C", con: C, }, ]); //通过点击事件切换组件 const boxTabClick = (con: any) => { coms.value = con; };

会产生报错

原因:比如ref(A)打印出来会有大量属性,会将组件信息做劫持,但是没必要且浪费性能。

解决办法:v3提供两个跳过属性的api

  1. shallowRef:代理最外面一层,属性只有.value,其余属性不做代理。
  2. 在对象中使用markRaw,markRaw会添加__skip__属性,reactive碰到这个属性,proxy的时候就会跳过他
import { ref, reactive,markRaw,shallowRef } from "vue";
import A from "./components/tab/A.vue";
import B from "./components/tab/B.vue";
import C from "./components/tab/C.vue";
const coms = shallowRef(A);
const com = reactive([
  {
    name: "组件A",
    con: markRaw(A),
  },
  {
    name: "组件B",
    con: markRaw(B),
  },
  {
    name: "组件C",
    con: markRaw(C),
  },
]);

const boxTabClick = (con: any) => {
  coms.value = con;
};

第二种方法





异步组件

Suspense | Vue.js

实验性功能,稳定之前相关 API 也可能会发生变化

使用 

//异步组件


插槽slot

匿名插槽

//使用 插入组件

    


//组件放入插槽

具名插槽

//使用 插入 组件