Vue3

1 什么是 Vue

Vue (发音为 /vjuː/,类似 view) 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。无论是简单还是复杂的界面,Vue 都可以胜任

1.1 快速体验

示例展示了 Vue 的两个核心功能:

  • 声明式渲染:Vue 基于标准 HTML 拓展了一套模板语法,使得我们可以声明式地描述最终输出的 HTML 和 JavaScript 状态之间的关系。
  • 响应性:Vue 会自动跟踪 JavaScript 状态并在其发生变化时响应式地更新 DOM
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>


<script src="./js/vue.js"></script>
<body>

<div id="app">
    <button @click="handleAdd">
        Count is: {{ count }}
    </button>
</div>

</body>
<script>
    let app=Vue.createApp({
        setup() {
            let count=Vue.ref(0)
            let handleAdd=()=>{
                count.value++
            }
            return {
                count,
                handleAdd
            }
        }
    })
    app.mount("#app")
</script>
</html>

1.2 渐进式框架

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

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

1.3 单文件组件

在大多数启用了构建工具的 Vue 项目中,我们可以使用一种类似 HTML 格式的文件来书写 Vue 组件,它被称为单文件组件 (也被称为 *.vue 文件,英文 Single-File Components,缩写为 SFC)。顾名思义,Vue 的单文件组件会将一个组件的逻辑 (JavaScript),模板 (HTML) 和样式 (CSS) 封装在同一个文件里。下面我们将用单文件组件的格式重写上面的计数器示例:

<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>

<template>
  <button @click="count++">Count is: {{ count }}</button>
</template>

<style scoped>
button {
  font-weight: bold;
}
</style>

单文件组件是 Vue 的标志性功能。如果你的用例需要进行构建,我们推荐用它来编写 Vue 组件。你可以在后续相关章节里了解更多关于单文件组件的用法及用途。但你暂时只需要知道 Vue 会帮忙处理所有这些构建工具的配置就好

1.4 API 风格

Vue 的组件可以按两种不同的风格书写:选项式 API组合式 API

1.4.1 选项式 API (Options API)

使用选项式 API,我们可以用包含多个选项的对象来描述组件的逻辑,例如 datamethodsmounted。选项所定义的属性都会暴露在函数内部的 this 上,它会指向当前的组件实例。

<script>
export default {
  // data() 返回的属性将会成为响应式的状态
  // 并且暴露在 `this` 上
  data() {
    return {
      count: 0
    }
  },

  // methods 是一些用来更改状态与触发更新的函数
  // 它们可以在模板中作为事件处理器绑定
  methods: {
    increment() {
      this.count++
    }
  },

  // 生命周期钩子会在组件生命周期的各个不同阶段被调用
  // 例如这个函数就会在组件挂载完成后被调用
  mounted() {
    console.log(`The initial count is ${this.count}.`)
  }
}
</script>

<template>
  <button @click="increment">Count is: {{ count }}</button>
</template>

img

1.4.2 组合式 API (Composition API)

通过组合式 API,我们可以使用导入的 API 函数来描述组件逻辑。在单文件组件中,组合式 API 通常会与 setup 搭配使用。这个 setup attribute 是一个标识,告诉 Vue 需要在编译时进行一些处理,让我们可以更简洁地使用组合式 API。比如,<script setup> 中的导入和顶层变量/函数都能够在模板中直接使用。

下面是使用了组合式 API 与 <script setup> 改造后和上面的模板完全一样的组件:

vue

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

// 响应式状态
const count = ref(0)

// 用来修改状态、触发更新的函数
function increment() {
  count.value++
}

// 生命周期钩子
onMounted(() => {
  console.log(`The initial count is ${count.value}.`)
})
</script>

<template>
  <button @click="increment">Count is: {{ count }}</button>
</template>

img

2 vite工程化

2.1 创建vue3工程

2.1.1 vue-cli

# 1 官方地址(弃用了)
https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create
#2 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue --version
# 3 安装或者升级你的@vue/cli
npm install -g @vue/cli
#4 创建
vue create vue_test
#5 启动
cd vue_test
npm run serve

2.1.2 vite

2.1.2.1 介绍

什么是vite?—— 新一代前端构建工具。
优势如下:
开发环境中,无需打包操作,可快速的冷启动。
轻量快速的热重载(HMR)。
真正的按需编译,不再等待整个应用编译完成。
传统构建 与 vite构建对比图

image-20240505215258111

2.1.2.2 创建vue工程

# 1 兼容性注意
Vite 需要 Node.js 版本 18+,20+。然而,有些模板需要依赖更高的 Node 版本才能正常运行,当你的包管理器发出警告时,请注意升级你的 Node 版本
https://cn.vitejs.dev/

# 2 创建工程
# 不指定名字创建:npm create vite@latest
# 配置npm镜像站:npm config set registry https://registry.npmmirror.com
# 查看镜像站:npm config get registry
# 指定名字创建 
cnpm create vite@latest vue3_demo002

# 3 使用编辑器打开vue3项目
# 4 安装依赖
	npm install 
# 5 运行项目
	npm run dev

image-20240505220826036

3 常用API

3.1 setup

  1. setup为Vue3.0中一个新的配置项,值为一个函数

  2. setup是所有Composition API(组合API)编写的位置

  3. 组件中所用到的:数据、方法等等,均要配置在setup中

  4. setup函数的返回值:返回一个对象,对象中的属性、方法, 在模板中均可以直接使用

  5. 注意:

    尽量不要与Vue2.x配置混用

    • Vue2.x配置(data、methos、computed...)中可以访问到setup中的属性、方法。
    • 但在setup中不能访问到Vue2.x配置(data、methos、computed...)。
    • 如果有重名, setup优先。
<template>
  {{name}}--{{age}}--{{xx}}
</template>

<script>

export default {
  name: 'App',
  data(){
    return {
      xx:this.name
    }
  },
  setup(){
    let name='lqz'
    let age =19
    return {
      name,age
    }
  },
}
</script>

3.2 ref函数

  • 作用: 定义一个响应式的数据

  • 语法:

    const xxx = ref(initValue)
    
    • 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)。
    • JS中操作数据: xxx.value
    • 模板中读取数据: 不需要.value,直接:<div>{{xxx}}</div>
  • 备注:

    • 接收的数据可以是:基本类型、也可以是对象类型。
    • 基本类型的数据:响应式依然是靠Object.defineProperty()getset完成的
    • 对象类型的数据:内部 求助 了Vue3.0中的一个新函数—— reactive函数

3.3 reactive函数

  • 作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数)
  • 语法:const 代理对象= reactive(源对象)接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)
  • reactive定义的响应式数据是“深层次的”
  • 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作
<template>
  {{ name }}--{{ age }}--{{ xx }}
  <br>
  {{person}}
  <button @click="handleClick">点我</button>
  <br>

</template>

<script>

import {ref, reactive} from 'vue'

export default {
  name: 'App',
  data() {
    return {
      xx: this.name
    }
  },
  setup() {
    let name = 'lqz'
    let age = ref(19)
    // const person = ref({    // 内部包装成reactive
    const person = reactive({
      name: '彭于晏',
      age: 88
    })

    function handleClick() {
      // console.log(age)
      // age.value++


      console.log(person)
      person.age++

    }
    return {
      name, age, handleClick,person
    }
  },
}
</script>

3.4 reactive对比ref

  • 从定义数据角度对比:
    • ref用来定义:基本类型数据
    • reactive用来定义:对象(或数组)类型数据
    • 备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过reactive转为代理对象
  • 从原理角度对比:
    • ref通过Object.defineProperty()getset来实现响应式(数据劫持)。
    • reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。
  • 从使用角度对比:
    • ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value
    • reactive定义的数据:操作数据与读取数据:均不需要.value

3.5 setup的两个注意点

  • setup执行的时机
    • 在beforeCreate之前执行一次,this是undefined。
  • setup的参数
    • props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
    • context:上下文对象
      • attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于 this.$attrs
      • slots: 收到的插槽内容, 相当于 this.$slots
      • emit: 分发自定义事件的函数, 相当于 this.$emit

3.6 计算属性与监视

1.computed函数

  • 与Vue2.x中computed配置功能一致

  • 写法

    <template>
      <p>姓:<input type="text" v-model="person.firstName"></p>
      <p>名:<input type="text" v-model="person.lastName"></p>
      <p>全名:{{ person.fullName }}</p>
      <p>全名修改:<input type="text" v-model="person.fullName"></p>
    
    </template>
    
    <script>
    
    import {ref, reactive} from 'vue'
    import {computed} from 'vue'
    
    export default {
      name: 'App',
      setup() {
        const person = reactive({
          firstName: '刘',
          lastName: '清政'
        })
    
        // let fullName = computed(() => {
        //   return person.firstName + '-' + person.lastName
        // })
        // 或者,传入箭头函数
        // person.fullName=computed(() => {
        //   return person.firstName + '-' + person.lastName
        // })
        // 修改,传入配置项目
        person.fullName = computed({
          get() {
            return person.firstName + '-' + person.lastName
          },
          set(value) {
            const nameArr = value.split('-')
            person.firstName = nameArr[0]
            person.lastName = nameArr[1]
          }
        })
        return {person}
      },
    }
    </script>
    

2.watch函数

  • 与Vue2.x中watch配置功能一致

  • 两个小“坑”:

    • 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。
    • 监视reactive定义的响应式数据中某个属性时:deep配置有效。
    <template>
      <h2>年龄是:{{ age }}</h2>
      <button @click="age++">点我年龄增加</button>
      <hr>
      <h2>姓名是:{{ person.name }}</h2>
      <button @click="person.name+='?'">点我姓名变化</button>
      <hr>
      <h2>sum是:{{ sum }},msg是:{{ msg }}</h2>
      <button @click="sum++">点我sum变化</button>
      |
      <button @click="msg+='?'">点我msg变化</button>
    
    </template>
    
    <script>
    
    import {ref, reactive} from 'vue'
    import {watch} from 'vue'
    
    export default {
      name: 'App',
      setup() {
        const age = ref(19)
        const person = reactive({
          name: 'lqz',
          age: 20
        })
        //1 监听普通
        watch(age, (newValue, oldValue) => {
          console.log('sum变化了', newValue, oldValue)
        })
        // 2 监听对象
        watch(() => person.name, (newValue, oldValue) => {
          console.log('person.name变化了', newValue, oldValue)
        })
        // 3 监听多个
        const sum = ref(100)
        const msg = ref('很好')
    
        watch([sum, msg], (newValue, oldValue) => {
          console.log('sum或msg变化了', newValue, oldValue)
        })
        return {person, age, sum, msg}
      },
    }
    </script>
    

3.watchEffect函数

  • watch的套路是:既要指明监视的属性,也要指明监视的回调。

  • watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。

  • watchEffect有点像computed:

    • 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
    • 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。
    //watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。
    watchEffect(() => {
          const x1 = sum.value
          const x2 = person.age
          console.log('watchEffect配置的回调执行了')
        })
    

3.7 生命周期

[组件生命周期图示](http://photo.liuqingzheng.top/2022 10 30 00 57 10 /lifecycle.16e4c08e.png)

  • Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名:
    • beforeDestroy改名为 beforeUnmount
    • destroyed改名为 unmounted
  • Vue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:
    • beforeCreate===>setup()
    • created=======>setup()
    • beforeMount ===>onBeforeMount
    • mounted=======>onMounted
    • beforeUpdate===>onBeforeUpdate
    • updated =======>onUpdated
    • beforeUnmount ==>onBeforeUnmount
    • unmounted =====>onUnmounted

3.8 自定义hook函数

  • 什么是hook?—— 本质是一个函数,把setup函数中使用的Composition API进行了封装。
  • 类似于vue2.x中的mixin。
  • 自定义hook的优势: 复用代码, 让setup中的逻辑更清楚易懂。

3.8.1 打点功能

<template>
  <h2>点击的x坐标:{{ point.x }},y坐标:{{ point.y }}</h2>

</template>

<script>

import {reactive} from 'vue'
import {onMounted, onBeforeUnmount} from 'vue'

export default {
  name: 'Point',
  setup() {
    const point = reactive({
      x: 0,
      y: 0
    })

    function getPoint(event) {
      console.log(event.pageX)
      console.log(event.pageY)
      point.x = event.pageX
      point.y = event.pageY
    }

    // 挂在完成开始执行
    onMounted(() => {
      window.addEventListener('click', getPoint)
    })
    // 接除挂载时执行
    onBeforeUnmount(() => {
      console.log('sss')
      window.removeEventListener('click', getPoint)
    })
    return {point}
  },
}
</script>
<template>
  <div>
    <button @click="isShow=!isShow">点我显示隐藏</button>
    <Point v-if="isShow"></Point>

  </div>


</template>

<script>

import {ref, reactive} from 'vue'
import Point from "./components/Point.vue";
import Demo from './components/HelloWorld.vue'

export default {
  name: 'App',
  components: {Demo, Point},
  setup() {
    const isShow = ref(true)
    return {isShow}
  },
}
</script>

3.8.2 使用hook实现打点

uesPoint.js

import {onBeforeUnmount, onMounted, reactive} from "vue";

export default function () {
    let point = reactive({
        x: 0,
        y: 0
    })
    function getPoint(event) {
        console.log(event.pageX)
        console.log(event.pageY)
        point.x = event.pageX
        point.y = event.pageY
    }
    // 挂在完成开始执行
    onMounted(() => {
        window.addEventListener('click', getPoint)
    })
    // 接除挂载时执行
    onBeforeUnmount(() => {
        console.log('sss')
        window.removeEventListener('click', getPoint)
    })
    return point
}

Point.vue

<template>
  <h2>点击的x坐标:{{ point.x }},y坐标:{{ point.y }}</h2>

</template>

<script>

import usePoint from '../hooks/usePoint.js'
export default {
  name: 'Point',
  setup() {
    let point = usePoint()
    console.log(point)
    return {point}
  },
}
</script>

10.toRef

  • 作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。
  • 语法:const name = toRef(person,'name')
  • 应用: 要将响应式对象中的某个属性单独提供给外部使用时。
  • 扩展:toRefstoRef功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)
<template>
  <div>
    <h2>姓名:{{ name }}</h2>
    <h2>年龄:{{ age }}</h2>
    <button @click="age++">改年龄</button>| <button @click="name+='~'">改姓名</button>

  </div>


</template>

<script>

import {ref, reactive,toRefs} from 'vue'

export default {
  name: 'App',
  setup() {
    const person = reactive({
      name: 'lqz',
      age: 19
    })
    return {
      ...toRefs(person)
    }
  },
}
</script>

//对象展开语法

let obj1 = {foo: 'bar', x: 42};
let obj2 = {foo: 'baz', y: 13};
let mergedObj = {...obj1, ...obj2};
console.log(mergedObj)

4 setup写法

4.1 App.vue

<script setup>
import HelloWorld from './components/HelloWorld.vue'
</script>

<template>
  <HelloWorld msg="Vite + Vue" />
</template>

<style scoped>

</style>

4.2 HelloWorld.vue

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

defineProps({
  msg: String,
})

const count = ref(0)
</script>

<template>
  <h1>{{ msg }}</h1>

  <div class="card">
    <button type="button" @click="count++">count is {{ count }}</button>
  </div>

</template>

<style scoped>

</style>

5 axios使用

5.1 加载电影案例

# 1 安装axios
npm install axios

# 2 HelloWorld.vue  注意响应式
<script setup>
import {ref, reactive} from 'vue'

defineProps({
  msg: String,
})
const count = ref(0)

// 加载电影案例
import axios from "axios";

const filmList = reactive({results:[]})
let load = () => {
  axios.get('http://127.0.0.1:5000/films').then(res => {
    console.log(res.data.results)
    filmList.results = res.data.results
    console.log(filmList)
  })
}

</script>

<template>
  <h1>{{ msg }}</h1>

  <div class="card">
    <button type="button" @click="count++">count is {{ count }}</button>
  </div>
  <div>
    <button @click="load">加载</button>
    <div v-for="item in filmList.results">
      <p>{{ item.name }}</p>
    </div>

  </div>

</template>

<style scoped>

</style>

5.2 async和await

// 加载电影案例
import axios from "axios";

const filmList = reactive({results: []})

async function load() {
  console.log('zzz')
  // let response=await axios.get('http://127.0.0.1:5000/films')
  // console.log(response.data)
  // filmList.results=response.data.results
  // 简写1
  // let response=await axios.get('http://127.0.0.1:5000/films')
  // Object.assign(filmList.results,response.data.results)
  // 简写2
  // let {data}=await axios.get('http://127.0.0.1:5000/films')
  // Object.assign(filmList.results,data.results)
  // 简写3
  // let {data: {results}} = await axios.get('http://127.0.0.1:5000/films')
  // Object.assign(filmList.results, results)
  
  // 简写4--》assign 复制的两个对象中有同名属性
  let {data} = await axios.get('http://127.0.0.1:5000/films')
  Object.assign(filmList, data)

}

5.3 axios 可选配置项

{
  //1 `url` 是用于请求的服务器 URL
  url: '/user',
  //2 `method` 是创建请求时使用的方法
  method: 'get', // 默认值
  //3 `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
  // 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对 URL
  baseURL: 'https://some-domain.com/api/',
  //4 `transformRequest` 允许在向服务器发送前,修改请求数据
  // 它只能用于 'PUT', 'POST' 和 'PATCH' 这几个请求方法
  // 数组中最后一个函数必须返回一个字符串, 一个Buffer实例,ArrayBuffer,FormData,或 Stream
  // 你可以修改请求头。
  transformRequest: [function (data, headers) {
    // 对发送的 data 进行任意转换处理
    return data;
  }],
  // `transformResponse` 在传递给 then/catch 前,允许修改响应数据
  transformResponse: [function (data) {
    // 对接收的 data 进行任意转换处理
    return data;
  }],
  //5  自定义请求头
  headers: {'X-Requested-With': 'XMLHttpRequest'},
  //6 params` 是与请求一起发送的 URL 参数
  // 必须是一个简单对象或 URLSearchParams 对象
  params: {
    ID: 12345
  },
  // 7 aramsSerializer`是可选方法,主要用于序列化`params`
  // (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/)
  paramsSerializer: function (params) {
    return Qs.stringify(params, {arrayFormat: 'brackets'})
  },
  //8 data` 是作为请求体被发送的数据
  // 仅适用 'PUT', 'POST', 'DELETE 和 'PATCH' 请求方法
  // 在没有设置 `transformRequest` 时,则必须是以下类型之一:
  // - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
  // - 浏览器专属: FormData, File, Blob
  // - Node 专属: Stream, Buffer
  data: {
    firstName: 'Fred'
  },
  // 发送请求体数据的可选语法
  // 请求方式 post
  // 只有 value 会被发送,key 则不会
  data: 'Country=Brasil&City=Belo Horizonte',
  // 0imeout` 指定请求超时的毫秒数。
  // 如果请求时间超过 `timeout` 的值,则请求会被中断
  timeout: 1000, // 默认值是 `0` (永不超时)
  // 11 thCredentials` 表示跨域请求时是否需要使用凭证
  withCredentials: false, // default
  //12 dapter` 允许自定义处理请求,这使测试更加容易。
  // 返回一个 promise 并提供一个有效的响应 (参见 lib/adapters/README.md)。
  adapter: function (config) {
    /* ... */
  },
  //13 auth` HTTP Basic Auth
  auth: {
    username: 'lqz'
    password: '123‘
  },
  //14 `responseType` 表示浏览器将要响应的数据类型
  // 选项包括: 'arraybuffer', 'document', 'json', 'text', 'stream'
  // 浏览器专属:'blob'
  responseType: 'json', // 默认值
  //15 `responseEncoding` 表示用于解码响应的编码 (Node.js 专属)
  // 注意:忽略 `responseType` 的值为 'stream',或者是客户端请求
  // Note: Ignored for `responseType` of 'stream' or client-side requests
  responseEncoding: 'utf8', // 默认值
  //16  `xsrfCookieName` 是 xsrf token 的值,被用作 cookie 的名称
  xsrfCookieName: 'XSRF-TOKEN', // 默认值
  //17  `xsrfHeaderName` 是带有 xsrf token 值的http 请求头名称
  xsrfHeaderName: 'X-XSRF-TOKEN', // 默认值
  //18  `onUploadProgress` 允许为上传处理进度事件
  // 浏览器专属
  onUploadProgress: function (progressEvent) {
    // 处理原生进度事件
  },
  //19  `onDownloadProgress` 允许为下载处理进度事件
  // 浏览器专属
  onDownloadProgress: function (progressEvent) {
    // 处理原生进度事件
  },
  // 20 `maxContentLength` 定义了node.js中允许的HTTP响应内容的最大字节数
  maxContentLength: 2000,
  //21  `maxBodyLength`(仅Node)定义允许的http请求内容的最大字节数
  maxBodyLength: 2000,
  // 22 `validateStatus` 定义了对于给定的 HTTP状态码是 resolve 还是 reject promise。
  // 如果 `validateStatus` 返回 `true` (或者设置为 `null` 或 `undefined`),
  // 则promise 将会 resolved,否则是 rejected。
  validateStatus: function (status) {
    return status >= 200 && status < 300; // 默认值
  },
  // 23 `maxRedirects` 定义了在node.js中要遵循的最大重定向数。
  // 如果设置为0,则不会进行重定向
  maxRedirects: 5, // 默认值
  //24  `socketPath` 定义了在node.js中使用的UNIX套接字。
  // e.g. '/var/run/docker.sock' 发送请求到 docker 守护进程。
  // 只能指定 `socketPath` 或 `proxy` 。
  // 若都指定,这使用 `socketPath` 。
  socketPath: null, // default
  //25  `httpAgent` and `httpsAgent` define a custom agent to be used when performing http
  // and https requests, respectively, in node.js. This allows options to be added like
  // `keepAlive` that are not enabled by default.
  httpAgent: new http.Agent({ keepAlive: true }),
  httpsAgent: new https.Agent({ keepAlive: true }),
  //26  `proxy` 定义了代理服务器的主机名,端口和协议。
  // 您可以使用常规的`http_proxy` 和 `https_proxy` 环境变量。
  // 使用 `false` 可以禁用代理功能,同时环境变量也会被忽略。
  // `auth`表示应使用HTTP Basic auth连接到代理,并且提供凭据。
  // 这将设置一个 `Proxy-Authorization` 请求头,它会覆盖 `headers` 中已存在的自定义 `Proxy-Authorization` 请求头。
  // 如果代理服务器使用 HTTPS,则必须设置 protocol 为`https`
  proxy: {
    protocol: 'https',
    host: '127.0.0.1',
    port: 9000,
    auth: {
      username: 'lqz',
      password: '123'
    }
  },
  // 27 see https://axios-http.com/zh/docs/cancellation
  cancelToken: new CancelToken(function (cancel) {
  }),
  // 28 `decompress` indicates whether or not the response body should be decompressed 
  // automatically. If set to `true` will also remove the 'content-encoding' header 
  // from the responses objects of all decompressed responses
  // - Node only (XHR cannot turn off decompression)
  decompress: true // 默认值
}

5.3 axios请求和响应拦截器

如果想在axios发送请求之前,或者是数据响应回来在执行then方法之前做一些额外的工作,可以通过拦截器完成

// 添加请求拦截器 请求发送之前
axios.interceptors.request.use(
  function (config) {
    // 在发送请求之前做些什么
    return config;
  }, 
  function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  }
);

// 添加响应拦截器 数据响应回来
axios.interceptors.response.use(
  function (response) {
    // 2xx 范围内的状态码都会触发该函数。
    // 对响应数据做点什么
    return response;
  }, 
  function (error) {
    // 超出 2xx 范围的状态码都会触发该函数。
    // 对响应错误做点什么
    return Promise.reject(error);
  }
);
  • 定义src/axios.js提取拦截器和配置语法
import axios from 'axios'


//  创建instance实例
const instance = axios.create({
    baseURL:'https://api.lqz.com',
    timeout:10000
})

//  添加请求拦截
instance.interceptors.request.use(
    // 设置请求头配置信息
    config=>{
        //处理指定的请求头
        console.log("before request")
        config.headers.Accept = 'application/json, text/plain, text/html,*/*'
        return config
    },
    // 设置请求错误处理函数
    error=>{
        console.log("request error")
        return Promise.reject(error)
    }
)
// 添加响应拦截器
instance.interceptors.response.use(
    // 设置响应正确时的处理函数
    response=>{
        console.log("after success response")
        console.log(response)
        return response
    },
    // 设置响应异常时的处理函数
    error=>{
        console.log("after fail response")
        console.log(error)
        return Promise.reject(error)
    }
)
// 默认导出
export default instance

6 Promise 语法

6.1 普通函数和回调函数

普通函数: 正常调用的函数,一般函数执行完毕后才会继续执行下一行代码

<script>
    let fun1 = () =>{
        console.log("fun1 执行了")
    }
    // 调用函数
    fun1()
    // 函数执行完毕,继续执行后续代码
    console.log("其他代码继续执行")
</script>

回调函数: 一些特殊的函数,表示未来才会执行的一些功能,后续代码不会等待该函数执行完毕就开始执行了

<script>
    // 设置一个2000毫秒后会执行一次的定时任务,基于事件自动调用,console.log先执行
    setTimeout(function (){
        console.log("延迟演示执行了")
    },2000)
    console.log("其他代码执行")
</script>

6.2 Promise 简介

前端中的异步编程技术,多线程+线程结果回调!

  • Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6将其写进了语言标准,统一了用法,原生提供了Promise对象。

  • 所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

Promise对象有以下两个特点。

(1)Promise对象代表一个异步操作,有三种状态:`Pending`(进行中)、`Resolved`(已完成,又称 Fulfilled)和`Rejected`(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是`Promise`这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从`Pending`变为`Resolved`和从`Pending`变为`Rejected`。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。

生活中案例

你跟你女朋友说,5年后等我赚够500w就结婚--》定义函数

​ -1 进行中(努力赚钱,其他代码继续执行)

​ -2 成功(赚够500w---》结婚)

​ -3 失败(没赚够---》分手)

6.3 Promise 基本用法

6.3.1 基本使用

ES6规定,Promise对象是一个构造函数,用来生成Promise实例。

    <script>
        
       /*  
        1.实例化promise对象,并且执行(类似Java创建线程对象,并且start)
        参数: resolve,reject随意命名,但是一般这么叫!
        参数: resolve,reject分别处理成功和失败的两个函数! 成功resolve(结果)  失败reject(结果)
        参数: 在function中调用这里两个方法,那么promise会处于两个不同的状态
        状态: promise有三个状态
                pending   正在运行
                resolved  内部调用了resolve方法
                rejected  内部调用了reject方法
        参数: 在第二步回调函数中就可以获取对应的结果 
        */
        let promise =new Promise(function(resolve,reject){
            console.log("promise 开始执行")
            //resolve("promise 执行成功")
            reject("promise 执行失败")
        })
        console.log('11111')
        //2.获取回调函数结果  then在这里会等待promise中的运行结果,但是不会阻塞代码继续运行
        promise.then(
            function(value){console.log(`promise中执行了resolve:${value}`)},
            function(error){console.log(`promise中执行了reject:${error}`)}
        )
        // 3 其他代码执行   
        console.log('222222')
    </script>

// 执行顺序
1 console.log("promise 开始执行")
2 console.log('11111')
3 console.log('222222')
4 promise执行成功或失败打印的代码

6.3.2 Promise catch()

Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。

<script>
    let promise =new Promise(function(resolve,reject){
        console.log("promise 开始执行")
        //1 执行成功--走then
        //resolve("promise 执行成功")
        //2 执行失败--走catch
        //reject("promise 执行失败")
        //3 故意响应一个异常对象--走catch
        throw new Error("error message")
    })
    console.log('11111')
    /* 
        then中的reject()的对应方法可以在产生异常时执行,接收到的就是异常中的提示信息
        then中可以只留一个resolve()的对应方法,reject()方法可以用后续的catch替换
        then中的reject对应的回调函数被后续的catch替换后,catch中接收的数据是一个异常对象
        */
    promise.then(
        function(resolveValue){console.log(`promise中执行了resolve:${resolveValue}`)}
    ).catch(
        function(error){console.log(error)} 
    )
    console.log('222222')
</script>

6.3.3 async和await的使用

async和await是ES6中用于处理异步操作的新特性。通常,异步操作会涉及到Promise对象,而async/await则是在Promise基础上提供了更加直观和易于使用的语法。

async 用于标识函数的

  1. async标识函数后,async函数的返回值会变成一个promise对象
  2. 如果函数内部返回的数据是一个非promise对象,async函数的结果会返回一个成功状态 promise对象
  3. 如果函数内部返回的是一个promise对象,则async函数返回的状态与结果由该对象决定
  4. 如果函数内部抛出的是一个异常,则async函数返回的是一个失败的promise对象

let aaa=new Promise(function(resolve,reject){})

等同于

async function aaa(){}

等同于

let aaa=async ()=>{}

<script>

    /* 
        async 用于标识函数的
            1. async标识函数后,async函数的返回值会变成一个promise对象
            2. 如果函数内部返回的数据是一个非promise对象,async函数的结果会返回一个成功状态 promise对象
            3. 如果函数内部返回的是一个promise对象,则async函数返回的状态与结果由该对象决定
            4. 如果函数内部抛出的是一个异常,则async函数返回的是一个失败的promise对象

        */
    	async function fun1(){
            //return 10   // 成功状态,走then
            //throw new Error("异常状态,走catch")
            //let promise = Promise.resolve("成功状态,走then")
            let promise = Promise.reject("失败状态,走catch")
            return promise
        }

        let promise =fun1()

        promise.then(
            function(value){
                console.log("success:"+value)
            }
        ).catch(
            function(value){
                console.log("fail:"+value)
            }
        )
</script>

await

  1. await右侧的表达式一般为一个promise对象,但是也可以是一个其他值
  2. 如果表达式是promise对象,await返回的是promise成功的值
  3. await会等右边的promise对象执行结束,然后再获取结果,后续代码也会等待await的执行
  4. 如果表达式是其他值,则直接返回该值
  5. await必须在async函数中,但是async函数中可以没有await
  6. 如果await右边的promise失败了,就会抛出异常,需要通过 try ... catch捕获处理
<script>
    /* 
            1. await右侧的表达式一般为一个promise对象,但是也可以是一个其他值
            2. 如果表达式是promise对象,await返回的是promise成功的值
            3. await会等右边的promise对象执行结束,然后再获取结果,后续代码也会等待await的执行
            4. 如果表达式是其他值,则直接返回该值
            5. await必须在async函数中,但是async函数中可以没有await
            6. 如果await右边的promise失败了,就会抛出异常,可以通过 try ... catch捕获处理
        */

		async function fun1(){
            return 10
        
        }

        async function fun2(){
            try{
                
                let res = await fun1()
                //let res = await Promise.reject("something wrong")
            }catch(e){
                console.log("catch got:"+e)   
            }
            
            console.log("await got:"+res)
        }

        fun2()
</script>

7 Vue3路由机制router

7.1 路由简介

1 什么是路由?

  • 定义:路由就是根据不同的 URL 地址展示不同的内容或页面。
  • 通俗理解:路由就像是一个地图,我们要去不同的地方,需要通过不同的路线进行导航。

2 路由的作用

  • 单页应用程序(SPA)中,路由可以实现不同视图之间的无刷新切换,提升用户体验;
  • 路由还可以实现页面的认证和权限控制,保护用户的隐私和安全;
  • 路由还可以利用浏览器的前进与后退,帮助用户更好地回到之前访问过的页面。

7.2 路由入门案例

1 案例需求分析

2 创建项目和导入路由依赖

npm create vite //创建项目cd 项目文件夹 //进入项目文件夹
npm install //安装项目需求依赖
npm install vue-router@4 --save //安装全局的vue-router 4版本

3 准备页面和组件

  • components/Home.vue
<script setup>
</script>

<template>
    <div>
        <h1>Home页面</h1>
    </div>
</template>

<style scoped>
</style>

  • components/List.vue
<script setup>
</script>

<template>
    <div>
        <h1>List页面</h1>
    </div>
</template>

<style scoped>
</style>
  • components/Add.vue
<script setup>
</script>

<template>
    <div>
        <h1>Add页面</h1>
    </div>
</template>

<style scoped>
</style>
  • components/Update.vue
<script setup>
</script>

<template>
    <div>
        <h1>Update页面</h1>
    </div>
</template>

<style scoped>
</style>
  • App.vue
<script setup>
</script>

<template>
    <div>
      <h1>App页面</h1>
      <hr/>
        <!-- 路由的连接 -->
        <router-link to="/">home页</router-link> <br>
        <router-link to="/list">list页</router-link> <br>
        <router-link to="/add">add页</router-link> <br>
        <router-link to="/update">update页</router-link> <br>
      <hr/>
      <!-- 路由连接对应视图的展示位置 -->
      <hr>
      默认展示位置:<router-view></router-view>
      <hr>
      Home视图展示:<router-view name="homeView"></router-view>
      <hr>
      List视图展示:<router-view name="listView"></router-view>
      <hr>
      Add视图展示:<router-view name="addView"></router-view>
      <hr>
      Update视图展示:<router-view name="updateView"></router-view>
    </div>
</template>

<style scoped>
</style>

4 准备路由配置

  • src/routers/router.js
// 导入路由创建的相关方法
import {createRouter,createWebHashHistory} from 'vue-router'

// 导入vue组件
import Home from '../components/Home.vue'
import List from '../components/List.vue'
import Add from '../components/Add.vue'
import Update from '../components/Update.vue'

// 创建路由对象,声明路由规则
const router = createRouter({
    //createWebHashHistory() 是 Vue.js 基于 hash 模式创建路由的工厂函数。在使用这种模式下,路由信息保存在 URL 的 hash 中,
    //使用 createWebHashHistory() 方法,可以创建一个路由历史记录对象,用于管理应用程序的路由。在 Vue.js 应用中,
    //通常使用该方法来创建路由的历史记录对象。
    //就是路由中缓存历史记录的对象,vue-router提供
    history: createWebHashHistory(),
    routes:[
        {
            path:'/',
            /* 
                component指定组件在默认的路由视图位置展示
                components:Home
                components指定组件在name为某个值的路由视图位置展示
                components:{
                    default:Home,// 默认路由视图位置
                    homeView:Home// name为homeView的路由视图位置
                }   
            */
            components:{
                default:Home,
                homeView:Home
            }      
        },
        {
            path:'/list',
            components:{
                listView : List
            } 
        },
        {
            path:'/add',
            components:{
                addView:Add
            } 
        },
        {
            path:'/update',
            components:{
                updateView:Update
            }  
        },
    ]

})

// 对外暴露路由对象
export default router;

5 main.js引入router配置

  • 修改文件:main.js (入口文件)
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
//导入router模块
import router from './routers/router.js'
let app = createApp(App)
//绑定路由对象
app.use(router)
//挂在试图
app.mount("#app")

6 启动测试

npm run dev

7.3 路由重定向

重定向的作用:将一个路由重定向到另一个路由上

  • 修改案例:访问/list和/showAll都定向到List.vue
  • router.js
// 导入路由创建的相关方法
import {createRouter,createWebHashHistory} from 'vue-router'

// 导入vue组件
import Home from '../components/Home.vue'
import List from '../components/List.vue'
import Add from '../components/Add.vue'
import Update from '../components/Update.vue'

// 创建路由对象,声明路由规则
const router = createRouter({
    history: createWebHashHistory(),
    routes:[
        {
            path:'/',
            components:{
                default:Home,
                homeView:Home
            }      
        },
        {
            path:'/list',
            components:{
                listView : List
            } 
        },
        {
            path:'/showAll',
            // 重定向
            redirect :'/list'
        },
        {
            path:'/add',
            components:{
                addView:Add
            } 
        },
        {
            path:'/update',
            components:{
                updateView:Update
            }  
        },
    ]

})

// 对外暴露路由对象
export default router;
  • App.vue
<script setup>
</script>

<template>
    <div>
      <h1>App页面</h1>
      <hr/>
        <!-- 路由的连接 -->
        <router-link to="/">home页</router-link> <br>
        <router-link to="/list">list页</router-link> <br>
        <router-link to="/showAll">showAll页</router-link> <br>
        <router-link to="/add">add页</router-link> <br>
        <router-link to="/update">update页</router-link> <br>
      <hr/>
      <!-- 路由连接对应视图的展示位置 -->
      <hr>
      默认展示位置:<router-view></router-view>
      <hr>
      Home视图展示:<router-view name="homeView"></router-view>
      <hr>
      List视图展示:<router-view name="listView"></router-view>
      <hr>
      Add视图展示:<router-view name="addView"></router-view>
      <hr>
      Update视图展示:<router-view name="updateView"></router-view>
    </div>
</template>

<style scoped>
</style>

7.4 编程式路由(useRouter)

普通路由

  • <router-link to="/list">list页</router-link> 这种路由,to中的内容目前是固定的,点击后只能切换/list对象组件(声明式路由)

编程式路由

  • 通过useRouter,动态决定向那个组件切换的路由
  • 在 Vue 3 和 Vue Router 4 中,你可以使用 useRouter 来实现动态路由(编程式路由)
  • 这里的 useRouter 方法返回的是一个 router 对象,你可以用它来做如导航到新页面、返回上一页面等操作。

案例需求: 通过普通按钮配合事件绑定实现路由页面跳转,不直接使用router-link标签

  • App.vue
<script setup type="module">

  import {useRouter} from 'vue-router'
  import {ref} from 'vue'
  //创建动态路由对象
  let router = useRouter()

  let  routePath =ref('')
  let  showList= ()=>{
      // 编程式路由
      // 直接push一个路径
      //router.push('/list')
      // push一个带有path属性的对象
      router.push({path:'/list'})
  }
</script>

<template>
    <div>
      <h1>App页面</h1>
      <hr/>
        <!-- 路由的连接 -->
        <router-link to="/">home页</router-link> <br>
        <router-link to="/list">list页</router-link> <br>
        <router-link to="/showAll">showAll页</router-link> <br>
        <router-link to="/add">add页</router-link> <br>
        <router-link to="/update">update页</router-link> <br>
        <!-- 动态输入路径,点击按钮,触发单击事件的函数,在函数中通过编程是路由切换页面 -->
        <button @click="showList()">showList</button> <br>
      <hr/>
      <!-- 路由连接对应视图的展示位置 -->
      <hr>
      默认展示位置:<router-view></router-view>
      <hr>
      Home视图展示:<router-view name="homeView"></router-view>
      <hr>
      List视图展示:<router-view name="listView"></router-view>
      <hr>
      Add视图展示:<router-view name="addView"></router-view>
      <hr>
      Update视图展示:<router-view name="updateView"></router-view>
    </div>
</template>

<style scoped>
</style>

7.5 路由传参(useRoute)

路径参数

  • 在路径中使用一个动态字段来实现,我们称之为 路径参数
    • 例如: 查看数据详情 /showDetail/1 ,1就是要查看详情的id,可以动态添值!

键值对参数

  • 类似与get请求通过url传参,数据是键值对形式的

    • 例如: 查看数据详情/showDetail?hid=1,hid=1就是要传递的键值对参数

    • 在 Vue 3 和 Vue Router 4 中,你可以使用 useRoute 这个函数从 Vue 的组合式 API 中获取路由对象。

    • useRoute 方法返回的是当前的 route 对象,你可以用它来获取关于当前路由的信息,如当前的路径、查询参数等。

案例需求 : 切换到ShowDetail.vue组件时,向该组件通过路由传递参数

  • 修改App.vue文件
<script setup type="module">

  import {useRouter} from 'vue-router'

  //创建动态路由对象
  let router = useRouter()
  //动态路由路径传参方法
  let showDetail= (id,language)=>{
      // 尝试使用拼接字符串方式传递路径参数
      //router.push(`showDetail/${id}/${languange}`)
      /*路径参数,需要使用params  */
      router.push({name:"showDetail",params:{id:id,language:language}})
  }
  let showDetail2= (id,language)=>{
      /*uri键值对参数,需要使用query */
      router.push({path:"/showDetail2",query:{id:id,language:language}})
  }
</script>

<template>
    <div>
      <h1>App页面</h1>
      <hr/>
      <!-- 路径参数   -->
      <router-link to="/showDetail/1/JAVA">showDetail路径传参显示JAVA</router-link> 
      <button @click="showDetail(1,'JAVA')">showDetail动态路由路径传参显示JAVA</button>
      <hr/>
      <!-- 键值对参数 -->
      <router-link v-bind:to="{path:'/showDetail2',query:{id:1,language:'Java'}}">showDetail2键值对传参显示JAVA</router-link> 
      <button @click="showDetail2(1,'JAVA')">showDetail2动态路由键值对传参显示JAVA</button>
      <hr>
      showDetail视图展示:<router-view name="showDetailView"></router-view>
      <hr>
      showDetail2视图展示:<router-view name="showDetailView2"></router-view>
    </div>
</template>

<style scoped>
</style>

  • 修改router.js增加路径参数占位符
// 导入路由创建的相关方法
import {createRouter,createWebHashHistory} from 'vue-router'

// 导入vue组件

import ShowDetail from '../components/ShowDetail.vue'
import ShowDetail2 from '../components/ShowDetail2.vue'

// 创建路由对象,声明路由规则
const router = createRouter({
    history: createWebHashHistory(),
    routes:[
        
        {
            /* 此处:id  :language作为路径的占位符 */
            path:'/showDetail/:id/:language',
            /* 动态路由传参时,根据该名字找到该路由 */
            name:'showDetail',
            components:{
                showDetailView:ShowDetail
            }  
        },
        {
            path:'/showDetail2',
            components:{
                showDetailView2:ShowDetail2
            }  
        },
    ]

})

// 对外暴露路由对象
export default router;
  • ShowDetail.vue 通过useRoute获取路径参数
<script setup type="module">
    import{useRoute} from 'vue-router'
    import { onUpdated,ref } from 'vue';
    // 获取当前的route对象
    let route =useRoute()
    let languageId = ref(0)
    let languageName = ref('')
    //  借助更新时生命周期,将数据更新进入响应式对象
    onUpdated (()=>{
        // 获取对象中的参数
        languageId.value=route.params.id
        languageName.value=route.params.language
        console.log(languageId.value)
        console.log(languageName.value)
    })
    
</script>

<template>
    <div>
        <h1>ShowDetail页面</h1>
        <h3>编号{{route.params.id}}:{{route.params.language}}是世界上最好的语言</h3>
        <h3>编号{{languageId}}:{{languageName}}是世界上最好的语言</h3>
    </div>
</template>

<style scoped>
</style>

  • ShowDetail2.vue通过useRoute获取键值对参数
<script setup type="module">
    import{useRoute} from 'vue-router'
    import { onUpdated,ref } from 'vue';
    // 获取当前的route对象
    let route =useRoute()
    let languageId = ref(0)
    let languageName = ref('')
    //  借助更新时生命周期,将数据更新进入响应式对象
    onUpdated (()=>{
        // 获取对象中的参数(通过query获取参数,此时参数是key-value形式的)
        console.log(route.query)
        console.log(languageId.value)
        console.log(languageName.value)
        languageId.value=route.query.id
        languageName.value=route.query.language
       
    })
    
</script>

<template>
    <div>
        <h1>ShowDetail2页面</h1>
        <h3>编号{{route.query.id}}:{{route.query.language}}是世界上最好的语言</h3>
        <h3>编号{{languageId}}:{{languageName}}是世界上最好的语言</h3>
    </div>
</template>

<style scoped>
</style>

7.6 路由守卫

在 Vue 3 中,路由守卫是用于在路由切换期间进行一些特定任务的回调函数。路由守卫可以用于许多任务,例如验证用户是否已登录、在路由切换前提供确认提示、请求数据等。Vue 3 为路由守卫提供了全面的支持,并提供了以下几种类型的路由守卫:

  1. 全局前置守卫:在路由切换前被调用,可以用于验证用户是否已登录、中断导航、请求数据等。
  2. 全局后置守卫:在路由切换之后被调用,可以用于处理数据、操作 DOM 、记录日志等。
  3. 守卫代码的位置: 在router.js中
//全局前置路由守卫
router.beforeEach( (to,from,next) => {
    //to 是目标地包装对象  .path属性可以获取地址
    //from 是来源地包装对象 .path属性可以获取地址
    //next是方法,不调用默认拦截! next() 放行,直接到达目标组件
    //next('/地址')可以转发到其他地址,到达目标组件前会再次经过前置路由守卫
    console.log(to.path,from.path,next)

    //需要判断,注意避免无限重定向
    if(to.path == '/index'){
        next()
    }else{
        next('/index')
    }
    
} )

//全局后置路由守卫
router.afterEach((to, from) => {
    console.log(`Navigate from ${from.path} to ${to.path}`);
});

登录案例,登录以后才可以进入home,否则必须进入login

  • 定义Login.vue
<script setup>
    import {ref} from 'vue'
    import {useRouter} from 'vue-router'
    let username =ref('')
    let password =ref('')
    let router = useRouter();
    let login = () =>{
        console.log(username.value,password.value)
        if(username.value == 'root' & password.value == '123456'){
            router.push({path:'/home',query:{'username':username.value}})
            //登录成功利用前端存储机制,存储账号!
            localStorage.setItem('username',username.value)
            //sessionStorage.setItem('username',username)
        }else{
            alert('登录失败,账号或者密码错误!');
        }
    }

</script>

<template>
    
    <div>
        账号: <input type="text" v-model="username" placeholder="请输入账号!"><br>
        密码: <input type="password" v-model="password" placeholder="请输入密码!"><br>
        <button @click="login()">登录</button>
    </div>

</template>

<style scoped>
</style>

  • 定义Home.vue
<script setup>
 import {ref} from 'vue'
 import {useRoute,useRouter} from 'vue-router'

 let route =useRoute()
 let router = useRouter()
 //  并不是每次进入home页时,都有用户名参数传入
 //let username = route.query.username
 let username =window.localStorage.getItem('username'); 

 let logout= ()=>{
    // 清除localStorge中的username
    //window.sessionStorage.removeItem('username')
    window.localStorage.removeItem('username')
    // 动态路由到登录页
    router.push("/login")

 }

</script>

<template>
    <div>
        <h1>Home页面</h1>
        <h3>欢迎{{username}}登录</h3>
        <button @click="logout">退出登录</button>
    </div>
</template>

<style scoped>

</style>

  • App.vue
<script setup type="module">

  
</script>

<template>
    <div>
      
      <router-view></router-view>
      
    </div>
</template>

<style scoped>
</style>

  • 定义routers.js
// 导入路由创建的相关方法
import {createRouter,createWebHashHistory} from 'vue-router'

// 导入vue组件

import Home from '../components/Home.vue'
import Login from '../components/login.vue'
// 创建路由对象,声明路由规则
const router = createRouter({
    history: createWebHashHistory(),
    routes:[
        {
            path:'/home',
            component:Home
        },
        {
            path:'/',
            redirect:"/home"
        },
        {
            path:'/login',
            component:Login
        },
    ]

})

// 设置路由的全局前置守卫
router.beforeEach((to,from,next)=>{
    /* 
    to 要去那
    from 从哪里来
    next 放行路由时需要调用的方法,不调用则不放行
    */
    console.log(`从哪里来:${from.path},到哪里去:${to.path}`)

    if(to.path == '/login'){
        //放行路由  注意放行不要形成循环  
        next()
    }else{
        //let username =window.sessionStorage.getItem('username'); 
        let username =window.localStorage.getItem('username'); 
        if(null != username){
            next()
        }else{
            next('/login')
        }

    }
})
// 设置路由的全局后置守卫
router.afterEach((to,from)=>{
    console.log(`从哪里来:${from.path},到哪里去:${to.path}`)
})

// 对外暴露路由对象
export default router;
  • 启动测试
npm run dev

8 Vue3状态管理Pinia

8.1 Pinia介绍

如何实现多个组件之间的数据传递?

  • 方式1 组件传参

  • 方式2 路由传参

  • 方式3 通过pinia状态管理定义共享数据

当我们有多个组件共享一个共同的状态(数据源)时,多个视图可能都依赖于同一份状态。来自不同视图的交互也可能需要更改同一份状态。虽然我们的手动状态管理解决方案(props,组件间通信,模块化)在简单的场景中已经足够了,但是在大规模的生产应用中还有很多其他事项需要考虑:

  • 更强的团队协作约定
  • 与 Vue DevTools 集成,包括时间轴、组件内部审查和时间旅行调试
  • 模块热更新 (HMR)
  • 服务端渲染支持

Pinia 就是一个实现了上述需求的状态管理库,由 Vue 核心团队维护,对 Vue 2 和 Vue 3 都可用。https://pinia.vuejs.org/zh/introduction.html

8.2 Pinia基本用法

1 准备vite项目

npm create vite
npm install 
npm install vue-router@4 --save

2 安装pinia

npm install pinia

3 定义pinia store对象 src/store/store.js [推荐这么命名不是强制]

import {defineStore } from 'pinia'

//定义数据并且对外暴露
// store就是定义共享状态的包装对象
// 内部包含四个属性: id 唯一标识 state 完整类型推理,推荐使用箭头函数 存放的数据 getters 类似属性计算,存储放对数据
// 操作的方法  actions 存储数据的复杂业务逻辑方法
// 理解: store类似Java中的实体类, id就是类名, state 就是装数据值的属性  getters就是get方法,actions就是对数据操作的其他方法
export const definedPerson = defineStore(
    {
        id: 'personPinia', //必须唯一
        state:()=>{ // state中用于定义数据
            return {
                username:'张三',
                age:0,
                hobbies:['唱歌','跳舞']
            }
        },
        getters:{// 用于定义一些通过数据计算而得到结果的一些方法 一般在此处不做对数据的修改操作
                 // getters中的方法可以当做属性值方式使用
            getHobbiesCount(){
                return this.hobbies.length
            },
            getAge(){
                return this.age
            }
        },
        actions:{ // 用于定义一些对数据修改的方法
            doubleAge(){
                this.age=this.age*2
            }
        }
    }
)

4 在main.js配置pinia组件到vue中

import { createApp } from 'vue'
import App from './App.vue'
import router from './routers/router.js'
// 导pinia
import { createPinia } from 'pinia'
// 创建pinia对象
let pinia= createPinia()

let app =createApp(App)
app.use(router)
// app中使用pinia功能
app.use(pinia) 
app.mount('#app')

5 Operate.vue 中操作Pinia数据

<script setup type="module">
    import { ref} from 'vue';
    import { definedPerson} from '../store/store';
    // 读取存储的数据
    let person= definedPerson()

    let hobby = ref('')
   
</script>

<template>
    <div>
        <h1>operate视图,用户操作Pinia中的数据</h1>
        请输入姓名:<input type="text" v-model="person.username"> <br>
        请输入年龄:<input type="text" v-model="person.age"> <br>
        请增加爱好:
        <input type="checkbox" value="吃饭"  v-model="person.hobbies"> 吃饭
        <input type="checkbox" value="睡觉"  v-model="person.hobbies"> 睡觉
        <input type="checkbox" value="打豆豆"  v-model="person.hobbies"> 打豆豆 <br>
        
        <!-- 事件中调用person的doubleAge()方法 -->
        <button @click="person.doubleAge()">年龄加倍</button> <br>
        <!-- 事件中调用pinia提供的$reset()方法恢复数据的默认值 -->
        <button @click="person.$reset()">恢复默认值</button> <br>
        <!-- 事件中调用$patch方法一次性修改多个属性值 -->
        <button @click="person.$patch({username:'奥特曼',age:100,hobbies:['晒太阳','打怪兽']})">变身奥特曼</button> <br>
		显示pinia中的person数据:{{person}}
    </div>
</template>
<style scoped>
</style>

6 List.vue中展示Pinia数据

<script setup type="module">
    import { definedPerson} from '../store/store';
    // 读取存储的数据
    let person= definedPerson()
</script>

<template>
    <div>
        <h1>List页面,展示Pinia中的数据</h1>
        读取姓名:{{person.username}} <br>
        读取年龄:{{person.age}} <br>
        通过get年龄:{{person.getAge}} <br>
        爱好数量:{{person.getHobbiesCount}} <br>
        所有的爱好:
        <ul>
            <li v-for='(hobby,index) in person.hobbies' :key="index" v-text="hobby"></li>
        </ul>
    </div>
</template>

<style scoped>
</style>

7 定义组件路由router.js

// 导入路由创建的相关方法
import {createRouter,createWebHashHistory} from 'vue-router'

// 导入vue组件
import List  from '../components/List.vue'
import Operate  from '../components/Operate.vue'
// 创建路由对象,声明路由规则
const router = createRouter({
    history: createWebHashHistory(),
    routes:[
        {
            path:'/opearte',
            component:Operate
        },
        
        {
            path:'/list',
            component:List
        },
    ]

})

// 对外暴露路由对象
export default router;

8 App.vue中通过路由切换组件

<script setup type="module">

  
</script>

<template>
    <div>
      <hr>
      <router-link to="/opearte">显示操作页</router-link> <br>
      <router-link to="/list">显示展示页</router-link> <br>
      <hr>
      <router-view></router-view>
    </div>
</template>

<style scoped>
</style>

9 启动测试

npm run dev

8.3 Pinia其他细节

State (状态) 在大多数情况下,state 都是你的 store 的核心。人们通常会先定义能代表他们 APP 的 state。在 Pinia 中,state 被定义为一个返回初始状态的函数。

  • store.js
import {defineStore} from 'pinia'

export const definedPerson = defineStore('personPinia',
    {
        state:()=>{
            return {
                username:'',
                age:0,
                hobbies:['唱歌','跳舞']
            }
        },
        getters:{
            getHobbiesCount(){
                return this.hobbies.length
            },
            getAge(){
                return this.age
            }
        },
        actions:{
            doubleAge(){
                this.age=this.age*2
            }
        }
    }
)
  • Operate.vue
<script setup type="module">
    import { ref} from 'vue';
    import { definedPerson} from '../store/store';
    // 读取存储的数据
    let person= definedPerson()

    let hobby = ref('')
    let addHobby= ()=> {
        console.log(hobby.value)
        person.hobbies.push(hobby.value)
    }
    // 监听状态
    person.$subscribe((mutation,state)=>{
        console.log('---subscribe---')
        /* 
        mutation.storeId
            person.$id一样
        mutation.payload
            传递给 cartStore.$patch() 的补丁对象。
        state 数据状态,其实是一个代理
        */
        console.log(mutation)
        console.log(mutation.type)
        console.log(mutation.payload)
        console.log(mutation.storeId)
        console.log(person.$id)
        // 数据 其实是一个代理对象
        console.log(state)
    })


</script>

<template>
    <div>
        <h1>operate视图,用户操作Pinia中的数据</h1>
        请输入姓名:<input type="text" v-model="person.username"> <br>
        请输入年龄:<input type="text" v-model="person.age"> <br>
        请增加爱好:
        <input type="checkbox" value="吃饭"  v-model="person.hobbies"> 吃饭
        <input type="checkbox" value="睡觉"  v-model="person.hobbies"> 睡觉
        <input type="checkbox" value="打豆豆"  v-model="person.hobbies"> 打豆豆 <br>
        <input type="text" @change="addHobby" v-model="hobby"> <br>  
        <!-- 事件中调用person的doubleAge()方法 -->
        <button @click="person.doubleAge()">年龄加倍</button> <br>
        <!-- 事件中调用pinia提供的$reset()方法恢复数据的默认值 -->
        <button @click="person.$reset()">恢复默认值</button> <br>
        <!-- 事件中调用$patch方法一次性修改多个属性值 -->
        <button @click="person.$patch({username:'奥特曼',age:100,hobbies:['晒太阳','打怪兽']})">变身奥特曼</button> <br>
		person:{{person}}
    </div>
</template>
<style scoped>
</style>

2 Getter 完全等同于 store 的 state 的计算值。可以通过 defineStore() 中的 getters 属性来定义它们。推荐使用箭头函数,并且它将接收 state 作为第一个参数:

export const useStore = defineStore('main', {
  state: () => ({
    count: 0,
  }),
  getters: {
    doubleCount: (state) => state.count * 2,
  },
})

3 Action 相当于组件中的 method。它们可以通过 defineStore() 中的 actions 属性来定义,并且它们也是定义业务逻辑的完美选择。类似 getter,action 也可通过 this 访问整个 store 实例,并支持完整的类型标注(以及自动补全)。不同的是,action 可以是异步的,你可以在它们里面 await 调用任何 API,以及其他 action!

export const useCounterStore = defineStore('main', {
  state: () => ({
    count: 0,
  }),
  actions: {
    increment() {
      this.count++
    },
    randomizeCounter() {
      this.count = Math.round(100 * Math.random())
    },
  },
})

9 Element-plus组件库

9.1 Element-plus介绍

Element Plus 是一套基于 Vue 3 的开源 UI 组件库,是由饿了么前端团队开发的升级版本 Element UI。Element Plus 提供了丰富的 UI 组件、易于使用的 API 接口和灵活的主题定制功能,可以帮助开发者快速构建高质量的 Web 应用程序。

  • Element Plus 支持按需加载,且不依赖于任何第三方 CSS 库,它可以轻松地集成到任何 Vue.js 项目中。Element Plus 的文档十分清晰,提供了各种组件的使用方法和示例代码,方便开发者快速上手。

  • Element Plus 目前已经推出了大量的常用 UI 组件,如按钮、表单、表格、对话框、选项卡等,此外还提供了一些高级组件,如日期选择器、时间选择器、级联选择器、滑块、颜色选择器等。这些组件具有一致的设计和可靠的代码质量,可以为开发者提供稳定的使用体验。

  • 与 Element UI 相比,Element Plus 采用了现代化的技术架构和更加先进的设计理念,同时具备更好的性能和更好的兼容性。Element Plus 的更新迭代也更加频繁,可以为开发者提供更好的使用体验和更多的功能特性。

  • Element Plus 可以在支持 ES2018ResizeObserver 的浏览器上运行。 如果您确实需要支持旧版本的浏览器,请自行添加 Babel 和相应的 Polyfill

  • 官网https://element-plus.gitee.io/zh-CN/

  • 由于 Vue 3 不再支持 IE11,Element Plus 也不再支持 IE 浏览器。

9.2 Element-plus入门案例

1 准备vite项目

npm create vite  // 注意选择 vue+typeScript
npm install 
npm install vue-router@4 --save
npm install pinia
npm install axios

2 安装element-plus

npm install element-plus

3 完整引入element-plus

  • main.js
import { createApp } from 'vue'
//导入element-plus相关内容
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

import App from './App.vue'

const app = createApp(App)

app.use(ElementPlus)
app.mount('#app')

4 入门案例

  • App.vue
<script setup>
  import { ref } from 'vue'

  const value = ref(true)
</script>

<template>
  <div>
    <!-- 直接使用element-plus组件即可 -->
    <el-button>按钮</el-button>
    <br>
    <el-switch
      v-model="value"
      size="large"
      active-text="Open"
      inactive-text="Close"
    />
    <br />
    <el-switch v-model="value" active-text="Open" inactive-text="Close" />
    <br />
    <el-switch
      v-model="value"
      size="small"
      active-text="Open"
      inactive-text="Close"
    />
  </div>
</template>

<style scoped>

</style>

5 启动测试

npm run dev

9.3 Element-plus常用组件

https://element-plus.gitee.io/zh-CN/component/button.html

10 其他

10.1 组件使用

10.2 ref

# 1 ref属性---》setup写法中取

-在子组件中暴露
<script setup>
    import {reactive} from "vue";
    const person=reactive({name:'lqz',age:19})
    function add(){
      console.log('asfdasfd')
    }
    defineExpose({person,add})
</script>
-在父组件中获取
# template中:  <HelloWorld ref="helloworld"></HelloWorld>
# js中:
import {ref, reactive,} from "vue";
# 1 拿到ref对应的组件对象
const helloworld = ref() # 变量名必须跟 在组件上定义的名字一致
function handleClick() {
  # this.$refs.helloworld  现在不能这么拿
  console.log(helloworld.value) #  helloworld.value拿到组件对象
  # 使用组件对象的属性和方法---》vue3---》不能直接使用,需要子组件暴露---》只能用子组件暴露的
  helloworld.value.add()
  console.log(helloworld.value.person.name)
}

10.3 toRefs

###1 结构赋值
let person = {name: 'lqz', age: 19}
let {name, age} = person //  普通解构赋值,name=person.name   age=person.age
let {name:a, age} = person // 重命名解构赋值:a=person.name   age=person.age
console.log(a)
console.log(age)


###2 toRefs的使用--把对象中的普通变量都做成响应式

# toRefs 可以把对象内部的所有值,都做成响应式
# 通过结构赋值,赋值给某些变量---》这些变量就有响应式
# 改这些变量会影响到原来对象中得值

const person = reactive({name: 'lqz', age: 19})
# let res = toRefs(person)  // 把里面所有 普通变量做成响应式
# console.log(res)
# console.log(person)
let {name, age:nl} = toRefs(person)  // 等同于: name=ref(lqz)   age=ref(19)
# let {name, age} = person  // 等同于: name=lqz   age=19
function add() {
  nl.value++
  console.log(nl.value)
}



## 3 toRef的使用  只对对象中的某个值做成响应式
const person = reactive({name: 'lqz', age: 19})
# const name=toRefs(person)  //{name:ref(name),age:ref(age)}
const name = toRef(person, 'name')  //name=ref(person.name)
function change() {
  name.value = 'xxx'
}

10.4 父子通信

10.4.1 父传子

### 父组件####
<script setup>

  import Son from './components/Son.vue'
  import {ref,reactive,toRefs} from 'vue'

  let message = ref('parent data!')
  let title = ref(42)

  function changeMessage(){
    message.value = '修改数据!'
    title.value++
  }
</script>

<template>
  <div>
    <h2>{{ message }}</h2>
    <hr>
    <!-- 使用子组件,并且传递数据! -->
    <Son :message="message" :title="title"></Son>
    <hr>
    <button @click="changeMessage">点击更新</button>
  </div>
</template>

<style scoped>
</style>



### 子组件###
<script setup type="module">
    import {ref,isRef,defineProps} from 'vue'
    //声明父组件传递属性值
    defineProps({
        message:String ,
        title:Number
    })
</script>

<template>
    <div>
    <div>{{ message }}</div>
    <div>{{ title }}</div>
    </div>
</template>

<style>
</style>

10.4.2 子传父

###父组件##
<script setup>
    import Son from './components/Son.vue'
    import {ref} from 'vue'
    let pdata = ref('')
    const padd = (data) => {
        console.log('2222');
        pdata.value =data;
    }
    //自定义接收,子组件传递数据方法! 参数为数据!
    const psub = (data) => {
        console.log('11111');
        pdata.value = data;
    }
</script>

<template>
    <div>
        <!-- 声明@事件名应该等于子模块对应事件名!调用方法可以是当前自定义!-->
        <Son @add="padd" @sub="psub"></Son>
        <hr>
        {{ pdata }}
    </div>
</template>

### 子组件###
<script setup>
    import {ref,defineEmits} from 'vue'
    //1.定义要发送给父组件的方法,可以1或者多个
    let emites = defineEmits(['add','sub']);
    let data = ref(1);
    function sendMsgToParent(){
        console.log('-------son--------');
        //2.出发父组件对应的方法,调用defineEmites对应的属性
        emites('add','add data!'+data.value)
        emites('sub','sub data!'+data.value)
        data.value ++;
    }
</script>
<template>
    <div>
      <button @click="sendMsgToParent">发送消息给父组件</button>
    </div>
</template>
posted @ 2024-05-07 08:33  刘清政  阅读(265)  评论(0编辑  收藏  举报