npm install -g @vue/cli
vue ui
使用图形向导来创建 vue 项目,如下图:
不想用git,可以取消勾选初始化git仓库,也可以创建完之后,删除.git文件夹
一个是实现组件之间的跳转,一个是实现组件之间数据的共享。
安装完毕后会跳转到一个页面:
官方浏览器插件,用于调试 Vue.js 应用。你可以检查组件、vuex store 和事件等。
进入项目目录,执行
npm run serve
启动成功:
前端服务器默认占用了 8080 端口,需要修改一下
const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({ // ... devServer: { port: 8082 // 设置前端服务器端口 } })
为了避免前后端服务器联调时, fetch、xhr 请求产生跨域问题,需要配置代理
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
// ...
devServer: {
// 端口
port: 8082,
// 代理,将来只要以/api打头的请求,都会走代理的逻辑
proxy: {
'/api': {
target: 'http://localhost:8080', //设置成后端的服务器地址
changeOrigin: true
}
}
}
})
测试:
后端服务器在8080端口:
PS D:\code\VScodeProjects\fountendstudy\study03-vue2> tree src
D:\CODE\VSCODEPROJECTS\FOUNTENDSTUDY\STUDY03-VUE2\SRC
├─assets
├─components
├─router
├─store
└─views
以后还会添加
Vue 的组件文件以 .vue 结尾,每个组件由三部分组成
<template></template>
<script></script>
<style></style>
入口组件是 App.vue
先删除原有代码,来个 Hello, World 例子
<template>
<!-- {{}} 文本插值 -->
<h1>{{msg}}</h1>
</template>
<script>
// vue组件的`<script>`中必须默认导出一个`options`对象
const options = {
data: function(){
// options的属性data,的函数返回值才是模板要使用的数据对象
return {msg:"Hello World!"};
}
};
export default options;
</script>
注意: vue组件的
<script>
中必须默认导出一个options
对象。
{{}}
在 Vue 里称之为插值表达式,用来绑定 data 方法返回的对象属性,绑定的含义是数据发生变化时,页面显示会同步变化那么是谁在使用App.vue这个组件?
main.js
导入了App.vue:
import Vue from 'vue'
// 1.
import App from './App.vue'
import router from './router'
import store from './store'
Vue.config.productionTip = false
new Vue({
router,
store,
// 2.
render: h => h(App)
// 3.
}).$mount('#app')
import App from './App.vue'
:实这个导入我们可以简单理解为把App.vue的模板部分拿到了main.js,并对模板部分进一步解析(h => h(App)
),最终将{{msg}}解析成hello world。h => h(App)
:对模板进行解析,解析后生成一个虚拟节点(简单理解,他也是一种HTML元素,只不过还没有跟最终的页面结合到一起,还没有显示出来)$mount('#app')
:上面提到了虚拟节点未显示出来,那么哪一步将这个虚拟的节点显示到页面上呢?
$mount('#app')
:会把解析后的虚拟节点放到页面上展示出来。
放到哪里去了呢?
#app
就是一个id选择器:
可以看到/public/index.html
里有这个标签,所以解析后的虚拟节点放到这里。
我们打开F12,选中Hello world可以看到index.html里的<div id="app"></div>
被替换成了解析后的虚拟节点:
<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>
<h1>{{name age}}</h1>
</template>
// 会编译报错。
<template>
<h1>{{name}}</h1>
<h1>{{age}}</h1>
</template>
// 会报错:
// error The template root requires exactly one element vue/no-multiple-template-root
对于标签中的文本数据,可以使用文本插值{{}}进行绑定,但是对于标签里的属性来讲,他的语法就不一样了,这就用到了属性绑定:
<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>
<!-- 事件绑定 -->
<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>
data
是给模板提供数据的,methods
是给模板提供方法的。先阅读下面的代码:
<template>
<div>
<div>
<label>请输入姓名:</label>
<input type="text" v-bind:value="name">
</div>
<div>
<label>请输入年龄:</label>
<input type="text" v-bind:value="age">
</div>
</div>
</template>
<script>
const optinos = {
data: function () {
return { name: '李刚', age: null }
},
methods: {
}
};
export default optinos;
</script>
这段代码就是将javaScript的数据与标签中的属性进行绑定,但是这种绑定是单向的,只能将javaScript中的数据传到文本框中,但是文本框中用户输入的数据无法同步到javaScript这边。
这里的双向绑定就是用户输入的数据也同步到javaScript这边:
<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>
<checkbox>
这种标签,双向绑定的 javascript 数据类型一般用数组vue的调试工具: F12-->vue,就可以看到之前下载的devtools调试工具了:
可以在这里看到data
返回的数据对象的变化:
<!-- 计算属性 -->
<template>
<div>
<!-- 方法1:直接拼接-->
<!-- <h2>{{lastName + firstName}}</h2>-->
<!-- 方法2:使用方法-->
<!-- <h2>{{fullName()}}</h2>-->
<!-- 方法3:采用计算属性的写法(推荐)-->
<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;
fullName()
发生计算后,会将结果缓存,下次再计算时,只要数据没有变化,不会重新计算,直接返回缓存结果。
使用methods:<template>
<div>
<h1>{{fullName()}}</h1>
<h1>{{fullName()}}</h1>
<h1>{{fullName()}}</h1>
</div>
</template>
使用computed:
<template>
<div>
<h1>{{fullName}}</h1>
<h1>{{fullName}}</h1>
<h1>{{fullName}}</h1>
</div>
</template>
axios 它的底层是用了 XMLHttpRequest(xhr)方式发送请求和接收响应,xhr 相对于之前讲过的 fetch api 来说,功能更强大,但由于是比较老的 api,不支持 Promise,axios 对 xhr 进行了封装,使之支持 Promise,并提供了对请求、响应的统一拦截功能(相当于后端的过滤器,拦截器)
axios就是 ajax的一种实现。因为axios的底层是XMLHttpRequest,所以会发生跨域,下面因为使用了代理,所以没有出现跨域的问题。
npm install axios -S
import axios from 'axios'
<template>
<div>
<input type="button" value="获取远程数据" v-on:click="sendRequest()">
</div>
</template>
<script>
// 导入axiso
import axios from 'axios'
const options = {
methods:{
async sendRequest(){
const resp = await axios.get("/api/students");
console.log(resp)
}
}
};
export default options
</script>
F12,查看控制台:
请求 | 备注 |
---|---|
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]]) |
例子
<template>
<div>
<input type="button" value="获取远程数据" @click="sendReq()">
</div>
</template>
<script>
import axios from 'axios'
const options = {
methods: {
async sendReq() {
//因为我们配置了代理,以/api打头的请求都会代理到8080,所以axiso发起的请求不用写那么完整。
------------------------------------------------------------------------
// 1. 演示 get, post
// const resp = await axios.get('/api/a1');
// const resp = await axios.post('/api/a2');
------------------------------------------------------------------------
// 2. 发送请求头
// go使用r.Header.Get()接收
// const resp = await axios.post('/api/a3',{},{
// headers:{
// Authorization:'abc'
// }
// });
------------------------------------------------------------------------
// 3. 发送请求时携带查询参数 ?name=xxx&age=xxx
// go使用r.FormValue()接收
// 方法1:拼字符串
// const name = encodeURIComponent('&&&');
// const age = 18;
// const resp = await axios.post(`/api/a4?name=${name}&age=${age}`);
// 方法2(推荐):
// 不想自己拼串、处理特殊字符、就用下面的办法
// const resp = await axios.post('/api/a4', {}, {
// params: {
// "name":'&&&&',
// "age": 20
// }
// });
------------------------------------------------------------------------
// 4. 用请求体发数据,格式为 urlencoded
// go使用r.FormValue()接收
// const params = new URLSearchParams();
// params.append("name", "张三");
// params.append("age", 24)
// const resp = await axios.post('/api/a4', params);
------------------------------------------------------------------------
// 5. 用请求体发数据,格式为 multipart,
// go使用r.ParseMultipartForm()接收
// const params = new FormData();
// params.append("name", "李四");
// params.append("age", 30);
// const resp = await axios.post('/api/a5', params);
------------------------------------------------------------------------
// 6. 用请求体发数据,格式为 json
// go 使用 io.ReadAll(r.Body)+json.Unmarshal() 接收
const resp = await axios.post('/api/a5json', {
name: '王五',
age: 50
});
------------------------------------------------------------------------
console.log(resp);
}
}
};
export default options;
</script>
const name = encodeURIComponent('&&&');
const resp = await axios.post('/api/a4', {
name: "123",
age: 20
});
console.log(resp)
× //fmt.Println("name: "+r.FormValue("name"), "age: "+r.FormValue("age"))
---------------------------------------------------------------------
type student struct {
Name string
Age int
}
stu := new(student)
body, _ := io.ReadAll(r.Body)
json.Unmarshal(body, stu)
fmt.Println(stu)
上面使用axios,是import之后直接使用它里面那些发送请求的方法,这样做是有一个问题的,这种情况下,我们发送每个请求的时候使用的都是默认设置,如果你发请求的时候不想用他的默认设置了,那每个请求方法里都需要跟上config参数,这样不够通用。
解决办法就是,自己创建axios对象,进行配置。
创建实例
const _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')
Access-Control-Allow-Origin
头。)
使用代理的方式主要是用在开发环境,中间经过代理,性能肯定会受到影响,真正生存环境中解决跨域问题是不用代理的。名称 | 含义 |
---|---|
data | 响应体数据 ⭐️ |
status | 状态码 ⭐️ |
headers | 响应头 |
注意: 这个status响应状态码与后端经常返回的code不一样,后端返回的code可以根据项目来设置,比如用1001表示错误1,1002表示错误2... 后端经常返回的code时候应用程序的状态码 这个status则是整个响应的状态码,是HTTP协议固定好的。
例子:
响应状态码200以下都会正常进行,200以上会出现异常,不在往下执行。
<template>
<div>
<input type="button" value="获取远程数据" v-on:click="sendRequest()">
</div>
</template>
<script>
// 导入axiso
import axios from 'axios'
const options = {
methods: {
async sendRequest() {
const newAxios = new axios.create({
baseURL: "http://localhost:8080",
withCredentials: true
})
// 访问一个不存在的资源--->404,
const resp = await newAxios.get('/api/students2');
console.log(resp)
}
}
};
export default options
</script>
可以看到,报错了,且没有继续往下执行:
可以使用try-catch捕获异常
try {
const resp = await newAxios.get('/api/students2');
console.log(resp)
} catch (error) {
console.log(error.response)
}
1. 请求拦截器
_axios.interceptors.request.use(
function(config) {
// 比如在这里添加统一的 headers
return config;
},
function(error) {
return Promise.reject(error);
}
);
// 拦截器:
newAxios.interceptors.request.use(
function (config) {
// 比如在这个添加统一的headers
config.headers = {
Authorization: "aaa.bbb.ccc"
}
return config;
},
function (error) {
return Promise.reject(error);
}
)
2. 响应拦截器
_axios.interceptors.response.use(
function(response) {
// 状态码在 2xx 范围内走这里
return response;
},
function(error) {
// 状态码 超出 2xx, 比如 4xx, 5xx 走这里
return Promise.reject(error);
}
);
return Promise.reject(error);
相当于抛出了异常
外层如果没有捕捉的话,还是会在控制台显示出错误的,如果想要达到类似于捉住异常的效果,应该这样写:
// 响应拦截器:
newAxios.interceptors.response.use(
function (response) {
return response;
},
function (error) {
switch (error.response.status) {
case 400:
console.log("请求参数不正确")
return
case 401:
console.log("认证未通过,跳转至登录页面")
return
case 403:
console.log("权限不够")
return
case 404:
console.log("资源未找到")
return
case 500:
console.log("服务器异常")
return
}
//
return Promise.reject(error);
}
)
newAxios.get("/api/jwt")
在本部分我们自己创建了axiso对象,并且配置了请求拦截器和响应拦截器,这些代码具有一定通用性,我们没有必要在每个vue组件里都写一遍,所以像这种具有通用性的代码,我们可以把他们单独抽到一个js文件里:
/src/util/myaxiso.js
// 导入axiso
import axios from 'axios'
const newAxios = new axios.create({
baseURL: "http://localhost:8080",
withCredentials: true
});
// 请求拦截器:
newAxios.interceptors.request.use(
function (config) {
// 比如在这个添加统一的headers
config.headers = {
Authorization: "aaa.bbb.ccc"
}
return config;
},
function (error) {
return Promise.reject(error);
}
);
// 响应拦截器:
newAxios.interceptors.response.use(
function (response) {
return response;
},
function (error) {
switch (error.response.status) {
case 400:
console.log("请求参数不正确")
return Promise.resolve(400)
case 401:
console.log("认证未通过,跳转至登录页面")
return Promise.resolve(401)
case 403:
console.log("权限不够")
return Promise.resolve(403)
case 404:
console.log("资源未找到")
return Promise.resolve(404)
case 500:
console.log("服务器异常")
return Promise.resolve(500)
}
return Promise.reject(error);
}
);
// 将自己的axiso默认导出,让其他地方使用
export default newAxios;
之后我们就可以在vue组件里,使用这个js文件了:
<template>
<div>
<input type="button" value="获取远程数据" v-on:click="sendRequest()">
</div>
</template>
<script>
// 导入自己的axiso
import axios from "../util/myaxiso";
const options = {
methods: {
async sendRequest() {
const resp = await axios.get("/api/students")
console.log(resp)
}
}
};
export default options
</script>
上面讲述的axios
知识主要是为了接下来的vue小案例,这个案例里就可以使用axios
,获取服务端的一些真实数据了,通过这个案例可以学到vue里的条件渲染与列表渲染。
<template>
<div>
<input type="button" value="获取远程数据" v-on: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>
<!-- 要求:如果没有学生数据,做出处理 -->
<!-- vue里v打头的这个叫做指令 -->
<div class="tbody">
<div v-if="students.length>0">
<!-- v-for 循环遍历,in of 都可以 -->
<!-- 注意:使用v-for的时候,需要v-bind:key='可以唯一标识的字段' -->
<div class="row" v-for="stu of students" v-bind:key="stu.ID">
<div class="col">{{stu.ID}}</div>
<div class="col">{{stu.Name}}</div>
<div class="col">{{stu.Sex}}</div>
<div class="col">{{stu.Age}}</div>
</div>
</div>
<div v-else>
暂无学生数据
</div>
</div>
</div>
</template>
<script>
import axios from '../util/myaxiso'
const options = {
data: function () {
return { students: [] };
},
methods: {
async sendReq() {
const resp = await axios.get("/api/students")
// 要把服务端返回的data数据赋值给我们的data方法里的students
// 因为我们页面上的这些模板需要进行数据绑定,或者数据需要进行条件判断,
// 数据必须来自我们options的data数据对象,不可以直接来自response数据
// console.log(resp.data.data)
this.students = resp.data.data
}
},
};
export default options
</script>
<!-- scoped: 样式仅影响当前组件,不影响其他组件 -->
<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 #007acc;
width: 15%;
height: 35px;
text-align: center;
line-height: 35px;
}
.bold .col {
background-color: #f1f1f1;
}
</style>
但是当前的页面实现是,我们必须点一下按钮,才去服务器获取学生数据,能不能加载页面的时候就获取呢?
只需要在options对象里再加入一个方法属性mounted
:
<script>
import axios from '../util/myaxiso'
const options = {
data: function () {
return { students: [] };
},
methods: {
async sendReq() {
const resp = await axios.get("/api/students")
this.students = resp.data.data
}
},
mounted:function(){
this.sendReq()
}
};
export default options
</script>
这样就可以在加载页面的时候就获取数据了。
注意:
我们页面上肯定有很多HTML 或者JS 代码,具备一定的重用性,那么我们可不可以吧这些可重用的代码集中起来,形成一个可重用的组件呢? 可重用的组件一般放在/src/components
里:
按钮组件
<template>
<div class="button primary middle">
a<slot></slot>b
</div>
</template>
<script>
const options = {};
export default options;
</script>
<style scoped>
...
</style>
<slot>
:插槽,起到占位的作用,后面你在父组件里my-button
标签里写的数据会被展示到页面,否则子组件是不会使用父组件里写在my-button
里的数据的。使用组件
<template>
<div>
<h1>父组件</h1>
<!--2. 页面内导入子组件标签-->
<my-button>1</my-button>
<my-button>2</my-button>
<my-button>3</my-button>
</div>
</template>
<script>
// 导入子组件
import MyButton from '../components/MyButton.vue'
const options = {
components: {
// 简写形式:
// "MyButton":MyButton
MyButton
}
};
export default options;
</script>
components
属性:表示当前父组件要使用哪些子组件。my-button
但是上面那种子组件还不够通用,他的颜色和大小样式是写死的,那能不能让他采用什么样式都是由父组件传入的呢?
采用自定义属性
<template>
<div>
<h1>父组件</h1>
<!--2. 页面内导入子组件标签,-->
<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":MyButton
MyButton
}
};
export default options;
</script>
<template>
<div class="button" v-bind:class="[type,size]">
a<slot></slot>b
</div>
</template>
<script>
const options = {
props: ["type", "size"]
};
export default options;
</script>
<style>
...
</style>
props
:自定义属性,父组件向子组件传值。原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。