vue3快速入门-程序员宅基地

技术标签: 前端  vue.js  Vue学习  javascript  

一、Vue 介绍

1.1vue概述

Vue 是一个框架,也是一个生态。VUE是一套前端框架,免除了原生 JavaScript 中的 DOM 操作,简化了很多书写内容,其功能覆盖了大部分前端开发常见的需求。但 Web 世界是十分多样化的,不同的开发者在 Web 上构建的东西可能在形式和规模上会有很大的不同。考虑到这一点,Vue 的设计非常注重灵活性和“可以被逐步集成”这个特点。根据你的需求场景,你可以用不同的方式使用 Vue:

  • 无需构建步骤,渐进式增强静态的 HTML
  • 在任何页面中作为 Web Components 嵌入
  • 单页应用 (SPA)
  • 全栈 / 服务端渲染 (SSR)
  • Jamstack / 静态站点生成 (SSG)
  • 开发桌面端、移动端、WebGL,甚至是命令行终端中的界面

1.2Node.js 介绍

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。

Node 是一个让 JavaScript 运行在服务端的开发平台,它让 JavaScript 成为与 PHP、Python、Perl、Ruby 等服务端语言平起平坐的脚本语言。 发布于 2009 年 5 月,由 Ryan Dahl 开发,实质是对 Chrome V8 引擎进行了封装。

Node 对一些特殊用例进行优化,提供替代的 API,使得 V8 在非浏览器环境下运行得更好。V8 引擎执行 Javascript 的速度非常快,性能非常好。 Node 是一个基于 Chrome JavaScript 运行时建立的平台, 用于方便地搭建响应速度快、易于扩展的网络应用。Node 使用事件驱动, 非阻塞 I/O 模型而得以轻量和高效,非常适合在分布式设备上运行数据密集型的实时应用。

1.3vue搭建

基于 vue-cli 创建

## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue --version

## 安装或者升级你的@vue/cli 
npm install -g @vue/cli

## 执行创建命令
vue create vue_test

##  随后选择3.x
##  Choose a version of Vue.js that you want to start the project with (Use arrow keys)
##  > 3.x
##    2.x

## 启动
cd vue_test
npm run serve
```

 基于 vite 创建

## 1.创建命令
npm create vue@latest

## 2.具体配置
## 配置项目名称
√ Project name: vue3_test
## 是否添加TypeScript支持
√ Add TypeScript?  Yes
## 是否添加JSX支持
√ Add JSX Support?  No
## 是否添加路由环境
√ Add Vue Router for Single Page Application development?  No
## 是否添加pinia环境
√ Add Pinia for state management?  No
## 是否添加单元测试
√ Add Vitest for Unit Testing?  No
## 是否添加端到端测试方案
√ Add an End-to-End Testing Solution? » No
## 是否添加ESLint语法检查
√ Add ESLint for code quality?  Yes
## 是否添加Prettiert代码格式化
√ Add Prettier for code formatting?  No

1.4文件介绍

.env.d.ts 文件

.env.d.ts 文件的用途是向 TypeScript 编译器提供项目的环境变量类型声明。

在 .env.d.ts 文件中,/// <reference types="vite/client" /> 是一个 TypeScript 的引用指令,它的作用是向 TypeScript 编译器引入 Vite 的客户端类型定义。 

vite/client 是一个提供 Vite 客户端 API 的模块,它包含了与 Vite 服务器通信的类型定义。

 index.html 文件

 index.html 文件通常作为项目的入口文件,它会被打包工具(如 Webpack)处理,并最终生成一个静态的 HTML 文件,用于在浏览器中展示项目。

vite.config.ts文件

vite.config.ts 是 Vite.js 项目的核心配置文件,它用于定义和配置项目的构建工具链。在 Vue 3 中,Vite 是一个非常流行的构建工具,它提供了很多优秀的特性,如快速的热更新、按需加载、支持多种前端框架等。

components

Vue 3 中的组件(Components)是 Vue.js 最核心的概念之一,它是 Vue 应用的基本构建单元。组件允许你将 UI 分解为独立可复用的部分,每个部分都具有自己的视图和逻辑。

assets 目录

assets 目录通常用于存放项目中的静态资源文件,如图片、字体、CSS 文件(如果选择不使用 CSS 预处理器的话)、音频、视频等。这些静态资源文件不会被 Webpack 或 Vite 等构建工具进行处理和转换,而是会被直接复制到最终的构建目录中。 

 main.ts

main.ts 文件通常作为应用程序的入口文件。

// 导入主 CSS 样式表文件,用于应用全局样式
import './assets/main.css'

// 从 Vue 包中导入 createApp 函数,用于创建 Vue 应用实例
import { createApp } from 'vue'

// 导入根组件 App.vue,这是应用的核心组件
import App from './App.vue'

// 导入路由配置模块,提供页面间的路由跳转功能
import router from './router'

// 使用 createApp 函数创建一个 Vue 应用实例
const app = createApp(App)

// 将路由器注入到 Vue 应用中,使其具有路由功能
app.use(router)

// 将根组件挂载到 HTML 文档中 id 为 'app' 的元素上
// 这一步会启动整个 Vue 应用程序的渲染和运行
app.mount('#app')

app.vue

App.vue 是一个 Vue 项目的入口组件,通常位于 src 目录下,是整个应用程序的根组件。它定义了应用程序的主要结构和布局,并处理应用程序级别的逻辑和状态。 App.vue 组件通常包含三个部分:模板(template)、脚本(script)和样式(style)。

  • 模板(template):定义了组件的结构和布局,使用 Vue 的模板语法来组织 HTML 代码。
  • 脚本(script):定义了组件的行为和逻辑,包括组件的属性、数据、方法和生命周期钩子等。
  • 样式(style):定义了组件的样式,可以使用 CSS、Sass、Less 等样式语言来编写。
<script setup lang="ts"></script>
<template>
  <div>
    <h1>App</h1>
  </div>
</template>
<style></style>

scoped组件内声明的局部作用域css 代码 编译时生成追加属性值(哈希值)

tsconfig.json

tsconfig.json是TypeScript项目的配置文件,用于配置TypeScript编译器和项目的选项。以下是tsconfig.json文件中一些常见的配置选项:

  • compilerOptions:编译器选项,例如目标语言版本、模块化系统、源码文件路径等。
  • include:指定需要编译的源码文件路径。
  • exclude:指定不需要编译的文件或文件夹路径。
  • files:指定需要编译的特定文件路径。
  • references:指定其他需要引用的项目配置文件路径。

vite.config.ts

是Vite(一个由Vue.js作者尤雨溪开发的现代化前端构建工具)项目的配置文件,用于自定义Vite的行为和设置

 流程介绍

- `Vite` 项目中,`index.html` 是项目的入口文件,在项目最外层。

- 加载`index.html`后,`Vite` 解析 `<script type="module" src="xxx">` 指向的`JavaScript`。

- `Vue3`**中是通过 **`createApp` 函数创建一个应用实例。

 二、vue3基础入门

2.1基础语法

写组件

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

<script lang="ts">
export default {
  name: 'App',
  data() {
    return {
      name: '张三',
      age: 18,
      tel: '13888888888'
    }
  },
  methods: {
    changeName() {
      this.name = 'zhang-san'
    },
    changeAge() {
      this.age += 1
    },
    showTel() {
      alert(this.tel)
    }
  }
}
</script>

<style scoped>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>
  • { { }} 变量、表达式渲染

注册组件

方法一vue2

<template>
  <div class="app">
    <h1>你好啊!</h1>
    <Person />
  </div>
</template>

<script lang="ts">
import Person from './components/Person.vue'

export default {
  name: 'App', // 组件名
  components: {
    Person // 注册组件
  }
}
</script>

<style>
/* 在这里添加您的样式 */
</style>

方法二 

<template>
  <!-- 定义页面主容器 -->
  <div>
    <!-- 页面标题 -->
    <h1>App</h1>
    
    <!-- 引入并使用名为Person的子组件 -->
    <!-- 组件位于@/components/Person.vue -->
    <Person />
  </div>
</template>

<!-- 使用<script setup>语法糖进行脚本编写 -->
<script setup lang="ts">
  // 导入并使用Person组件
  import Person from '@/components/Person.vue';
</script>

<!-- 针对当前组件的样式设置作用域 -->
<style scoped></style>

setup

  • 若返回对象:则对象中的:属性、方法等,在模板中均可以直接使用(重点关注
  • 若返回一个函数:则可以自定义渲染内容,代码如下:
    setup(){
      return ()=> '你好啊!'
    }

标签代码在组件对象创建前执行,因此没有也无需通过`this`获取当前组件对象

 setup 与 Options API 的关系

  • - `Vue2` 的配置(`data`、`methos`......)中**可以访问到** `setup`中的属性、方法。
  • - 但在`setup`中**不能访问到**`Vue2`的配置(`data`、`methos`......)。
  • - 如果与`Vue2`冲突,则`setup`优先。

setup函数与Vue 2中的Options API是两种不同的组件编程模型,它们在Vue 3中可以共存但有着根本的区别:

  1. Options API

    • Vue 2及以前版本的核心API,通过一系列选项对象(如datamethodscomputedwatch等)来组织组件的状态和行为。
    • 在这个API模式下,每个选项都是一个属性,可以在组件实例上直接访问。
  2. Composition API (包含 setup 函数)

    • Vue 3引入的新型API,旨在提供更好的代码组织性和复用性。
    • setup 函数是Composition API的核心入口点,它会在组件实例初始化之前执行,并且在这个函数内部可以使用 reactiverefcomputed 等函数来定义响应式状态和计算属性,以及使用 onMountedwatchEffect 等生命周期钩子函数。

综上所述,两者之间的关系更多体现在Vue 3中可以自由选择使用哪种API编写组件,也可以混合使用。然而,setup函数所提供的编程模型更注重逻辑的集中与模块化,不依赖于组件实例的存在,因此不能像Options API那样简单地互相访问彼此内部定义的内容。

ref

基本类型的响应式数据

 **作用:**定义响应式变量。

- **语法:**`let xxx = ref(初始值)`。

- **返回值:**一个`RefImpl`的实例对象,简称`ref对象`或`ref`,`ref`对象的`value`**属性是响应式的

优化

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

<script setup lang="ts" name="Person">
import { ref } from 'vue'
// name和age是一个RefImpl的实例对象,简称ref对象,它们的value属性是响应式的。
let name = ref('张三')
let age = ref(18)
// tel就是一个普通的字符串,不是响应式的
let tel = '13888888888'

function changeName() {
  // JS中操作ref对象时候需要.value
  name.value = '李四'
  console.log(name.value)

  // 注意:name不是响应式的,name.value是响应式的,所以如下代码并不会引起页面的更新。
  // name = ref('zhang-san')
}
function changeAge() {
  // JS中操作ref对象时候需要.value
  age.value += 1
  console.log(age.value)
}
function showTel() {
  alert(tel)
}
</script>
对象类型的响应式数据
<template>
  <div class="person">
    <h2>汽车信息:一台{
   { car.brand }}汽车,价值{
   { car.price }}万</h2>
    <h2>游戏列表:</h2>
    <ul>
      <li v-for="g in games" :key="g.id">{
   { g.name }}</li>
    </ul>
    <h2>测试:{
   {obj.a.b.c.d}}</h2>
    <button @click="changeCarPrice">修改汽车价格</button>
    <button @click="changeFirstGame">修改第一游戏</button>
    <button @click="test">测试</button>
  </div>
</template>

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

// 数据
let car = ref({ brand: '奔驰', price: 100 })
let games = ref([
  { id: 'ahsgdyfa01', name: '英雄联盟' },
  { id: 'ahsgdyfa02', name: '王者荣耀' },
  { id: 'ahsgdyfa03', name: '原神' }
])
let obj = ref({
  a:{
    b:{
      c:{
        d:666
      }
    }
  }
})

console.log(car)

function changeCarPrice() {
  car.value.price += 10
}
function changeFirstGame() {
  games.value[0].name = '流星蝴蝶剑'
}
function test(){
  obj.value.a.b.c.d = 999
}
</script>

 reactive

 **作用:**定义一个**响应式对象**(基本类型不要用它,要用`ref`,否则报错)

- **语法:**`let 响应式对象= reactive(源对象)`。

- **返回值:**一个`Proxy`的实例对象,简称:响应式对象。

- **注意点:**`reactive`定义的响应式数据是“深层次”的。

<template>
  <div class="Game">
    <!-- 使用响应式对象 car 中的 brand 和 price 属性展示汽车信息 -->
    <h2>汽车信息:一台{
   { car.brand }}汽车,价值{
   { car.price }}万</h2>

    <h2>游戏列表:</h2>
    <ul>
      <!-- 使用 v-for 指令遍历响应式数组 games,并通过 :key 绑定每个元素的 id -->
      <li v-for="g in games" :key="g.id">{
   { g.name }}</li>
    </ul>

    <!-- 测试显示一个嵌套在响应式对象 obj 中的深层属性值 -->
    <h2>测试:{
   { obj.a.b.c.d }}</h2>

    <!-- 定义一个按钮触发 changeCarPrice 函数以修改汽车价格 -->
    <button @click="changeCarPrice">修改汽车价格</button>

    <!-- 定义一个按钮触发 changeFirstGame 函数以修改第一个游戏名称 -->
    <button @click="changeFirstGame">修改第一游戏</button>

    <!-- 定义一个按钮触发 test 函数以修改 obj 的深层属性 -->
    <button @click="test">测试</button>
  </div>
</template>

<script lang="ts" setup name="Game111">
import { reactive } from 'vue'

// 使用 Vue3 的 reactive 函数创建一个响应式对象,表示汽车信息
let car = reactive({
  brand: '奔驰', // 响应式的汽车品牌属性
  price: 100     // 响应式的汽车价格属性(假设单位为万元)
})

// 创建一个响应式数组,存储多个游戏对象
let games = reactive([
  { id: 'ahsgdyfa01', name: '英雄联盟' },
  { id: 'ahsgdyfa02', name: '王者荣耀' },
  { id: 'ahsgdyfa03', name: '原神' }
])

// 创建一个具有深层嵌套结构的响应式对象
let obj = reactive({
  a: {
    b: {
      c: {
        d: 666 // 响应式的深层属性值
      }
    }
  }
})

// 修改汽车价格的函数,由于 car 是响应式对象,所以直接更改其属性会触发视图更新
function changeCarPrice() {
  car.price += 10
}

// 修改第一个游戏名称的函数,同样利用 games 数组作为响应式数据源
function changeFirstGame() {
  games[0].name = '流星蝴蝶剑'
}

// 测试函数,用于改变响应式对象 obj 的深层属性值
function test() {
  obj.a.b.c.d = 999
}
</script>

 

toRefs 与 toRef

- 作用:将一个响应式对象中的每一个属性,转换为`ref`对象。

- 备注:`toRefs`与`toRef`功能一致,但`toRefs`可以批量转换。

template>
  <div class="person">
    <h2>姓名:{
   {person.name}}</h2>
    <h2>年龄:{
   {person.age}}</h2>
    <h2>性别:{
   {person.gender}}</h2>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年龄</button>
    <button @click="changeGender">修改性别</button>
  </div>
</template>

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

  // 数据
  let person = reactive({name:'张三', age:18, gender:'男'})
	
  // 通过toRefs将person对象中的n个属性批量取出,且依然保持响应式的能力
  let {name,gender} =  toRefs(person)
	
  // 通过toRef将person对象中的gender属性取出,且依然保持响应式的能力
  let age = toRef(person,'age')

  // 方法
  function changeName(){
    name.value += '~'
  }
  function changeAge(){
    age.value += 1
  }
  function changeGender(){
    gender.value = '女'
  }
</script>

 2.2常用api

computed

作用:根据已有数据计算出新数据

<template>
  <div class="Name">
    <!-- 输入框绑定姓 -->
    姓:
    <input type="text" v-model="firstName" />
    <br />

    <!-- 输入框绑定名 -->
    名:
    <input type="text" v-model="lastName" />
    <br />

    <!-- 使用计算属性fullName显示全名 -->
    全名:
    <span>{
   { fullName }}</span>
    <br />

    <!-- 点击按钮调用changeFullName方法,修改fullName的值 -->
    <button @click="changeFullName">全名改为:li-si</button>
  </div>
</template>

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

// 创建响应式变量存储姓和名
let firstName = ref('zhang')
let lastName = ref('san')

// 计算属性 - 双向绑定实现全名读取与修改
// 当fullName被赋值时,会触发set函数,并自动更新firstName和lastName
let fullName = computed({
  // get 方法用于获取全名(读取操作)
  get() {
    return `${firstName.value}-${lastName.value}`
  },

  // set 方法用于处理对fullName的赋值操作,并据此更新firstName和lastName
  set(val) {
    console.log('有人修改了fullName', val)
    const names = val.split('-')
    firstName.value = names[0]
    lastName.value = names[1]
  }
})

// 更改全名的方法,通过给fullName计算属性赋值触发set方法
function changeFullName() {
  fullName.value = 'li-si'
}
</script>

ref()函数,当获取value属性时,调用对应的get()函数,get()函数内部执行track()追踪函数。即改变value的引用时,可实现对新数据的追踪,创建新数据的代理对象

watch

- 作用:监视数据的变化(和`Vue2`中的`watch`作用一致)

- 特点:`Vue3`中的`watch`只能监视以下**四种数据**:

> 1. `ref`定义的数据。

> 2. `reactive`定义的数据。

> 3. 函数返回一个值(`getter`函数)。

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

情况一 `ref`定义的数据。

监视`ref`定义的【基本类型】数据:直接写数据名即可,监视的是其`value`值的改变。

<template>
  <div class="person">
    <h1>情况一:监视【ref】定义的【基本类型】数据</h1>
    <h2>当前求和为:{
   {sum}}</h2>
    <button @click="changeSum">点我sum+1</button>
  </div>
</template>

<script lang="ts" setup name="Person">
  import {ref,watch} from 'vue'
  // 数据
  let sum = ref(0)
  // 方法
  function changeSum(){
    sum.value += 1
  }
  // 监视,情况一:监视【ref】定义的【基本类型】数据
  const stopWatch = watch(sum,(newValue,oldValue)=>{
    console.log('sum变化了',newValue,oldValue)
    if(newValue >= 10){
      stopWatch()
    }
  })
</script>
 情况二监视`ref`定义的【对象类型】数据:

直接写数据名,监视的是对象的【地址值】,若想监视对象内部的数据,要手动开启深度监视。

> 注意:

> * 若修改的是`ref`定义的对象中的属性,`newValue` 和 `oldValue` 都是新值,因为它们是同一个对象。

> * 若修改整个`ref`定义的对象,`newValue` 是新值, `oldValue` 是旧值,因为不是同一个对象了。

<template>
  <div class="person">
    <h1>情况二:监视【ref】定义的【对象类型】数据</h1>
    <h2>姓名:{
   { person.name }}</h2>
    <h2>年龄:{
   { person.age }}</h2>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年龄</button>
    <button @click="changePerson">修改整个人</button>
  </div>
</template>

<script lang="ts" setup name="Person">
  import {ref,watch} from 'vue'
  // 数据
  let person = ref({
    name:'张三',
    age:18
  })
  // 方法
  function changeName(){
    person.value.name += '~'
  }
  function changeAge(){
    person.value.age += 1
  }
  function changePerson(){
    person.value = {name:'李四',age:90}
  }
  /* 
    监视,情况一:监视【ref】定义的【对象类型】数据,监视的是对象的地址值,若想监视对象内部属性的变化,需要手动开启深度监视
    watch的第一个参数是:被监视的数据
    watch的第二个参数是:监视的回调
    watch的第三个参数是:配置对象(deep、immediate等等.....) 
  */
  watch(person,(newValue,oldValue)=>{
    console.log('person变化了',newValue,oldValue)
  },{deep:true})
  
</script>
 情况三监视`reactive`定义的【对象类型】数据,

且默认开启了深度监视。

<template>
  <div class="person">
    <h1>情况三:监视【reactive】定义的【对象类型】数据</h1>
    <h2>姓名:{
   { person.name }}</h2>
    <h2>年龄:{
   { person.age }}</h2>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年龄</button>
    <button @click="changePerson">修改整个人</button>
    <hr>
    <h2>测试:{
   {obj.a.b.c}}</h2>
    <button @click="test">修改obj.a.b.c</button>
  </div>
</template>

<script lang="ts" setup name="Person">
  import {reactive,watch} from 'vue'
  // 数据
  let person = reactive({
    name:'张三',
    age:18
  })
  let obj = reactive({
    a:{
      b:{
        c:666
      }
    }
  })
  // 方法
  function changeName(){
    person.name += '~'
  }
  function changeAge(){
    person.age += 1
  }
  function changePerson(){
    Object.assign(person,{name:'李四',age:80})
  }
  function test(){
    obj.a.b.c = 888
  }

  // 监视,情况三:监视【reactive】定义的【对象类型】数据,且默认是开启深度监视的
  watch(person,(newValue,oldValue)=>{
    console.log('person变化了',newValue,oldValue)
  })
  watch(obj,(newValue,oldValue)=>{
    console.log('Obj变化了',newValue,oldValue)
  })
</script>
情况四监视`ref`或`reactive`定义的【对象类型】数据中的**某个属性**

1. 若该属性值**不是**【对象类型】,需要写成函数形式。

2. 若该属性值是**依然**是【对象类型】,可直接编,也可写成函数,建议写成函数。

结论:监视的要是对象里的属性,那么最好写函数式,注意点:若是对象监视的是地址值,需要关注对象内部,需要手动开启深度监视。

<template>
  <div class="Test">
    <h1>情况四:监视【ref】或【reactive】定义的【对象类型】数据中的某个属性</h1>
    <h2>姓名:{
   { person.name }}</h2>
    <h2>年龄:{
   { person.age }}</h2>
    <h2>汽车:{
   { person.car.c1 }}、{
   { person.car.c2 }}</h2>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年龄</button>
    <button @click="changeC1">修改第一台车</button>
    <button @click="changeC2">修改第二台车</button>
    <button @click="changeCar">修改整个车</button>
  </div>
</template>

<script lang="ts" setup name="Test">
import { reactive, watch } from 'vue'

// 数据
let person = reactive({
  name: '张三',
  age: 18,
  car: {
    c1: '奔驰',
    c2: '宝马'
  }
})
// 方法
function changeName() {
  person.name += '~'
}
function changeAge() {
  person.age += 1
}
function changeC1() {
  person.car.c1 = '奥迪'
}
function changeC2() {
  person.car.c2 = '大众'
}
function changeCar() {
  person.car = { c1: '雅迪', c2: '爱玛' }
}

// 监视,情况四:监视响应式对象中的某个属性,且该属性是基本类型的,要写成函数式
/* watch(()=> person.name,(newValue,oldValue)=>{
      console.log('person.name变化了',newValue,oldValue)
    }) */

// 监视,情况四:监视响应式对象中的某个属性,且该属性是对象类型的,可以直接写,也能写函数,更推荐写函数
watch(
  () => person.car,
  (newValue, oldValue) => {
    console.log('person.car变化了', newValue, oldValue)
  },
  { deep: true }
)
</script>
情况五监视上述的多个数据
watch([()=>person.name,person.car],(newValue,oldValue)=>{
    console.log('person.car变化了',newValue,oldValue)
  },{deep:true})
watchEffect

 `watch`对比`watchEffect`

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

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

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

<template>
    <div class="person">
      <h1>需求:水温达到50℃,或水位达到20cm,则联系服务器</h1>
      <h2 id="demo">水温:{
   {temp}}</h2>
      <h2>水位:{
   {height}}</h2>
      <button @click="changePrice">水温+1</button>
      <button @click="changeSum">水位+10</button>
    </div>
  </template>
  
  <script lang="ts" setup name="Person">
    import {ref,watch,watchEffect} from 'vue'
    // 数据
    let temp = ref(0)
    let height = ref(0)
  
    // 方法
    function changePrice(){
      temp.value += 10
    }
    function changeSum(){
      height.value += 1
    }
  
    // 用watch实现,需要明确的指出要监视:temp、height
    watch([temp,height],(value)=>{
      // 从value中获取最新的temp值、height值
      const [newTemp,newHeight] = value
      // 室温达到50℃,或水位达到20cm,立刻联系服务器
      if(newTemp >= 50 || newHeight >= 20){
        console.log('联系服务器')
      }
    })
  
    // 用watchEffect实现,不用
    const stopWtach = watchEffect(()=>{
      // 室温达到50℃,或水位达到20cm,立刻联系服务器
      if(temp.value >= 50 || height.value >= 20){
        console.log(document.getElementById('demo')?.innerText)
        console.log('联系服务器')
      }
      // 水温达到100,或水位达到50,取消监视
      if(temp.value === 100 || height.value === 50){
        console.log('清理了')
        stopWtach()
      }
    })
  </script>

 

2.3标签的 ref 属性 

作用:用于注册模板引用。

> * 用在普通`DOM`标签上,获取的是`DOM`节点。

> * 用在组件标签上,获取的是组件实例对象。

用在普通`DOM`标签上:

<template>
  <div class="person">
    <!-- 给三个标题元素绑定ref属性 -->
    <h1 ref="title1">尚硅谷</h1>
    <h2 ref="title2">前端</h2>
    <h3 ref="title3">Vue</h3>

    <!-- 给输入框元素绑定ref属性 -->
    <input type="text" ref="inpt"> <br><br>

    <!-- 创建一个按钮,点击时触发showLog方法 -->
    <button @click="showLog">点我打印内容</button>
  </div>
</template>

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

  // 定义并初始化三个refs引用,分别对应页面上的三个标题元素
  let title1 = ref<HTMLElement | undefined>()
  let title2 = ref<HTMLElement | undefined>()
  let title3 = ref<HTMLElement | undefined>()

  // 定义showLog函数,用于打印标题和输入框的内容
  function showLog() {
    // 使用原生DOM API获取id为'title1'的元素(此处代码未使用实际id,故无法通过id获取)
    const t1 = document.getElementById('title1')

    // 如果t1存在,则打印其innerText(文本内容)
    if (t1) {
      console.log((t1 as HTMLElement).innerText)
      console.log((<HTMLElement>t1).innerText)
      console.log(t1?.innerText) // 使用可选链操作符避免可能的null或undefined错误
    }

    /* ************************************/

    // 通过Vue的ref属性获取对应的DOM元素
    // 注意:这里不需要ID,而是直接通过之前定义的ref变量获取
    console.log(title1.value?.innerText) // 打印第一个标题的文本内容
    console.log(title2.value?.innerText) // 打印第二个标题的文本内容
    console.log(title3.value?.innerText) // 打印第三个标题的文本内容
  }
</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>

2.4props

TypeScript

TypeScript数据类型

TypeScript Interface

 TypeScript 暴露接口

TypeScript Operato
  • non-null assertion operator,非空断言操作符`!`
  • optional chaining operator,可选链运算符`?`
  • null coalescing operator,空值合并运算符`??`

TypeScript 整合

接口

// 定义一个接口 PersonInter,描述人的属性结构
export interface PersonInter {
  id: string
  name: string
  age: number
}

// 定义一个自定义类型 Persons,它是一个 PersonInter 接口对象的数组
export type Persons = Array<PersonInter>

App.vue

<template>
  <Person :list="persons"/>
</template>

<script lang="ts" setup name="App">
  import Person from './components/Person.vue'
  import {reactive} from 'vue'
  import {type Persons} from '@/types'
  
  let persons = Array<Persons>([
    {id:'e98219e12',name:'张三',age:18},
    {id:'e98219e13',name:'李四',age:19},
    {id:'e98219e14',name:'王五',age:20}
  ])
  // 使用reactive函数将原始数据转换为响应式数据
  let persons = reactive<Persons>([
    {id:'e98219e12',name:'张三',age:18},
    {id:'e98219e13',name:'李四',age:19},
    {id:'e98219e14',name:'王五',age:20}
  ])
</script>

`Person.vue`

<template>
  <div class="person">
    <!-- 渲染一个列表,循环遍历props中的list属性 -->
    <ul>
      <li v-for="item in list" :key="item.id">
        <!-- 显示列表中每一项的name和age属性 -->
        {
   {item.name}}--{
   {item.age}}
      </li>
    </ul>
  </div>
</template>

<script lang="ts" setup name="Person">
  // 导入Vue的defineProps函数
  import { defineProps } from 'vue'
  // 导入自定义类型PersonInter
  import { type PersonInter } from '@/types'

  // 注释掉了第一种仅接收props的写法
  // const props = defineProps(['list'])

  // 注释掉了第二种限制props类型的写法
  // defineProps<{list: Persons}>()

  // 第三种写法:同时限制props类型、指定默认值,并指定了list属性不是必须的
  let props = withDefaults(defineProps<{ list?: Persons }>(), {
    // 如果父组件没有传递list属性,则使用以下默认值
    list: () => [{ id: 'asdasg01', name: '小猪佩奇', age: 18 }]
  })

  // 打印当前组件接收到的所有props
  console.log(props)
</script>

defineProps()函数

defineEmits()函数

defineExpose()函数

总结‘

组件闭包下,当父子组件间需要相互调用执行操作时 需子组件执行父组件函数

  • defineProps(),声明组件接收的参数属性。可将需子组件渲染/执行的数据/函数传入。即,声明在父组件,执行在子组件
  • defineEmits(),声明组件对外暴露的事件。子组件中声明的事件被激活,通知父组件执行操作。即,声明在子组件,执行在子组件,同时执行父组件

需父组件执行子组件函数

  • defineExpose(),声明组件对外暴露的属性。即,声明在子组件,由父组件执行操作子组件
  • 基于全局数据状态

2.5生命周期

概念

概念:`Vue`组件实例在创建时要经历一系列的初始化步骤,在此过程中`Vue`会在合适的时机,调用特定的函数,从而让开发者有机会在特定阶段运行自己的代码,这些特定的函数统称为:生命周期钩子

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

`Vue3`的生命周期 

`Vue3`的生命周期

  > 创建阶段:`setup`

  > 挂载阶段:`onBeforeMount`、`onMounted`

  > 更新阶段:`onBeforeUpdate`、`onUpdated`

  > 卸载阶段:`onBeforeUnmount`、`onUnmounted`

* 常用的钩子:`onMounted`(挂载完毕)、`onUpdated`(更新完毕)、`onBeforeUnmount`(卸载之前)

 <template>
    <div class="person">
      <h2>当前求和为:{
   { sum }}</h2>
      <button @click="changeSum">点我sum+1</button>
    </div>
  </template>
  
  <!-- vue3写法 -->
  <script lang="ts" setup name="Person">
    import { 
      ref, 
      onBeforeMount, 
      onMounted, 
      onBeforeUpdate, 
      onUpdated, 
      onBeforeUnmount, 
      onUnmounted 
    } from 'vue'
  
    // 数据
    let sum = ref(0)
    // 方法
    function changeSum() {
      sum.value += 1
    }
    console.log('setup')
    // 生命周期钩子
    onBeforeMount(()=>{
      console.log('挂载之前')
    })
    onMounted(()=>{
      console.log('挂载完毕')
    })
    onBeforeUpdate(()=>{
      console.log('更新之前')
    })
    onUpdated(()=>{
      console.log('更新完毕')
    })
    onBeforeUnmount(()=>{
      console.log('卸载之前')
    })
    onUnmounted(()=>{
      console.log('卸载完毕')
    })
  </script>

自定义构子

 什么是`hook`?—— 本质是一个函数,把`setup`函数中使用的`Composition API`进行了封装,类似于`vue2.x`中的`mixin`

2.6指令(Directives)

指令(Directives),是带有v-前缀的特殊特性

指令特性值,预期是单个 JavaScript 表达式

指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM

  • v-html html 模板,渲染 html
  • v-model 绑定值(双向绑定)
  • v-if v-else-if v-else 判断
  • v-bind 简写 : 绑定属性
  • v-on 简写 @ 事件绑定
  • v-for 循环

v-if/v-else/v-else-if

根据表达式的值的真假条件渲染元素 

指令,必须声明在HTML标签或组件标签。如果需要包含多个元素,而又无法声明div等元素包裹,可以使用<template>元素作为不可见的包裹元素

v-show

v-if有更高的切换开销,而v-show有更高的初始渲染开销。因此,如果需要非常频繁地切换,使用v-show较好;如果在运行时条件很少改变,则使用v-if较好。

v-bind 

 

v-for

为了跟踪每个节点的标识,从而重用和重新排序现有元素,需要为每项提供一个唯一 key 属性。理想的 key 值是每项都有的且唯一值

 支持用 v-for 来遍历一个对象而非数组的 property 共支持3个参数。value,对象属性值;name,对象属性名称;index,索引

v-on 指令

v-model指令(重点)

在表单元素上创建双向数据绑定 它会根据控件类型自动选取正确的方法来更新元素 v-model本质上是语法糖,它负责监听用户的输入事件以更新数据

v-model会,忽略所有表单元素的 value/checked/selected 特性的初始值,而总是将 Vue 实例的数据作为数据来源

Components Dynamic

三、路由

基于Vue实现组合组件,组成应用

基于vue-router实现SPA应用 即,由vue-router在同一html页面切换显示不同的组件,从而实现单页面应用

 3.1基本切换效果

<template>
  <!-- 定义页面主容器 -->
  <div>
    <!-- 页面标题 -->
    <h2 class="title">Vue路由测试</h2>
    <!-- 导航区 -->
    <div class="navigate">
      <RouterLink to="/home" active-class="active">首页</RouterLink>
      <RouterLink to="/news" active-class="active">新闻</RouterLink>
      <RouterLink to="/about" active-class="active">关于</RouterLink>
    </div>
    <!-- 展示区 -->
    <div class="main-content">
      <RouterView></RouterView>
    </div>
  </div>
</template>

<!-- 使用<script setup>语法糖进行脚本编写 -->
<script setup lang="ts">
import { RouterLink, RouterView } from 'vue-router'
</script>

<!-- 针对当前组件的样式设置作用域 -->
<style scoped></style>

index.ts

import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/components/Home.vue'
import News from '@/components/News.vue'
import About from '@/components/About.vue'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/home',
      component: Home
    },
    {
      path: '/about',
      component: About
    }
  ]
})
export default router

 main.ts

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

const app = createApp(App)
app.use(router)

app.mount('#app')

1. 路由组件通常存放在`pages` 或 `views`文件夹,一般组件通常存放在`components`文件夹。

 2. 通过点击导航,视觉效果上“消失” 了的路由组件,默认是被**卸载**掉的,需要的时候再去**挂载**。

3.2路由器工作模式 

const router = createRouter({
  history: createWebHashHistory(), // hash模式
  // 路由配置...
});

> 优点:`URL`更加美观,不带有`#`,更接近传统的网站`URL`。

> 缺点:后期项目上线,需要服务端配合处理路径问题,否则刷新会有`404`错误。

const router = createRouter({
  history: createWebHistory(), // history模式
  // 路由配置
});

 `hash`模式

 > 优点:兼容性更好,因为不需要服务器端处理路径。

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

 3.3to的两种写法

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

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

 3.4命名路由

作用:可以简化路由跳转及传参(后面就讲)。

给路由规则命名:

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>

3.5嵌套路由

1. 编写`News`的子路由:`Detail.vue`

2. 配置路由规则,使用`children`配置项:

const router = createRouter({
     history:createWebHistory(),
   	routes:[
   		{
   			name:'zhuye',
   			path:'/home',
   			component:Home
   		},
   		{
   			name:'xinwen',
   			path:'/news',
   			component:News,
   			children:[
   				{
   					name:'xiang',
   					path:'detail',
   					component:Detail
   				}
   			]
   		},
   		{
   			name:'guanyu',
   			path:'/about',
   			component:About
   		}
   	]
   })
   export default router
<router-link to="/news/detail">xxxx</router-link>
   <!-- 或 -->
   <router-link :to="{path:'/news/detail'}">xxxx</router-link>

支持多个<router-view>。通过name属性区分,指定加载的组件到指定渲染标签

3.6Router Passing Parameter

query参数

 传递参数

new.vue

<!-- 跳转并携带query参数(to的字符串写法) -->
      <router-link to="/news/detail?a=1&b=2&content=欢迎你">
      	跳转
      </router-link>
      				
      <!-- 跳转并携带query参数(to的对象写法) -->
      <RouterLink 
        :to="{
          //name:'xiang', //用name也可以跳转
          path:'/news/detail',
          query:{
            id:news.id,
            title:news.title,
            content:news.content
          }
        }"
      >
        {
   {news.title}}
      </RouterLink>

接收参数

 import {useRoute} from 'vue-router'
      const route = useRoute()
      // 打印query参数
      console.log(route.query)

params参数

// 引入一个一个可能要呈现组件
import Home from '@/pages/Home.vue'
import News from '@/pages/News.vue'
import About from '@/pages/About.vue'
import Detail from '@/pages/Detail.vue'

// 第二步:创建路由器
const router = createRouter({
  history:createWebHistory(), //路由器的工作模式(稍后讲解)
  routes:[ //一个一个的路由规则
  
    {
      name:'xinwen',
      path:'/news',
      component:News,
      children:[
        {
          name:'xiang',
          path:'detail/:id/:title/:content?',
          component:Detail
        }
      ]
    }
  ]
})

// 暴露出去router
export default router

> 备注1:传递`params`参数时,若使用`to`的对象写法,必须使用`name`配置项,不能用`path`。

> 备注2:传递`params`参数时,需要提前在规则中占位。

3.7 useRoute

是 Vue Router 提供的一个 Composition API 函数,它允许你在 Vue 组件的 setup 函数或者其他支持 Composition API 的上下文中获取当前活跃的路由实例。这个路由实例包含了与当前页面相关的所有路由信息,例如路径(path)、查询参数(query)、动态参数(params)等。

// 从 vue-router 模块中导入 useRoute 函数
import { useRoute } from 'vue-router'

// 在 setup() 函数或任何支持Composition API的Vue组件内部使用
export default {
  setup() {
    // 使用 useRoute 函数来获取当前激活的路由信息
    const route = useRoute()

    // 现在你可以访问 route 对象,它包含了当前路由的所有信息,如 name、params、query 等
    // 示例:console.log(route.path) 输出当前路由的路径

    return {
      // 可以将 route 作为计算属性返回给模板,以便在模板中使用
      route,
    }
  },
}

3.8 路由的props配置

  • 组件内通过defineProps<T>()函数声明接收参数属性
  • 支持声明传入的参数类型。基于路由从地址栏传入的参数,必须是string类型(类似地址栏向服务器的请求
{
	name:'xiang',
	path:'detail/:id/:title/:content',
	component:Detail,

  // props的对象写法,作用:把对象中的每一组key-value作为props传给Detail组件
  // props:{a:1,b:2,c:3}, 

  // props的布尔值写法,作用:把收到了每一组params参数,作为props传给Detail组件
  // props:true
  
  // props的函数写法,作用:把返回的对象中每一组key-value作为props传给Detail组件
  props(route){
    return route.query
  }
}

 方法一:

方法二:

3.9 replace属性

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

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

  - ```push```是追加历史记录(默认值)。

 - `replace`是替换当前记录。

  3. 开启`replace`模式:

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

3.10 编程式导航

路由组件的两个重要的属性:`$route`和`$router`变成了两个`hooks`

js

import {useRoute,useRouter} from 'vue-router'

const route = useRoute()
const router = useRouter()

console.log(route.query)
console.log(route.parmas)
console.log(router.push)
console.log(router.replace)

3.11重定向

作用:将特定的路径,重新定向到已有路由。

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

3.12Navigation Guards

在router路由规则中,为权限组件声明meta附属数据,通过路由守卫判断当前路由组件所需角色与sessionstorage中role是否相符,从而决定渲染,或路由至指定路径,多权限可声明为数组。

 

四、数据存储

4.1概述

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

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

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

原生

<template>
  <div class="count">
    <h2>当前求和为:{
   { sum }}</h2>
    <select v-model.number="n">
      <option value="1">1</option>
      <option value="2">2</option>
      <option value="3">3</option>
    </select>
    <button @click="add">加</button>
    <button @click="minus">减</button>
  </div>
</template>

<script setup lang="ts" name="Count">
import { ref } from 'vue'
// 数据
let sum = ref(1) // 当前求和
let n = ref(1) // 用户选择的数字

// 方法
function add() {
  sum.value += n.value
}
function minus() {
  sum.value -= n.value
}
</script>

<style scoped>
.count {
  background-color: skyblue;
  padding: 10px;
  border-radius: 10px;
  box-shadow: 0 0 10px;
}
select,
button {
  margin: 0 5px;
  height: 25px;
}
</style>

在Vue.js中,v-model.number 是一个修饰符,用于确保绑定值被转换为数字类型。当把这个修饰符应用到表单元素上时,它会自动将用户输入的字符串转化为数字。

在Vue.js中,这段代码定义了一个下拉选择框(select元素),并且使用了v-model.number指令与组件的数据属性 n 进行双向绑定。 

4.2pinia

pinia环境搭建

import { createApp } from 'vue'

import App from './App.vue'

import router from './router/index'

// 导入Pinia库,这是一个基于Vue的状态管理库,用于在应用中集中管理和存储状态(store)
import { createPinia } from 'pinia'

const app = createApp(App)

// 使用Pinia创建一个状态存储实例
const pinia = createPinia()

app.use(router)

// 将Pinia状态存储实例注入到Vue应用实例中,使得在整个应用范围内都可以通过Pinia管理状态数据
app.use(pinia)

// 将Vue应用挂载到HTML文档中id为'app'的元素上
app.mount('#app')

 基于pinia的state

Store对象为基于Proxy的响应式对象

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

// 定义并暴露一个名为'count'的store
export const useCountStore = defineStore('count',{
  // 定义store中的动作(actions)
  actions:{
  },

  // 定义store中的状态(state)
  state(){
    // 返回包含sum属性的对象,初始值为6
    return {
      sum:6
    }
  },

  // 定义store中的计算属性(getters)
  getters:{
  }
})

defineStore 是 Pinia 库中用于创建 Vue.js 应用状态管理(store)的一个函数。在 Pinia 中,每个 store 都是一个独立的模块,用来集中管理和存储应用的状态数据、定义相应的操作(actions)以及计算属性(getters)。

reactive 定义的响应式对象  在访问其内部属性时,自动解包 

 修改数据(三种方式)

直接修改
countStore.sum = 666
批量修改
 countStore.$patch({
     sum:999,
     school:'atguigu'
   })
借助`action`修改(

`action`中可以编写一些业务逻辑)

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

// 定义并暴露一个store
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
      }
    }
  },
  // 状态
  state() {
    return {
      sum: 6
    }
  },
  // 计算
  getters: {}
})
<template>
  <div class="count">
    <h2>当前求和为:{
   { countStore.sum }}</h2>
    <select v-model.number="n">
      <option value="1">1</option>
      <option value="2">2</option>
      <option value="3">3</option>
    </select>
    <button @click="add">加</button>
    <button @click="minus">减</button>
  </div>
</template>

<script setup lang="ts" name="Count">
import { ref } from 'vue'
import { useCountStore } from '@/store/count'

const countStore = useCountStore()

// 以下两种方式都可以拿到state中的数据
// console.log('@@@',countStore.sum)
// console.log('@@@',countStore.$state.sum)
//o 数据

let n = ref(1) // 用户选择的数字

// 方法
function add() {
  // sum.value += n.value
  countStore.increment(n.value)
}
function minus() {
  // sum.value -= n.value
  countStore.decrement(n.value)
}
</script>

<style scoped>

</style>

storeToRefs

- 借助`storeToRefs`将`store`中的数据转为`ref`对象,方便在模板中使用。

- 注意:`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>

 不用toref原因

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()
         }
       }
     })

 $subscribe

通过 store 的 `$subscribe()` 方法侦听 `state` 及其变化

// 引入并使用了某个store(这里称为talkStore),可能是通过Pinia或其他状态管理库创建的

// 使用store的$subscribe方法订阅store状态的变化
talkStore.$subscribe(
  // subscribe方法接收一个回调函数,每当store的状态发生改变时,该函数会被调用
  (mutate, state) => {
    // mutate参数包含了有关状态变更的信息,例如变更的类型、字段名等(具体内容取决于使用的状态管理库)

    // state参数提供了当前store的最新状态
    console.log('LoveTalk', mutate, state);

    // 当store的状态发生变化时,将当前的talkList值序列化为JSON字符串
    // 并将其存储到浏览器的localStorage中,键名为'talk'
    localStorage.setItem('talk', JSON.stringify(talkList.value));
  }
);

 store组合式写法

import {defineStore} from 'pinia'
import axios from 'axios'
import {nanoid} from 'nanoid'
import {reactive} from 'vue'

export const useTalkStore = defineStore('talk',()=>{
  // talkList就是state
  const talkList = reactive(
    JSON.parse(localStorage.getItem('talkList') as string) || []
  )

  // getATalk函数相当于action
  async function getATalk(){
    // 发请求,下面这行的写法是:连续解构赋值+重命名
    let {data:{content:title}} = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json')
    // 把请求回来的字符串,包装成一个对象
    let obj = {id:nanoid(),title}
    // 放到数组中
    talkList.unshift(obj)
  }
  return {talkList,getATalk}
})

4.3VueUse

VueUse是一款基于组合式API,整合封装了包括,数据状态管理/网络请求/事件监听等一系列Vue常用功能的函数集合

在项目根目录运行命令安装依赖 npm i @vueuse/core

Defining a Store

 State

在组件内通过函数获取的store对象是普通对象(与pinia不同)

注意

Actions 

为便于追踪调试,对state数据的更新可通过store暴露函数实现。类似Java中建议使用getter/setter操作属性

 Getters

当多组件均需基于state数据计算并响应式更新时,在组件重复计算不利于维护

因此,可在Store基于state数据声明计算属性

 五、组件通信

5.1props

概述:`props`是使用频率最高的一种通信方式,常用与 :**父 子**。

- 若 **父传子**:属性值是**非函数**。

- 若 **子传父**:属性值是**函数**。

父组件:

<template>
  <div class="father">
    <h3>父组件,</h3>
		<h4>我的车:{
   { car }}</h4>
		<h4>儿子给的玩具:{
   { toy }}</h4>
		<Child :car="car" :getToy="getToy"/>
  </div>
</template>

<script setup lang="ts" name="Father">
	import Child from './Child.vue'
	import { ref } from "vue";
	// 数据
	const car = ref('奔驰')
	const toy = ref()
	// 方法
	function getToy(value:string){
		toy.value = value
	}
</script>

子组件

<template>
  <div class="child">
    <h3>子组件</h3>
		<h4>我的玩具:{
   { toy }}</h4>
		<h4>父给我的车:{
   { car }}</h4>
		<button @click="getToy(toy)">玩具给父亲</button>
  </div>
</template>

<script setup lang="ts" name="Child">
	import { ref } from "vue";
	const toy = ref('奥特曼')
	
	defineProps(['car','getToy'])
</script>

父与子通信,给值,子与父通信,父亲写方法去拿

5.2自定义事件defineEmits()函数

概述:自定义事件常用于:**子 => 父。**

概念

 原生事件:

  - 事件名是特定的(`click`、`mosueenter`等等)

  - 事件对象`$event`: 是包含事件相关信息的对象(`pageX`、`pageY`、`target`、`keyCode`)

自定义事件:

  - 事件名是任意名称

  - <strong style="color:red">事件对象`$event`: 是调用`emit`时所提供的数据,可以是任意类型!!!</strong >

5.3mitt

概述:与消息订阅与发布(`pubsub`)功能类似,可以实现任意组件间通信。

npm i mitt

1

接收数据的:提前绑定好事件(提前订阅消息)
提供数据的:在合适的时候触发事件(发布消息)

新建文件:`src\utils\emitter.ts`

// 引入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

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

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

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

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

【第三步】:提供数据的组件,在合适的时候触发事件

import emitter from "@/utils/emitter";

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

5.4v-model

实现 **父子** 之间相互通信。

  <AtguiguInput :modelValue="userName" @update:model-value="userName = $event"/> 
  • :modelValue="userName":这是Vue3中v-model指令的新写法,它将父组件的数据属性userName绑定到子组件的modelValue prop上。这里的冒号(:)表示这是一个动态属性绑定。

  • @update:model-value="userName = $event":当子组件内部触发update:modelValue事件时,父组件会执行这个事件处理器。在Vue3中,为了实现自定义组件与v-model的双向绑定,子组件需要手动触发update:modelValue事件,并传递新的值。这里的 $event 参数代表了子组件通过事件传递过来的新值。

5.5$attrs

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

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

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

父组件:

<template>
  <div class="father">
    <h3>父组件</h3>
		<Child :a="a" :b="b" :c="c" :d="d" v-bind="{x:100,y:200}" :updateA="updateA"/>
  </div>
</template>

<script setup lang="ts" name="Father">
	import Child from './Child.vue'
	import { ref } from "vue";
	let a = ref(1)
	let b = ref(2)
	let c = ref(3)
	let d = ref(4)

	function updateA(value){
		a.value = value
	}
</script>

子组件:

<template>
	<div class="child">
		<h3>子组件</h3>
		<GrandChild v-bind="$attrs"/>
	</div>
</template>

<script setup lang="ts" name="Child">
	import GrandChild from './GrandChild.vue'
</script>

孙组件:

<template>
	<div class="grand-child">
		<h3>孙组件</h3>
		<h4>a:{
   { a }}</h4>
		<h4>b:{
   { b }}</h4>
		<h4>c:{
   { c }}</h4>
		<h4>d:{
   { d }}</h4>
		<h4>x:{
   { x }}</h4>
		<h4>y:{
   { y }}</h4>
		<button @click="updateA(666)">点我更新A</button>
	</div>
</template>

<script setup lang="ts" name="GrandChild">
	defineProps(['a','b','c','d','x','y','updateA'])
</script>

5.6$refs、$parent

* `$refs`用于 :**父→子。**

* `$parent`用于:**子→父。**

父组件

<template>
	<div class="father">
		<h3>父组件</h3>
		<h4>房产:{
   { house }}</h4>
		<button @click="changeToy">修改Child1的玩具</button>
		<button @click="changeComputer">修改Child2的电脑</button>
		<button @click="getAllChild($refs)">让所有孩子的书变多</button>
		<Child1 ref="c1"/>
		<Child2 ref="c2"/>
	</div>
</template>

<script setup lang="ts" name="Father">
	import Child1 from './Child1.vue'
	import Child2 from './Child2.vue'
	import { ref,reactive } from "vue";
	let c1 = ref()
	let c2 = ref()

	// 注意点:当访问obj.c的时候,底层会自动读取value属性,因为c是

     在obj这个响应式对象中的
	/* let obj = reactive({
		a:1,
		b:2,
		c:ref(3)
	})
	let x = ref(4)

	console.log(obj.a)
	console.log(obj.b)
	console.log(obj.c)
	console.log(x) */
	

	// 数据
	let house = ref(4)
	// 方法
	function changeToy(){
		c1.value.toy = '小猪佩奇'
	}
	function changeComputer(){
		c2.value.computer = '华为'
	}
	function getAllChild(refs:{[key:string]:any}){
		console.log(refs)
		for (let key in refs){
			refs[key].book += 3
		}
	}
	// 向外部提供数据
	defineExpose({house})
</script>

<style scoped>
	
</style>

子组件

<template>
  <div class="child1">
    <h3>子组件1</h3>
		<h4>玩具:{
   { toy }}</h4>
		<h4>书籍:{
   { book }} 本</h4>
		<button @click="minusHouse($parent)">干掉父亲的一套房产</button>
  </div>
</template>

<script setup lang="ts" name="Child1">
	import { ref } from "vue";
	// 数据
	let toy = ref('奥特曼')
	let book = ref(3)

	// 方法
	function minusHouse(parent:any){
		parent.house -= 1
	}

	// 把数据交给外部
	defineExpose({toy,book})

</script>

<style scoped>

</style>

5.7provide、inject

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

 具体使用:

   * 在祖先组件中通过`provide`配置向后代组件提供数据

   * 在后代组件中通过`inject`配置来声明接收数据

父组件

<template>
     <div class="father">
       <h3>父组件</h3>
       <h4>资产:{
   { money }}</h4>
       <h4>汽车:{
   { car }}</h4>
       <button @click="money += 1">资产+1</button>
       <button @click="car.price += 1">汽车价格+1</button>
       <Child/>
     </div>
   </template>
   
   <script setup lang="ts" name="Father">
     import Child from './Child.vue'
     import { ref,reactive,provide } from "vue";
     // 数据
     let money = ref(100)
     let car = reactive({
       brand:'奔驰',
       price:100
     })
     // 用于更新money的方法
     function updateMoney(value:number){
       money.value += value
     }
     // 提供数据
     provide('moneyContext',{money,updateMoney})
     provide('car',car)
   </script>

 孙组件

  <template>
     <div class="grand-child">
       <h3>我是孙组件</h3>
       <h4>资产:{
   { money }}</h4>
       <h4>汽车:{
   { car }}</h4>
       <button @click="updateMoney(6)">点我</button>
     </div>
   </template>
   
   <script setup lang="ts" name="GrandChild">
     import { inject } from 'vue';
     // 注入数据
    let {money,updateMoney} = inject('moneyContext',{money:0,updateMoney:(x:number)=>{}})
     let car = inject('car')
</script>

5.8pinia

pinia也是集中式管理状态容器,类似于vuex。但是核心概念没有mutation、modules

选择式API

组合式API

5.9Slots插槽

组件能够接收任意类型的 JavaScript 值作为 props,但在某些场景中,需要为子组件传递一些模板片段,让子组件在它们的组件中渲染这些片段。

即,功能/样式相似,又有独立功能的组件。将通用部分抽象为带插槽的组件,使用时,将独立部分填充到组件的插槽(Slot)

默认插槽 

<template>
  <div class="father">
    <h3>父组件</h3>
    <div class="content">
      <Game>
        <template v-slot="params">
          <ul>
            <li v-for="y in params.youxi" :key="y.id">
              {
   { y.name }}
            </li>
          </ul>
        </template>
      </Game>

      <Game>
        <template v-slot="params">
          <ol>
            <li v-for="item in params.youxi" :key="item.id">
              {
   { item.name }}
            </li>
          </ol>
        </template>
      </Game>

      <Game>
        <template #default="{youxi}">
          <h3 v-for="g in youxi" :key="g.id">{
   { g.name }}</h3>
        </template>
      </Game>

    </div>
  </div>
</template>

<script setup lang="ts" name="Father">
  import Game from './Game.vue'
</script>

<style scoped>
 
</style>

Named Slots

父组件中:

 <Category title="今日热门游戏">
          <template v-slot:s1>
            <ul>
              <li v-for="g in games" :key="g.id">{
   { g.name }}</li>
            </ul>
          </template>
          <template #s2>
            <a href="">更多</a>
          </template>
        </Category>

子组件中:

 <template>
          <div class="item">
            <h3>{
   { title }}</h3>
            <slot name="s1"></slot>
            <slot name="s2"></slot>
          </div>
        </template>

5.10defineModel()​​​​​​​​​​​​​​

详情请见vue3.4新出了defineModel() 宏,让你轻松实现双向数据绑定​​​​​​​

Scoped Slots

理解:<span style="color:red">数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。</span>(新闻数据在`News`组件中,但使用数据所遍历出来的结构由`App`组件决定)

 父组件中:
         <Game v-slot="params">
         <!-- <Game v-slot:default="params"> -->
         <!-- <Game #default="params"> -->
           <ul>
             <li v-for="g in params.games" :key="g.id">{
   { g.name }}</li>
           </ul>
         </Game>
   
   子组件中:
         <template>
           <div class="category">
             <h2>今日游戏榜单</h2>
             <slot :games="games" a="哈哈"></slot>
           </div>
         </template>
   
         <script setup lang="ts" name="Category">
           import {reactive} from 'vue'
           let games = reactive([
             {id:'asgdytsa01',name:'英雄联盟'},
             {id:'asgdytsa02',name:'王者荣耀'},
             {id:'asgdytsa03',name:'红色警戒'},
             {id:'asgdytsa04',name:'斗罗大陆'}
           ])
         </script>

六、其他API

6.1shallowRef 与 shallowReactive

shallowRef

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

 特点:只跟踪引用值的变化,不关心值内部的属性变化。(不能细化,用法同只能整体修改)

shallowReactive

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

特点:对象的顶层属性是响应式的,但嵌套对象的属性不是。

6.2readonly 与 shallowReadonly

readonly

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

 特点:

   * 对象的所有嵌套属性都将变为只读。

   * 任何尝试修改这个对象的操作都会被阻止(在开发模式下,还会在控制台中发出警告)。

 应用场景:

   * 创建不可变的状态快照。

   * 保护全局状态或配置不被修改。

shallowReadonly

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

特点:

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

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

<template>
	<div class="app">
		<h2>当前sum1为:{
   { sum1 }}</h2>
		<h2>当前sum2为:{
   { sum2 }}</h2>
		<h2>当前car1为:{
   { car1 }}</h2>
		<h2>当前car2为:{
   { car2 }}</h2>
		<button @click="changeSum1">点我sum1+1</button>
		<button @click="changeSum2">点我sum2+1</button>
		<button @click="changeBrand2">修改品牌(car2)</button>
		<button @click="changeColor2">修改颜色(car2)</button>
		<button @click="changePrice2">修改价格(car2)</button>
	</div>
</template>

<script setup lang="ts" name="App">
	import { ref,reactive,readonly,shallowReadonly } from "vue";

	let sum1 = ref(0)
	let sum2 = readonly(sum1)
	let car1 = reactive({
		brand:'奔驰',
		options:{
			color:'红色',
			price:100
		}
	})
	let car2 = shallowReadonly(car1)

	function changeSum1(){
		sum1.value += 1
	}
	function changeSum2(){
		sum2.value += 1 //sum2是不能修改的
	}

	function changeBrand2(){
		car2.brand = '宝马'
	}
	function changeColor2(){
		car2.options.color = '绿色'
	}
	function changePrice2(){
		car2.options.price += 10
	}
</script>

<style scoped>
	
</style>

6.3toRaw 与 markRaw

toRaw

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

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

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

markRaw

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

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

<template>
	<div class="app">
		<h2>姓名:{
   { person.name }}</h2>
		<h2>年龄:{
   { person.age }}</h2>
		<button @click="person.age += 1">修改年龄</button>
		<hr>
		<h2>{
   { car2 }}</h2>
		<button @click="car2.price += 10">点我价格+10</button>
	</div>
</template>

<script setup lang="ts" name="App">
	import { reactive,toRaw,markRaw } from "vue";
	import mockjs from 'mockjs'

	/* toRaw */
	let person = reactive({
		name:'tony',
		age:18
	})
	// 用于获取一个响应式对象的原始对象
	let rawPerson = toRaw(person)
	// console.log('响应式对象',person)
	// console.log('原始对象',rawPerson)


	/* markRaw */
	let car = markRaw({brand:'奔驰',price:100})
	let car2 = reactive(car)

	console.log(car)
	console.log(car2)

	let mockJs = markRaw(mockjs)


</script>

<style scoped>
	.app {
		background-color: #ddd;
		border-radius: 10px;
		box-shadow: 0 0 10px;
		padding: 10px;
	}
	button {
		margin:0 5px;
	}
</style>

6.4customRef

作用:创建一个自定义的`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新组件

7.1Teleport

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

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

7.2Suspense异步

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

-  使用步骤:

   -  异步引入组件

   -  使用`Suspense`包裹组件,并配置好`default` 与 `fallback`

Loading

Suspense

实验性功能。Suspense是一个内置组件,用来在组件树中编排异步依赖。它可以在等待组件树下的多个嵌套异步依赖项解析完成时,呈现加载状态。

 当#default插槽中的异步组件没有加载时,自动加载#fallback插槽中组件。异步组件达到可用状态后自动切换。

component标签

  • component标签在Vue中是一个特殊的标签,用于动态渲染组件。
  • 它可以使用is属性来指定要渲染的组件,值可以是一个字符串,代表组件的名称,也可以是一个对象或一个返回组件名称的函数。
  • 通过动态改变is属性的值,可以实现同一个位置上动态渲染不同的组件。

v-if标签

v-if。异步子组件挂载时机由父组件决定。例如,需要父组件发出网络请求,数据返回后挂载子组件,并将返回数据置于store或传入子组件渲染,子组件自己无法请求数据。

7.3全局API转移到应用对象

- `app.component`

- `app.config`

- `app.directive`

- `app.mount`

- `app.unmount`

- `app.use`

`app.component`

app.component() 方法用于全局注册一个组件

// 首先导入Vue库以及创建应用的函数
import { createApp } from 'vue';

// 定义一个单文件组件(SFC),这里以假设它已经被导出为MyComponent.vue为例
import MyComponent from './MyComponent.vue';

// 创建Vue应用实例
const app = createApp({}); // Vue 3根组件通常是空对象{}

// 使用app.component全局注册组件
app.component('MyComponent', MyComponent);

// 现在可以在任何子组件或者应用模板中使用这个全局注册的组件
app.mount('#app');

app.config`

在所有组件中注入一个全局方法或属性

import { createApp } from 'vue';

// 创建应用实例
const app = createApp(App);

// 在所有组件的原型上添加全局方法
app.config.globalProperties.$myGlobalMethod = function() {
  console.log('This is a global method');
}

// 或者提供全局状态
app.provide('globalState', { message: 'Hello from global state' });

// 然后在任何子组件中通过inject来使用这些全局属性或方法
app.mount('#app');

// 子组件中使用示例:
export default {
  inject: ['globalState'],
  methods: {
    useGlobalMethod() {
      this.$myGlobalMethod();
    }
  },
  created() {
    console.log(this.globalState.message);
  }
}

`app.directive`

 注册或定义全局指令。指令是Vue中自定义行为的一种方式,它们可以被附加到DOM元素上,并在特定的生命周期钩子点上执行相应的操作。

7.4Vue3的非兼容性改变

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

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

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

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

- 移除了`$on`、`$off` 和 `$once` 实例方法。

- 移除了过滤器 `filter`。

- 移除了`$children` 实例 `propert`。

7.5promise

setTimeout函数

setTimeout函数是JavaScript中的一个内置函数,用于在指定的时间(以毫秒为单位)后执行一个回调函数。它通常用于在代码中创建延迟或定时执行的功能。

setTimeout函数的语法如下:

setTimeout(callback, delay, [param1, param2, ...]);

参数说明:

  • callback:必需,指定要执行的回调函数。
  • delay:必需,指定延迟的时间,以毫秒为单位。
  • [param1, param2, ...]:可选,传递给回调函数的参数。
<script setup>
import { ref, onUnmounted } from 'vue';

let timerId;

const message = ref('初始消息');

onMounted(() => {
  timerId = setTimeout(() => {
    message.value = '延迟2秒后的消息';
  }, 2000);
});

// 组件卸载时清除定时器
onUnmounted(() => {
  clearTimeout(timerId);
});
</script>

promise函数

Promise 是 JavaScript 中用于处理异步操作的一个内置对象,它提供了一种在异步操作成功或失败时执行回调函数的方法,并且可以链式调用。Promise 对象代表一个现在、将来或永远可能可用的值(通常是一个异步操作的结果)。

Promise 有三种状态:

  1. pending:初始状态,既不是 fulfilled 也不是 rejected。
  2. fulfilled:已成功完成的操作,也称为 resolved。
  3. rejected:操作已失败。

Promise 构造函数接受一个executor函数作为参数,这个函数接收两个回调函数作为参数:resolve 和 reject。当异步操作成功时,调用 resolve 函数并将结果传递给它;如果失败,则调用 reject 函数并传递错误信息。

// 创建一个Promise实例
const myPromise = new Promise((resolve, reject) => {
  // 异步操作模拟
  setTimeout(() => {
    if (/* 操作成功 */) {
      resolve('成功的数据');
    } else {
      reject(new Error('操作失败'));
    }
  }, 2000);
});

// 处理Promise的结果
myPromise.then(result => {
  console.log('成功:', result);
}).catch(error => {
  console.error('失败:', error.message);
});

Promise 还支持链式调用 .then() 方法来处理多个异步任务,每个 .then() 方法都可以返回一个新的 Promise,从而实现更复杂的异步流程控制。

此外,还有 .finally() 用于无论 Promise 状态如何都会执行的清理罗辑,.all() 和 .race() 用于处理多个 Promise 的集合,以及 .async/await 语法糖,它们基于 Promise 实现,使得异步代码更加简洁和易于理解。

 async/await

async/await 是 JavaScript 中基于 Promise 的一种异步编程解决方案,它提供了一种更接近同步代码的写法来处理异步操作。async 关键字用于定义一个异步函数,而 await 关键字则用于在异步函数内部等待 Promise 对象的结果。

async 函数

 当你在函数前加上 async 关键字时,该函数将返回一个 Promise 对象,即使没有明确地使用 return 语句。如果函数体内的代码执行完毕且没有返回值,则返回 Promise.resolve();如果函数体中包含一个 return 值(非 Promise),那么该值会被包装成一个 resolved 状态的 Promise 返回。

// 定义一个异步函数 `getData`,它会发送网络请求获取数据并将其解析为 JSON 格式
async function getData() {
  // 使用 `await` 关键字等待 fetch API 发送 GET 请求到指定 URL 并返回一个 Promise 对象
  const response = await fetch('https://api.example.com/data');

  // 等待 `response.json()` 方法将响应体解析为 JSON 数据,该方法也返回一个 Promise 对象
  const data = await response.json();

  // 将解析后的 JSON 数据作为结果返回
  return data;
}

// 调用 `getData` 函数,由于它返回的是一个 Promise,所以可以使用 `.then` 方法处理成功时的回调
// 当 `getData` 的 Promise 成功解决(resolve)时,将解析得到的数据传递给回调函数,并在控制台打印出来
getData().then(data => console.log(data));
await 关键字

在 async 函数内部,你可以使用 await 关键字后面跟一个 Promise,这会使得 JavaScript 引擎暂停当前 async 函数的执行,直到 Promise 解决(resolve)或拒绝(reject)。如果 Promise 被解决,那么 await 表达式的值将是 Promise 的结果;如果 Promise 被拒绝,那么将会抛出异常,可以在 try...catch 结构中捕获。

async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    if (!response.ok) {
      throw new Error('请求失败');
    }
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('获取数据时发生错误:', error);
  }
}

fetchData();

八、项目工具篇

8.1pnpm

*pnpm:performant npm ,意味“高性能的 npm”。[pnpm](https://so.csdn.net/so/search?q=pnpm&spm=1001.2101.3001.7020)由npm/yarn衍生而来,解决了npm/yarn内部潜在的bug,极大的优化了性能,扩展了使用场景。被誉为“最先进的包管理工具”**

pnpm安装指令

npm i -g pnpm

进入到项目根目录pnpm install安装全部依赖.安装完依赖运行程序:pnpm run dev

运行完毕项目跑在http://127.0.0.1:5173/,可以访问你得项目啦

详情请看使用pnpm+vite创建vue

小技巧

8.2eslint配置

ESLint最初是由[Nicholas C. Zakas](http://nczonline.net/) 于2013年6月创建的开源项目。它的目标是提供一个插件化的**javascript代码检测工具**

安装eslint

pnpm i eslint -D

生成配置文件:.eslint.cjs

npx eslint --init

.eslint.cjs配置文件

module.exports = {
   //运行环境
    "env": { 
        "browser": true,//浏览器端
        "es2021": true,//es2021
    },
    //规则继承
    "extends": [ 
       //全部规则默认是关闭的,这个配置项开启推荐规则,推荐规则参照文档
       //比如:函数不能重名、对象不能出现重复key
        "eslint:recommended",
        //vue3语法规则
        "plugin:vue/vue3-essential",
        //ts语法规则
        "plugin:@typescript-eslint/recommended"
    ],
    //要为特定类型的文件指定处理器
    "overrides": [
    ],
    //指定解析器:解析器
    //Esprima 默认解析器
    //Babel-ESLint babel解析器
    //@typescript-eslint/parser ts解析器
    "parser": "@typescript-eslint/parser",
    //指定解析器选项
    "parserOptions": {
        "ecmaVersion": "latest",//校验ECMA最新版本
        "sourceType": "module"//设置为"script"(默认),或者"module"代码在ECMAScript模块中
    },
    //ESLint支持使用第三方插件。在使用插件之前,您必须使用npm安装它
    //该eslint-plugin-前缀可以从插件名称被省略
    "plugins": [
        "vue",
        "@typescript-eslint"
    ],
    //eslint规则
    "rules": {
    }
}

.eslintignore忽略文件

dist
node_modules

package.json新增两个运行脚本

"scripts": {

    "lint": "eslint src",

    "fix": "eslint src --fix",

}

8.3配置**prettier**

*eslint和prettier这俩兄弟一个保证js代码质量,一个保证代码美观

所以它就把eslint没干好的事接着干,

安装依赖包

pnpm install -D eslint-plugin-prettier prettier eslint-config-prettier

prettierrc.json添加规则

{
  // 设置使用单引号包裹字符串而非双引号
  "singleQuote": true,

  // 是否在语句末尾添加分号,默认情况下禁用分号
  "semi": false,

  // 控制对象大括号内的间距,默认开启,即在对象的大括号内换行时保留空格
  "bracketSpacing": true,

  // 设置 HTML 白空格敏感度,"ignore" 表示忽略 HTML 文档中的空白,不会因为空白而影响格式化结果
  "htmlWhitespaceSensitivity": "ignore",

  // 设置文件的结束符风格,"auto" 表示根据项目其他文件的实际情况决定换行符类型(LF 或 CRLF)
  "endOfLine": "auto",

  // 控制数组和对象最后一个元素之后是否添加逗号,默认为 "none",这里设置为 "all" 表示总是添加尾随逗号
  "trailingComma": "all",

  // 设置制表符宽度,这里设置为 2 个空格
  "tabWidth": 2
}

.prettierignore忽略文件

/dist/*
/html/*
.local
/node_modules/**
**/*.svg
**/*.sh
/public/*

8.4配置stylelint

[stylelint](https://stylelint.io/)为css的lint工具。可格式化css代码,检查css语法错误与不合理的写法,指定css书写顺序等。官网:https://stylelint.bootcss.com/

.stylelintrc.cjs`**配置文件**

module.exports = {
  extends: [
    'stylelint-config-standard', // 配置stylelint拓展插件
    'stylelint-config-html/vue', // 配置 vue 中 template 样式格式化
    'stylelint-config-standard-scss', // 配置stylelint scss插件
    'stylelint-config-recommended-vue/scss', // 配置 vue 中 scss 样式格式化
    'stylelint-config-recess-order', // 配置stylelint css属性书写顺序插件,
    'stylelint-config-prettier', // 配置stylelint和prettier兼容
  ],
  overrides: [
    {
      files: ['**/*.(scss|css|vue|html)'],
      customSyntax: 'postcss-scss',
    },
    {
      files: ['**/*.(html|vue)'],
      customSyntax: 'postcss-html',
    },
  ],
  ignoreFiles: [
    '**/*.js',
    '**/*.jsx',
    '**/*.tsx',
    '**/*.ts',
    '**/*.json',
    '**/*.md',
    '**/*.yaml',
  ],
  /**
   * null  => 关闭该规则
   * always => 必须
   */
  rules: {
    'value-keyword-case': null, // 在 css 中使用 v-bind,不报错
    'no-descending-specificity': null, // 禁止在具有较高优先级的选择器后出现被其覆盖的较低优先级的选择器
    'function-url-quotes': 'always', // 要求或禁止 URL 的引号 "always(必须加上引号)"|"never(没有引号)"
    'no-empty-source': null, // 关闭禁止空源码
    'selector-class-pattern': null, // 关闭强制选择器类名的格式
    'property-no-unknown': null, // 禁止未知的属性(true 为不允许)
    'block-opening-brace-space-before': 'always', //大括号之前必须有一个空格或不能有空白符
    'value-no-vendor-prefix': null, // 关闭 属性值前缀 --webkit-box
    'property-no-vendor-prefix': null, // 关闭 属性前缀 -webkit-mask
    'selector-pseudo-class-no-unknown': [
      // 不允许未知的选择器
      true,
      {
        ignorePseudoClasses: ['global', 'v-deep', 'deep'], // 忽略属性,修改element默认样式的时候能使用到
      },
    ],
  },
}

.stylelintignore忽略文件

/node_modules/*
/dist/*
/html/*
/public/*

运行脚本

"scripts": {
    "dev": "vite --open",
    "build": "vue-tsc && vite build",
    "preview": "vite preview",
    "lint": "eslint src",
    "fix": "eslint src --fix",
    "format": "prettier --write \"./**/*.{html,vue,ts,js,json,md}\"",
    "lint:eslint": "eslint src/**/*.{ts,vue} --cache --fix",
    "lint:style": "stylelint src/**/*.{css,scss,vue} --cache --fix"
  },

当我们运行`pnpm run format`的时候,会把代码直接格式化

8.5src别名的配置

在开发项目的时候文件与文件关系可能很复杂,因此我们需要给src文件夹配置一个别名!!!

// 导入 Node.js 中的 fileURLToPath 和 URL 工具函数,用于处理文件路径和 URL 之间的转换。
import { fileURLToPath, URL } from 'node:url'

// 导入 Vite.js 的核心配置函数 defineConfig,用于创建和配置项目构建选项。
import { defineConfig } from 'vite'

// 导入 Vite.js 的 Vue 插件,该插件提供了对 .vue 文件的编译支持以及其它 Vue 相关功能增强。
import vue from '@vitejs/plugin-vue'

// Vite.js 配置文档参考链接
// https://vitejs.dev/config/

// 定义并导出默认的 Vite.js 配置对象
export default defineConfig({
  // 配置项目中的插件列表,这里包含了一个 Vue 插件实例。
  plugins: [
    vue(),
  ],

  // 配置模块解析规则
  resolve: {
    // 创建一个别名 '@',它指向项目源码目录(src)的实际路径。
    // 使用 import.meta.url 获取当前模块的 URL,并结合 new URL('./src', import.meta.url) 计算出 src 目录的完整 URL,
    // 然后通过 fileURLToPath 将 URL 转换为可被 Node.js 解析的本地路径。
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
})

8.6环境变量的配置

**项目开发过程中,至少会经历开发环境、测试环境和生产环境(即正式环境)三个阶段。不同阶段请求的状态(如接口地址等)不尽相同,若手动切换接口地址是相当繁琐且易出错的。于是环境变量配置的需求就应运而生,我们只需做简单的配置,把环境状态切换的工作交给代码。**

开发环境(development)

顾名思义,开发使用的环境,每位开发人员在自己的dev分支上干活,开发到一定程度,同事会合并代码,进行联调。

测试环境(testing)

测试同事干活的环境啦,一般会由测试同事自己来部署,然后在此环境进行测试

生产环境(production)

生产环境是指正式提供对外服务的,一般会关掉错误报告,打开错误日志。(正式提供给客户使用的环境。)

注意:一般情况下,一个环境对应一台服务器,也有的公司开发与测试环境是一台服务器!!!

项目根目录分别添加 开发、生产和测试环境的文件!

.env.development
.env.production
.env.test
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
NODE_ENV = 'development'
VITE_APP_TITLE = '硅谷甄选运营平台'
VITE_APP_BASE_API = '/dev-api'



NODE_ENV = 'production'
VITE_APP_TITLE = '硅谷甄选运营平台'
VITE_APP_BASE_API = '/prod-api'


# 变量必须以 VITE_ 为前缀才能暴露给外部读取
NODE_ENV = 'test'
VITE_APP_TITLE = '硅谷甄选运营平台'
VITE_APP_BASE_API = '/test-api'

配置运行命令:package.json

 "scripts": {
    "dev": "vite --open",
    "build:test": "vue-tsc && vite build --mode test",
    "build:pro": "vue-tsc && vite build --mode production",
    "preview": "vite preview"
  },

通过import.meta.env获取环境变量

最后

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_62377885/article/details/136769623

智能推荐

攻防世界_难度8_happy_puzzle_攻防世界困难模式攻略图文-程序员宅基地

文章浏览阅读645次。这个肯定是末尾的IDAT了,因为IDAT必须要满了才会开始一下个IDAT,这个明显就是末尾的IDAT了。,对应下面的create_head()代码。,对应下面的create_tail()代码。不要考虑爆破,我已经试了一下,太多情况了。题目来源:UNCTF。_攻防世界困难模式攻略图文

达梦数据库的导出(备份)、导入_达梦数据库导入导出-程序员宅基地

文章浏览阅读2.9k次,点赞3次,收藏10次。偶尔会用到,记录、分享。1. 数据库导出1.1 切换到dmdba用户su - dmdba1.2 进入达梦数据库安装路径的bin目录,执行导库操作  导出语句:./dexp cwy_init/[email protected]:5236 file=cwy_init.dmp log=cwy_init_exp.log 注释:   cwy_init/init_123..._达梦数据库导入导出

js引入kindeditor富文本编辑器的使用_kindeditor.js-程序员宅基地

文章浏览阅读1.9k次。1. 在官网上下载KindEditor文件,可以删掉不需要要到的jsp,asp,asp.net和php文件夹。接着把文件夹放到项目文件目录下。2. 修改html文件,在页面引入js文件:<script type="text/javascript" src="./kindeditor/kindeditor-all.js"></script><script type="text/javascript" src="./kindeditor/lang/zh-CN.js"_kindeditor.js

STM32学习过程记录11——基于STM32G431CBU6硬件SPI+DMA的高效WS2812B控制方法-程序员宅基地

文章浏览阅读2.3k次,点赞6次,收藏14次。SPI的详情简介不必赘述。假设我们通过SPI发送0xAA,我们的数据线就会变为10101010,通过修改不同的内容,即可修改SPI中0和1的持续时间。比如0xF0即为前半周期为高电平,后半周期为低电平的状态。在SPI的通信模式中,CPHA配置会影响该实验,下图展示了不同采样位置的SPI时序图[1]。CPOL = 0,CPHA = 1:CLK空闲状态 = 低电平,数据在下降沿采样,并在上升沿移出CPOL = 0,CPHA = 0:CLK空闲状态 = 低电平,数据在上升沿采样,并在下降沿移出。_stm32g431cbu6

计算机网络-数据链路层_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输-程序员宅基地

文章浏览阅读1.2k次,点赞2次,收藏8次。数据链路层习题自测问题1.数据链路(即逻辑链路)与链路(即物理链路)有何区别?“电路接通了”与”数据链路接通了”的区别何在?2.数据链路层中的链路控制包括哪些功能?试讨论数据链路层做成可靠的链路层有哪些优点和缺点。3.网络适配器的作用是什么?网络适配器工作在哪一层?4.数据链路层的三个基本问题(帧定界、透明传输和差错检测)为什么都必须加以解决?5.如果在数据链路层不进行帧定界,会发生什么问题?6.PPP协议的主要特点是什么?为什么PPP不使用帧的编号?PPP适用于什么情况?为什么PPP协议不_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输

软件测试工程师移民加拿大_无证移民,未受过软件工程师的教育(第1部分)-程序员宅基地

文章浏览阅读587次。软件测试工程师移民加拿大 无证移民,未受过软件工程师的教育(第1部分) (Undocumented Immigrant With No Education to Software Engineer(Part 1))Before I start, I want you to please bear with me on the way I write, I have very little gen...

随便推点

Thinkpad X250 secure boot failed 启动失败问题解决_安装完系统提示secureboot failure-程序员宅基地

文章浏览阅读304次。Thinkpad X250笔记本电脑,装的是FreeBSD,进入BIOS修改虚拟化配置(其后可能是误设置了安全开机),保存退出后系统无法启动,显示:secure boot failed ,把自己惊出一身冷汗,因为这台笔记本刚好还没开始做备份.....根据错误提示,到bios里面去找相关配置,在Security里面找到了Secure Boot选项,发现果然被设置为Enabled,将其修改为Disabled ,再开机,终于正常启动了。_安装完系统提示secureboot failure

C++如何做字符串分割(5种方法)_c++ 字符串分割-程序员宅基地

文章浏览阅读10w+次,点赞93次,收藏352次。1、用strtok函数进行字符串分割原型: char *strtok(char *str, const char *delim);功能:分解字符串为一组字符串。参数说明:str为要分解的字符串,delim为分隔符字符串。返回值:从str开头开始的一个个被分割的串。当没有被分割的串时则返回NULL。其它:strtok函数线程不安全,可以使用strtok_r替代。示例://借助strtok实现split#include <string.h>#include <stdio.h&_c++ 字符串分割

2013第四届蓝桥杯 C/C++本科A组 真题答案解析_2013年第四届c a组蓝桥杯省赛真题解答-程序员宅基地

文章浏览阅读2.3k次。1 .高斯日记 大数学家高斯有个好习惯:无论如何都要记日记。他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记_2013年第四届c a组蓝桥杯省赛真题解答

基于供需算法优化的核极限学习机(KELM)分类算法-程序员宅基地

文章浏览阅读851次,点赞17次,收藏22次。摘要:本文利用供需算法对核极限学习机(KELM)进行优化,并用于分类。

metasploitable2渗透测试_metasploitable2怎么进入-程序员宅基地

文章浏览阅读1.1k次。一、系统弱密码登录1、在kali上执行命令行telnet 192.168.26.1292、Login和password都输入msfadmin3、登录成功,进入系统4、测试如下:二、MySQL弱密码登录:1、在kali上执行mysql –h 192.168.26.129 –u root2、登录成功,进入MySQL系统3、测试效果:三、PostgreSQL弱密码登录1、在Kali上执行psql -h 192.168.26.129 –U post..._metasploitable2怎么进入

Python学习之路:从入门到精通的指南_python人工智能开发从入门到精通pdf-程序员宅基地

文章浏览阅读257次。本文将为初学者提供Python学习的详细指南,从Python的历史、基础语法和数据类型到面向对象编程、模块和库的使用。通过本文,您将能够掌握Python编程的核心概念,为今后的编程学习和实践打下坚实基础。_python人工智能开发从入门到精通pdf

推荐文章

热门文章

相关标签