目录
一、环境搭建
创建项目
编码 IDE
修改端口
配置代理
项目架构
二、Vue组件
main.ts
属性绑定
事件绑定
表单绑定
计算属性
xhr
axios
环境变量
baseURL
拦截器
条件和列表
监听器
vueuse
useRequest
usePagination(分页)
子组件
采用 vite 作为前端项目的打包,构建工具
npm init vite@latest
按照提示操作
cd 项目目录
npm install
npm run dev
推荐采用微软的 VSCode 作为开发工具,到它的官网 Visual Studio Code - Code Editing. Redefined 下载安装即可。
要对 *.vue 做语法支持,还要安装一个 Volar 插件
打开项目根目录下 vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
server: {
port: 7070
}
})
文档地址:配置 Vite {#configuring-vite} | Vite中文网 (vitejs.cn)
为了避免前后端服务器联调时, fetch、xhr 请求产生跨域问题,需要配置代理,同样是修改项目根目录下 vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
server: {
port: 7070,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
})
index.html 为主页面
package.json npm 配置文件
tsconfig.json typescript 配置文件
vite.config.ts vite 配置文件
public 静态资源
src/components 可重用组件
src/model 模型定义
src/router 路由
src/store 共享存储
src/views 视图组件
ue 的组件文件以 .vue 结尾,每个组件由三部分组成
script 代码部分,控制模板的数据来源和行为
template 模板部分,由它生成 html 代码
style 样式部分
根组件是 src/App.vue,先来个 Hello,world 例子
{{ msg }}
{{msg}} 用来把一个变量绑定到页面上某个位置
绑定的变量必须用 ref 函数来封装
ref 返回的是【响应式】数据,即数据一旦变化,页面展示也跟着变化
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
createApp(App)
.mount('#app')
createApp 是创建一个 Vue 应用程序,它接收的参数 App 即之前我们看到的根组件
mount 就是把根组件生成的 html 代码片段【挂载】到 index.html 中 id 为 app 的 html 元素上
可以修改自己的组件文件,挂载到主页面。
新建 src/views/E0.vue,内容如下
{{ msg }}
修改 main.ts 将自己的组件文件挂载
import { createApp } from 'vue'
import './style.css'
// import App from './App.vue'
import E0 from './views/E0.vue'
createApp(E0).mount('#app')
打开浏览器控制台,进入 Vue 的开发工具,尝试做如下修改
当把 msg 的值由 "Hello, World" 改为 "你好" 时,会发现页面展示同步发生了变化 ,这个就是响应式。
【:属性名】用来将标签属性与【响应式】变量绑定 v-bind
【@事件名】用来将标签属性与函数绑定,事件发生后执行函数内代码
{{count}}
用 v-model 实现双向绑定,即
javascript 数据可以同步到表单标签
反过来用户在表单标签输入的新值也会同步到 javascript 这边
双向绑定只适用于表单这种带【输入】功能的标签,其它标签的数据绑定,单向就足够了
复选框这种标签,双向绑定的 javascript 数据类型一般用数组
男
女
游泳
打球
健身
有时在数据展示时要做简单的计算
{{lastName + firstName}}
{{lastName + firstName}}
{{lastName + firstName}}
看起来较为繁琐,可以用计算属性改进
{{fullName}}
{{fullName}}
{{fullName}}
fullName 即为计算属性,它具备缓存功能,即 firstName 和 lastName 的值发生了变化,才会重新计算
如果用函数实现相同功能,则没有缓存功能。
{{fullName()}}
{{fullName()}}
{{fullName()}}
浏览器中有两套 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()
xhr.onload函数会在xhr接收到响应后才执行方法。
但这套 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 就是对 xhr api 的封装,手法与前面例子类似。
安装
npm install axios
一个简单的例子
学生人数为:{{ count }}
onMounted 指 vue 组件生成的 html 代码片段,挂载完毕后被执行
再来看一个 post 例子
男
女
开发环境下,联调的后端服务器地址是 http://localhost:8080
,
上线改为生产环境后,后端服务器地址为 http://itheima.com
这就要求我们区分开发环境和生产环境,这件事交给构建工具 vite 来做
默认情况下,vite 支持上面两种环境,需要我们分别在对应根目录下创建两个配置文件
.env.development - 开发环境
.env.production - 生产环境
针对以上需求,分别在两个文件中加入
VITE_BACKEND_API_BASE_URL = 'http://localhost:8080'
和
VITE_BACKEND_API_BASE_URL = 'http://itheima.com'
然后在代码中使用 vite 给我们提供的特殊对象 import.meta.env
,就可以获取到 VITE_BACKEND_API_BASE_URL
在不同环境下的值
import.meta.env.VITE_BACKEND_API_BASE_URL
默认情况下,不能智能提示自定义的环境变量,做如下配置:新增文件 src/env.d.ts
并添加如下内容
///
interface ImportMetaEnv {
readonly VITE_BACKEND_API_BASE_URL: string
// 更多环境变量...
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
参考文档地址 环境变量和模式 | Vite 官方中文文档 (vitejs.dev)
可以自己创建一个 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,就不用自己拼接路径前缀了
// 创建新的 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 {
data: T[],
message?: string,
code: number
}
// 如果 spring 成功,返回 page 情况
export interface SpringPage {
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 { }
export interface AxiosRespList extends AxiosResponse> { }
export interface AxiosRespPage extends AxiosResponse> { }
export interface AxiosRespString extends AxiosResponse { }
其中
AxiosRespPage 代表分页时的响应类型
AxiosRespList 代表返回集合时的响应类型
AxiosRespString 代表返回字符串时的响应类型
AxiosRespError 代表 Spring 出错时时的响应类
学生列表
编号
姓名
性别
年龄
暂无数据
{{ s.id }}
{{ s.name }}
{{ s.sex }}
{{ s.age }}
加入泛型是为了更好的提示
v-if 与 v-else 不能和 v-for 处于同一标签
template 标签还有一个用途,就是用它少生成一层真正 html 代码
可以看到将结果封装为响应式数据还是比较繁琐的,后面会使用 useRequest 改进
利用监听器,可以在【响应式】的基础上添加一些副作用,把更多的东西变成【响应式的】
原本只是数据变化 => 页面更新
watch 可以在数据变化时 => 其它更新
下述代码就可以通过watch监听事件和sessionStorage 来实现响应式数据。
watch监听当数据发生变化是,存入sessionStorage,然后在通过sessionStorage获取新值
名称为 useXXXX 的函数,作用是返回带扩展功能的【响应式】数据
localStorage 即使浏览器关闭,数据还在
sessionStorage 数据工作在浏览器活动期间
安装
npm install @vueuse/core
一些函数的用法
useMouse:鼠标移动、useCount:数字运算、useStorage: 存取数据
X: {{x}}
Y: {{y}}
{{count}}
响应式的 axios 封装,官网地址 一个 Vue 请求库 | VueRequest (attojs.org)
首先安装 vue-request
npm install vue-request@next
组件
暂无数据
-
{{s.name}}
{{s.sex}}
{{s.age}}
data.value 的取值一开始是 undefined,随着响应返回变成 axios 的响应对象
用 computed 进行适配
在 src/model/Model8080.ts 中补充类型说明
export interface StudentQueryDto {
name?: string,
sex?: string,
age?: string, // 18,20
page: number,
size: number
}
js 中类似于 18,20 这样以逗号分隔字符串,会在 get 传参时转换为 java 中的整数数组
编写组件
暂无数据
-
{{s.name}}
{{s.sex}}
{{s.age}}
总记录数{{total}} 总页数{{totalPage}}
usePagination 只需要定义一次,后续还想用它内部的 axios 发请求,只需调用 run 函数
例1
定义子组件 Child1
{{name}}
{{country}}
父组件引用
例2
首先添加类型说明 model/ModelRandomUser.ts
import { AxiosResponse } from "axios";
export interface AxiosRespResults extends AxiosResponse{}
export interface Results {
info: {
page: number,
results: number
},
results: Result[]
}
export interface Result {
gender: 'male' | 'female',
name: {
first: string,
last: string
},
location: {
country: string
},
picture: {
medium: string
},
login: {
username: string
}
}
子组件不变,父组件使用子组件
如果觉得 Result 数据结构嵌套太复杂,还可以做一个类型映射
resultToUser 将 Result 类型映射为 User 类型