用最短时间,实现Vue3快速上手开发(全文2万字)

目录

一、Vue3 VS Vue2

1.1 Vue3的简介

1.2 Vue3的优化

二、Vue3 工程的创建

2.1 基于 vue-cli进行构建 (耗时,不推荐!)

2.2 基于vite创建(推荐)

2.3 vue3项目结构说明

2.4 一个简单的vue程序

三、Vue3的核心语法

3.1 组合式API 和 选项式API

3.2 初识setup语法

3.2.1 朴素的setup写法

3.2.2 setup语法糖写法

3.2.3 (面试题)请你谈谈 setup 和 Options API 之间的关系

3.3 创建响应式数据——ref 和 reactive

3.4 toRefs 和 toRef的用法

3.5 computed的用法

3.6 Watch的用法

3.7 WatchEffect的用法

3.8 标签的ref属性

3.9 ts语法回顾

3.10 (todo)props的用法

3.11 生命周期

3.12 自定义钩子函数 hook

四、Vue3路由理解

4.1 对路由的理解

4.2 路由的基本使用步骤

4.3 路由工作模式

4.4 RouterLink中 to属性的两种写法

4.5 二级路由【嵌套路由】

4.6 嵌套路由——query参数

4.7 嵌套路由——params参数

4.8 路由的props配置

4.9 replace模式

4.10 编程式导航跳转

4.11 路由重定向

五、pinia——集中式状态管理工具

5.1 pinia环境搭建

5.2存储 + 读取 数据

5.3 修改数据的三种方式

5.4 解构响应式数据——storeToRefs

5.5 getters

5.6 subscribe方法的使用

5.7 store组合式的写法

六、Vue3组件通信

6.1. props实现父子通信——主导权永远在父亲

6.2 自定义事件——子传父

6.3 mitt——消息订阅与发布(与pubsub类似的中间人)

6.4 v-model 数据双向绑定实现双向通信

6.5 $attrs实现祖孙通信——主导权永远在祖先——需要搭中间人

6.6 $refs、$parent

6.7  provide、inject——不需要搭中间人

6.8 slot 插槽的基本使用

七、其他API

7.1 shallowRef 与 shallowReactive——浅层响应式数据

7.2 readonly 与 shallowReadonly——只读

7.3 toRaw 与 markRaw —— 原始/永久原始

7.4 customRef —— 自定义Ref

八、Vue3新组件

8.1 Teleport —— 移动组件

8.2 Suspense —— 异步组件

 8.3 全局API转移到应用对象

8.4 其他非兼容性重大改变


一、Vue3 VS Vue2

1.1 Vue3的简介

官方文档:简介 | Vue.js (vuejs.org)

        Vue3是Vu2后一个大版本,从整体来看,Vue3很好的兼容了Vue2中所有的特性,与此同时Vue3还创造出了其他优秀、便捷的特性,例如:组合式API、setup语法糖、支持TypeScript等等,本文适用的人群:

        1. 如果你是想快速上手前端框架,了解Vue的使用方式的人群

        2. 想要重新回顾vue语法,或是刚刚学完vue2,想要继续学习更新的Vue3语法的人群

        当然,如果你有足够的时间和兴趣,我还是十分建议你从vue2的用法看起。毕竟如果以后需要维护vue相关的项目,还是有很多项目任然在使用vue2。那你的学习路线可以是:

        1. 学习axios【vue中发送请求】:Axios中文文档 | Axios中文网 (axios-http.cn)

        2. 系统学习vue2:尚硅谷Vue2.0 vuejs从入门到精通_哔哩哔哩_bilibili

1.2 Vue3的优化

性能的提升——更快更轻量

  • 打包大小减少41%
  • 初次渲染快55%,更新渲染快133%
  • 内存减小54%

源码升级

  • 使用Proxy 代替 defineProperty 实现响应式
  • 重写虚拟Dom的实现和 Tree-Shaking

新特性

  • 组合式API 取代 选项式API
  • setup语法糖
  • ref 与 reactive
  • computed 和 watch
  • 新的生命周期钩子 onBeforeUpdate 和 onUpdated

【补充说明】

        在Vue 2.x中,Vue的响应式系统是通过Object.defineProperty实现的,这种方法有一些限制,比如不能监听属性的添加或删除,也不能很好地处理数组和嵌套对象。而在Vue 3.x中,Vue团队采用了Proxy来替代Object.defineProperty实现响应式系统。

  • Object.defineProperty:这种方式只能劫持对象的属性,不能监听数组索引的变化,且需要在对象属性被访问和修改时拦截它们。
  • Proxy:可以创建一个对象的代理,从而可以基本上拦截并定义对象属性的基本操作,如属性查找、赋值、枚举、函数调用等。Proxy 不仅可以拦截属性的读取和设置,还可以拦截属性的添加、删除以及数组的索引操作等,更加全面和灵活。
  • 在前端框架中应用Tree-Shaking:这意味着框架的库文件或模块被设计为支持Tree-Shaking,使得开发者在引入框架的某些部分时,可以只包含这些部分的实际代码,而不包含整个库的其他未使用的部分。这对于减少最终应用的体积非常有帮助,尤其是在使用现代打包工具时。
  • 重写虚拟DOM:这可能意味着对现有框架的虚拟DOM实现进行优化,以提高性能、减少内存占用或改进API的使用体验。例如,改进算法以更高效地比较和更新DOM树,优化节点的创建和销毁过程,或者提供新的特性如更细粒度的更新控制。

二、Vue3 工程的创建

2.1 基于 vue-cli进行构建 (耗时,不推荐!)

        这种方式在创建vue2项目时就使用了,目前vue-cli 已经处于维护模式了,而官方也不推荐这种实现方式。创建一个项目 | Vue CLI

输入指令,选择创建版本、包管理器:

开始创建项目:耗时26S + 5S

启动项目: 耗时5.5s

2.2 基于vite创建(推荐)

第一中构建方式耗时特别长,官方推荐使用新一代前端构建工具—基于vite构建

vite的优势 Home | Vite中文网 (vitejs.cn)

        1. 轻量快速的热重载(`HMR`),能实现极速的服务启动。

        2.对 `TypeScript`、`JSX`、`CSS` 等支持开箱即用。

        3.真正的按需编译,不再等待整个应用编译完成。

vue-cli (webpack)构建 与 vite 构建的对比

从对比图也可以发现,vite采用的懒汉式,可以使得构建速度大大提高。

输入指令,选择配置,创建项目: 一瞬间就完成了

初始化工程,启动项目

2.3 vue3项目结构说明

1. Vite项目中,index.html 是项目的入口文件,在项目最外层。
2. 加载index.html后,Vite解析 `<script type="module" src="xxx">` 指向的JavaScript
3.Vue3中是通过 createApp函数创建一个应用实例

4.Vue3向下兼容Vue2语法,且Vue3中的模板中可以没有根标签

2.4 一个简单的vue程序

2.4.1 如何在根组件中联动子组件

1. 导入组件

2. 使用 components 注册组件

3. 在模板中使用组件

2.4.2 编写一个vue程序

App.vue

<template>
  <div class="app">
    <h1>你好啊!</h1>
    <!--3. 使用组件-->
    <person></person>
  </div>
</template>
 
<script lang="ts">
  // 1. 导入组件
  import person from './components/person.vue';

  export default {
    name:'App', //组件名
    components:{person} //2. 注册组件
  }
</script>
 
<style>
  .app {
    background-color: #ddd;
    box-shadow: 0 0 10px;
    border-radius: 10px;
    padding: 20px;
  }
</style>

 person.vue

<template>
  <div class="person">
    <h2>姓名: {{name}}</h2>
    <h2>年龄: {{age}}</h2>
    <button @click="showTel()">查看联系方式</button>
  </div>
</template>

<script lang="ts">
export default {
  name: 'person',

  data() {
    return {
      name: 'zhicong',
      age: 18,
      tel: '123456789'

    }
  },
  
  methods: {
    showTel() {
      alert(this.tel)
    }
  }
}
</script>

<style scoped>
.person {
  background-color: skyblue;
  box-shadow: 0 0 10px;
  border-radius: 10px;
  padding: 20px;
}
</style>

三、Vue3的核心语法

3.1 组合式API 和 选项式API

如图所示:

        左边是vue2采用的选项式API的写法。也就是采用 data、methods、computed分开来写,导致同一个功能的逻辑被拆散成多块【同一颜色表示一个功能】,这样子在进行项目开发的过程中会比较难维护,功能数据、方法、计算属性之间的跨度大。

        右边采用的是vue3新增的组合式API的写法,它允许同一功能的数据、方法、计算属性写在同一块,使得项目的可读性与可维护性增强。因此我们学习vue3,正是要学习组合式API开发。

组合式API的优势:

1. 代码组织性提升

  • 逻辑集中化:组合式API允许开发者将相关的逻辑(如响应式数据、计算属性、方法等)封装在setup函数中,从而避免了传统选项式API中因逻辑分散在datacomputedmethods等选项中而导致的管理复杂性。
  • 结构清晰度:通过组合式API,组件的逻辑可以根据功能或用途进行自然分组,而不是依赖于Vue的内置选项。

2. 类型推断的增强

  • TypeScript友好:组合式API与TypeScript的集成更加紧密,能够充分利用TypeScript的类型系统。开发者可以在编写Vue组件时享受到更强大的类型检查和自动完成功能,从而提高开发效率和代码质量。

3. 代码复用性的增强

  • 可复用逻辑封装:组合式API使得将可复用的逻辑(如API调用、状态管理等)封装成独立的函数或对象变得更加容易。这些函数或对象可以在多个组件之间共享,从而减少了代码的重复,提高了代码的可维护性。

3.2 初识setup语法

        为了能够实现组合式API,vue3新增了一个配置项——setup。组件中所使用的 数据、方法、计算属性、监视等,都需要写在setup之中。

3.2.1 朴素的setup写法

        setup是组合式API的入口,在组件创建前执行。在使用的过程中,你可以在setup函数中定义需要的响应式状态、方法函数、计算属性、监视......然后将需要用到的部分通过返回值返回,才能在模板中被使用。

<template>
  <div>{{ name }}</div>
  <div>{{ age }}</div>    
</template>

<script>
import { ref } from 'vue';

export default {

// 书写setup函数,在函数中定义需要用到的响应式状态、方法、计算属性,并返回它们
  setup() {

    // 定义了一个响应式的变量
    const name = ref('zhicong');
    const age = ref(18)
    // 返回的对象中的属性和函数在模板中可用
    return { name,age };
  }
}
</script>

3.2.2 setup语法糖写法

        有没有觉得现在需要多写一个setup函数有点麻烦,而且用到什么都必须手动返回,不然就没办法在模板中使用。如果以后需要返回的变量和方法特别多,就十分麻烦。对此,Vue3提供了一种语法糖写法,很好的解决了上述问题:它可以不需要你写setup函数、并且自动帮你返回对象。

注意:语法糖写法通常会出现两个script标签,不影响,一个专门导入组件、一个专门写数据、方法、逻辑。

<template>
  <div class="person">
    <h2>姓名:{{name}}</h2>
    <h2>年龄:{{age}}</h2>
    <button @click="changName">修改名字</button>
    <button @click="changAge">年龄+1</button>
    <button @click="showTel">点我查看联系方式</button>
  </div>
</template>
 

<script lang="ts">
  export default {
    name:'Person',
  }
</script>
 

<!-- 下面的写法是setup语法糖 -->
<script setup lang="ts">
  console.log(this) //undefined
  
  // 数据(注意:此时的name、age、tel都不是响应式数据)
  let name = '张三'
  let age = 18
  let tel = '13888888888'
 
  // 方法
  function changName(){
    name = '李四'//注意:此时这么修改name页面是不变化的
  }
  function changAge(){
    console.log(age)
    age += 1 //注意:此时这么修改age页面是不变化的
  }
  function showTel(){
    alert(tel)
  }
</script>

3.2.3 (面试题)请你谈谈 setup 和 Options API 之间的关系

1. setup 与 data、methods 等是可以混合使用的,也就是在写Vue3的同时兼容Vue2

2. setup 在所有钩子函数之前就执行了,因此data、methods是可以访问setup中的属性

3. setup无法访问data、methods之中的配置

4. setup函数中,this指代的是 Undefined,因此我们基本上不会在setup中使用this

setup最早​

3.3 创建响应式数据——ref 和 reactive

        在vue2中,只要将数据写在data中,数据就是响应式的。而在vue3中,需要通过ref 和 reactive来定义响应式数据

 【使用步骤】先引入后使用

3.3.1 基于ref创建响应式数据

1. 使用ref创建基本类型的响应式数据

        使用ref创建基本类型的响应数据,在浏览器控制台中就会发现ref会将数据封装成一个对象。其中的value的值就是我们原本的数据。因此,我们想要使用数据,必须要利用对象.value才能获得。

        在模板中,vue已经提前帮我们做好了,所以我们不需要做这一步,但是在script中必须不能忽略这一步。

2. 使用ref创建对象类型的响应式数据

3.3.2 基于reactive创建对象类型的响应式数据

        使用reactive创建对象响应式数据,打开浏览器控制台会发现对象被vue包裹成了一个代理对象,与ref不同的是,这个对象可以直接取到值。不需要.value

        reactive是深层次的,不管被包裹的对象层次有多深,都能变成响应式数据。

不能定义基本类型,会报错

        修改对象数据时,必须整个对象进行深拷贝,如果单单修改数据,或者修改完数据又加一层reactive都是不行的。

3.3.3 ref 与 reactive的比较

宏观角度:

ref 用来定义:基本数据类型、对象数据类型
reactive 用来定义:对象数据类型


区别:

ref 创建的变量必须使用 .value (可以使用 volar 插件自动添加 .value)
reactive 重新分配一个新对象,会失去响应式 (可以使用 Object.assign 去整体替换)


使用原则:

  • 若需要一个基本类型的响应式数据,必须使用 ref
  • 若需要一个响应式对象,层级不深,ref、reactive 都可以
  • 若需要一个响应式对象,且层级较深,推荐使用 reactive

3.4 toRefs 和 toRef的用法

【使用场景】将一个响应式对象的每个属性,转换成为一个 ref 对象。其中toRefs可以批量替换

3.5 computed的用法

【作用】根据已有数据,计算出新的数据

【优势】计算属性相比于函数实现,有缓存,对于相同数据的重复请求,并不会重新调用一次,从而提高了性能。

看看vue官方怎么说:

3.6 Watch的用法

监视,用于监视数据的变化。可以监视以下4种数据:

1. ref定义的数据

2. reactive定义的数据

3. 函数返回一个值 监视getter函数

4. 一个包含上述内容的数组

情况1:监视【ref】定义的【基本类型】数据

// 情况一:监视【ref】定义的 【基本类型】数据
let sum = ref(0);
const stopWatch = watch(sum, (newVal, oldVal) => {
  console.log(newVal, oldVal);
  //这个watch还返回一个函数,解除监视
  if (newVal > 10) {
    stopWatch();
  }
});
console.log(stopWatch); //返回一个函数

监视的基本语法:

取消监视:使用变量接收,监听条件达标执行stopWatch()函数取消监听

情况2:监视【ref】定义的【对象类型】数据

let person = ref({
  name: "张三",
  age: 18,
});
const changeName = () => {
  person.value.name += "~";
};
const changeAge = () => {
  person.value.age += 1;
};
const changePerson = () => {
  person.value = { name: "李四", age: 20 };
};
//监视【ref】定义的 【对象类型】数据,监视的是对象的地址值,
// 若想监视对象内部的属性变化,需要手动开启深度监视。
watch(
  person,
  (newVal, oldVal) => {
    console.log(newVal, oldVal);
  },
  { deep: true }
);

默认直接监视整个对象

        这种情况监视的是对象的地址值,只有对象的地址值发生了改变,才会被监视到。内部个别数据改变不会被监视。

手动开启深度监视

        开启深度监视后,内部数据变化都会被监视到。

情况3: 监视【reactive】定义的【对象类型】数据,默认隐式开启深度监视

let p = reactive({
  name: "张三",
  age: 20,
});
const pName = () => {
  p.name += "~";
};
const pAge = () => {
  p.age += 1;
};
const pPerson = () => {
  //这里不是真正意义上的修改整个人,而是进行了批量的修改!!!
  Object.assign(p, { name: "李四", age: 19 });
};

//监视【reactive】定义的 【对象类型】数据,默认开启了深度监视!!!
watch(p, (newVal, oldVal) => {
  console.log(newVal, oldVal);
});

【如何修改reactive定义的数据】

        注意reactive定义的对象数据不能整体修改,需要使用obiect.assign 进行深拷贝。而且,这种拷贝方式并没有修改对象的地址值。

默认监视时,数据发生改变就能监视到【区别于ref

隐式创建了深度监视,无法关闭

情况4: 只想监视ref 或 reactive定义的【对象类型】的某个属性

let p2 = reactive({
  name: "xiaoyu",
  age: 18,
  car: {
    c1: "奔驰",
    c2: "宝马",
  },
});
const p2Car = () => {
  p2.car = { c1: "雅迪", c2: "飞机" };
};
//情况四:监视响应式对象中的某个属性,且该属性是基本数据类型,要写成函数式。
watch(
  () => p2.name,
  (newVal, oldVal) => {
    console.log(newVal, oldVal);
  }
);
//下面这个情况就很诡异,能监视到对象里面的值变化,对象本身却不进行监视!
// watch(p2.car,(newVal,oldVal)=>{
//     console.log(newVal,oldVal);
// })

//建议写成监视其中的属性任然是一个对象类型的,也推荐使用函数形式的!
watch(
  () => p2.car,
  (newVal, oldVal) => {
    console.log(newVal, oldVal);
  },
  { deep: true }
);

1. 若属性不是对象类型,则需要写成函数形式

        写箭头函数进行返回需要监视的属性

2. 若属性依然是对象类型,可以直接编写,但是也建议写出函数形式

情况5: 监听多个数据

 使用函数返回 : 

由于车是对象属性,也可以这么写:

3.7 WatchEffect的用法

  当watch如果要监视的数据变得特别多才能判断的时候,要写就比较麻烦了,这个时候我们可以使用WatchEffect去替换watch。

watch对比watchEffect

  1. 都能监听响应式数据的变化,不同的是监听数据变化的方式不同

  2. watch:要明确指出监视的数据

  3. watchEffect:不用明确指出监视的数据(函数中用到哪些属性,那就监视哪些属性)。

        具体的,WatchEffect的作用为: 自动监视需要监视的对象,无需指名道姓。

3.8 标签的ref属性

作用:用于注册模板引用

  • 用于普通 DOM 标签上,获取的是 DOM 节点
  • 用于组件标签上,获取的是组件实例对象
  • 比id标签好,id标签在不同组件可能会重复导致犯病,但是ref能确保组件唯一

 注意,在vue3中,父组件想要获取子组件的东西时,必须要经过子组件同意(暴露)

用在普通DOM标签上:

<template>
  <div class="person">
    <h1 ref="title1">尚硅谷</h1>
    <h2 ref="title2">前端</h2>
    <h3 ref="title3">Vue</h3>
    <input type="text" ref="inpt"> <br><br>
    <button @click="showLog">点我打印内容</button>
  </div>
</template>

<script lang="ts" setup name="Person">
  import {ref} from 'vue'

// 声明ref空对象	
  let title1 = ref()
  let title2 = ref()
  let title3 = ref()

  function showLog(){
    // 通过id获取元素
    const t1 = document.getElementById('title1')
    // 打印内容
    console.log((t1 as HTMLElement).innerText)
    console.log((<HTMLElement>t1).innerText)
    console.log(t1?.innerText)
    
		/************************************/
		
    // 通过ref获取元素
    console.log(title1.value)
    console.log(title2.value)
    console.log(title3.value)
  }
</script>

用在组件标签上:

<!-- 父组件App.vue -->
<template>
  <Person ref="ren"/>
  <button @click="test">测试</button>
</template>

<script lang="ts" setup name="App">
  import Person from './components/Person.vue'
  import {ref} from 'vue'

  let ren = ref()

  function test(){
    console.log(ren.value.name)
    console.log(ren.value.age)
  }
</script>


<!-- 子组件Person.vue中要使用defineExpose暴露内容 -->
<script lang="ts" setup name="Person">
  import {ref,defineExpose} from 'vue'
	// 数据
  let name = ref('张三')
  let age = ref(18)
  /****************************/
  /****************************/
  // 使用defineExpose将组件中的数据交给外部
  defineExpose({name,age})
</script>

3.9 ts语法回顾

ts用于规范

1. 定义ts规范

2. 导入使用ts规范

3. 查看效果

4. 拓展多个

5. 自定义类型

6. 定义响应式对象——传泛型参数

3.10 (todo)props的用法

props用于父组件向子组件传递数据。主要用法:

1. 在子组件中,你需要通过组件的 props 选项来声明你想要从父组件接收哪些数据。

<!-- ChildComponent.vue -->  
<template>  
  <div>  
    <p>{{ message }}</p>  
  </div>  
</template>  
  
<script>  
export default {  
  props: ['message'] // 声明一个名为 message 的 prop  
}  
</script>

2. 在父组件中,你需要引入并使用子组件,并通过在子组件的标签上添加自定义属性(这些属性的名称需要与子组件中声明的 props 名称相匹配)来传递数据。

<!-- ParentComponent.vue -->  
<template>  
  <div>  
    <ChildComponent :message="parentMessage" />  
  </div>  
</template>  
  
<script>  
import ChildComponent from './ChildComponent.vue'  
  
export default {  
  components: {  
    ChildComponent  
  },  
  data() {  
    return {  
      parentMessage: 'Hello from Parent!'  
    }  
  }  
}  
</script>

注意:在父组件的模板中,向子组件传递 props 时,你应该使用 v-bind 指令(或简写为 :)来绑定数据。这样,Vue就知道你正在传递一个JavaScript表达式到子组件的 props 中,而不是一个普通的字符串。

3. 最后,在子组件的模板或逻辑中,你可以像使用普通数据属性一样使用通过 props 接收到的数据。

<!-- ChildComponent.vue -->  
<template>  
  <div>  
    <!-- 使用 props 中的 message -->  
    <p>{{ message }}</p>  
  </div>  
</template>  
  
<script>  
export default {  
  props: ['message'] // 声明 prop  
}  
</script>

4. Vue 允许你为每个 prop 指定类型、默认值、验证函数等,这有助于确保传递给子组件的数据是有效的。

<script>  
export default {  
  props: {  
    // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)  
    propA: Number,  
    // 带有默认值的类型检查  
    propB: {  
      type: String,  
      default: 'default value'  
    },  
    // 自定义验证函数  
    propC: {  
      validator: function (value) {  
        // 这个值必须匹配下列字符串中的一个  
        return ['option1', 'option2'].includes(value)  
      }  
    },  
    // 带有默认值的对象  
    propD: {  
      type: Object,  
      // 对象或数组默认值必须从一个工厂函数返回  
      default: function () {  
        return { message: 'hello' }  
      }  
    }  
  }  
}  
</script>

3.11 生命周期

概念】每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤,比如设置好数据侦听,编译模板,挂载实例到 DOM,以及在数据改变时更新 DOM。在此过程中,它也会运行被称为生命周期钩子的函数,让开发者有机会在特定阶段运行自己的代码。

规律】生命周期整体分为四个阶段,分别是:创建、挂载、更新、销毁,每个阶段都有两个钩子,一前一后。

vue2的生命周期函数

创建阶段:beforeCreatecreated

挂载阶段:beforeMountmounted

更新阶段:beforeUpdateupdated

销毁阶段:beforeDestroydestroyed

vue3的生命周期函数

创建阶段:setup

挂载阶段:onBeforeMountonMounted

更新阶段:onBeforeUpdateonUpdated

卸载阶段:onBeforeUnmountonUnmounte

注册周期钩子

<script setup>
import { onMounted } from 'vue'

onMounted(() => {
  console.log(`the component is now mounted.`)
})
</script>

最常用的周期钩子】onMounted(挂载完毕)、onUpdated(更新完毕)、onBeforeUnmount(卸载之前)

3.12 自定义钩子函数 hook

为什么要有hook,先看下列现象:

现在我们写的vue3逻辑:实际上,和vue2选项式API还是很像:

hook:让相同功能的数据和方法合在一起,封装成模块化。

使用时,只需要获取自定义hook函数的返回值即可

注意:hook里面还可以写钩子、计算属性

四、Vue3路由理解

4.1 对路由的理解

        路由就是进行页面切换规则匹配的组件。在我们进行单页面开发时,往往需要对某个导航页中的内容进行切换,这种切换并不是对整个页面的切换,而是对页面中特定的组件进行切换。至于如何指定特定的组件,这就需要用到我们的路由器,去进行路由规则的匹配了。

4.2 路由的基本使用步骤

1. 创建router文件夹,在文件夹下创建index.ts文件,编辑路由参数和配置路由规则,并将路由进行暴露

2. 配置main.ts 文件,将路由器进行引入并注册挂在到app容器之中

3. 在App.vue中使用配置好的路由器,具体的,在需要跳转路由的导航栏使用RouterLink配置跳转规则,在需要展示路由组件的位置 使用 RouterView进行标记声明

4. 编写路由组件

        区分路由组件和一般组件的方法。很简单,路由组件我们一般都会用 RouterLink标签去配置它的路由路径和匹配规则,而一般组件的标签一般都是直接写组件名字。我们习惯性把路由组件统一放到pages / view 文件夹下,而把一般组件放到 component文件夹下。

【注意】

1. 路由组件通常放在pages 或 view文件夹中,一般组件通常存放在 components文件夹中

2. 通过点击导航,视觉上消失的路由组件,默认是被卸载掉的,只有需要的时候才会去挂载

4.3 路由工作模式

        路由分成两种工作模式,各有其特点。我们前面演示的时候使用的是 history模式。除此之外,还有hash模式。具体的:

【history模式】

优点: URL美观,不带有#,更加接近传统的网页URL,一般多见于前台界面

缺点:后期项目上线,需要服务端额外处理配置来配合解决路径问题,否则刷新会显示404错误

用法:

const router = createRouter({
	history:createWebHistory(), //history模式
	/******/
})

【vue2】 mode : ‘history’

【vue3】 history :createWebHistory()

【React】 BrowserRouter

【hash模式】

优点:兼容性更好,不需要服务器的额外配置,一般多见于后台界面

缺点:URL带有#不太美观,而且在SEO优化方面相对较差

用法:

const router = createRouter({
	history:createWebHashHistory(), //hash模式
	/******/
})

4.4 RouterLink中 to属性的两种写法

<!-- 第一种:to的字符串写法 -->
<router-link active-class="active" to="/home">主页</router-link>

<!-- 第二种:to的对象写法 -->
<router-link active-class="active" :to="{path:'/home'}">Home</router-link>

给路由规则命名

routes:[
  {
    name:'zhuye',
    path:'/home',
    component:Home
  },
  {
    name:'xinwen',
    path:'/news',
    component:News,
  },
  {
    name:'guanyu',
    path:'/about',
    component:About
  }
]

<!--简化前:需要写完整的路径(to的字符串写法) -->
<router-link to="/news/detail">跳转</router-link>

<!--简化后:直接通过名字跳转(to的对象写法配合name属性) -->
<router-link :to="{name:'guanyu'}">跳转</router-link>

4.5 二级路由【嵌套路由】

如果要编写二级路由,实现下列的效果:

1. 配置子级路由

2. 配置路由使用路径和路由组件展示区

4.6 嵌套路由——query参数

嵌套路由种,我们常常需要进行传参。因此我们需要用以下两种方法,对RouterLink进行改造:

4.7 嵌套路由——params参数

1. 需要先在子路由中占位:

2. 配置传参路径

3. 使用

4. 如何可选传参,占位了但是有些时候不需要用上

注意:

params有一些使用限制

1. 传递params参数时,如果to使用的是对象写法,必须使用name配置项,不能使用path

2. 传递params参数时,必须提前在规则中进行占位

3. 对于可选参数,必须在规则中声明 ' ? ' 

4.8 路由的props配置

现在写法还是有些麻烦,体现在 

如何简化这里的写法,我们可以参考组件的props,使用props简化这部分的传参

4.9 replace模式

    1. 作用:控制路由跳转时操作浏览器历史记录的模式。

    2. 浏览器的历史记录有两种写入方式:分别为```push```和```replace```:

       - ```push```是追加历史记录(默认值)。
       - `replace`是替换当前记录。

    3. 开启`replace`模式:

<RouterLink replace .......>News</RouterLink>

4.10 编程式导航跳转

4.11 路由重定向

 {
      path:'/',
      redirect:'/home'
    }

五、pinia——集中式状态管理工具

Vue全家桶 - pinia 的理解和学习1(Pinia 核心概念的 Store、State、Getter、Action)_pinia getters和actions-CSDN博客

5.1 pinia环境搭建

第一步:使用命令 npm i pinia 进行安装

第二步:导入pinia

        1. 导入createPinia

        2. 创建pinia

        3. 挂载使用pinia

import { createApp } from 'vue'
import App from './App.vue'

/* 引入createPinia,用于创建pinia */
import { createPinia } from 'pinia'

/* 创建pinia */
const pinia = createPinia()
const app = createApp(App)

/* 使用插件 */{}
app.use(pinia)
app.mount('#app')

5.2存储 + 读取 数据

1. `Store`是一个保存:**状态**、**业务逻辑** 的实体,每个组件都可以**读取**、**写入**它。

2. 它有三个概念:`state`、`getter`、`action`,相当于组件中的: `data`、 `computed` 和 `methods`。

3. 具体编码:`src/store/count.ts`

// 引入defineStore用于创建store
import {defineStore} from 'pinia'

// 定义并暴露一个store
export const useCountStore = defineStore('count',{
  // 动作
  actions:{},
  // 状态
  state(){
    return {
      sum:6
    }
  },
  // 计算
  getters:{}
})

4. 具体编码:src/store/talk.ts  

// 引入defineStore用于创建store
import {defineStore} from 'pinia'

// 定义并暴露一个store
export const useTalkStore = defineStore('talk',{
  // 动作
  actions:{},
  // 状态
  state(){
    return {
      talkList:[
        {id:'yuysada01',content:'你今天有点怪,哪里怪?怪好看的!'},
     		{id:'yuysada02',content:'草莓、蓝莓、蔓越莓,你想我了没?'},
        {id:'yuysada03',content:'心里给你留了一块地,我的死心塌地'}
      ]
    }
  },
  // 计算
  getters:{}
})

5. 组件中使用`state`中的数据

<template>
  <h2>当前求和为:{{ sumStore.sum }}</h2>
</template>

<script setup lang="ts" name="Count">
  // 引入对应的useXxxxxStore	
  import {useSumStore} from '@/store/sum'
  
  // 调用useXxxxxStore得到对应的store
  const sumStore = useSumStore()
</script>
<template>
	<ul>
    <li v-for="talk in talkStore.talkList" :key="talk.id">
      {{ talk.content }}
    </li>
  </ul>
</template>

<script setup lang="ts" name="Count">
  import axios from 'axios'
  import {useTalkStore} from '@/store/talk'

  const talkStore = useTalkStore()
</script>

5.3 修改数据的三种方式

第一种修改方式,直接修改

countStore.sum = 666

第二种修改方式:批量修改

countStore.$patch({
  sum:999,
  school:'atguigu'
})

第三种修改方式:借助`action`修改(`action`中可以编写一些业务逻辑)

import { defineStore } from 'pinia'

export const useCountStore = defineStore('count', {
  /*************/
  actions: {
    //加
    increment(value:number) {
      if (this.sum < 10) {
        //操作countStore中的sum
        this.sum += value
      }
    },
    //减
    decrement(value:number){
      if(this.sum > 1){
        this.sum -= value
      }
    }
  },
  /*************/
})

组件中调用`action`即可

// 使用countStore
const countStore = useCountStore()

// 调用对应action
countStore.incrementOdd(n.value)

5.4 解构响应式数据——storeToRefs

1. 借助storeToRefs将store中的数据转为ref对象,方便在模板中使用。
2. 注意:pinia提供的storeToRefs只会将数据做转换,而Vue的toRefs会转换store中数据。

<template>
	<div class="count">
		<h2>当前求和为:{{sum}}</h2>
	</div>
</template>

<script setup lang="ts" name="Count">
  import { useCountStore } from '@/store/count'
  /* 引入storeToRefs */
  import { storeToRefs } from 'pinia'

	/* 得到countStore */
  const countStore = useCountStore()
  /* 使用storeToRefs转换countStore,随后解构 */
  const {sum} = storeToRefs(countStore)
</script>

注意storeToRefs 解构 store 中的 状态 或 getter,跳过所有的 action 或非响应式 (不是 ref 或 reactive) 的属性。‌
action 或 非响应式 (不是 ref 或 reactive) 的属性 直接在 Store 中解构。

5.5 getters

    1. 概念:当state中的数据,需要经过处理后再使用时,可以使用getters配置。

    2. 追加getters配置。

// 引入defineStore用于创建store
import {defineStore} from 'pinia'

// 定义并暴露一个store
export const useCountStore = defineStore('count',{
  // 动作
  actions:{
    /************/
  },
  // 状态
  state(){
    return {
      sum:1,
      school:'atguigu'
    }
  },
  // 计算
  getters:{
    bigSum:(state):number => state.sum *10,
    upperSchool():string{
      return this. school.toUpperCase()
    }
  }
})

组件中读取数据:

const {increment,decrement} = countStore
let {sum,school,bigSum,upperSchool} = storeToRefs(countStore)

5.6 subscribe方法的使用

通过 store 的 $subscribe() 方法侦听 state 及其变化。当store中的state变化到我们想要的那个值时,我们需要去做些什么,那么我们就需要用到$subscribe。相比于watch,使用 $subscribe() 的好处是 subscriptions 在 patch 后只触发一次。

talkStore.$subscribe((mutate,state)=>{
  console.log('LoveTalk',mutate,state)
  localStorage.setItem('talk',JSON.stringify(talkList.value))
})

5.7 store组合式的写法

 

六、Vue3组件通信

组件之间互相传递数据,可能是父子、爷孙等不同的通信关系。

Vue3组件通信和Vue2的区别:

  • 移出事件总线,使用mitt代替。

  • vuex换成了pinia

  • .sync优化到了v-model里面了。

  • $listeners所有的东西,合并到$attrs中了。

  • $children被砍掉了。

常见搭配形式:

6.1. props实现父子通信——主导权永远在父亲

初始化代码

父亲传递数据给儿子——属性值是非函数

很直接,只需要父亲将声明的数据发给儿子,儿子接收了就可以使用

孩子传递数据给父亲——属性值是函数

主导权还是在父亲手上,父亲需要先给孩子传递一个函数方法,孩子接收该方法,然后再调用该方法,把需要传递的参数通过该方法传递给父亲

6.2 自定义事件——子传父

1. 概述:自定义事件常用于:**子 => 父。**
2. 注意区分好:原生事件、自定义事件。

2.1 原生事件:
          事件名是特定的(click、mosueenter等等)    
          事件对象$event: 是包含事件相关信息的对象(pageX、pageY、target、keyCode)
2.2 自定义事件:
          事件名是任意名称
          事件对象$event: 是调用emit时所提供的数据,可以是任意类型!!!

6.3 mitt——消息订阅与发布(与pubsub类似的中间人)

1. 安装mitt

npm i mitt

2. 新建文件,引入mitt,并暴露出去

// 引入mitt 
import mitt from "mitt";

// 创建emitter
const emitter = mitt()

/*
  // 绑定事件
  emitter.on('abc',(value)=>{
    console.log('abc事件被触发',value)
  })
  emitter.on('xyz',(value)=>{
    console.log('xyz事件被触发',value)
  })

  setInterval(() => {
    // 触发事件
    emitter.emit('abc',666)
    emitter.emit('xyz',777)
  }, 1000);

  setTimeout(() => {
    // 清理事件
    emitter.all.clear()
  }, 3000); 
*/

// 创建并暴露mitt
export default emitter

3. 接收数据的组件中:绑定事件、同时在销毁前解绑事件:

import emitter from "@/utils/emitter";
import { onUnmounted } from "vue";

// 绑定事件
emitter.on('send-toy',(value)=>{
  console.log('send-toy事件被触发',value)
})

onUnmounted(()=>{
  // 解绑事件
  emitter.off('send-toy')
})

4. 提供数据的组件,在合适的时候触发事件

import emitter from "@/utils/emitter";

function sendToy(){
  // 触发事件
  emitter.emit('send-toy',toy.value)
}

6.4 v-model 数据双向绑定实现双向通信

v-model用在html标签上

v-model是语法糖:是:value 和 @input标签的语法糖写法,实现双向绑定

回顾v-model的本质

<!-- 使用v-model指令 -->
<input type="text" v-model="userName">

<!-- v-model的本质是下面这行代码 -->
<input 
  type="text" 
  :value="userName" 
  @input="userName =(<HTMLInputElement>$event.target).value"
>

v-model用在组件标签上

参考v-model的本质,我们在vue3中可以写出以下代码实现相关功能:

vue3 v-model 底层命令的名称和 vue2 的不一样

 v-model后面可以指定名字,同步到底层组件时:@update:abc

<AtguiguInput v-model:abc="userName" v-model:xyz="password"/>

6.5 $attrs实现祖孙通信——主导权永远在祖先——需要搭中间人

1. 概述:$attrs用于实现当前组件的父组件,向当前组件的子组件通信(祖→孙 /  孙→祖)。

2. 具体说明:$attrs是一个对象,包含所有父组件传入的标签属性。

   >  注意:$attrs会自动排除props中声明的属性(可以认为声明过的 props被子组件自己“消费”了)

实现组给孙传递数据


实现孙给祖修改数据 

和props一样,传递方法就可以了,省略不写了。主导权永远在祖先!

6.6 $refs、$parent

1. 概述:

  • $refs用于 :父→子。
  • $parent用于:子→父。

2. 原理如下:

属性说明
$refs值为对象,包含所有被`ref`属性标识的`DOM`元素或组件实例。
$parent值为对象,当前组件的父组件实例对象

父亲修改儿子的数据

使用Ref——获取一个儿子对象

升级$refs——获取所有的子组件对象

$parent 拿父亲数据

6.7  provide、inject——不需要搭中间人

1. 概述:实现祖孙组件直接通信

2. 具体使用:

  •    在祖先组件中通过provide配置向后代组件提供数据
  •    在后代组件中通过inject配置来声明接收数据

注意

在给后代传数据的时候,不能传 money.value,这个不是响应式的!!!

6.8 slot 插槽的基本使用

简单来说,就是模板。插槽部分可以自定义内容

6.8.1 默认插槽

使用slot标签声明插槽位,可以将子组件标签内的内容声明到插槽内

6.8.2 具名插槽

当需要多个不同的插槽作为填充位时,需要给插槽进行命名调用

6.8.3 作用域插槽

假设对于同一个数据的插槽,需要不同的样式、不同的展示方式。例如下面说的:第一个要求无序列表、第二个要求有序列表、第三个要求不是列表。

思考过程:

七、其他API

7.1 shallowRef 与 shallowReactive——浅层响应式数据

shallowRef

  1. 作用:创建一个响应式数据,但只对顶层属性进行响应式处理。

  2. 用法:

    let myVar = shallowRef(initialValue);
  3. 特点:只跟踪引用值的变化,不关心值内部的属性变化

shallowReactive

  1. 作用:创建一个浅层响应式对象,只会使对象的最顶层属性变成响应式的,对象内部的嵌套属性则不会变成响应式的

  2. 用法:

    const myObj = shallowReactive({ ... });
  3. 特点:对象的顶层属性是响应式的,但嵌套对象的属性不是。

总结

通过使用 shallowRef()shallowReactive() 来绕开深度响应。浅层式 API 创建的状态只在其顶层是响应式的,对所有深层的对象不会做任何处理,避免了对每一个内部属性做响应式所带来的性能成本,这使得属性的访问变得更快,可提升性能。

7.2 readonly 与 shallowReadonly——只读

readonly

  1. 作用:用于创建一个对象的深只读副本。

  2. 用法:

    const original = reactive({ ... });
    const readOnlyCopy = readonly(original);

  3. 特点:

  • 对象的所有嵌套属性都将变为只读。
  • 任何尝试修改这个对象的操作都会被阻止(在开发模式下,还会在控制台中发出警告)。

  1. 应用场景:

  • 创建不可变的状态快照。
  • 保护全局状态或配置不被修改。

shallowReadonly

  1. 作用:与 readonly 类似,但只作用于对象的顶层属性。

  2. 用法:

    const original = reactive({ ... });
    const shallowReadOnlyCopy = shallowReadonly(original);
  3. 特点:

  • 只将对象的顶层属性设置为只读,对象内部的嵌套属性仍然是可变的。

  • 适用于只需保护对象顶层属性的场景。

7.3 toRaw 与 markRaw —— 原始/永久原始

toRaw

  1. 作用:用于获取一个响应式对象的原始对象, toRaw 返回的对象不再是响应式的,不会触发视图更新。

    官网描述:这是一个可以用于临时读取而不引起代理访问/跟踪开销,或是写入而不触发更改的特殊方法。不建议保存对原始对象的持久引用,请谨慎使用。

    何时使用? —— 在需要将响应式对象传递给非 Vue 的库或外部系统时,使用 toRaw 可以确保它们收到的是普通对象

    2.使用场景: 有时候希望数据改了但是页面不会渲染,这个时候可以使用原始数据 

markRaw

  1. 作用:标记一个对象,使其永远不会变成响应式的。

    例如使用mockjs时,为了防止误把mockjs变为响应式对象,可以使用 markRaw 去标记mockjs

7.4 customRef —— 自定义Ref

作用:创建一个自定义的ref,并对其依赖项跟踪和更新触发进行逻辑控制。

封装代码

实现防抖效果(useSumRef.ts):

import {customRef } from "vue";

export default function(initValue:string,delay:number){
  let msg = customRef((track,trigger)=>{
    let timer:number
    return {
      get(){
        track() // 告诉Vue数据msg很重要,要对msg持续关注,一旦变化就更新
        return initValue
      },
      set(value){
        clearTimeout(timer)
        timer = setTimeout(() => {
          initValue = value
          trigger() //通知Vue数据msg变化了
        }, delay);
      }
    }
  }) 
  return {msg}
}

八、Vue3新组件

8.1 Teleport —— 移动组件

什么是Teleport?—— Teleport 是一种能够将我们的组件html结构移动到指定位置的技术。

【基本用法】

        有时我们可能会遇到这样的场景:一个组件模板的一部分在逻辑上从属于该组件,但从整个应用视图的角度来看,它在 DOM 中应该被渲染在整个 Vue 应用外部的其他地方。

        这类场景最常见的例子就是全屏的模态框。理想情况下,我们希望触发模态框的按钮和模态框本身是在同一个组件中,因为它们都与组件的开关状态有关。但这意味着该模态框将与按钮一起渲染在应用 DOM 结构里很深的地方。这会导致该模态框的 CSS 布局代码很难写。

<teleport to='body' >
    <div class="modal" v-show="isShow">
      <h2>我是一个弹窗</h2>
      <p>我是弹窗中的一些内容</p>
      <button @click="isShow = false">关闭弹窗</button>
    </div>
</teleport>

8.2 Suspense —— 异步组件

  • 等待异步组件时渲染一些额外内容,让应用有更好的用户体验

  • 使用步骤:

    • 异步引入组件

    • 使用Suspense包裹组件,并配置好defaultfallback

import { defineAsyncComponent,Suspense } from "vue";
const Child = defineAsyncComponent(()=>import('./Child.vue'))
<template>
    <div class="app">
        <h3>我是App组件</h3>
        <Suspense>
          <template v-slot:default>
            <Child/>
          </template>
          <template v-slot:fallback>
            <h3>加载中.......</h3>
          </template>
        </Suspense>
    </div>
</template>

 8.3 全局API转移到应用对象

1. app.component

在Vue.js中,app.component用于全局注册组件,这样组件就可以在应用的任何模板中直接使用。通过这个方法注册的组件是全局可用的,不需要在每个组件文件中重复引入。

用法示例

import { createApp } from 'vue'; 
import App from './App.vue'; 
import MyComponent from './components/MyComponent.vue'; 


const app = createApp(App); 
app.component('MyComponent', MyComponent); // 全局注册组件 
app.mount('#app');

2. app.config(Vue中的配置方式)

虽然Vue.js中没有直接的app.config属性用于配置(与AngularJS中的app.config不同),但Vue允许通过Vue实例或全局配置对象来设置一些全局配置,如性能监控、开发模式等。

Vue全局配置示例(通过Vue实例的config属性):

const app = createApp({}); 
app.config.productionTip = false; // 关闭生产提示

3. app.directive

在Vue.js中,app.directive用于注册全局自定义指令。自定义指令用于对DOM元素进行底层操作,以封装一些常见的DOM操作逻辑。

用法示例

app.directive('focus', { 
mounted(el) { 
el.focus(); 
} 
});

4. app.mount

app.mount是Vue.js中用于将Vue应用程序实例挂载到DOM元素上的方法。这是Vue应用程序启动的最后一步,它将Vue的根组件渲染到指定的DOM元素上。

用法示例

const app = createApp(App); 
app.mount('#app'); // 将Vue应用挂载到id为app的DOM元素上

5. app.unmount

app.unmount是Vue.js中用于从DOM元素上卸载Vue应用程序实例的方法。这通常用于在单页面应用(SPA)中销毁并移除当前视图,以便加载新的视图。

用法示例(假设已有一个挂载的Vue实例):

app.unmount('#app'); // 从id为app的DOM元素上卸载Vue应用

6. app.use

app.use在Vue.js中用于安装Vue插件。Vue插件可以扩展Vue的功能,如添加全局指令、混入(mixins)、过滤器等。

用法示例

import { createApp } from 'vue'; 
import App from './App.vue'; 
import router from './router'; 
import store from './store'; 


const app = createApp(App); 
app.use(router); // 使用Vue Router插件 
app.use(store); // 使用Vuex插件 
app.mount('#app');

在Express等Node.js框架中,app.use也用于注册中间件,但它与Vue.js中的用途完全不同。

8.4 其他非兼容性重大改变

  • 过渡类名 v-enter 修改为 v-enter-from、过渡类名 v-leave 修改为 v-leave-from

  • keyCode 作为 v-on 修饰符的支持。

  • v-model 指令在组件上的使用已经被重新设计,替换掉了 v-bind.sync。

  • v-ifv-for 在同一个元素身上使用时的优先级发生了变化。

  • 移除了$on$off$once 实例方法。

  • 移除了过滤器 filter

  • 移除了$children 实例 propert

    ......Vue 3 迁移指南 (vuejs.org)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值