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,我们可以用包含多个选项的对象来描述组件的逻辑,例如 data
、methods
和 mounted
。选项所定义的属性都会暴露在函数内部的 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>
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>
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构建对比图
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
3 常用API
3.1 setup
-
setup为Vue3.0中一个新的配置项,值为一个函数
-
setup是所有Composition API(组合API)编写的位置
-
组件中所用到的:数据、方法等等,均要配置在setup中
-
setup函数的返回值:返回一个对象,对象中的属性、方法, 在模板中均可以直接使用
-
注意:
尽量不要与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()
的get
与set
完成的 - 对象类型的数据:内部 求助 了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()
的get
与set
来实现响应式(数据劫持)。 - reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。
- ref通过
- 从使用角度对比:
- ref定义的数据:操作数据需要
.value
,读取数据时模板中直接读取不需要.value
。 - reactive定义的数据:操作数据与读取数据:均不需要
.value
。
- ref定义的数据:操作数据需要
3.5 setup的两个注意点
- setup执行的时机
- 在beforeCreate之前执行一次,this是undefined。
- setup的参数
- props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
- context:上下文对象
- attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于
this.$attrs
。 - slots: 收到的插槽内容, 相当于
this.$slots
。 - emit: 分发自定义事件的函数, 相当于
this.$emit
。
- attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于
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')
- 应用: 要将响应式对象中的某个属性单独提供给外部使用时。
- 扩展:
toRefs
与toRef
功能一致,但可以批量创建多个 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 用于标识函数的
- async标识函数后,async函数的返回值会变成一个promise对象
- 如果函数内部返回的数据是一个非promise对象,async函数的结果会返回一个成功状态 promise对象
- 如果函数内部返回的是一个promise对象,则async函数返回的状态与结果由该对象决定
- 如果函数内部抛出的是一个异常,则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
- await右侧的表达式一般为一个promise对象,但是也可以是一个其他值
- 如果表达式是promise对象,await返回的是promise成功的值
- await会等右边的promise对象执行结束,然后再获取结果,后续代码也会等待await的执行
- 如果表达式是其他值,则直接返回该值
- await必须在async函数中,但是async函数中可以没有await
- 如果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 为路由守卫提供了全面的支持,并提供了以下几种类型的路由守卫:
- 全局前置守卫:在路由切换前被调用,可以用于验证用户是否已登录、中断导航、请求数据等。
- 全局后置守卫:在路由切换之后被调用,可以用于处理数据、操作 DOM 、记录日志等。
- 守卫代码的位置: 在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 可以在支持 ES2018 和 ResizeObserver 的浏览器上运行。 如果您确实需要支持旧版本的浏览器,请自行添加 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>