Vue
初识Vue
在Vscode
中创建html
文件,然后打开该文件,输入英文!
即可显示出提示框,选定第一个即可出现默认的html
模板。
案例一:值的传递
使用大括号{{}}
声明 一个将要被Vue
所控制的 DOM 区域,其值可以在<script>
标签中使用data()
方法进行传递。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> <!-- 引入Vue.js脚本 -->
</head>
<body>
<div id="app">
{{message}}
</div>
<script>
Vue.createApp({ // 创建APP
data() {
return {
message: 'Hello Vue!'
}
}
}).mount('#app') // 应用到 app 视图中
</script>
</body>
</html>
案例二:内容渲染指令
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<body>
<div id = "app">
<p>姓名:{{username}}</p> <!-- vue里面的模板语法 -->
<p>性别:{{gender}}</p>
<p>{{desc}}</p> <!-- 不会进行渲染,直接以字符串格式显示-->
<p v-html="desc"></p> <!-- 指令,及那个desc以html的方式渲染出来 -->
</div>
<script>
const vm = {
data:function(){ // data用于描述数据
return{
username: 'hangkang',
gender: '男',
desc: '<a href = "http://www.baidu.com">百度</a>'
}
}
}
const app = Vue.createApp(vm)
app.mount('#app')
</script>
</body>
</html>
案例三:属性绑定指令
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<body>
<div id="app">
<a v-bind:href="link">百度 </a> <!-- 第二种方式绑定属性, 一般省略v-bind -->
<input type="text" :placeholder="inputValue">
<br>
<br>
<img :src="imgSrc" :style="{width:w}" alt=""> <!-- 只要属性前面加有冒号,那么后面的值就是一个变量或者说是一个表达式 -->
</div>
<script>
const vm = {
data:function(){
return{
link: "http://www.baidu.com",
// 文本框占位符
inputValue: "请输入内容",
// 图片的src地址
imgSrc: "./images/demo.png",
w:"500px"
}
}
}
const app = Vue.createApp(vm)
app.mount("#app")
</script>
</body>
</html>
案例四:使用JavaScript表达式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<body>
<!-- vue实例要控制的DOM区域 -->
<div id="app">
<p>{{number + 1}}</p>
<p>{{ok? 'True': 'False'}}</p>
<p>{{message.split('').reverse().join('')}}</p> <!-- 使用js中的语法 -->
<p :id = "'list -' + id">xxx</p>
<p>{{user.name}}</p>
</div>
<script>
const vm = {
data: function(){
return{
number: 123465,
ok: true,
message: 'ABC',
id: 3,
user:{ // 创建的是一个user对象
name:'hangkang',
}
}
}
}
const app = Vue.createApp(vm)
app.mount("#app")
</script>
</body>
</html>
案例五:事件绑定指令
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<body>
<div id="app">
<h3>count 的值为:{{count}}</h3>
<button v-on:click="addCount">+1</button> <!-- 使用v-on来进行事件的监听 -->
<button @click="count+=1">+1</button> <!-- @符号与v-on的效果一致 -->
</div>
<script>
const vm = {
data: function(){ // data表示定义属性,function表示方法
return{
count: 0
}
},
methods: { // method表示定义函数
// 点击按钮,让count自增 +1
addCount(){ // 自定义的方法放在methods里面
this.count += 1
},
}
}
const app = Vue.createApp(vm)
app.mount('#app')
</script>
</body>
</html>
案例六:条件渲染指令
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<body>
<div id="app">
<button @click="flag = !flag">Toggle Flag</button>
<p v-if="flag">请求成功 --- 被v-if控制</p>
<!-- v-if 需要传递的值是一个bool值,true则被渲染,否则不被渲染,如果为false就不会创建,会被css的方式进行隐藏 -->
<p v-show="flag">请求成功 --- 被v-show控制</p>
<!-- v-show与v-if类似需要传递的值是一个bool值,true则被渲染,否则不被渲染,无论true还是false都会被创建,如果该标签需要被频繁的被切换,就需要使用v-show -->
</div>
<script>
const vm = {
data: function(){
return {
flag: false,
}
}
}
const app = Vue.createApp(vm)
app.mount('#app')
</script>
</body>
</html>
v-if
与v-show
的区别:如果
v-if
的条件不成立 ,那么v-if
这个标签不会被创建,但是同样的情况下v-show
标签会被创建,但是会通过CSS
样式给隐藏了。使用时机:如果该标签需要频繁的被切换,使用
v-show
的性能会更好。
案例七:v-else 和 v-else-if 指令
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue 3 Example</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<body>
<div id="app">
<!-- 显示随机数大于0.5时的文本 -->
<p v-if="num > 0.5">随机数 > 0.5</p>
<!-- 显示随机数小于或等于0.5时的文本 -->
<p v-else>随机数 <= 0.5</p>
<hr>
<!-- 根据type的值显示不同的评级 -->
<p v-if="type === 'A'">优秀</p>
<p v-else-if="type === 'B'">良好</p>
<p v-else-if="type === 'C'">一般</p>
<p v-else>差</p>
<hr>
<!-- 根据a的布尔值显示或隐藏文本 -->
<div v-show="a">
测试
</div>
<!-- 点击按钮切换a的值 -->
<button @click="a = !a">点击</button>
</div>
<script>
const vm = {
data() {
return {
// 生成0到1之间的随机数
num: Math.random(),
type: 'A',
a: true
};
}
};
const app = Vue.createApp(vm); // 创建Vue应用实例
app.mount('#app'); // 挂载到id为'app'的元素上
</script>
</body>
</html>
案例八:列表渲染指令
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<body>
<div id="app">
<ul>
<li v-for="(user, i) in userList">索引是: {{i}},姓名是:{{user.name}}</li> <!--user表示值, i表示索引 -->
</ul>
</div>
<script>
const vm = {
data: function(){
return {
userList:[
{id: 1, name: 'zhangsan'},
{id: 2, name: 'lisi'},
{id: 3, name: 'wangwu'}
]
}
}
}
const ct = Vue.createApp(vm)
ct.mount('#app')
</script>
</body>
</html>
案例九:v-for 中的 key
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=i, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<body>
<div id="app">
<!-- 添加用户的区域 -->
<div>
<input type="text" v-model="name"> <!-- v-model是一个双向绑定的属性,即:页面的变化会影响变量name的变化,同时name的变化会影响页面的变化 -->
<button @click="addNewUser">添加</button>
</div>
<ul>
<!-- 用户区域列表 -->
<li v-for="(user, index) in userList" :key="user.id"> <!-- 表示主键的值是唯一的,一般其值等于一个唯一的主键标识 -->
<input type="checkbox" />
姓名: {{user.name}}
</li>
</ul>
</div>
<script>
const vm = {
data: function(){
return {
// 用户列表
userList: [
{id: 1, name: 'zs'},
{id: 2, name: 'ls'},
{id: 3, name: 'bs'}
],
// 输入用户名
name: '',
// 下一个可用的id值
nextId: 4
}
},
methods: {
// 点击了添加按钮
addNewUser(){
this.userList.unshift({id: this.nextId, name: this.name}) // 向数组的起始位置添加元素
this.name=''
this.nextId++
}
}
}
const ppa = Vue.createApp(vm)
ppa.mount('#app')
</script>
</body>
</html>
创建Vue项目
要想创建Vue项目,就需要使用官方提供的构建工具Vue CLI
,通常称为脚手架,Vue CLI
用于快速大家一个带有热重载及构建生产版本等功能的单页面应用。Vue CLI基于webpack构建,也可以通过项目内的配置文件进行配置。
安装指令:npm install -g @vue/cli
,使用该命令时需要电脑安装有Node.js
。
在需要创建创建项目的文件夹下打开cmd窗口,输入:vue create 项目名称
,回车
在弹出的窗口中,选择最后一个Manually select features
,手动配置选项(回车确认):
接着,取消Linter / Formatter
(按空格键进行取消),回车:
根据实际情况选择版本情况,这里选择2.x
版本:
选择In Package.json
,这样会将配置记录到该文件夹中,该文件夹相当于Spring Boot中的pom.xml
:
最后一个询问是否将以上所选配置存为一个快照,后面可以利用这个快照快速创建项目,这里不需要输入N
即可:
等待项目的创建:
成功创建项目:
使用VsCode打开项目:
项目的结构如下:
assets
文件夹:用于存放静态资源component
文件夹:用于创建组件,以后的代码文件基本都在这个文件夹中App.vue
文件:是一个组件的汇总文件。main.js
文件:程序的入口文件。package.js
文件:存放了项目的基本信息,第三方库的信息也记录在该文件中,是一个记录配置的文件相当于Spring Boot中的pom.xml
。
Vue支持组件化开发,可以将界面拆分成独立的、可复用的组件,每个组件管理自己的视图和状态,组件可以扩展HTML元素,封装可重用的代码,Vue组件系统允许我们使用小型、独立和通常可复用的组件构建大型应用。
组件的构成:
Vue中规定组件的后缀名是.vue
,每个.vue
组件都由三部分组成:
template
:组件的模板结构,可以包含HTML
标签及其他的组件script
:组件的JavaScript
代码style
:组件的样式
项目的启动方式:
在控制台输入:npm run serve
即可,默认端口号是8080,Ctrl + 单击
下面任意一个链接即可跳转到浏览器对应的页面:
Vue 可自定义标签:
创建
.vue
组件文件,如:
注:一个
<template>
标签中只能有一个标签,可以使用<div>
标签进行封装。在
App.vue
中导入该文件:
如此便可以使用自定义的标签了:
组件间的传值:
组件可以由内部的Data
提供数据,也可以由父组件通过props
的方式传值,兄弟组件之间可以通过Vuex
等统一数据源提供数据共享。
自己写的组件一般会通过直接或间接的放到App.vue
中,因为App.vue
是一个根组件。
在组件的<script>
标签中,定义export default{}
里面可以定义name
,这样可以在其他组件中引用这个名字。data仍然为该组件提供数据,methods 为组件提供方法。
<script>
export default{
name: 'name',
props:["title"]
}
</script>
定义props
后,外面就可以使用title
了,并且该组件内部也可以使用title
,这样就实现了父组件向子组件之间的传值。
父组件:
<template>
<div>
<h1>父组件</h1>
<!-- 使用子组件,并传递 message 作为 prop -->
<child-component :message="parentMessage"></child-component>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
data() {
return {
// 定义父组件中要传递给子组件的数据
parentMessage: 'Hello from Parent!'
};
}
}
</script>
子组件:
<template>
<div>
<p>这是从父组件传来的消息: {{ message }}</p>
</div>
</template>
<script>
export default {
name: 'ChildComponent',
props: {
// 定义一个名为 message 的 prop
message: String
}
}
</script>
element-ui
Element 是国内饿了吗公司提供的一套开源前端框架,简介优雅,提供Vue、React、Angular等多个版本。
文档地址:
- Vue 2.x:Element - 网站快速成型工具
- Vue 3.x:一个 Vue 3 UI 框架 | Element Plus (element-plus.org)
安装:1. 局部安装:npm i element-ui
或 npm install element-ui
; 2. 全局安装:npm i element-ui -S
或 npm install element-ui -S
如果安装过程出现问题,可以尝试更换npm
源:npm config set registry https://registry.npm.taobao.org
(淘宝源)。
引入Element(main.js
):
-
Vue 2.x的引入:
import Vue from 'vue'; import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; import App from './App.vue'; Vue.use(ElementUI); // 进行全局注册 new Vue({ el: '#app', render: h => h(App) });
-
Vue 3.x的引入:
// main.ts import { createApp } from 'vue' 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')
使用方法:
-
引入Element
-
从官方文档中挑选需要的样式
-
将代码复制到项目中,并做相应的修改。
-
在
App.vue
文件中进行引入,运行项目根据需求修改即可。
第三方图标库
由于Element UI 提供的字体图标较少,一般会采用其他图标库,如著名的 Font Awesome,该图表库提供了657个可缩放的矢量图标,可以使用CSS所提供的所有特性对它们进行更改,包括大小、颜色、阴影或者其他任何支持的效果。
文档地址:Font Awesome,一套绝佳的图标字体库和CSS框架 (dashgame.com)
安装:npm install font-awesome
使用:
import 'font-awesome/css/font-awesome.min.css'
<template>
<i class="fa fa-camera-retro fa-2x"></i>
</template>
Axios 请求
在实际项目开发中,前端页面所需要的数据往往需要服从服务器端获取,这必然涉及与服务器通信,Axios 是一个基于 promise网络请求库,作用于node.js 和浏览器中,Axios 在浏览器端使用 XML HttpRequests发送网络请求,并能自动完成 JSON 数据的转换。
安装:npm install axios
地址:Axios中文文档 | Axios中文网 (axios-http.cn)
发送网络请求:
-
发送GET请求:
<script> axios.get('/user?ID=123') .then(function(response){ // 处理成功的情况 console.log(response); }) .catch(function(error){ // 处理错误的情况 console.log(error); }) .then(function(){ // 总会执行 }); </script>
还可以使用以下方式完成:
<script> axios.get('/user', { params:{ // 将参数写为列表进行发送 ID: 123 } }) .then(function(response){ // 处理成功的情况 console.log(response); }) .catch(function(error){ // 处理错误的情况 console.log(error); }) .then(function(){ // 总会执行 }); </script>
-
发送POST请求:
<script> axios.post('/user', { firstName: 'Fred', lastName: 'Flintstone' }) .then(function(response){ // 处理成功的情况 console.log(response); }) .catch(function(error){ // 处理错误的情况 console.log(error); }); </script>
-
异步回调,支持
async/await
用法:<script> async function getUser(){ try{ const response = await axios.get('/user?ID=123'); console.log(response); } catch (error){ console.error(error); } } </script>
-
直接使用
axios
发送数据:<script> axios({ method: 'post', url: '/user/12345', data:{ firstName: 'Fred', lastName: 'Flintstone' } }); </script>
其他请求方式:
- 参考:Axios API | Axios中文文档 | Axios中文网 (axios-http.cn)
axios.request(config) 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]]) axios.postForm(url[, data[, config]]) axios.putForm(url[, data[, config]]) axios.patchForm(url[, data[, config]])
Vue 中的生命周期函数,生命周期函数要与 data()
同级,不能写在method
里面,method
里面只能写自定义的函数。
<script>
created:function(){ // 当组件被创建时调用
this.$http.get('/user/findAll').then((response)=>{ // 箭头函数可以解决作用域的问题,其作用域与其父类一致
this.tableData = response.data
})
},
mounted:function(){
// 当组件被挂载时调用,较created靠后
}
</script>
在main.js
中的全局配置:
// 配置请求的根路径
axios.defaults.baseURL = 'http://api.com'
// 将axios作为全局的自定义属性,每个组件可以在内部直接访问
// Vue 3.x
// app.config.globalProperties.$http = axios
// Vue 2.x
Vue.prototype.$http = axios
解决跨域问题
为了保证浏览器的安全,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源,称为同源策略,同源策略时浏览器安全的基石。
同源策略(Same Origin Policy)是一种约定,它时浏览器最核心也是最基本的安全功能,所谓同源就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)。当一个请求 URL 的协议、域名、端口三者之间任意一个与当前页面 URL 不同即为跨域,此时无法读取非同源网页的Cookie,无法向非同源地址发送 AJAX 请求。
Spring Boot中配置CORS:
在传统的JAVA EE开发中,可以通过过滤器统一配置,而Spring Boot中对此则提供了更加简介的解决方案。
@Configuration
public class CorsConfig implements WebMvcConfigurer{
@Override
public void addCorsMappings(CorsRegistry registry){
registry.addCorsMappings("/**") // 允许跨域访问的路径
.allowedOrigins("*") // 允许跨域访问的源
.allowedMethod('POST', "GET", "PUT", "OPTIONS", "DELETE") // 允许请求的方法
.maxAge(16800) // 预检间隔时间
.allowedHeader("*") // 允许头部设置
.allowCredentials(true); // 是否发送cookie
}
}
Vue Router
Vue Router时Vue的路由插件,能够轻松的管理SPA项目中组件的切换,Vue的单页面应用时基于路由和组件的,路由用于所设定访问路径,并将路径和组件映射起来,Vue Router目前有 3.x 版本和 4.x 版本,分别对应 Vue 2.x 和 Vue 3.x 。
安装:npm install vue-router@3
,这里的@3
表示的时 3.x 版本,如果是 @4
表示的是 4.x的版本。
使用路由
<template>
<div>
<h1>APP 组件</h1>
<!-- 声明路由链接 -->
<router-link to = "/discover">发现音乐</router-link>
<router-link to = "/my">我的音乐</router-link>
<router-link to = "/friend">关注</router-link>
<!-- 声明路由占位符 -->
<router-view></router-view>
</div>
</template>
创建路由模块,在项目中创建index.js路由模块,加入以下代码:
import VueRouter from 'vue-router'
import Vue from 'vue'
import Discover from '@/componse/Discover.vue'
import Friend from '@/componse/Friend.vue'
import My from '@/componse/My.vue'
// 将VueRouter设置为Vue的插件
Vue.use(VueRouter)
const router = new VueRouter({
// 指定hash属性与组件的对应关系
routes: [
{path: '/discover', component: Discover},
{path: '/friends', component: Friend},
{path: 'my', component: My},
]
})
export default router
挂载路由模块,在main.js
中导入并挂载router
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
router: router
}).$mount('#App')
嵌套路由
在Discover.vue组件中,声明toplist 和 playlist 的子路由链接以及子路由占位符。
<template>
<div>
<h1>发现音乐</h1>
<!-- 子路由链接 -->、
<router-link to="/discover/toplist">"推荐"</router-link>
<router-link to="/discover/playlist">"歌单"</router-link>
<hr>
<router-view></router-view>
</div>
</template>
在src/router/index.js路由模块中,导入需要的组件,并使用children属性声明子路由规则:
const router = new VueRouter({
// 指定hash属性与组件的对应关系
routes: [
{path: '/', redirect: '/discover'},
{
path: '/discover',
component: Discover,
// 通过children属性,嵌套声明子路由
children: [
{path: "toplist", component: TopList},
{path: "playlist", component: PlayList},
]
},
{path: '/friends', component: Friends},
{path: '/my', component: My},
]
})
动态路由
思考:有如下三个路由链接
<template>
<router-link to='/product/1'>商品1</router-link>
<router-link to='/product/2'>商品2</router-link>
<router-link to='/product/3'>商品3</router-link>
</template>
const router = new VueRouter({
// 指定hash属性与组件的对应关系
routes: [
{path: 'product/1', component: Product},
{path: 'product/2', component: Product},
{path: 'product/3', component: Product},
]
})
上述方式复用性非常差。
动态路由是指:把Hash地址中可变的部分定义为参数项,从而提高路由规则的复用性。在 vue-router 中使用英文的冒号:
来定义路由的参数项,如:{path: 'product/:id', component: Product}
,通过动态路由匹配的方式渲染出来的组件中,可以使用$route.params
对象访问到动态匹配的参数值,比如在商品性情组件的内部,根据 id
值,请求不同的商品数据。
<template>
<h1>Product组件</h1>
<!-- 获取动态的id值 -->
<p>{{$route.params.id}}</p>
</template>
<script>
export default{
// 组件名称
name: 'Product'
}
</script>
为了简化路由参数的获取形式,vue-router允许在路由规则中开启 props传参。示例代码如下:
{path: 'product/:id', component: Product, props: true}
<template>
<h1>Product组件</h1>
<!-- 获取动态id值 -->
<p>{{id}}</p>
</template>
<script>
export default{
// 组件名称
name: 'Product',
props: [id]
}
</script>
编程式导航
声明式 | 编程 |
---|---|
<router-link to = '...'> | router.push(...) |
除了使用<router-link>
创建 a
标签来定义导航链接,我们还可以借助router
的实例方法,通过编写代码来实现,想要导航到不同的 URL,则使用router.push
方法。这个方法会向history
栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,则回到之前的 URL。
当你点击 <router-link>
时,这个方法会在内部调用,所以说点击<router-link: to = "...">
等同于调用router.push(...)
。
<template>
<button @click='gotoProduct(2)'>跳转到商品2</button>
</template>
<script>
export default{
methods:{
gotoProduct: function(id){
this.$router.push('/movie/${id}')
}
}
}
</script>
导航守卫
导航守卫可以控制路由的访问权限。
全局导航守卫会拦截每个路由规则,从而每个路由进行访问权限的控制。
你可以使用router.beforeEach
注册一个全局前置守卫。
router.beforeEach((to, from, next) =>{
if(to.paht === '/main' && !isAuthenticated){
next('/login')
}else{
next()
}
})
to
:表示即将要进入的目标。from
:当前导航正要离开的路由。- 在守卫方法中如果声明了
next
形参,则必须调用next()
函数,否则不允许访问任何一个路由!
- 直接放行:
next()
- 强制其停留在当前页面:
next(false)
- 强制其跳转到登录页面:
next('/login')
Vuex
对于组件化开发来说,大型应用的状态往往跨越多个组件。在多层嵌套的父子组件之间传递状态已经十分麻烦了,而Vue更是没有为兄弟组件提供直接共享数据的办法。基于这个问题,许多框架提供了解决方案 —— 使用全局的状态管理器,将所有分散的共享数据交由状态管理器保管,Vue也不例外。
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式和库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方路由库 Vue Router 中。
在Vuex中每一个Vuex应用都是一个store,与普通的全局对象不同的是,基于Vue数据与视图绑定的特点,当store中的状态发生变化时,与之绑定的视图也会 被重新渲染。store中的状态不允许被直接修改,改变store中的状态的唯一途径就是显示地提交(commit)mutation,这可以让我们方便地跟踪每一个状态的变化。在大型复杂应用中,如果无法有效地跟踪到状态的变化将会对理解和维护代码带来极大的困扰。
Vuex中的核心概念
-
State:Vuex 存储的所有状态,Vue 组件可以通过
this.$store.state
访问。 -
Getters:允许你从 store 中的 state 中派生出一些状态,类似于组件中的计算属性。
-
Mutations:是同步函数,用于更改 store 中的状态。当你想要提交 mutation 时,需要使用
store.commit
方法。 -
Actions:类似于 mutation,但它们负责提交 mutation 而不是直接变更状态,可以包含任意异步操作。
-
Modules:允许你将 store 分割成模块,每个模块拥有自己的 state、mutation、action、getter,甚至是嵌套子模块。
-
Strict Mode:在严格模式下,任何试图直接修改 state 中的变量的行为都会抛出错误。
state
state 用于存放数据,其他任何地方的文件都可以访问。state 用于维护所有应用层的状态,并确保应用只有唯一的数据源。
<script>
// 创建一个新的 store 实例
const store = createStre({
state(){
return {
count: 0
}
},
mutations: {
increment(state){
state.count++
}
}
})
</script>
在组件中,可以直接使用 this.$store.state.count
访问数据,也可以先用 mapState
辅助函数将其映射下来。
// 在单独构建的版本中辅助函数为 Vuex.mapState
import { mapState } from 'vuex'
export default {
// ...
computed: mapState({
// 箭头函数可使代码更简练
count: state => state.count,
// 传字符串参数 'count' 等同于 `state => state.count`
countAlias: 'count',
// 为了能够使用 `this` 获取局部状态,必须使用常规函数
countPlusLocalState (state) {
return state.count + this.localCount
}
})
}
Mutations
Mutations 用于修改 state 中的数据,Mutations 提供修改 state 状态的方法。
const store = createStore({
state: {
count: 1
},
mutations: {
increment (state) {
// 变更状态
state.count++
}
}
})
在组件中,可以直接使用 store.commit
来提交mutation
<script>
methods:{
increment(){
this.$store.commit('increment') // increment 是方法的名称
console.log(this.$store.state.count)
}
}
</script>
也可以先用mapMutation辅助函数将其映射下来
<script>
methods: {
...mapMutations([
'increment', // 将 'this.increment()' 映射为 'this.$store.commit('increment')'
// 'mapMutations' 也支持载荷
'incrementBy' // 将 'this.incrementBy(amount)' 映射为 'this.$store.commit('increment', 'amount')'
])
}
</script>
Getter
Getter 相当于计算属性,用于缓存,对 state 中的数据进行进一步处理,可直接访问 Getter 中的处理过的数据。Getter 维护由 State 派生的一些状态,这些状态随着 State 状态的变化而变化。
<script>
const store = createStore({
state: {
todos:[
{id: 1, text: '...', done: true},
{id: 2, text: '...', done: false}
]
},
getters: {
doneTodos: (state) => {
return state.todos.filter(todo => todo.done) // 过滤操作
}
}
})
</script>
在组件中,可以直接使用 this.$store.getters.doneTodos
,也可以先用 mapGetters
辅助函数将其映射下来。
<script>
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
}
}
</script>
Action
Action 用来做异步包装。Action 类似于 Mutation,不同在于 Action 不能直接修改状态,只能通过提交 mutation 来修改,Action 可以包含异步操作。
<script>
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})
</script>
在组件中,可以直接使用 this.$store.dispatch('xxx')
分发 action,或者使用 mapActions
辅助函数将组件的 methods 映射为 store.dispatch
调用(需要先在根节点注入 store
)
<script>
import { mapActions } from 'vuex'
export default {
// ...
methods: {
...mapActions([
'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
// `mapActions` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
]),
...mapActions({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
})
}
}
</script>
Modules
Modules 用于封装需要区别的状态
<script>
const moduleA = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
</script>
Mock.js
Mock.js 是一个非常流行的前端开发工具库,主要用于生成随机数据和拦截 Ajax 请求。它可以帮助前端开发者在没有后端支持的情况下独立开发和测试。
Mock.js 的特性:
-
前后端分离:允许前端开发者在没有后端接口的情况下进行开发。
-
增加单元测试的真实性:通过随机数据模拟各种场景。
-
开发无侵入:不需要修改既有代码,可以拦截 Ajax 请求并返回模拟的响应数据。
-
用法简单:提供符合直觉的接口。
-
数据类型丰富:支持生成随机文本、数字、布尔值、日期、邮箱、链接、图片、颜色等。
基本使用
安装方式: npm install mockjs
<script>
// 引入 mockjs
import Mock from 'mockjs'
// 使用 mockjs 模拟数据
Mock.mock('/product/search', { // 设置拦截路径
"ret": 0,
"data": {
"mtime": "@datetime", // 随机生成日期时间
"score | 1 - 800": 800, // 随机生成 1 - 800 的数字
"rank | 1 - 100": 100, // 随机生成 1 - 100 的数字
"stars | 1 - 5": 5, // 随机生成 1 - 5 的数字
"nickname": "@cname", // 随机生成中文名字
"img": "@image('200 x 100', '#ffcc33', '#FFF', 'png', 'FastMock')" // 生成一张图片
}
});
</script>
组件中调用 mock.js
中模拟的数据接口,这时返回的 response
就是 mock.js
中用 Mock.mock('url', data)
中设置的 data
。
<script>
import axios from 'axios'
export default {
mounted: function(){
axios.get('/product/search').then(res => {
console.log(res)
})
}
}
</script>
核心方法
Mock.mock(rurl?, rtype?, template | function(options))
-
rurl (可选): 这是一个字符串或正则表达式,用于指定要拦截的请求 URL。如果提供了这个参数,Mock.js 会拦截匹配该 URL 的 Ajax 请求。
-
rtype (可选): 这是一个字符串,指定了请求的类型,如 ‘get’、‘post’、‘put’、‘delete’ 等。如果提供了这个参数,Mock.js 会拦截匹配该请求类型的 Ajax 请求。如果不提供,它会拦截所有类型的请求。
-
template | function(options) (必需): 这是用于生成模拟数据的模板或函数。
- template: 一个对象或字符串,定义了要生成的数据的结构。Mock.js 会根据这个模板生成数据。
- function(options): 一个函数,它接受一个参数
options
,这个参数包含了请求的信息,如 URL、类型等。函数应该返回一个对象或字符串,作为模拟的响应数据。
-
设置延时请求到的数据:
// 延时 400ms 请求到的数据 Mock.setup({ timeout: 400 }) // 延时200 - 600毫秒请求到数据 Mock.setup({ timeout: '200 - 600' })
Mock.js 的数据生成规则
Mock.js 数据的生成规则是通过在属性名后面添加一个模板字符串来定义的,这个模板字符串描述了如何生成该属性的值。生成规则可以包含多种类型的占位符和参数,用于控制生成数据的类型、范围、格式等。以下是一些常用的生成规则:
1. 基本类型生成规则
- 字符串:
'name|rule': 'value'
'name|min-max': 'value'
:生成一个字符串,重复value
的次数在min
到max
之间。'name|count': 'value'
:生成一个字符串,重复value
指定的count
次。
- 数字:
'name|rule': number
'name|+1': number
:属性值从number
开始自增。'name|min-max': number
:生成一个大于等于min
、小于等于max
的整数。
- 布尔值:
'name|rule': boolean
'name|1': boolean
:随机生成true
或false
。'name|min-max': value
:生成一个布尔值,值为value
的概率是min / (min + max)
。
2. 数组生成规则
-
'name|rule': array
'name|1': array
:从数组中随机选取一个元素。'name|min-max': array
:从数组中随机选取min
到max
个元素。'name|count': array
:从数组中随机选取count
个元素。
3. 对象生成规则
-
'name|rule': object
'name|count': object
:从对象中随机选取count
个属性。
4. 正则表达式生成规则
-
'name': regexp
- 根据正则表达式生成一个匹配该模式的字符串。
示例:
const Mock = require('mockjs');
// 字符串生成规则
const data = Mock.mock({
'string|1-3': 'hello' // 生成一个重复 1 到 3 次 "hello" 的字符串
});
// 数字生成规则
const numberData = Mock.mock({
'number|100-200': 1 // 生成一个 100 到 200 之间的整数
});
// 布尔值生成规则
const boolData = Mock.mock({
'bool|1': true // 有 50% 的概率生成 true
});
// 数组生成规则
const arrayData = Mock.mock({
'list|1-3': ['apple', 'banana', 'cherry'] // 生成一个包含 1 到 3 个元素的数组
});
// 对象生成规则
const objectData = Mock.mock({
'info|1': {
'name': '@cname',
'age|18-30': 20
}
});
// 正则表达式生成规则
const regexData = Mock.mock({
'str': /[a-zA-Z0-9]{6}/ // 生成一个匹配正则表达式的字符串
});
JWT
简介
JSON Web Token (简称:JWT),是一个 token 的具体实现方式,是目前最流行的跨域认证解决方案。
JWT 的原理是服务器认证以后,生成一个 JSON 对象,将其返回给用户,具体原理如下:
{
"姓名": "张三 ",
"角色": "管理员",
"到期时间": "2020年7月1日0点0分"
}
用户于服务端通信的时候,都要返回这个 JSON 对象,服务区完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在是生成这个对象的时候 ,会加上签名。
一个 JWT 实际上就是一个字符串,它由三部分组成,用点(.
)分隔:
-
Header(头部):通常包含两部分:令牌的类型(即JWT)和所使用的签名算法,如HMAC SHA256或RSA。
{ "alg": "HS256", "typ": "JWT" }
-
Payload(负载):包含所要传递的信息。负载可以包含多个声明。声明是关于实体(通常是用户)和其他数据的声明。声明有三种类型:注册的声明、公共的声明和私有的声明。
{ "sub": "1234567890", "name": "John Doe", "admin": true }
-
Signature(签名):用于验证消息在传输过程中没有被更改,并且,对于使用私钥签名的令牌,还可以验证发送者的身份。
- 对于使用HMAC算法的JWT,签名是通过使用一个密钥和头部以及负载的Base64Url编码后的字符串进行HMAC算法计算得到的。
- 对于使用RSA或ECDSA的JWT,签名是通过使用私钥对头部和负载的Base64Url编码后的字符串进行签名得到的。
三部分最终组合为完整的字符串,中间使用.
分隔,如:Header.Payload.Signature
JWT的工作原理
- 创建:服务器在用户登录成功后创建一个JWT,并将一些用户信息和其他必要信息作为声明放入负载中。
- 发送:服务器将JWT发送给客户端。
- 存储:客户端收到JWT后,可以将其存储在Cookie中,或者存储在本地存储中,如LocalStorage。
- 使用:客户端在随后的每个请求中将JWT放在HTTP请求的Authorization头部中发送给服务器。
- 验证:服务器接收到请求,通过解码JWT来验证其有效性,并从中提取用户信息。
JWT的优点
- 无状态和可扩展性:由于服务器不需要存储会话信息,因此JWT适用于分布式系统和SOA架构中。
- 安全:JWT可以被签名,确保了传输过程中不能被篡改。
- 紧凑:JWT通常只有几百字节,适合在网络中传输。
JWT的缺点
- 大小:JWT可能会变得相当大,特别是当负载很大时,可能会对性能产生影响。
- 状态性:JWT本身不包含任何状态信息,一旦发行,不能被撤销。如果需要撤销,需要额外的机制,如维护一个黑名单。
- 存储:JWT通常存储在客户端,如果使用LocalStorage存储,可能会面临XSS攻击的风险。
JWT 在 Spring Boot 中的实现
-
添加依赖:首先需要在项目的
pom.xml
文件中添加 JWT 相关的依赖。<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
-
生成 Token
// 7 天过期 private static long expire = 604800; // 32 位密钥 private static String secret = "awszxdcasdfghbvfrejuiklmnbvsde"; // 生成 Token public static String generateToken(String username){ Date now = new Date(); Date expiration = new Date(now.getTime() + 1000 * expire); return Jwts.builder() .setHeaderParam("type", "JWT") .setSubject(username) .setIssuedAt(now) .setExpiration(expiration) .signWith(SignatureAlgorithm.HS512, secret) .compact(); }
-
解析 Token
// 解析 Token public static Claims getClaimsByToken(String token) { return Jwts.parser() .setSigningKey(SECRET) .parseClaimsJws(token) .getBody(); }
参考
- 1天搞定SpringBoot+Vue全栈开发-bili_13969681152-默认收藏夹-哔哩哔哩视频 (bilibili.com)
- 快速上手 | Vue.js (vuejs.org)
- 开始 | Vuex (vuejs.org)
- Vuex 是什么? | Vuex (vuejs.org)
- 介绍 | vue-element-admin (panjiachen.github.io)
- Axios中文文档 | Axios中文网 (axios-http.cn)
- Font Awesome,一套绝佳的图标字体库和CSS框架 (dashgame.com)
- Font Awesome,一套绝佳的图标字体库和CSS框架 (dashgame.com)