1. Vue2 基础
1) 环境准备
安装脚手架
npm install -g @vue/cli
- -g 参数表示全局安装,这样在任意目录都可以使用 vue 脚本创建项目
创建项目
vue ui
使用图形向导来创建 vue 项目,如下图,输入项目名
选择手动配置项目
添加 vue router 和 vuex
选择版本,创建项目
安装 devtools
- devtools 插件网址:https://devtools.vuejs.org/guide/installation.html
运行项目
进入项目目录,执行
npm run serve
修改端口
前端服务器默认占用了 8080 端口,需要修改一下
-
文档地址:DevServer | webpack
-
打开 vue.config.js 添加
const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({ // ... devServer: { port: 7070 } })
添加代理
为了避免前后端服务器联调时, fetch、xhr 请求产生跨域问题,需要配置代理
-
文档地址同上
-
打开 vue.config.js 添加
const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({ // ... devServer: { port: 7070, proxy: { '/api': { //如localhost:8080/api/**的所有请求都会走代理 target: 'http://localhost:8080', changeOrigin: true } } } })
Vue 项目结构
PS D:\2022.js\代码\第3章\client> tree src
D:\2022.JS\代码\第3章\CLIENT\SRC
├─assets
├─components
├─router
├─store
└─views
- assets - 静态资源
- components - 可重用组件
- router - 路由
- store - 数据共享
- views - 视图组件
以后还会添加
- api - 跟后台交互,发送 fetch、xhr 请求,接收响应
- plugins - 插件
2) Vue 组件
Vue 的组件文件以 .vue 结尾,每个组件由三部分组成
<template></template>
<script></script>
<style></style>
- template 模板部分,由它生成 html 代码
- script 代码部分,控制模板的数据来源和行为
- style 样式部分,一般不咋关心
入口组件是 App.vue
先删除原有代码,来个 Hello, World 例子
<template>
<h1>{{msg}}</h1>
</template>
<script>
export default {
data() {
return {
msg: "Hello, Vue!"
}
}
}
</script>
解释
- export default 导出组件对象,供 main.js 导入使用
- 这个对象有一个 data 方法,返回一个对象,给 template 提供数据
{{}}
在 Vue 里称之为插值表达式,用来绑定 data 方法返回的对象属性,绑定的含义是数据发生变化时,页面显示会同步变化
文本插值
<template>
<div>
<h1>{{ name }}</h1>
<h1>{{ age > 60 ? '老年' : '青年' }}</h1>
</div>
</template>
<script>
const options = {
data: function () {
return { name: '张三', age: 70 };
}
};
export default options;
</script>
{{}}
里只能绑定一个属性,绑定多个属性需要用多个{{}}
分别绑定- template 内只能有一个根元素
- 插值内可以进行简单的表达式计算
属性绑定
<template>
<div>
<div><input type="text" v-bind:value="name"></div>
<div><input type="date" v-bind:value="birthday"></div>
<div><input type="text" :value="age"></div>
</div>
</template>
<script>
const options = {
data: function () {
return { name: '王五', birthday: '1995-05-01', age: 20 };
}
};
export default options;
</script>
- 简写方式:可以省略 v-bind 只保留冒号
事件绑定
<!-- 事件绑定 -->
<template>
<div>
<div><input type="button" value="点我执行m1" v-on:click="m1"></div>
<div><input type="button" value="点我执行m2" @click="m2"></div>
<div>{{count}}</div>
</div>
</template>
<script>
const options = {
data: function () {
return { count: 0 };
},
methods: {
m1() {
this.count ++;
console.log("m1")
},
m2() {
this.count --;
console.log("m2")
}
}
};
export default options;
</script>
- 简写方式:可以把 v-on: 替换为 @
- 在 methods 方法中的 this 代表的是 data 函数返回的数据对象
双向绑定
<template>
<div>
<div>
<label for="">请输入姓名</label>
<input type="text" v-model="name">
</div>
<div>
<label for="">请输入年龄</label>
<input type="text" v-model="age">
</div>
<div>
<label for="">请选择性别</label>
男 <input type="radio" value="男" v-model="sex">
女 <input type="radio" value="女" v-model="sex">
</div>
<div>
<label for="">请选择爱好</label>
游泳 <input type="checkbox" value="游泳" v-model="fav">
打球 <input type="checkbox" value="打球" v-model="fav">
健身 <input type="checkbox" value="健身" v-model="fav">
</div>
</div>
</template>
<script>
const options = {
data: function () {
return { name: '', age: null, sex:'男' , fav:['打球']};
},
methods: {
}
};
export default options;
</script>
- 用 v-model 实现双向绑定,即
- javascript 数据可以同步到表单标签
- 反过来用户在表单标签输入的新值也会同步到 javascript 这边
- 双向绑定只适用于表单这种带【输入】功能的标签,其它标签的数据绑定,单向就足够了
- 复选框这种标签,双向绑定的 javascript 数据类型一般用数组
计算属性
<!-- 计算属性 -->
<template>
<div>
<!-- <h2>{{fullName()}}</h2>-->//使用方法
<h2>{{fullName}}</h2> //使用计算属性,区别就是有无加括号
<h2>{{fullName}}</h2>
</div>
</template>
<script>
const options = {
data: function () {
return { firstName: '三', lastName: '张' };
},
/* methods: {
fullName() {
console.log('进入了 fullName')
return this.lastName + this.firstName;
}
},*/
computed: {
fullName() {
console.log('进入了 fullName')
return this.lastName + this.firstName;
}
}
};
export default options;
- 普通方法调用必须加 (),没有缓存功能
- 计算属性使用时就把它当属性来用,不加 (),有缓存功能:
- 一次计算后,会将结果缓存,下次再计算时,只要数据没有变化,不会重新计算,直接返回缓存结果
axios
axios 它的底层是用了 XMLHttpRequest(xhr)方式发送请求和接收响应,xhr 相对于之前讲过的 fetch api 来说,功能更强大,但由于是比较老的 api,不支持 Promise,axios 对 xhr 进行了封装,使之支持 Promise,并提供了对请求、响应的统一拦截功能
安装
npm install axios -S
导入
import axios from 'axios'
- axios 默认导出一个对象,这里的 import 导入的就是它默认导出的对象
方法
请求 | 备注 |
---|---|
axios.get(url[, config]) | ⭐️ |
axios.delete(url[, config]) | |
axios.head(url[, config]) | |
axios.options(url[, config]) | |
axios.post(url[, data[, config]]) | ⭐️ |
axios.put(url[, data[, config]]) | |
axios.patch(url[, data[, config]]) |
- config - 选项对象、例如查询参数、请求头…
- data - 请求体数据、最常见的是 json 格式数据
- get、head 请求无法携带请求体,这应当是浏览器的限制所致(xhr、fetch api 均有限制)
- options、delete 请求可以通过 config 中的 data 携带请求体
例子
<template>
<div>
<input type="button" value="获取远程数据" @click="sendReq()">
</div>
</template>
<script>
import axios from 'axios'
const options = {
methods: {
async sendReq() {
// 1. 演示 get, post
// const resp = await axios.post('/api/a2');
// 2. 发送请求头
// const resp = await axios.post('/api/a3',{},{
// headers:{
// Authorization:'abc'
// }
// });
// 3. 发送请求时携带查询参数 ?name=xxx&age=xxx
// const name = encodeURIComponent('&&&');
// const age = 18;
// const resp = await axios.post(`/api/a4?name=${name}&age=${age}`);
// 不想自己拼串、处理特殊字符、就用下面的办法
// const resp = await axios.post('/api/a4', {}, {
// params: {
// name:'&&&&',
// age: 20
// }
// });
// 4. 用请求体发数据,格式为 urlencoded
// const params = new URLSearchParams();
// params.append("name", "张三");
// params.append("age", 24)
// const resp = await axios.post('/api/a4', params);
// 5. 用请求体发数据,格式为 multipart
// const params = new FormData();
// params.append("name", "李四");
// params.append("age", 30);
// const resp = await axios.post('/api/a5', params);
// 6. 用请求体发数据,格式为 json
const resp = await axios.post('/api/a5json', {
name: '王五',
age: 50
});
console.log(resp);
}
}
};
export default options;
</script>
创建实例
const _axios = axios.create(config);
- axios 对象可以直接使用,但使用的是默认的设置
- 用 axios.create 创建的对象,可以覆盖默认设置,config 见下面说明
解决跨域问题
常见的 config 项有
名称 | 含义 |
---|---|
baseURL | 将自动加在 url 前面 |
headers | 请求头,类型为简单对象 |
params | 跟在 URL 后的请求参数,类型为简单对象或 URLSearchParams |
data | 请求体,类型有简单对象、FormData、URLSearchParams、File 等 |
withCredentials | 跨域时是否携带 Cookie 等凭证,默认为 false |
responseType | 响应类型,默认为 json |
例
const _axios = axios.create({
baseURL: 'http://localhost:8080',
withCredentials: true
});
await _axios.post('/api/a6set')
await _axios.post('/api/a6get')
- 生产环境希望 xhr 请求不走代理,可以用 baseURL 统一修改
- 希望跨域请求携带 cookie,需要配置 withCredentials: true,服务器也要配置 allowCredentials = true,否则浏览器获取跨域返回的 cookie 时会报错
响应格式
名称 | 含义 |
---|---|
data | 响应体数据 ⭐️ |
status | 状态码 ⭐️ |
headers | 响应头 |
- 200 表示响应成功
- 400 请求数据不正确 age=abc
- 401 身份验证没通过
- 403 没有权限
- 404 资源不存在
- 405 不支持请求方式 :如接受的请求为Get,发送的请求post
- 500 服务器内部错误
请求拦截器(发送请求到服务端时拦截)
_axios.interceptors.request.use(
function(config) {
// 比如在这里添加统一的 headers
return config;
},
function(error) {
return Promise.reject(error);
}
);
响应拦截器(服务端响应数据后进行拦截)
_axios.interceptors.response.use(
function(response) {
// 2xx 范围内走这里
return response;
},
function(error) {
// 超出 2xx, 比如 4xx, 5xx 走这里
return Promise.reject(error);
}
);
条件渲染
<template>
<div>
<input type="button" value="获取远程数据" @click="sendReq()">
<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 class="row" v-if="students.length > 0">显示学生数据</div>
<div class="row" v-else>暂无学生数据</div>
</div>
</div>
</template>
<script>
import axios from '../util/myaxios'
const options = {
data: function() {
return {
students: []
};
},
methods : {
async sendReq() {
const resp = await axios.get("/api/students");
console.log(resp.data.data)
this.students = resp.data.data;
}
}
};
export default options;
</script>
<style scoped>
div {
font-family: 华文行楷;
font-size: 20px;
}
.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>
列表渲染
<template>
<div>
<!-- <input type="button" value="获取远程数据" @click="sendReq()"> -->
<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 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>
</div>
<div class="row" v-else>暂无学生数据</div>
</div>
</div>
</template>
<script>
import axios from '../util/myaxios'
const options = {
mounted: function(){
this.sendReq()
},
data: function() {
return {
students: []
};
},
methods : {
async sendReq() {
const resp = await axios.get("/api/students");
console.log(resp.data.data)
this.students = resp.data.data;
}
}
};
export default options;
</script>
- v-if 和 v-for 不能用于同一个标签
- v-for 需要配合特殊的标签属性 key 一起使用,并且 key 属性要绑定到一个能起到唯一标识作用的数据上,本例绑定到了学生编号上
- options 的 mounted 属性对应一个函数,此函数会在组件挂载后(准备就绪)被调用,可以在它内部发起请求,去获取学生数据
重用组件
按钮组件
<template>
<div class="button" :class="[type,size]">
a<slot></slot>b
</div>
</template>
<script>
const options = {
props: ["type", "size"]
};
export default options;
</script>
- 注意,省略了样式部分
使用组件
<template>
<div>
<h1>父组件</h1>
<my-button type="primary" size="small">1</my-button>
<my-button type="danger" size="middle">2</my-button>
<my-button type="success" size="large">3</my-button>
</div>
</template>
<script>
import MyButton from '../components/MyButton.vue'
const options = {
components: {
MyButton
}
};
export default options;
</script>