属性绑定
<script setup lang="ts">
import { ref } from 'vue'
const path = ref('/src/assets/vue.svg')
</script>
<template>
<img :src="path" alt="">
</template>
-
【:属性名】用来将标签属性与【响应式】变量绑定
-
<script setup lang="ts"> import { ref } from 'vue' const count = ref(0) function dec() { count.value-- } function inc() { count.value++ } </script> <template> <input type="button" value="-" @click="dec"> <h2>{{count}}</h2> <input type="button" value="+" @click="inc">
-
【@事件名】用来将标签属性与函数绑定,事件发生后执行函数内代码
-
表单绑定
<script setup lang="ts"> import { ref } from "vue"; const user = ref({ name:'张三', age:18, sex:'男', fav:['游泳','打球'] }) function saveUser() { console.log(user.value) } </script> <template> <div class="outer"> <div> <label for="">请输入姓名</label> <input type="text" v-model="user.name"/> </div> <div> <label for="">请输入年龄</label> <input type="text" v-model="user.age"/> </div> <div> <label for="">请选择性别</label> 男 <input type="radio" value="男" v-model="user.sex"/> 女 <input type="radio" value="女" v-model="user.sex"/> </div> <div> <label for="">请选择爱好</label> 游泳 <input type="checkbox" value="游泳" v-model="user.fav"/> 打球 <input type="checkbox" value="打球" v-model="user.fav"/> 健身 <input type="checkbox" value="健身" v-model="user.fav"/> </div> <div> <input type="button" value="保存" @click="saveUser"> </div> </div> </template> <style scoped> div { margin-bottom: 8px; } .outer { width: 100%; position: relative; padding-left: 80px; } label { text-align: left; width: 100px; display: inline-block; position: absolute; left :0; } </style>
页面效果
-
-
用 v-model 实现双向绑定,即
-
JavaScript 数据可以同步到表单标签
-
反过来用户在表单标签输入的新值也会同步到 JavaScript 这边
-
-
双向绑定只适用于表单这种带【输入】功能的标签,其它标签的数据绑定,单向就足够了
-
复选框这种标签,双向绑定的 JavaScript 数据类型一般用数组
-
计算属性
- 有时在数据展示时要做简单的计算
-
<script setup lang="ts"> import { ref } from 'vue' const firstName = ref('三') const lastName = ref('张') </script> <template> <h2>{{lastName + firstName}}</h2> <h3>{{lastName + firstName}}</h3> <h4>{{lastName + firstName}}</h4> </template>
看起来较为繁琐,可以用计算属性改进
-
<script setup lang="ts"> import { ref, computed } from 'vue' const firstName = ref('三') const lastName = ref('张') const fullName = computed(() => { console.log('enter') return lastName.value + firstName.value }) </script> <template> <h2>{{fullName}}</h2> <h3>{{fullName}}</h3> <h4>{{fullName}}</h4> </template>
-
fullName 即为计算属性,它具备缓存功能,即 firstName 和 lastName 的值发生了变化,才会重新计算
-
如果用函数实现相同功能,则没有缓存功能
<script setup lang="ts"> import { ref } from 'vue' const firstName = ref('三') const lastName = ref('张') function fullName() { console.log('enter') return lastName.value + firstName.value } </script> <template> <h2>{{fullName()}}</h2> <h3>{{fullName()}}</h3> <h4>{{fullName()}}</h4> </template>
xhr
浏览器中有两套 API 可以和后端交互,发送请求、接收响应,fetch API 前面我们已经介绍过了,另一套 API 是 xhr,基本用法如下
const xhr = new XMLHttpRequest() xhr.onload = function() { console.log(xhr.response) } xhr.open('GET', 'http://localhost:8080/api/students') xhr.responseType = "json" xhr.send()
但这套 API 虽然功能强大,但比较老,不直接支持 Promise,因此有必要对其进行改造
-
function get(url: string) { return new Promise((resolve, reject)=>{ const xhr = new XMLHttpRequest() xhr.onload = function() { if(xhr.status === 200){ resolve(xhr.response) } else if(xhr.status === 404) { reject(xhr.response) } // 其它情况也需考虑,这里简化处理 } xhr.open('GET', url) xhr.responseType = 'json' xhr.send() }) }
-
Promise 对象适合用来封装异步操作,并可以配合 await 一齐使用
-
Promise 在构造时,需要一个箭头函数,箭头函数有两个参数 resolve 和 reject
-
resolve 是异步操作成功时被调用,把成功的结果传递给它,最后会作为 await 的结果返回
-
reject 在异步操作失败时被调用,把失败的结果传递给它,最后在 catch 块被捉住
-
-
await 会一直等到 Promise 内调用了 resolve 或 reject 才会继续向下运行
-
调用示例1:同步接收结果,不走代理
-
try { const resp = await get("http://localhost:8080/api/students") console.log(resp) } catch (e) { console.error(e) }
调用示例2:走代理
-
try { const resp = await get('/api/students') console.log(resp) } catch(e) { console.log(e) }
-
走代理明显慢不少
axios
基本用法
axios 就是对 xhr API 的封装,手法与前面例子类似
安装
npm install axios
一个简单的例子
<script setup lang="ts">
import { ref, onMounted } from "vue";
import axios from "axios";
let count = ref(0);
async function getStudents() {
try {
const resp = await axios.get("/api/students");
count.value = resp.data.data.length;
} catch (e) {
console.log(e);
}
}
onMounted(() => {
getStudents()
})
</script>
<template>
<h2>学生人数为:{{ count }}</h2>
</template>
onMounted 指 vue 组件生成的 HTML 代码片段,挂载完毕后被执行
再来看一个 post 例子
<script setup lang="ts">
import { ref } from "vue";
import axios from "axios";
const student = ref({
name: '',
sex: '男',
age: 18
})
async function addStudent() {
console.log(student.value)
const resp = await axios.post('/api/students', student.value)
console.log(resp.data.data)
}
</script>
<template>
<div>
<div>
<input type="text" placeholder="请输入姓名" v-model="student.name"/>
</div>
<div>
<label for="">请选择性别</label>
男 <input type="radio" value="男" v-model="student.sex"/>
女 <input type="radio" value="女" v-model="student.sex"/>
</div>
<div>
<input type="number" placeholder="请输入年龄" v-model="student.age"/>
</div>
<div>
<input type="button" value="添加" @click="addStudent"/>
</div>
</div>
</template>
<style scoped>
div {
font-size: 14px;
}
</style>
环境变量
-
开发环境下,联调的后端服务器地址是
http://localhost:8080
, -
上线改为生产环境后,后端服务器地址为
http://lalala.com
这就要求我们区分开发环境和生产环境,这件事交给构建工具 vite 来做
默认情况下,vite 支持上面两种环境,分别对应根目录下两个配置文件
-
.env.development - 开发环境
-
.env.production - 生产环境
针对以上需求,分别在两个文件中加入
//VITE_BACKEND_API_BASE_URL是自定义的名字,但前面的VITE_是不能变的
VITE_BACKEND_API_BASE_URL = 'http://localhost:8080'
和
VITE_BACKEND_API_BASE_URL = 'http://lalala.com'
然后在代码中使用 vite 给我们提供的特殊对象 import.meta.env
,就可以获取到 VITE_BACKEND_API_BASE_URL
在不同环境下的值
import.meta.env.VITE_BACKEND_API_BASE_URL
在main.ts文件中添加代码console.log(import.meta.env.VITE_BACKEND_API_BASE_URL)
在控制台运行开发环境npm run dev
在控制台运行生产环境1,先打包 npm run build2,然后在把项目部署到服务器
执行打包命令会在在项目的/dist/assets目录下生成一个以index开头的js文件,查找console.log,会找到生产环境下的BASE_URL的值
默认情况下,不能智能提示自定义的环境变量,做如下配置:新增文件 src/env.d.ts
并添加如下内容
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_BACKEND_API_BASE_URL: string
// 更多环境变量...
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
参考文档地址 环境变量和模式 | Vite 官方中文文档 (vitejs.dev)
baseURL
可以自己创建一个 axios 对象,方便添加默认设置,新建文件 /src/api/request.ts
// 创建新的 axios 对象
import axios from 'axios'
const _axios = axios.create({
baseURL: import.meta.env.VITE_BACKEND_API_BASE_URL
})
export default _axios
然后在其它组件中引用这个 ts 文件,例如 /src/views/E8.vue,就不用自己拼接路径前缀了
<script setup lang="ts">
import axios from '../api/request'
// ...
await axios.post('/api/students', ...)
</script>
拦截器
// 创建新的 axios 对象
import axios from 'axios'
const _axios = axios.create({
baseURL: import.meta.env.VITE_BACKEND_API_BASE_URL
})
// 请求拦截器
_axios.interceptors.request.use(
//如果请求成功做的统一处理
(config)=>{ // 统一添加请求头
config.headers = {
Authorization: 'aaa.bbb.ccc'
}
return config
},
(error)=>{ // 请求出错时的处理
return Promise.reject(error)
}
)
// 响应拦截器
_axios.interceptors.response.use(
(response)=>{ // 状态码 2xx
// 这里的code是自定义的状态码
if(response.data.code === 200) {
return response
}
else if(response.data.code === 401) {
// 情况1
return Promise.resolve({})
}
// ...
},
(error)=>{ // 状态码 > 2xx, 400,401,403,404,500
console.error(error) // 处理了异常
if(error.response.status === 400) {
// 情况1
} else if(error.response.status === 401) {
// 情况2
}
// ...
return Promise.resolve({})
}
)
export default _axios
处理响应时,又分成两种情况
-
后端返回的是标准响应状态码,这时会走响应拦截器第二个箭头函数,用 error.response.status 做分支判断
-
后端返回的响应状态码总是200,用自定义错误码表示出错,这时会走响应拦截器第一个箭头函数,用 response.data.code 做分支判断
另外
-
Promise.reject(error) 类似于将异常继续向上抛出,异常由调用者(Vue组件)来配合 try ... catch 来处理
-
Promise.resolve({}) 表示错误已解决,返回一个空对象,调用者中接到这个空对象时,需要配合 ?. 来避免访问不存在的属性
条件与列表
首先,新增模型数据 src/model/Model8080.ts
export interface Student {
id: number;
name: string;
sex: string;
age: number;
}
// 如果 spring 错误,返回的对象格式
export interface SpringError {
timestamp: string,
status: number,
error: string,
message: string,
path: string
}
// 如果 spring 成功,返回 list 情况
export interface SpringList<T> {
data: T[],
message?: string,
code: number
}
// 如果 spring 成功,返回 page 情况
export interface SpringPage<T> {
data: { list: T[], total: number },
message?: string,
code: number
}
// 如果 spring 成功,返回 string 情况
export interface SpringString {
data: string,
message?: string,
code: number
}
import { AxiosResponse } from 'axios'
export interface AxiosRespError extends AxiosResponse<SpringError> { }
export interface AxiosRespList<T> extends AxiosResponse<SpringList<T>> { }
export interface AxiosRespPage<T> extends AxiosResponse<SpringPage<T>> { }
export interface AxiosRespString extends AxiosResponse<SpringString> { }
其中
-
AxiosRespPage 代表分页时的响应类型
-
AxiosRespList 代表返回集合时的响应类型
-
AxiosRespString 代表返回字符串时的响应类型
-
AxiosRespError 代表 Spring 出错时时的响应类型
-
<script lang="ts" setup> import { ref, onMounted } from "vue"; import axios from "../api/request"; import { Student, SpringList } from "../model/Model8080"; // 说明 students 数组类型为 Student[] const students = ref<Student[]>([]); async function getStudents() { // 说明 resp.data 类型是 SpringList<Student> const resp = await axios.get<SpringList<Student>>("/api/students"); console.log(resp.data.data); students.value = resp.data.data; } onMounted(() => getStudents()); </script> <template> <div class="outer"> <div class="title">学生列表</div> <div class="thead"> <div class="row bold"> <div class="col">编号</div> <div class="col">姓名</div> <div class="col">性别</div> <div class="col">年龄</div> </div> </div> <div class="tbody"> <div v-if="students.length === 0">暂无数据</div> <template v-else> <div class="row" v-for="s of students" :key="s.id"> <div class="col">{{ s.id }}</div> <div class="col">{{ s.name }}</div> <div class="col">{{ s.sex }}</div> <div class="col">{{ s.age }}</div> </div> </template> </div> </div> </template> <style scoped> .outer { font-family: 华文行楷; font-size: 20px; width: 500px; } .title { margin-bottom: 10px; font-size: 30px; color: #333; text-align: center; } .row { background-color: #fff; display: flex; justify-content: center; } .col { border: 1px solid #f0f0f0; width: 15%; height: 35px; text-align: center; line-height: 35px; } .bold .col { background-color: #f1f1f1; } </style>
-
加入泛型是为了更好的提示
-
v-if 与 v-else 不能和 v-for 处于同一标签
-
template 标签还有一个用途,就是用它少生成一层真正 HTML 代码
-
可以看到将结果封装为响应式数据还是比较繁琐的,后面会使用 useRequest 改进
-
监听器(参考代码E10.vue)
利用监听器,可以在【响应式】的基础上添加一些副作用,把更多的东西变成【响应式的】
-
原本只是数据变化 => 页面更新
-
watch 可以在数据变化时 => 其它更新
<template> <input type="text" v-model="name" /> </template> <script setup lang="ts"> import { ref, watch } from "vue"; function useStorage(name: string) { const data = ref(sessionStorage.getItem(name) ?? ""); watch(data, (newValue) => { sessionStorage.setItem(name, newValue); }); return data; } const name = useStorage("name"); </script>
-
名称为 useXXXX 的函数,作用是返回带扩展功能的【响应式】数据
-
localStorage 即使浏览器关闭,数据还在
-
sessionStorage 数据工作在浏览器活动期间