目录

1. 工程创建

 1.1 使用VUE-CLI创建前端项目

 1.2 使用create-vue创建前端项目

2.项目结构

3.基本指令

4.脚本语法 

4.1组合式API

4.2 响应式语法 

- reactive和ref函数

- computed函数 

- watch函数

4.3 生命周期  

4.4 组件通信 

- 父传子 defineProps

 - 子传父 defineEmits

 - 模板引用ref

- provide 和 inject

5.路由 

 5.1 动态路由

 扩展:跳转404页面的页面设置

5.2 嵌套路由 

5.3 懒加载

为什么需要路由懒加载?

5.4 导航守卫

5.5 编程式导航和滚动行为


本文将会从0开始详细介绍VUE3有关内容,以及有关八股文内容融合在一起,真正入门VUE3。 

1. 工程创建

现在,我们构建VUE的前端项目,一般会使用 VUE-CLI 或者 create-vue ,他们俩都是都是用于创建 Vue.js 项目脚手架工具。那么我们该如何选择使用哪种脚手架工具进行创建呢?这就不得不提到他们俩的差异了👇

特性Vue CLICreate-Vue
底层构建工具基于 Webpack基于 Vite
vue版本支持vue2,vue3只支持 vue3
开发服务器启动速度较慢,随着项目依赖增多会变慢极快,开发服务器启动速度是 Webpack 的 10-100 倍
热模块替换速度较慢极快,提供更好的开发体验
模块系统支持 CommonJS 和 ES 模块主要支持 ES 模块,对 CommonJS 支持有限
浏览器兼容性可以兼容旧浏览器仅支持现代浏览器(ES2015 及以上)
插件生态系统拥有丰富的插件系统,可通过安装各种插件扩展功能可利用 Vite 的插件生态系统,该生态系统与 Rollup 兼容
是否包含构建工具内置构建工具,可直接用于生产环境构建本身仅是脚手架工具,构建工作交给 Vite 处理

 从上面的表格,我们可以得出一条简单的结论:

  • 使用VUE-CLI:适合复杂项目,需要高度定制化配置,对浏览器兼容性要求高,团队开发需要共享和管理项目配置。或者需要使用VUE2的项目
  • 使用create-vue:适合快速搭建简单项目,对开发速度要求高,使用现代浏览器进行开发。

但无论选择哪一种脚手架,他们都需要Node.JS环境作为依赖,所以我们得先确保自己安装好Node.JS的环境。(其中create-vue的Node版本需要 ≥16.0,才可以)

跳转下载👉:Node.JS下载

可能安装Node的过程中,大家会碰到一些环境变量设置,权限问题,这里我就不多说了,大家可以参考这篇文章哈 -> Node.JS安装配置(超详细) 

 1.1 使用VUE-CLI创建前端项目

在自己的工程目录下,使用 vue create 项目名称,创建工程项目,这里要注意,项目名称不能包含大写字母,否则会报错(也可能是因为我的node版本原因,我的node版本为18.18)

 接着就会跳到选项,第一个为选择创建一个默认的VUE3项目,第二个选项为创建一个默认的VUE2项目,最后一个为创建一个自定义的模板项目,这也是VUE-CLI脚手架一个突出的地方,允许我们高度自定义,创建需要的项目。后续的选项选择,就看大家实际项目的需求了~

 

 1.2 使用create-vue创建前端项目

在项目目录中,输入命令 npm init vue@latest 接着依据提示,创建工程

 这里就不过多介绍每个选项了

附上VUE3官方链接:Vue.js - The Progressive JavaScript Framework | Vue.js

PS.你也可以使用比较新版的PycharmWebStorm等编译器创建VUE项目,可以手动选择对应的脚手架选项,由编译器帮你自动构建VUE项目。

小结: 

2.项目结构

 进入到我们新创建的项目当中,先使用 npm install 命令,将项目环境依赖创建好。

这里,我们以使用create-vue创建的vue3项目为例子:

首先式package.json文件,这里主要就是我们存储着我们项目所需要的依赖包,比如页面路由vue-route,全局状态管理工具Pinia,还有我们的代码规范工具,eslint,prettier等,都在我们的这个文件当中,定义了我们项目所用到的工具包与对应版本。

 我们点击preview旁边的按钮,便可执行 vite preview 命令,静态预览我们的项目👇:

在我们首次启动的页面右侧,可以看到官方提供的文档和社区资源,感兴趣的uu也可以点开看呐~如果想要我们修改代码的时候,页面能够同步更新,我们可以使用 npm run service 命令启动项目。

 而在vite.config.ts文件中(因为我使用的式TypeScript语言,所以文件结尾为.ts,如果你使用JavaScript,文件结尾就是.js)是基于vite的配置文件

 src文件下的APP.vue,便是我们项目的根组件,在这里你可以看见我上面启动项目之后的页面代码,而main.ts文件则是用于创建我们app实例,是我们项目的入口文件。还有一些其它的文件夹,具体作用如下:

 那我们的VUE3项目是如何启动的呢?请看下图👇

(1)解析项目入口

Vite 会找到项目入口文件(通常是 src/main.js 或 src/main.ts),并开始解析依赖。

(2)模块热替换(HMR)初始化

Vite 初始化模块热替换(HMR)功能,这样在开发过程中修改代码时,页面会自动更新而无需刷新。

(3)启动 HTTP 服务器

Vite 启动一个 HTTP 服务器,默认端口是 5173,你可以在浏览器中访问 http://localhost:5173

打开浏览器,访问 http://localhost:5173,Vite 会将请求的静态资源(如 index.html 和 JavaScript 文件)发送给浏览器。

(5)创建VUE运用实例

在浏览器中,Vite 加载 index.html 文件,该文件引用了 src/main.ts 文件。main.ts 中的代码会执行以下操作:通过 createApp(App) 创建 Vue 应用实例,其中 App 是根组件(App.vue)。使用 mount 方法将 Vue 应用挂载到 index.html 中的 DOM 元素上(通常是 #app)。

(6)渲染组件

Vue 将 App.vue 的模板渲染到页面上,显示初始页面内容。如果项目中引入了 Vue Router 或 Pinia 等插件,它们也会在此时初始化并开始工作。

 3.基本指令

我们使用 npm run service 命令启动项目,这样我们在修改的代码时候,页面能够同步修改。

v-model 与 插值显示

v-model用于绑定组件的输入值到某个变量中,使用{{ 变量名 }},便可以在我们的页面中显示变量的值。

<script setup>
import {ref} from "vue";
  let name = ref('')             # 定义一个字符串变量
</script>
<template>
  <input v-model=name type="text">   # 将输入框的输入内容绑定到变量 name
  <div>{{ name }}</div>      # 使用插值方法,显示变量的值
</template>

页面显示效果如下: 


v-show
用于控制组件的显示与隐藏,一个组件能否被用户看到,接收的数据类型为布尔值。

我们给组件绑定v-show指令,当其接收的值为true时,组件则会显示,与平常没有差别,接收值为flase,组件则不显示。


v-for 

类似Python语言当中的enumerate循环,常用来依次读取列表内容,返回元素的值和下标。

<script setup>
import { ref } from 'vue'
let list = ref(['苹果', '香蕉', '橙子'])
</script>
<template>
  <ul>
    <li v-for="(item, index) in list" :key="index">
      <strong> {{ item }}</strong>
      <div> {{index}} </div>
    </li>
  </ul>
</template>

 这里的 :key 相当于给我们每一对 li 添加索引,索引的值就是列表中的 index索引下标。


v-bind 和 v-on(@click)

  • v-bind用于为组件的某个属性绑定到变量上,通常简写为 :属性="变量"
  • v-on用于为组件绑定动态函数,当鼠标点击组件的时候,则会触发对应的函数功能,一般简写为 @click="函数名"
<script setup>
import { ref } from 'vue'
let imageUrl = ref('https://shadow.elemecdn.com/app/element/hamburger.9cf7b091-55e9-11e9-a976-7f4d0b07eef6.png')
let count = ref(0)
const handleClick = () => {
  count.value++
}
</script>
<template>
<!--  <img v-bind:src="imageUrl" alt="p">-->
  <img :src="imageUrl" alt="p">

<!--  <button v-on="handleClick">点击 {{ count }}</button>-->
  <button @click="handleClick">点击 {{ count }}</button>
</template>

 在上面的例子中,我们使用v-bind,将img的src属性绑定到变量imageUrl上,这样我们每次只需要更改imageUrl变量的值,img的图片就会对应更改;而button按钮则是绑定上的handleClick的函数,每次点击按钮时,便会触发该函数执行。


v-if

常用于通过某个变量判读,显示那部分内容 ,当v-if的判断值为true时,则显示对应部分的内容,否则显示v-else的内容。可以进行多个v-if-else的嵌套

<script setup>
import { ref } from 'vue'
let isVisible = ref(true)
</script>
<template>
  <button @click="isVisible = !isVisible">切换显示</button>
  <div v-if="isVisible">显示内容 AA</div>
  <h1 v-else>显示内容 BB</h1>
</template>

但使用v-if的时候,需要注意,v-if的组件和其它绑定v-if-else以及v-else的组件,顺序一定要相连,中间不能出现不带 v-if 类型的组件,否则会出错。 以下写法就是错误

<div v-if="isVisible">显示内容 AA</div>
<button @click="isVisible = !isVisible">切换显示</button>
<h1 v-else>显示内容 BB</h1>
# v-if和v-else之间,出现了不带 v-if类型 的组件 button,会出错!

为什么不建议 v-if 和 v-for 一起使用 ?

1.在Vue2中,当v-if和v-for同时用在一个元素上时,v-for的优先级更高。这意味着v-if会在循环内部的每次迭代中都被执行,可能导致性能问题。
2.而在Vue3中,优先级关系发生了变化,v-if的优先级高于v-for。这会导致v-if无法访问v-for作用域内定义的变量,从而引发错误。
3.这种优先级的不一致性使得代码在不同版本间的行为差异较大,容易引起混淆。


 v-html 

<script setup>
import { ref } from 'vue'
let htmlContent = ref('<h1>这是一个HTML内容</h1>')
</script>
<template>
  <div v-html="htmlContent"></div>
</template>

使用v-html,可以将html代码内容,自动渲染为网页,并进行显示,实际上有些博客系统,就是通过存储用户写博客时的html代码,后续用户想要查看时,就使用v-html,将用户所写的内容渲染到网页,从而保持博客当中的文字位置和字体效果。


v-slot

用于父子组件之间插槽功能的实现 

我们在子组件(ChildComponent)中使用slot标签,设置插槽。

父组件在使用子组件的时候,使用 v-slot:name 就可以定位到子组件中对应的插槽位置,设置对应的内容显示。

由于插槽的使用比较复杂,这里就是介绍比较简单的使用。 

VUE组件的命令规范 
使用 PascalCase(大驼峰)或kebab-case(短横线)命名
·在单文件组件、字符串模板和JSX中推荐使用PascalCase
在DOM模板中必须使用kebab-case
多单词命名,避免与HTML标签冲突
文件命名与组件名保持一致,同样使用PascalCase或kebab-case格式

小结:

4.脚本语法 

4.1组合式API

在VUE2中,我们的脚本语言,主要是选项式API的形式,而在VUE3中,主要为组合式API形式。

在组合式API中,我们通过 setup 选项启动,语法如下:

<script>
import {ref} from "vue";
export default {
  setup(){
    // 数据
    const vm = ref('');
    // 函数
    const fun = () => {};
    // 返回,使得外部可以直接调用
    return{
      vm,
      fun
    }
  }
}
</script>

PS. setup的执行时间,是在 beforeCreate 之前的

 或许你会觉得上面的写法太过于复杂,我们可以使用 <script setup>语法糖,进行简化

<script setup lang="ts">
import {ref} from "vue";
// 数据
const x = ref()
// 函数
const fun = () => {}
</script>

<template>
  <div>{{x}}</div>
  <button @click="fun"></button>/
</template>

 这个时候,我们就不再需要return了,可以直接获取变量和函数。

值得注意的是,使用我们的组合式API(开启setup),this 便不在指向组件实例,而是udefined,没有意义。

选项式API VS 组合式API

对比维度选项式 API组合式 API
定义Vue 2 中的主要 API 形式,以选项的形式组织代码,如 data、methods、computed 等选项来定义组件的不同部分Vue 3 中引入的新 API 形式,通过组合函数来组合不同的功能模块,将相关的逻辑代码集中在一起
代码组织方式以选项对象的形式组织代码,每个选项都有其特定的用途和生命周期通过组合函数(setup)来组织代码,将相关的逻辑组合在一起,不受组件生命周期的限制
数据管理data 选项返回一个对象,对象中的属性即为组件的数据,数据和方法在选项中是分离的在 setup 函数中定义响应式数据,通常使用 ref 或 reactive 函数来创建响应式数据,数据和方法可以在同一个函数中定义
方法定义在 methods 选项中定义方法,方法中可以通过 this 访问组件的数据和方法在 setup 函数中定义方法,方法中可以直接使用 setup 函数中定义的响应式数据和其他方法,无需通过 this
计算属性在 computed 选项中定义计算属性,计算属性的 getter 和 setter 都在 computed 选项中定义在 setup 函数中可以使用 computed 函数来定义计算属性,与选项式 API 的计算属性类似,但更加灵活
生命周期钩子在组件的选项中定义生命周期钩子,如 created、mounted 等在 setup 函数中可以通过 onBeforeMount、onMounted 等函数来注册生命周期钩子
代码复用性代码复用性较差,如果多个组件中有相同的逻辑,需要将这些逻辑提取到 mixins 中代码复用性较好,可以将相关的逻辑组合在一起,形成一个可复用的组合函数,然后在多个组件中导入和使用
可读性对于简单的组件,可读性较好,因为代码结构比较固定;但对于复杂的组件,代码可能会变得比较冗长和难以维护可读性较好,因为将相关的逻辑组合在一起,使得代码更加模块化和易于理解
适用场景适用于简单的组件或项目,尤其是在 Vue 2 项目中适用于复杂的组件或项目,尤其是在 Vue 3 项目中,能够更好地组织和管理代码

4.2 响应式语法 

- reactive和ref函数

 reactive和ref都是用于script中声明常量和变量的函数,但是reactive函数只能构造对象类型的数据,像字符串,数字类型的数据,它无法构造,ref函数则可以构造任何类型的数据,更加方便,所以平常开发中,我们一般只是用ref函数来声明变量和常量。(记得使用的时候,要先从vue中导入这两个函数哟~)

<script setup lang="ts">
import {reactive, ref} from "vue";
let x = ref(0);
let students = ref([{'id':1,'name':'张三'},{'id':2,'name':'李四'}])
const filteredStudents = students;
// reactive仅构造对象类型数据
const base = reactive(['1','2','3','4','5','6'])
</script>

而我们要想在脚本中获取由ref构造的变量的值,得再变量名后面加上.value 才能够真正访问到变量所蕴含的值,否则是访问不到,与我们直接在前端 {{变量名}} 的插值显示方法是不一样的。

举个例子体会一下:

<script setup lang="ts">
import {reactive, ref} from "vue";
let x = ref(0);
const students = ref([{'id':1,'name':'张三'},{'id':2,'name':'李四'}])
let filteredStudents = students;  // 复制

// 只要涉及到值的使用,一定记得加上.value
const fun = () =>{
  filteredStudents.value = students.value.filter(item => item.id > 1);  // 过滤,取出id大于1的内容
  x.value ++   // 让x的值增加
}
</script>
<template>
  <div>{{filteredStudents}}</div>  
  <button @click="fun">测试 {{x}}</button>
</template>

 运行结果:

可以看到当我们点击按钮的时候,就触发了fun函数,修改了变量x和filterstudent的值,在前端显示出来。 

- computed函数 

computed函数,用于对某个变量进行运算,返回处理之后的结果,语法格式如下:

 computed(()=>{运算,return ...})

举例:

<script setup lang="ts">
import {computed, ref} from "vue";
 const list = ref([1,2,3,4,5,6,7,8,9,10]);
 const ComputeList = computed(()=>{
    let temp = ref([]);
    for(let i=0;i<list.value.length;i++){
      temp.value.push(list.value[i]+100);
    }
    return temp;
 })
setTimeout(()=>{
  list.value.push(20,30,50)
},5000)
</script>
<template>
    <div>
      原数组:{{list}}
    </div>
    <div>
      computed数组:{{ComputeList}}
    </div>
</template>

 在上述代码中,我们使用ComputeList来记录computed函数计算list之后返回的结果,并设置了setTimeout函数,在5s之后,为list列表末尾中增加20,30,50三个值。运行结果如下:

可以看到我们的ComputeList是会随着List的变化而变化,会更具其内容的改变,对新改变的内容重新计算,存储到我们的ComputeList当中。 

- watch函数

 watch函数用于监听变量的变化,只要监听的变量的value发生变化,就自动触发watch当中的函数

--------------------------------------------------- 基 本 使 用-------------------------------------------------------

举例: 

<script setup lang="ts">
import {ref, watch} from "vue";
let count = ref(0);
const plus = ()=>{
  count.value++;
}
watch(count,()=>{
  console.log(count.value)
})
</script>
<template>
  <button @click="plus">{{count}}</button>
</template>

 当我们点击按钮的时候则就触发plus函数,修改count变量的值,而count变量的值一修改,就会被watch监听到,触发watch当中的操作,在操作台中输出count的值。

而watch函数中的操作部分可以接收两个变量,获取监听变量的新值和旧值

watch(count,(NewValue,OlderValue)=>{
  console.log(NewValue,OlderValue)
})
// NewValue 为 count 更改之后的新值
// OlderValue 为 count 更改之前的旧值

 当然我们也可以使用watch监听多个变量的值,只要当中的一个变量状态发生变化,就会触发watch,以下为一个示例:

<script setup lang="ts">
import {ref, watch} from "vue";
let count = ref(0)
let name = ref("SC")
const plus = ()=>{     // 修改count
  count.value++;
}
const changeName = ()=>{    // 修改name
  if(name.value == "SC"){
    name.value = "FJNU";
  }else{
    name.value = "SC";
  }
}
// 注意这里watch的用法,使用[]接收监听变量
watch([count,name], ([newCount,NewName],[oldCount,OldName])=>{
  console.log(newCount,oldCount)
  console.log(NewName,OldName);
})
</script>
<template>
  <button @click="plus">{{count}}</button>
  <button @click="changeName" style="margin-left: 15px">{{name}}</button>
</template>

如果你想在页面加载的时候,就自动触发一次watch,可以开启watch的immediate选项

watch([count,name], ([newCount,NewName],[oldCount,OldName])=>{
  console.log(newCount,oldCount)
  console.log(NewName,OldName);
},{
immediate:true
});

这样再打开页面,哪怕没点过按钮,也会自动触发一次watch函数的内容。

 --------------------------------------------------- 深 度 监 听-------------------------------------------------------

 如果你所想监听的变量是一个object变量,此时你会发现直接使用watch变量是无法监听得到的,此时得开启watch的深度监听选项——deep,这样才能够监听到object对象。

 如果你只想监听object对象当中的某个属性,而不是全部属性,你可以使用下面的语法:

watch(
  ()=>state.value.age,   // 表明监听的字段
  ()=>{console.log(0)},     // 操作
  {deep:true}
)

 举例:

<script setup>
import {ref, watch} from "vue";
const state = ref({age:18,name:'张三'})
watch(()=>state.value.age,()=>{
  console.log(state.value)
},{
  deep: true,
})
const changeAge = ()=>{
  state.value.age ++;
}
const changeName=()=>{
  state.value.name = 'fjnu';
}
</script>
<template>
  <button @click="changeAge">{{state.age}}</button>
  <button @click="changeName" style="margin-left: 15px">{{state.name}}</button>
</template>

运行结果如下:

 

4.3 生命周期  

如果你对vue2的生命周期有所了解,那么可以直接参考下面的比较,如果不熟悉也没事,且听我慢慢和你说~

Vue 2 生命周期钩子Vue 3 生命周期钩子说明
beforeCreatesetup()在实例初始化之后,数据观测和事件配置之前执行。对应 Vue 3 的 setup 函数。
createdsetup()实例创建完成后执行。对应 Vue 3 的 setup 函数。
beforeMountonBeforeMount()挂载开始之前执行。
mountedonMounted()挂载完成后执行。
beforeUpdateonBeforeUpdate()数据更新时执行,但在虚拟 DOM 重新渲染和打补丁之前。
updatedonUpdated()数据更新导致的虚拟 DOM 重新渲染和打补丁完成后执行。
beforeDestroyonBeforeUnmount()卸载组件实例之前执行。
destroyedonUnmounted()卸载组件实例完成后执行。
activatedonActivated()组件激活时执行(用于保持活跃状态的组件)。
deactivatedonDeactivated()组件停用时执行(用于保持活跃状态的组件)。
errorCapturedonErrorCaptured()捕获子组件错误时执行。

Vue 3 生命周期就像是一个虚拟角色从“诞生”到“成长”再到“老去”的过程,每个阶段都有特定的钩子函数,你可以在这个阶段给这个角色设置一些行为或进行一些操作。这些钩子函数让开发者能够在组件的每个关键阶段插入自己的逻辑。

组件的“诞生”

1. 初始化阶段(setup())
   - 在 Vue 3 中,`setup()` 函数就像是角色的“出生”阶段。在这里,你可以初始化角色的基本属性,比如角色的名字、年龄等(对应组件的数据和方法)。
   - 这个阶段主要处理:创建响应式数据(用 `ref` 和 `reactive`)、定义方法(函数)、注册生命周期钩子等。

组件的“成长”

2. 挂载前阶段(onBeforeMount())
   - 当角色准备好进入游戏世界,但还没正式出现的时候,就是挂载前阶段。这时你可以进行一些准备工作,比如检查角色的装备是否齐全(检查数据和 DOM 的准备情况)。

3. 挂载完成阶段(onMounted())
   - 角色正式进入游戏世界后,这个阶段就代表角色已经完全出现在场景中。你可以在这个阶段进行一些需要访问 DOM 的操作,比如让角色向玩家问好或者播放一段动画。

 组件的“变化”

4. 更新前阶段(onBeforeUpdate())
   - 当角色的属性发生变化(比如升级了、装备更新了),在角色发生变化但还没正式展示给玩家时,就是更新前阶段。你可以在这里做一些更新前的准备,比如记录旧状态或者检查更新的内容。

5. 更新完成阶段(onUpdated())
   - 角色的新状态已经展示给玩家后,这个阶段允许你进行一些更新后的操作。比如,你可以检查更新后的角色状态是否符合预期,或者根据更新后的状态触发一些新的行为。

 组件的“老去”

6. 卸载前阶段(onBeforeUnmount())
   - 当角色即将离开游戏世界时,这个阶段允许你做一些清理工作。比如,你可以保存角色的当前状态(数据),或者移除一些在角色存在期间设置的监听器。

7. 卸载完成阶段(onUnmounted())
   - 角色已经完全离开游戏世界后,这个阶段表示角色的所有资源都被释放。你可以在这里进行一些最后的记录或者通知,比如告诉系统角色已经离开了。

其他阶段

- 激活阶段(onActivated())
  - 当角色从一个不活跃状态(比如休眠)被激活时会触发这个阶段。比如,当你切换到一个之前访问过的页面(组件),角色重新变得活跃。

- 停用阶段(onDeactivated())
  - 当角色从活跃状态变成不活跃状态时会触发这个阶段。比如,当你切换到另一个页面(组件),原来的页面中的角色进入休眠状态。

- 错误捕获阶段(onErrorCaptured())
  - 如果角色在游戏中遇到错误(比如技能施放失败),这个阶段允许你捕获并处理这些错误,比如显示错误提示或者记录错误日志。

通过这些生命周期钩子,你可以精确地控制组件在不同阶段的行为,就像控制游戏中的角色一样。

PS.生命周期函数是可以执行多次的,多次执行时,传入的回调会在时机成熟时依次运行。所以,以后在我们的团队合作项目当中,如果害怕把别人写的omMounted代码搞乱,我们可以自己单独再写一个,把自己的逻辑功能写在里面,就不怕搞乱别人的代码了。

小结:

4.4 组件通信 

- 父传子 defineProps

我们可以在子组件中使用defineProps,定义父组件可以传给子组件的变量,在当中声明变量的名字和变量的数据类型 

<script setup lang="ts">
const props = defineProps({
  message:String,    // 格式 变量名:数据类型
  count:Number
})
</script>

 在父组件中,我们就可以通过导入子组件,并根据子组件中定义好的props,进行信息的传递。

(如果发现子组件无法导出,检查一下你的子组件中的script中是不是没加setup语法糖)

在下面的代码中,父组件(右边)将子组件的count变量,使用v-bind绑定到父组件的变量num中,所以只要父组件中的num变化,子组件中的count也会随之变化,而message则是在父组件中直接赋值的方式,传递给我们的子组件。

什么是单向数据流,什么是双向数据流? 

Vue中的单向数据流和双向数据绑定是两个重要但不同的概念。
单向数据流是一种设计理念,指父组件向子组件传递数据时,数据只能从父组件流向子组件,子组件不能直接修改父组件传来的数据。这通过props实现,所有props都遵循单向绑定原则。
双向数据绑定则是一种使数据和视图保持同步的机制,当数据变化时视图自动更新,用户在视图上的操作也能自动更新数据。在Vue中主要通过v-model指令实现,它本质上是v-bind和@input事件的语法糖。
两者并不矛盾,而是在不同层面上解决不同问题的机制。单向数据流保证了数据流动的可预测性,而双向绑定提供了便捷的表单处理方式。

 - 子传父 defineEmits

在子组件中,通过 emit = defineEmits(['事件名']) 来自定义事件

接着在子组件中通过传递信息的组件,触发事件emit,向父组件发送消息。

父组件中的子组件,通过 @事件名="函数名" ,进一步处理如何使用子组件传来的信息

 - 模板引用ref

通过ref属性,我们就可以获取dom对象或者组件实例对象

<script setup lang="ts">
import ChildComponent from "@/components/ChildComponent.vue";
import {onMounted, ref} from "vue";
const href = ref(null)
const cref = ref(null)
onMounted(()=>{
  console.log(href.value)
  console.log(cref.value)
})
</script>
<template>
  <h1 ref="href">我是父组件</h1>
  <child-component ref="cref"></child-component>
</template>

 

但是这样,你会发现我们拿不到子组件当中定义好的变量和函数方法。那是因为子组件当中的变量和函数方法是封闭的,出于安全考虑,一般外部无法直接调用。此时我们需要使用defineExpose函数,将变量和函数方法暴露给外部。

 暴露出去之后,你就会发现,此时我们可以访问到子组件内部的变量和函数方法了,那要使用他们,我只需要使用 .名字 的方法就可以了

<script setup lang="ts">
import ChildComponent from "@/components/ChildComponent.vue";
import {onMounted, ref} from "vue";
const href = ref(null)
const cref = ref(null)
onMounted(()=>{
  console.log(href.value)
  console.log(cref.value.num)
})
</script>
<template>
  <h1 ref="href">我是父组件</h1>
  <child-component ref="cref"></child-component>
  <button @click="cref.plus">{{cref.num}}</button>  // 使用子组件的plus函数
</template>

- provide 和 inject

你可能会遇到这样一种情况,一个组件中引用了一个子组件,而这个子组件又引用了另一个它的子组件,那么从父组件传递消息到最底层的子组件,一层一层传太过于麻烦。欸,这个时候我们就可以使用provide和inject方法。

  • provide:可以在顶层定义提供的数据或者方法,通过键值对的形式,进行传递
  • infect:接收provide提供的数据和方法,通过获取键的方式,进行匹配

5.路由 

首先我们先了解一些基本概念:

  • 路由 :路由是一种 URL 与页面或组件之间的映射关系。当用户访问不同的 URL 时,路由会根据映射关系加载相应的页面或组件。
  • 单页面应用程序(SPA) :只有一个主页面,其他页面都是通过 JavaScript 动态加载和渲染的。Vue 路由可以帮助我们实现这种无刷新的页面切换。

一般路由的记录都在route文件下的index.ts文件中,我自己个人习惯,我喜欢新建一个routers.ts文件,专门记录路由信息。 里面的结构如下,可以看到routes变量是一个路由列表,列表当中的每个元素都包括三个属性。

  • path:浏览器访问的路径,简单理解就是项目网址中除去ip的部分
  • name:这个路由的名字
  • component:页面(组件),一般对应某个vue文件

 通过上面的路由定义,我们就可以清楚地知道,在浏览器中,我们访问哪个路径,可以看到我们所写的哪个页面,形成1对1的关系。

 而在index.ts文件的最下面,你或许会看到这样的代码:

这里其实就是在定义路由的模式,vue router一共支持以下三种路由模式:

模式实现原理优点缺点适用场景
hash(默认)使用 URL 的 hash 值(# 后面的部分),如 http://example.com/#/home兼容性强,支持所有浏览器(包括 IE);无需服务器配置;hash 变化不会触发页面重新加载。URL 中包含 #,不够美观;不利于 SEO(搜索引擎优化),因为搜索引擎通常会忽略 hash 部分。中小型单页应用(SPA);对 URL 美观性要求不高的场景。
history使用 HTML5 History API(pushState、replaceState),如 http://example.com/homeURL 更美观,没有 #;对 SEO 友好;支持高级路由特性(如 history.state)。需要服务器配置支持,否则刷新页面会返回 404;低版本浏览器(如 IE9 及以下)不支持。企业官网、电商网站等对 SEO 要求较高的应用;需要美观 URL 的场景。
abstract不依赖浏览器环境,路由状态保存在内存中。适用于非浏览器环境(如 Node.js、Weex、小程序等);测试环境中使用方便。只能在非浏览器环境中使用,无法直接用于 Web 应用。服务端渲染(SSR)应用;原生应用(如 Weex);单元测试。

当使用 history 模式时,由于刷新页面会直接请求服务器,需确保服务器能够正确处理所有路由请求,通常需要配置所有路径都返回同一个 HTML 文件(如 index.html)。

三种模式的配置方式如下:

// hash 模式(默认)
const router = createRouter({
  history: createWebHashHistory(),
  routes: [...]
});

// history 模式
const router = createRouter({
  history: createWebHistory(), // 等价于 createWebHistory(process.env.BASE_URL)
  routes: [...]
});

// abstract 模式(用于非浏览器环境)
const router = createRouter({
  history: createMemoryHistory(),
  routes: [...]
});

 5.1 动态路由

 动态路由,实际上就是一种带参数的路由,比如CSDN当中的博客,你会发现大部分的博客,它的页面样式基本都一样,只有内容不一样,那它是写了好几个不同页面进行实现的吗?

显然不是,这样子太费效率和存储了,而且每次新增的博客,你就要多写一个页面文件。那这里我们就可以使用动态路由来实现,通过访问不同的博客id,显示不同的内容,但页面都是同一个页面。示例图如下:

在路由定义中使用冒号 : 标记动态参数:

// router/index.js
const routes = [
  {
    path: '/user/:id',
    name: 'User',
    component: UserComponent
  }
];
  • 匹配路径:/user/123 → params: { id: '123' }
  • 多参数支持:path: '/user/:id/post/:postId' → params: { id: '123', postId: '456' }

 而在我们的UserComponent页面当中,我们要获取路由中的id参数,从而发送给我们的后端,可以在UserComponent页面中使用以下方法实现:

选项式API中(多为VUE2),通过 $route.params 获取参数:

export default {
  mounted() {
    console.log(this.$route.params.id); // 获取路由中的动态参数id
  }
}

而在组合式API中,使用useRouter钩子

import { useRoute } from 'vue-router';

export default {
  setup() {
    const route = useRoute();
    console.log(route.params.id); // 获取动态参数中的id
  }
}

此外动态路由中的参数,也是支持正则约束的,举几个例子:

//限制参数格式
path: '/user/:id(\\d+)' // 仅匹配数字 ID
// ?表示可选参数
path: '/user/:id?' // 匹配 /user 或 /user/123
// *为通配符,匹配任意字符
path: '/user/*' // 匹配 /user/a/b/c,参数为 { pathMatch: ['a', 'b', 'c'] }

 扩展:跳转404页面的页面设置

{
  path: '/:pathMatch(.*)*',
  component: NotFoundComponent
}

使用pathMatch,配置通配符路由捕获未匹配路径

5.2 嵌套路由 

嵌套路由指的是在一个路由组件内部再渲染其他路由组件。比如,一个应用的布局是侧边栏搭配主内容区,主内容区会依据用户的操作显示不同的子页面,这时就可以采用嵌套路由。就比如下面的网页,点击头部菜单栏,头部菜单栏不变,但是下面显示的网页界面变了。

路由编写,通过children属性,来确定子路由。

const routes = [
  {
    path: '/user',
    name: 'User',
    component: UserLayout,
    children: [
      {
        path: 'profile',
        name: 'UserProfile',
        component: UserProfile
      },
      {
        path: 'settings',
        name: 'UserSettings',
        component: UserSettings
      }
    ]
  }
];

在上面的示例中,子路由UserProfile和UserSettings的path采用的是相对路径,实际上浏览器访问它们两个的路径是 /user/profile 和 /user/settings ,也就是在它们前面加上父路由的path。

而在父路由对应的vue组件文件中,页面切换的部分,使用 <router-view /> 标签进行子路由界面的显示 

<!-- UserLayout.vue -->
<template>
  <div class="user-layout">
    <Sidebar />
    <div class="main-content">
      <router-view />
    </div>
  </div>
</template>

在需要进行权限控制的场景中,嵌套路由也能发挥作用。可以把需要权限验证的路由作为一个父路由的子路由 。

5.3 懒加载

路由懒加载(也称为延迟加载)是一种优化技术,允许你在需要时才加载路由组件,而不是在应用初始化时就加载所有组件。这显著提高了应用的初始加载速度,尤其是对于大型应用而言。

为什么需要路由懒加载?

传统的路由配置会在应用启动时加载所有组件,即使这些组件可能永远不会被用户访问。这会导致以下问题:

  • 初始加载时间长:应用包体积过大,导致首屏加载缓慢。
  • 资源浪费:用户可能永远不会访问某些页面,但这些页面的代码仍然被加载。

路由懒加载通过按需加载组件解决了这些问题,只有当用户访问特定路由时,对应的组件才会被加载。

现在一般使用 ES6 的动态导入语法 (import()) 来实现路由懒加载 ,如下:

import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  {
    path: '/home',
    name: 'Home',
    // 懒加载Home组件
    component: () => import('../views/Home.vue')
  },
  {
    path: '/about',
    name: 'About',
    // 懒加载About组件
    component: () => import('../views/About.vue')
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router

在上述例子中,Home.vue和About.vue组件不会在应用初始化时加载,而是在用户首次访问/home或/about路径时才会被加载。 

路由懒加载的使用场景

  • 大型应用:当应用包含多个页面或功能模块时,使用懒加载可以显著减少初始加载时间。
  • 访问频率较低的页面:如帮助中心、隐私政策等页面,用户可能很少访问,适合懒加载。
  • 权限控制页面:某些需要特定权限才能访问的页面,如管理后台,只有授权用户才能访问,可以懒加载。
  • 第三方库集成:如果某个路由组件依赖大型第三方库(如 Chart.js、地图库等),可以将该组件懒加载,避免增加主包体积。

5.4 导航守卫

导航守卫主要用于在路由切换过程中执行特定逻辑,比如身份验证、权限检查、数据预加载等。Vue Router 提供了多种类型的导航守卫,它们会在不同的路由生命周期阶段被触发。

全局前置守卫(beforeEach)
在路由导航开始前触发,可以用于身份验证、权限检查等。

router.beforeEach((to, from, next) => {
  // to: 即将进入的目标路由
  // from: 当前导航正要离开的路由
  // next: 必须调用的函数,用于 resolve 这个钩子
  
  if (to.meta.requiresAuth && !isAuthenticated()) {
    next('/login') // 未登录时重定向到登录页
  } else {
    next() // 继续导航
  }
})

全局解析守卫(beforeResolve)
在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后触发。

router.beforeResolve((to, from, next) => {
  // 常用于处理路由组件的异步加载
  next()
})

 全局后置钩子(afterEach)
在导航完成后触发,不接受next函数,不能改变导航本身。

router.afterEach((to, from) => {
  // 常用于记录页面访问日志、滚动位置恢复等
  logPageView(to.fullPath)
})

路由独享守卫:定义在单个路由配置中的守卫,只会在特定路由被访问时触发。

const routes = [
  {
    path: '/admin',
    component: AdminPanel,
    beforeEnter: (to, from, next) => {
      // 只有管理员用户才能访问
      if (!isAdmin()) {
        next('/forbidden')
      } else {
        next()
      }
    }
  }
]

组件内守卫:直接在路由组件内部定义的守卫,用于控制组件自身的导航行为。

<script>
export default {
  beforeRouteEnter(to, from, next) {
    // 在渲染该组件的对应路由被验证前调用
    // 不能访问组件实例 `this`,因为守卫在导航确认前被调用
    // 可以通过传一个回调给 next 来访问组件实例
    next(vm => {
      // 通过 `vm` 访问组件实例
    })
  },
  
  beforeRouteUpdate(to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 /users/:id,在 /users/1 和 /users/2 之间跳转的时候
    // 由于会渲染同样的 User 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用
    // 可以访问组件实例 `this`
  },
  
  beforeRouteLeave(to, from, next) {
    // 在导航离开渲染该组件的对应路由时调用
    // 可以访问组件实例 `this`
    // 常用于阻止用户在未保存修改时离开
    if (this.hasUnsavedChanges) {
      next(false) // 取消导航
    } else {
      next()
    }
  }
}
</script>

下面就是导航守卫的大体触发流程

5.5 编程式导航和滚动行为

编程式导航是通过调用 Vue Router 提供的 API 实现路由跳转,而非依赖 <router-link> 标签的声明式导航。适用于需要动态决定跳转逻辑的场景(如按钮点击、表单提交、条件判断等)。

---------------------------------------------总结一下,其实就是下面一幅图--------------------------------------

举个跳转页面的例子:

// 组合式 API(setup 或 Composition API)
import { useRouter } from 'vue-router';
const router = useRouter();
router.push('/home'); // 字符串路径
router.push({ path: '/user', query: { name: 'John' } }); // 带查询参数
router.push({ name: 'UserDetail', params: { userId: 1 } }); // 命名路由+参数

滚动行为(Scroll Behavior):控制路由切换时的页面滚动位置,提升用户体验(如返回列表页时恢复滚动位置,进入详情页时滚动到顶部) 

在实际开发中,你或许会遇到一个问题:
我们在一个很长的列表页往下拉,然后点击列表中的某一个数据进入到详情页查看。此时我们决定返回列表也继续查看列表。
很多情况下,由于列表页的组件已经被销毁,所以我们返回到列表页后页面会置顶,不得不又重新下拉查看列表,这样就做了很多没有必要的操作,也是不符合用户的预期。用户希望当我查看玩详情页以后返回,返回列表页的位置是刚刚浏览的位置。

这个问题就可以使用我们的滚动行为解决。

1)在路由配置文件(index.ts)中添加 scrollBehavior

const router = createRouter({
  history: createWebHistory(),
  routes: [...],
  scrollBehavior(to, from, savedPosition) {
    // 从详情页返回列表页时,恢复滚动位置
    if (from.name && to.name === 'ListPage' && savedPosition) {
      return savedPosition; // 直接使用浏览器保存的位置(需配合组件内保存)
    }
    // 或手动记录位置(适用于组件被销毁的情况)
    if (to.meta.keepScroll) { // 给列表页路由添加 meta 标记
      return { top: window.scrollY }; // 返回指定位置
    }
    // 其他情况默认置顶
    return { top: 0 };
  }
});

2)在列表页组件中,离开时保存滚动位置(组件销毁前):

// Vue 组件
export default {
  data() {
    return { scrollPosition: 0 };
  },
  beforeRouteLeave(to, from, next) {
    this.scrollPosition = window.scrollY; // 保存当前滚动高度
    next();
  },
  mounted() {
    // 从详情页返回时,恢复位置(若 scrollBehavior 未生效)
    if (this.scrollPosition) {
      window.scrollTo(0, this.scrollPosition);
    }
  }
};
功能编程式导航滚动行为
作用动态控制路由跳转(替代 <router-link>控制路由切换时的滚动位置,优化用户体验
核心 APIpush/replace/go(组合式:useRouter()全局配置 scrollBehavior,返回滚动位置对象
应用场景条件跳转、携带参数、历史记录控制恢复滚动位置、滚动到顶部 / 锚点、平滑滚动
关键参数路由对象(path/name/params/querytofromsavedPosition(浏览器回退时)

--------------------------------------完结撒花,快上手试试吧-------------------------------------------- 

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐