Vue2 总结(开发)
1. Vue CLI 脚手架
1.1 安装
npm install -g @vue/cli
# OR
yarn global add @vue/cli
1.2 升级
npm update -g @vue/cli
# 或者
yarn global upgrade --latest @vue/cli
1.3 创建一个项目
vue create hello-world
会被提示选取一个 preset。你可以选默认的包含了基本的 Babel + ESLint 设置的 preset,也可以选“手动选择特性”来选取需要的特性
创建完成
1.4 目录结构
1.5 运行
vue-cli2.0
"scripts": {
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"start": "npm run dev",
"build": "node build/build.js"
}
vue-cli3.0
"scripts": {
"serve": "vue-cli-service serve", // 运行项目
"build": "vue-cli-service build", // build
"lint": "vue-cli-service lint" // 运行语法检查
},
启动运行项目
npm run serve
npm run xxx 中的 xxx 可以理解为键值对的 key,实际上 run 的是在 package.json 里面 scripts 配置的 value。比如,npm run serve 实际运行的是 vue-cli-service serve,而放在 3.0 以前 npm run dev 运行的则是 node build/dev-server.js 文件
2. Demo 案例
2.1 components
定义两个组件
<template>
<div class="demo">
<h2>学校名称:{{schoolName}}</h2>
<h2>学校地址:{{schoolAddress}}</h2>
<button @click="showName">点我提示学校名</button>
</div>
</template>
<script>
export default ({
// eslint-disable-next-line vue/multi-word-component-names
name: 'School',
data() {
return {
schoolName: 'Vue',
schoolAddress: '湖南'
}
},
methods: {
showName() {
alert(this.schoolName)
},
demo() { }
},
});
</script>
<style>
.demo {
background-color: orange;
}
</style>
<template>
<div>
<h2>学生姓名:{{name}} </h2>
<h2>学生年龄:{{age}} </h2>
</div>
</template>
<script>
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'Student',
data() {
return {
name: '张三',
age: 18
}
},
}
</script>
2.2 App.vue
管理所有的其他的组件
<template>
<div id="app">
<img alt="Vue logo"
src="./assets/logo.png">
<School></School>
<Student></Student>
</div>
</template>
<script>
import School from './components/School.vue'
import Student from './components/Student.vue'
export default {
name: 'App',
components: {
School,
Student
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
2.3 main.js
// 项目入口文件
// 引入 Vue
import Vue from 'vue'
// 引入 App 组件
import App from './App.vue'
// 关闭 Vue 的生产提示
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
2.4 index.html
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<!-- 针对IE浏览器的一个特殊配置,含义是让IE浏览器以最高的渲染级别渲染页面 -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- 开启移动端的理想视口 -->
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- 配置页签图标 -->
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<!-- 配置网页标题 -->
<title><%= htmlWebpackPlugin.options.title %>Fan</title>
</head>
<body>
<!-- 当浏览器不支持js时noscript中的元素就会被渲染 -->
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<!-- 容器 -->
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
3. render 函数
默认引入的 Vue 是一个不完整的 Vue(运行版的 vue),缺少模板解析器,此时使用 components 来注册组件会报错
可以看到有两个解决方案,第一种引入完整的 vue.js,第二种使用 render 函数
3.1 引入完整版的 vue.js
完整版的 Vue 在 vue/dist 下的 vue.js
引入完整版的 vue.js
// 引入 Vue
import Vue from 'vue/dist/vue'
// 引入 App 组件
import App from './App.vue'
// 关闭 Vue 的生产提示
Vue.config.productionTip = false
new Vue({
template: `
<div>
<App></App>
</div>
`,
components: {
App
}
}).$mount('#app')
此时运行成功
3.2 使用 render 函数
由于运行版的 vue 没有模板解析器,不能使用 template 配置项,需要使用 render 函数接收到的 createElement 函数去指定具体内容
// 引入 Vue
import Vue from 'vue'
// 引入 App 组件
import App from './App.vue'
// 关闭 Vue 的生产提示
Vue.config.productionTip = false
new Vue({
// render(createElement) {
// return createElement(App);
// },
// render:h => h(App)
}).$mount('#app')
4. 配置文件
- 使用 vue inspect > output.js 可以查看到 Vue 脚手架的默认配置
- 使用 vue.config.js 可以对脚手架进行个性化定制,官网配置地址 https://cli.vuejs.org/zh/config/#vue-config-js
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
lintOnSave: false,
pages: {
index: {
// page 的入口,默认 main.js
entry: 'src/aa.js'
}
}
})
5. ref 、props 与 mixin
5.1 ref
- 被用来给元素或子组件注册引用信息(id的替代者)
- 应用在 html 标签上获取的是真实 DOM 元素,应用在组件标签上是组件实例对象(vc)
- 使用方式:
- 打标识:
<h1 ref="xxx">.....\</h1> 或 \<School ref="xxx">\</School>
- 获取:
this.$refs.xxx
- 打标识:
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<h1 v-text="msg" ref="title"></h1>
<button ref="btn" @click="showDOM">点我输出上方的DOM元素</button>
<School ref="sch" />
<Student></Student>
</div>
</template>
<script>
import School from './components/School.vue'
import Student from './components/Student.vue'
export default {
name: 'App',
data() {
return {
msg: 'MSG'
}
},
methods: {
showDOM() {
console.log(this.$refs.title) // 真实DOM元素
console.log(this.$refs.btn) // 真实DOM元素
console.log(this.$refs.sch) // School组件的实例对象(vc)
}
},
components: {
School,
Student
}
}
</script>
5.2 props
5.2.1 概念
让组件接收外部传过来的数据
- 传递数据:
<Demo name="xxx"/>
- 接收数据:
- 第一种方式(只接收):
props:['name']
- 第二种方式(限制类型):
props:{name:String}
- 第三种方式(限制类型、限制必要性、指定默认值):
props:{ name:{ type: String, // 类型 required: true, // 必要性 default: '老王' // 默认值 } }
- 第一种方式(只接收):
- props 是只读的,Vue 底层会监测你对 props 的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么需要复制 props 的内容到 data 中一份,然后去修改 data 中的数据
5.2.2 使用
App.vue,使用组件 School.vue 时传入数据
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<!-- age 为数字类型,但传入的是一个字符串,此时需要用 v-vind 数据绑定,将 "" 里的看成一个表达式然后传入 -->
<School schoolName="Vue" schoolAddress="长沙" :age="18" />
<Student />
</div>
</template>
<script>
import School from './components/School.vue'
import Student from './components/Student.vue'
export default {
name: 'App',
components: {
School,
Student
}
}
</script>
School.vue,使用 props 接收传进来的数据
<template>
<!-- 组件的结构 -->
<div class="demo">
<h2>{{info}}</h2>
<h2>学校名称:{{schoolName}}</h2>
<h2>学校地址:{{schoolAddress}}</h2>
<h2> {{age + 1}} </h2>
<button @click="showName">点我提示学校名</button>
</div>
</template>
<script>
export default ({
// eslint-disable-next-line vue/multi-word-component-names
name: 'School',
data() {
return {
info: '原生信息'
}
},
// 接收数据
// props: ['schoolName', 'schoolAddress', 'age'],
props: {
schoolName: {
type: String, // 类型
required: true, // 必要性
default: 'Vue' // 默认值
},
schoolAddress: String,
age: Number
}
});
</script>
展示将传进来的数据
5.3 mixin(混入/合)
5.3.1 概念
可以把多个组件共用的配置提取成一个混入对象
- 定义混入:
{ data(){....}, methods:{....} .... }
- 使用混入:
全局混入:Vue.mixin(xxx),会给所有都加上混入
局部混入:mixins:['xxx']
5.3.2 使用
定义混入,mixin.js
export const mixin1 = {
methods: {
showName(){
alert(this.name)
}
},
mounted() {
console.log('你好啊!')
},
}
export const mixin2 = {
data() {
return {
x:100,
y:200
}
},
}
使用局部混入,School.vue
<script>
import { mixin1, mixin2 } from '../mixin.js';
export default ({
// eslint-disable-next-line vue/multi-word-component-names
name: 'School',
data() {
return {
info: '原生信息'
}
},
// 使用局部混入
mixins: [mixin1, mixin2],
props: ['schoolName', 'schoolAddress', 'age'],
});
</script>
使用全局混入,main.js
// 引入 Vue
import Vue from 'vue'
// 引入 App 组件
import App from './App.vue'
import { mixin1, mixin2 } from './mixin'
// 关闭 Vue 的生产提示
Vue.config.productionTip = false
// 全局混入
Vue.mixin(mixin1)
Vue.mixin(mixin2)
new Vue({
render:h => h(App)
}).$mount('#app')
6. 插件
6.1 概念
用于增强 Vue,包含 install 方法的一个对象,install 的第一个参数是 Vue,第二个以后的参数是插件使用者传递的数据
- 定义插件:
对象.install = function (Vue, options) { // 1. 添加全局过滤器 Vue.filter(....) // 2. 添加全局指令 Vue.directive(....) // 3. 配置全局混入(合) Vue.mixin(....) // 4. 添加实例方法 Vue.prototype.$myMethod = function () {...} // 5. 给 Vue 原型上添加一个方法(vm 和 vc 都能用) Vue.prototype.$myProperty = xxxx }
- 使用插件:Vue.use()
6.2 使用
定义一个插件 plugins.js
export default {
install(Vue, x, y, z){
console.log(Vue, x, y, z)
//全局过滤器
Vue.filter('mySlice',function(value){
return value.slice(0, 4)
})
//定义全局指令
Vue.directive('fbind',{
//指令与元素成功绑定时(一上来)
bind(element,binding){
element.value = binding.value
},
//指令所在元素被插入页面时
inserted(element){
element.focus()
},
//指令所在的模板被重新解析时
update(element,binding){
element.value = binding.value
}
})
//定义混入
Vue.mixin({
data() {
return {
x:100,
y:200
}
},
})
// 给 Vue原型上添加一个方法(vm 和 vc 都能用)
Vue.prototype.hello = ()=>{alert('你好啊')}
}
}
使用插件,main.js
// 引入 Vue
import Vue from 'vue'
// 引入 App 组件
import App from './App.vue'
import plugins from './plugins';
// 关闭 Vue 的生产提示
Vue.config.productionTip = false
// 使用插件,传入三个值
Vue.use(plugins, 1, 2, 3)
new Vue({
render:h => h(App)
}).$mount('#app')
7. scoped 样式
让样式在局部生效,防止冲突。写法:<style scoped>
组件中所写的样式最后都是会汇总到一起的,假如存在重名的情况,则会产生样式冲突,后引入的组件的样式会覆盖前面引入的组件中的重名样式。使用 scoped 可以让样式只在该组件作用域内生效
<template></template>
<script></script>
<style scoped>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
8. 组件化案例
- 组件化编码流程:
- 拆分静态组件:组件要按照功能点拆分,命名不要与 html 元素冲突
- 实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
- 一个组件在用:放在组件自身即可
- 一些组件在用:放在他们共同的父组件上(状态提升)
- 实现交互:从绑定事件开始
- props 适用于:
- 父组件 ===> 子组件 通信
- 子组件 ===> 父组件 通信(要求父先给子一个函数)
- 使用 v-model 时:v-model 绑定的值不能是 props 传过来的值,因为 props 是不可以修改的
- props 传过来的若是对象类型的值,修改对象中的属性时 Vue 不会报错,但不推荐这样做
8.1 定义 components
MyHeader.vue
<template>
<div class="todo-header">
<input type="text"
placeholder="请输入你的任务名称,按回车键确认"
v-model="name"
@keyup.enter="add" />
</div>
</template>
<script>
import { nanoid } from 'nanoid'
export default {
name: 'MyHeader',
data() {
return {
name: ''
}
},
methods: {
add() {
// 校验数据,输入不能为空
if (!this.name.trim()) return alert('输入不能为空');
// 将用户的输入包装成一个 todo 对象
const todoObj = { id: nanoid(), name: this.name, done: false };
// 调用 App 加到 vc 上的方法,添加一个 todo 对象
this.addTodo(todoObj);
// 清空输入
this.name = '';
}
},
props: ['addTodo'],
}
</script>
<style scoped></style>
MyList.vue
<template>
<ul class="todo-main">
<!-- 展示 App 传过来的数据, MyItem 组件,传入数据和方法 -->
<MyItem v-for="todoObj in todoList"
:key="todoObj.id"
:todo="todoObj"
:checkTodo="checkTodo"
:deleteTodo="deleteTodo" />
</ul>
</template>
<script>
import MyItem from './MyItem.vue';
export default {
name: 'MyList',
// 接收 App 传过来的数据,根据 :xxx='' 的 xxx 名称来接收
props: ['todoList', 'checkTodo', 'deleteTodo'],
components: {
MyItem
}
}
</script>
<style scoped></style>
MyItem.vue
<template>
<li>
<label>
<input type="checkbox"
:checked='todo.done'
@change="handleCheck(todo.id)" />
<!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,修改了props -->
<!-- <input type="checkbox" v-model="todo.done"/> -->
<span>{{todo.name}}</span>
</label>
<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
</li>
</template>
<script scoped>
export default {
name: 'MyItem',
methods: {
handleCheck(id) {
// 调用 App 组件的方法,将 todo.done 值取反
this.checkTodo(id);
},
// 调用 App 组件的方法,删除一个 todo
handleDelete(id) {
this.deleteTodo(id);
}
},
props: ['todo', 'checkTodo', 'deleteTodo'],
}
</script>
<style scoped></style>
MyFooter.vue
<template>
<div class="todo-footer"
v-show="total">
<label>
<!-- <input type="checkbox" :checked="isAll" @change="checkAll" /> -->
<input type="checkbox" v-model="isAll">
</label>
<span>
<span>已完成{{doneTotal}} </span> / 全部{{total}}
</span>
<button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
</div>
</template>
<script>
export default {
name: 'MyFooter',
computed: {
total() {
return this.todos.length
},
doneTotal() {
// return this.todos.reduce((pre, current) => {
// return pre + (current.done === true ? 1 : 0);
// }, 0)
return this.todos.reduce((pre, current) => pre + (current.done === true ? 1 : 0), 0)
},
isAll: {
get() {
return this.doneTotal === this.total && this.total > 0
},
set(value) {
this.checkAllTodo(value);
}
}
},
methods: {
clearAll() {
this.clearAllTodo();
}
},
props: ['todos', 'checkAllTodo', 'clearAllTodo']
}
</script>
<style scoped></style>
8.2 App.vue
<template>
<div id="app">
<div class="todo-container">
<div class="todo-wrap">
<MyHeader :addTodo="addTodo" />
<!-- 传递 todos 给 MyList,名称为 todoList,要用数据绑定,使 '' 里的内容为表达式 -->
<MyList :todoList="todos"
:checkTodo="checkTodo"
:deleteTodo="deleteTodo" />
<MyFooter :todos="todos"
:checkAllTodo="checkAllTodo"
:clearAllTodo="clearAllTodo" />
</div>
</div>
</div>
</template>
<script>
import MyHeader from './components/MyHeader.vue';
import MyFooter from './components/MyFooter.vue';
import MyList from './components/MyList.vue';
export default {
name: 'App',
data() {
return {
todos: [
{ id: '1001', name: '吃饭', done: true },
{ id: '1002', name: '睡觉', done: false },
{ id: '1003', name: '学习', done: true },
]
}
},
methods: {
// 添加一个 todo
addTodo(todoObj) {
this.todos.unshift(todoObj)
},
// 勾选或取消勾选一个 todo
checkTodo(id) {
this.todos.forEach(todo => {
if (todo.id === id) todo.done = !todo.done;
})
},
// 删除一个 todo
deleteTodo(id) {
if (confirm('确定删除吗?')) {
this.todos = this.todos.filter(todo => {
return todo.id !== id;
})
}
},
// 全选或全不选
checkAllTodo(done) {
this.todos.forEach(todo => {
todo.done = done;
})
},
// 清除所有已经完成的 todo
clearAllTodo() {
this.todos = this.todos.filter(todo => !todo.done)
}
},
components: {
MyHeader,
MyFooter,
MyList
}
}
</script>
<style></style>
8.3 reduce 方法
pre 初始为 0 ,之后为上一次这个函数的返回值,current 为当前对象
this.todos.reduce((pre, current) => {
console.log('@', pre);
console.log('@', current);
return pre + 1;
}, 0)
9. webStorage 浏览器本地存储
9.1 概念
- 存储内容大小一般支持 5MB 左右(不同浏览器可能不一样)
- 浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制
- 相关API:
xxxxxStorage.setItem('key', 'value');
该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值xxxxxStorage.getItem('person');
该方法接受一个键名作为参数,返回键名对应的值xxxxxStorage.removeItem('key');
该方法接受一个键名作为参数,并把该键名从存储中删除xxxxxStorage.clear()
该方法会清空存储中的所有数据
- 备注:
- SessionStorage 存储的内容会随着浏览器窗口关闭而消失
- LocalStorage 存储的内容,需要手动清除才会消失
- xxxxxStorage.getItem(xxx) 如果 xxx 对应的 value 获取不到,那么 getItem 的返回值是 null
- JSON.parse(null) 的结果依然是 null
9.2 纯 HTML 使用
<body>
<div id="root">
<h2>浏览器本地存储</h2>
<button onclick="saveData()">点击存储数据</button>
<button onclick="getData()">点击读取数据</button>
<button onclick="deleteData()">点击删除数据</button>
<button onclick="clearData()">点击清空数据</button>
</div>
<script type="text/javascript">
let person = {name: '张三', age: 18};
function saveData(){
localStorage.setItem('msg', 'hello');
localStorage.setItem('key', 'value');
localStorage.setItem('person', JSON.stringify(person))
}
function getData(){
console.log(localStorage.getItem('msg'))
console.log(localStorage.getItem('key'))
const result = localStorage.getItem('person')
console.log(JSON.parse(result))
}
function deleteData(){
localStorage.removeItem('key')
}
function clearData(){
localStorage.clear()
}
</script>
</body>
存储数据
读取数据
删除数据
清空数据
9.3 Vue 中使用
将组件化案例中写死的 todos 改为从浏览器本地存储中读取,读取不到时就为空数组,然后使用侦听器对 todos 进行监视,每当 todos 发生变化,就把 todos 重新存储到浏览器本地存储
<script>
export default {
name: 'App',
data() {
return {
// 从浏览器本地存储中读取
todos: JSON.parse(localStorage.getItem('todos')) || []
}
},
watch: {
// 需要开启深度监视,监视里面每一个对象的变化
todos: {
deep: true,
handler(value) {
localStorage.setItem('todos', JSON.stringify(value));
}
}
},
}
</script>
10. 组件自定义事件
10.1 概念
- 一种组件间通信的方式,适用于:子组件 ===> 父组件
- 使用场景:Fu 是父组件,Zi 是子组件,Zi 想给 Fu 传数据,那么就要在 Fu 中给 Zi 绑定自定义事件(事件的回调也在 Fu 中)
- 绑定自定义事件:
- 第一种方式,在父组件中:
<Demo @fan="test"/>
或<Demo v-on:fan="test"/>
,fan 表示自定义事件名,test 表示回调函数 - 第二种方式,在父组件中:
<Demo ref="de"/> ...... mounted(){ // ‘fan’ 表示自定义事件名,this.test 表示回调函数 this.$refs.de.$on('fan',this.test) }
- 若想让自定义事件只能触发一次,可以使用 once 修饰符,或 $once 方法
- 第一种方式,在父组件中:
- 触发自定义事件:this.$emit('fan',数据)
- 解绑自定义事件 :this.$off('fan')
- 组件上也可以绑定原生 DOM 事件,需要使用 native 修饰符
- 注意:通过 this.$refs.xxx.$on('fan', 回调函数)绑定自定义事件时,回调函数要么配置在 methods 中,要么用箭头函数,否则 this 指向会出问题(这时 this 代表触发自定义事件的组件 Zi,而并非自定义事件和回调函数所在的组件 Fu)
- 销毁当前 Student 组件的实例,销毁后所有 Student 实例的自定义事件全都不奏效,但原生 DOM 事件依然有效
10.2 绑定
App.vue,自定义事件
<template>
<div id="app">
<h2>接收到的学生名字:{{studentName}} </h2>
<!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
<!-- <School :getSchoolName="getSchoolName" /> -->
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用@或v-on) -->
<!-- <Student @fan.once="getStudentName" /> -->
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref),要配合 mounted 绑定自定义事件 -->
<Student ref="stu" />
</div>
</template>
<script>
import Student from './components/Student'
export default {
name: 'App',
data() {
return {
studentName: ''
}
},
methods: {
getSchoolName(name) {
console.log('App收到了学校名:', name)
},
getStudentName(name, ...params) {
console.log('App收到了学生名:', name, params, this)
this.studentName = name;
},
},
components: { Student },
mounted() {
this.$refs.stu.$on('fan', this.getStudentName) // 绑定自定义事件,配合 ref
// this.$refs.stu.$once('fan', this.getStudentName) // 绑定自定义事件(一次性)
},
}
</script>
<style></style>
Student.vue,触发自定义事件
<template>
<div class="student">
<h2>学生姓名:{{name}}</h2>
<h2>学生性别:{{sex}}</h2>
<button @click="sendStudentlName">把学生名给App</button>
</div>
</template>
<script>
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'Student',
data() {
return {
name: '张三',
sex: '男',
number: 0
}
},
methods: {
sendStudentlName() {
// 触发 Student组件实例身上的自定义事件 fan
this.$emit('fan', this.name, 11, 22, 33)
}
},
}
</script>
<style scoped>
点击将子组件的学生名字传给父组件,同时查看回调函数写在 methods 里的 this
查看回调函数直接写在 this.$refs.xxx.$on(‘组件名’, 回调函数) 里的 this
10.3 解绑和销毁
销毁当前 Student 组件的实例,销毁后所有 Student 实例的自定义事件全都不奏效,但原生 DOM 事件依然有效
<template>
<div class="student">
<h2>学生姓名:{{name}}</h2>
<h2>学生性别:{{sex}}</h2>
<h2>当前求和为:{{number}}</h2>
<button @click="add">点我number++</button>
<button @click="sendStudentlName">把学生名给App</button>
<button @click="unbind">解绑atguigu事件</button>
<button @click="death">销毁当前Student组件的实例(vc)</button>
</div>
</template>
<script>
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'Student',
data() {
return {
name: '张三',
sex: '男',
number: 0
}
},
methods: {
sendStudentlName() {
// 触发 Student组件实例身上的自定义事件 fan
this.$emit('fan', this.name, 11, 22, 33)
},
unbind() {
this.$off('fan');
// this.$off(['fan','demo']) // 解绑多个自定义事件
// this.$off() // 解绑所有的自定义事件
},
death() {
// 销毁了当前Student组件的实例,销毁后所有 Student 实例的自定义事件全都不奏效
this.$destroy();
},
add() {
console.log('add回调被调用了')
this.number++
},
},
}
</script>
<style scoped>
使用自定义事件
解绑自定义事件,此时该自定义事件不奏效
销毁实例,此时自定义事件全都不奏效,但原生 DOM 事件仍奏效
11. 全局事件总线(GlobalEventBus)
11.1 概念
一种组件间通信的方式,适用于任意组件间通信
- 安装全局事件总线:
new Vue({ ...... beforeCreate() { // 安装全局事件总线,$bus就是当前应用的vm Vue.prototype.$bus = this }, ...... })
- 使用事件总线:
- 接收数据:A组件想接收数据,则在A组件中给 $bus 绑定自定义事件,事件的回调留在A组件自身
methods(){ demo(data){......} } ...... mounted() { this.$bus.$on('xxxx',this.demo) }
- 提供数据:this.$bus.$emit('xxxx', 数据)
- 最好在 beforeDestroy 钩子中,用 $off 去解绑当前组件所用到的事件
- 接收数据:A组件想接收数据,则在A组件中给 $bus 绑定自定义事件,事件的回调留在A组件自身
11.2 使用
Student.vue,向 School.vue 传递数据,使用 this.$bus.$emit(‘xxx’, data) 触发自定义事件
<template>
<div class="student">
<h2>学生姓名:{{name}}</h2>
<h2>学生性别:{{sex}}</h2>
<button @click="sendStudentName">把学生名给School组件</button>
</div>
</template>
<script>
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'Student',
data() {
return {
name: '张三',
sex: '男',
}
},
methods: {
sendStudentName() {
this.$bus.$emit('hello', this.name);
}
},
}
</script>
<style scoped>
School.vue,接收从 Student.vue 传过来的数据,使用 this.$bus.$on(‘xxx’, data => { })) 监听自定义事件进行接收
<template>
<div class="school">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<h2>收到的学生姓名:{{studentName}} </h2>
</div>
</template>
<script>
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'School',
data() {
return {
name: 'Vue',
address: '北京',
studentName: ''
}
},
methods: {
// getName(data) {
// console.log('School 组件收到了数据:', data);
// this.studentName = data;
// }
},
mounted() {
// this.$bus.$on('hello', this.getName)
this.$bus.$on('hello', data => {
console.log('School 组件收到了数据:', data);
this.studentName = data;
});
},
// 解绑事件
beforeDestroy() {
this.$bus.$off('hello');
},
}
</script>
<style scoped>
点击传递数据
12. 消息订阅与发布(pubsub)
12.1 概念
一种组件间通信的方式,适用于任意组件间通信
- 安装 :
pubsub:npm install pubsub-j
- 引入 :
import pubsub from 'pubsub-js'
- 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身
methods(){ demo(MsgName, data){......} } ...... mounted() { this.pid = pubsub.subscribe('xxx',this.demo) // 订阅消息 }
- 提供数据:pubsub.publish('xxx',数据)
- 最好在 beforeDestroy 钩子中,用 pubSub.unsubscribe(pid) 去取消订阅
12.2 使用
Student.vue,提供数据(发布),使用 pubsub.publish('xxx', data)
<template>
<div class="student">
<h2>学生姓名:{{name}}</h2>
<h2>学生性别:{{sex}}</h2>
<button @click="sendStudentName">把学生名给School组件</button>
</div>
</template>
<script>
import pubsub from 'pubsub-js';
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'Student',
data() {
return {
name: '张三',
sex: '男',
}
},
methods: {
sendStudentName() {
// this.$bus.$emit('hello', this.name);
pubsub.publish('hello', this.name)
}
},
}
</script>
<style scoped>
School.vue,接收数据(订阅),使用 pubsub.subscribe('xxx', (msgName, data) => { })
<template>
<div class="school">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<h2>收到的学生姓名:{{studentName}} </h2>
</div>
</template>
<script>
import pubsub from 'pubsub-js';
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'School',
data() {
return {
name: 'Vue',
address: '北京',
studentName: ''
}
},
mounted() {
// this.$bus.$on('hello', data => {
// console.log('School 组件收到了数据:', data);
// this.studentName = data;
// });
this.pubId = pubsub.subscribe('hello', (msgName, data) => {
console.log('有人发布了 hello 消息,hello 消息的回调函数执行了', msgName, data);
this.studentName = data;
})
},
beforeDestroy() {
// this.$bus.$off('hello');
pubsub.unsubscribe(this.pubId)
},
}
</script>
<style scoped>
点击传递数据
13. nextTick
在下一次 DOM 更新结束后执行其指定的回调,当改变数据后,要基于更新后的新 DOM 进行某些操作时,要在 nextTick 所指定的回调函数中执行,语法:this.$nextTick(回调函数)
修改组件化案例,添加一个编辑的功能,MyItem.vue 添加编辑功能和焦点事件,省略其他代码
<template>
<li>
<label>
<span v-show="!todo.isEdit">{{todo.name}}</span>
<input v-show="todo.isEdit"
type="text"
:value="todo.name"
@blur="handleBlur(todo, $event)"
ref="inputName">
</label>
<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
<button class="btn btn-editor" @click="handleEdit(todo)">编辑</button>
</li>
</template>
<script scoped>
export default {
name: 'MyItem',
methods: {
// 编辑
handleEdit(todo) {
// if(todo.hasOwnProperty('isEdit')) {
// 点击编辑,假如有 isEdit 属性,则设置为 true,让其显示
if (Object.prototype.hasOwnProperty.call(todo, 'isEdit')) {
todo.isEdit = true;
} else {
// 否则给其添加一个 isedit 属性,设置为 true
this.$set(todo, 'isEdit', true);
}
// 在显示输入框之后,获取输入框焦点,配合 blur 使用
this.$nextTick(function () {
this.$refs.inputName.focus();
})
},
// 失去焦点回调(真正执行修改逻辑)
handleBlur(todo, event) {
// 失去焦点,将 isEdit 设置为 false,隐藏输入框,显示<span>
todo.isEdit = false;
// 进行空值判断
if (!event.target.value.trim()) return alert('输入不能为空');
// 传递修改的值给 App
this.$bus.$emit('updateTodo', todo.id, event.target.value)
}
},
props: ['todo'],
}
</script>
<style scoped>
App.vue,省略其他代码
<template></template>
<script>
export default {
name: 'App',
mounted() {
// 更新,接收传过来的值
this.$bus.$on('updateTodo', (id, name) => {
this.todos.forEach((todo) => {
if (todo.id === id) todo.name = name;
})
})
},
}
</script>
<style></style>
14. Vue 封装的过度与动画 transition
14.1 概念
在插入、更新或移除 DOM 元素时,在合适的时候给元素添加样式类名
- 准备好样式:
- 元素进入的样式(未设置 name 属性,默认为 v-xxx):
- v-enter:进入的起点,定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除
- v-enter-active:进入过程中,定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数
- v-enter-to:进入的终点,定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter 被移除),在过渡/动画完成之后移除
- 元素离开的样式:
- v-leave:离开的起点,定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除
- v-leave-active:离开过程中,定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数
- v-leave-to:离开的终点,定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave 被删除),在过渡/动画完成之后移除
- 元素进入的样式(未设置 name 属性,默认为 v-xxx):
- 使用 <transition> 包裹要过度的元素,并配置 name 属性:
<transition name="hello"> <h1 v-show="isShow">你好啊!</h1> </transition>
- 若有多个元素需要过度,则需要使用:<transition-group>,且每个元素都要指定 key 值
- 自定义过渡的类名
14.2 动画效果
先创建动画,然后使用 <transition> ... </transition>,将需要动画过度的 ... 包裹起来,用 .xxx-enter-active 表示进来的时候的效果,.xxx-leave-active 表示离开的时候的效果,xxx 可以在 <transition> 里用 name 属性指定,同时还可以用 apper 设置动画一进来就加载
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<!-- name 表示设置动画样式的名称,appper 表示一进来就加载动画 -->
<transition name="hello" appear>
<h1 v-show="isShow">你好啊!</h1>
</transition>
</div>
</template>
<script>
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'Test',
data() {
return {
isShow: true
}
},
}
</script>
<style scoped>
/* 创建动画 */
@keyframes fan {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0px);
}
}
/* 来的时候的效果 */
.hello-enter-active {
animation: fan 0.5s linear;
}
/* 离开的时候的效果 */
.hello-leave-active {
animation: fan 0.5s linear reverse;
}
h1 {
background-color: orange;
}
</style>
14.3 过度效果
<transition></transition> 只能使用一个元素,多个元素使用 <transition-group>
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<transition-group name="hello" appear>
<!-- 设置交替显示 -->
<h1 v-show="!isShow" key="1">你好啊!</h1>
<h1 v-show="isShow" key="2">Fan!</h1>
</transition-group>
</div>
</template>
<script>
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'Test',
data() {
return {
isShow: true
}
},
}
</script>
<style scoped>
h1 {
background-color: orange;
}
/* 进入的起点、离开的终点 */
.hello-enter, .hello-leave-to {
transform: translateX(-100%);
}
.hello-enter-active, .hello-leave-active {
transition: 0.5s linear;
}
/* 进入的终点、离开的起点 */
.hello-enter-to, .hello-leave {
transform: translateX(0);
}
</style>
14.4 集成第三方库 Animate.css
- 安装:
npm install animate.css --save
- 引入:
import 'animate.css';
- name 设置为 animate__animated 前缀加名字,然后使用自定义类名配合使用
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<transition appear
name="animate__animated animate__bounce"
enter-active-class="animate__swing"
leave-active-class="animate__backOutUp">
<h1 v-show="isShow">你好啊!</h1>
</transition>
</div>
</template>
<script>
import 'animate.css'
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'Test',
data() {
return {
isShow: true
}
},
}
</script>
<style scoped>
h1 {
background-color: orange;
}
</style>
15. Vue 中的 ajax
15.1 请求方式
发 ajax 请求的方式:
- xhr( new XMLHttpRequest() ):xhr.open(); xhr.send();
- JQuery:封装(很多 DOM 操作),$.get,$.post
- Axios:封装, 体积小
- fetch:与 xhr 同级,会包装两层,同时兼容性差
- vue-resource :Vue 里的插件库
15.2 解决 ajax 请求跨域问题
- cors(Cross-origin resource sharing):跨域资源共享,需要后端加上HTTP 头
- jsonp:只能解决 get 请求
- 代理服务器
15.2.1 代理方式一
在 vue.config.js 中添加如下配置:
devServer:{
proxy:"http://localhost:5000"
}
- 优点:配置简单,请求资源时直接发给前端(8080)即可,会代理到 5000
- 缺点:不能配置多个代理,不能灵活的控制请求是否走代理
- 工作方式:若按照上述配置代理,当请求了前端不存在的资源时(没有匹配到静态文件的请求),那么该请求会转发给服务器 (优先匹配前端资源)
发送请求
<template>
<div> <button @click="getStudents">获取学生信息</button> </div>
</template>
<script>
import axios from 'axios' // 引入 axios
export default {
name: 'App',
methods: {
getStudents() {
axios.get('http://localhost:8080/students').then(
response => {
console.log('请求成功了', response.data)
},
error => {
console.log('请求失败了', error.message)
}
)
},
},
}
</script>
15.2.2 代理方式二
编写 vue.config.js 配置具体代理规则:
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
devServer: {
proxy: {
'/fan': { // 匹配所有以 '/fan'开头的请求路径
target: 'http://localhost:5000', // 代理目标的基础路径
pathRewriter: {
// 请求默认会加上拦截的前缀,设置转发请求时去掉前缀 '/fan'
'^ /fan': ''
},
ws: true, // 用于支持 WebSocket
// 默认值为 true,服务器收到的请求头中的 host 为:localhost:5000,代理目标的地址
// 设置为 false 时,服务器收到的请求头中的 host 为:localhost:8080,代理服务器的地址
changeOrigin: true // 用于控制请求头中的 host 值
},
'/foo': {
target: '<other_url>'
}
}
}
})
- 优点:可以配置多个代理,且可以灵活的控制请求是否走代理
- 缺点:配置略微繁琐,请求资源时必须加前缀
发送请求,url 加上前缀
<template>
<div> <button @click="getStudents">获取学生信息</button> </div>
</template>
<script>
import axios from 'axios' // 引入 axios
export default {
name: 'App',
methods: {
getStudents() {
// 加上前缀 /fan,才会被匹配到走代理
axios.get('/fan/students').then(
response => {
console.log('请求成功了', response.data)
},
error => {
console.log('请求失败了', error.message)
}
)
},
},
}
</script>
15.3 使用
安装 Axios:npm install axios --save
单组件引入:import axios from 'axios'
,直接使用 axios
main.js 全局引入:Vue.prototype.$axios = axios
,使用 this.$axios
来使用 Axios
Search.vue,搜索框,将 ajax 请求到的数据传给 List.vue
<template>
<section class="jumbotron">
<h3 class="jumbotron-heading">Search Github Users</h3>
<div>
<input type="text"
placeholder="enter the name you search"
v-model="keyWord" />
<button @click="searchUsers">Search</button>
</div>
</section>
</template>
<script>
import axios from 'axios'
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'Search',
data() {
return {
keyWord: ''
}
},
methods: {
searchUsers() {
// 请求前更新 List 的数据
// 欢迎词为 true(只有第一次需要欢迎词,后面都不需要),加载中为 false,错误信息为 false,数据为空数组
this.$bus.$emit('updateListData', { isFirst: true, isLoading: false, errMsg: '', users: [] })
// 向 GitHub 发送 ajax 请求
axios.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(
response => {
console.log('请求成功了')
// 请求成功后更新 List 的数据,传递给 Lsit组件,不需要欢迎词,
this.$bus.$emit('updateListData', { isLoading: false, errMsg: '', users: response.data.items })
},
error => {
// 请求失败后更新 List 的数据
console.log('请求失败', error.message);
this.$bus.$emit('updateListData', { isLoading: false, errMsg: error.message, users: [] })
}
)
}
},
}
</script>
List.vue,接收数据进行展示
<template>
<div class="row">
<!-- 展示用户列表 -->
<div v-show="info.users.length"
class="card"
v-for="user in info.users"
:key="user.login">
<a :href="user.html_url" target="_blank">
<img :src="user.avatar_url" style='width: 100px' />
</a>
<p class="card-text">{{user.login}}</p>
</div>
<!-- 展示欢迎词 -->
<h1 v-show="info.isFirst">欢迎使用!</h1>
<!-- 展示加载中 -->
<h1 v-show="info.isLoading">加载中....</h1>
<!-- 展示错误信息 -->
<h1 v-show="info.errMsg">{{info.errMsg}}</h1>
</div>
</template>
<script>
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'List',
data() {
return {
info: {
isFirst: true,
isLoading: false,
errMsg: '',
users: []
}
}
},
mounted() {
this.$bus.$on('updateListData', dataObj => {
console.log('List 组件收到了数据');
// 设置后面存在的对象属性会覆盖原来的属性,不存在的则不会覆盖,保留原来的属性
this.info = { ...this.info, ...dataObj }
})
},
}
</script>
<style scoped></style>
15.4 请求携带参数
15.4.1 Get 请求
三种方式:
onSubmit() {
// axios.get('http://localhost:8080/hrms/recruitment/test', { params: { recrTitle: '开发' } }).then(
// axios.get('http://localhost:8080/hrms/recruitment/test?recrTitle=开发').then(
axios({
method: 'get',
url: 'http://localhost:8080/hrms/recruitment/test',
params: {
recrTitle: '开发'
},
headers: { token: ''}
}).then(
response => {
this.menuList = response.data
}
)
}
参数会带在 URL 后面
15.4.2 Post 请求
onSubmit() {
// axios.post('http://localhost:8080/hrms/recruitment/test', { recrTitle: '开发' }).then(
// axios.post('http://localhost:8080/hrms/recruitment/test', this.formData}).then(
axios({
method: 'post',
url: 'http://localhost:8080/hrms/recruitment/test',
data: {
recrTitle: '开发'
},
}).then(
response => {
this.menuList = response.data
}
)
}
15.4.3 Put 请求
与 Post 请求同
15.4.4 Delete 请求
onSubmit() {
// axios.delete('http://localhost:8080/hrms/recruitment/test', { data: { recrTitle: '开发' } }).then(
axios({
method: 'post',
url: 'http://localhost:8080/hrms/recruitment/test',
data: {
recrTitle: '开发'
},
}).then(
response => {
this.menuList = response.data
}
)
}
15.5 vue-resource(插件库)
- 安装:
npm install vue-resource
- 引入:
import vueResource from 'vue-resource'
- 使用:Vue.use(vueResource)
- 发送请求时,把 axios 换成 this.$http,其他与axios 同
this.$http.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(
response => {
console.log('请求成功了')
}
error => {
console.log('请求失败了')
}
)
16. 插槽
让父组件可以向子组件指定位置插入 html 结构,也是一种组件间通信的方式,适用于 父组件 ===> 子组件
16.1 默认插槽
Category.vue,定义插槽,子组件
<template>
<div class="category">
<h3>{{title}}分类</h3>
<!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
<slot>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
</div>
</template>
<script>
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'Category',
props: ['title']
}
</script>
<style scoped></style>
App.vue,父组件,填入 html 结构到子组件 Category.vue
<template>
<div class="container">
<Category title="美食">
<img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg"
alt="">
</Category>
<Category title="游戏">
<ul>
<li v-for="(g,index) in games"
:key="index">{{g}}</li>
</ul>
</Category>
<Category title="电影">
<video controls
src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
</Category>
</div>
</template>
<script>
import Category from './components/Category'
export default {
name: 'App',
components: { Category },
data() {
return {
games: ['红色警戒', '穿越火线', '劲舞团', '超级玛丽'],
}
},
}
</script>
<style scoped></style>
16.2 具名插槽
Category.vue,定义插槽,使用 slot 给插槽定义名字,假如存在多个插槽时必须定义名字,只有一个插槽可忽略
<template>
<div class="category">
<h3>{{title}}分类</h3>
<!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
<slot name="center">Center 我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
<slot name="footer">Footer 我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
</div>
</template>
<script>
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'Category',
props: ['title']
}
</script>
<style scoped></style>
App.vue,父组件,填入 html 结构到子组件 Category.vue,使用 slot 表示填入对应的插槽
<template>
<div class="container">
<Category title="美食">
<!-- 填入插槽 center -->
<img slot="center"
src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg"
alt="">
<!-- 插槽 footer 未填入,则显示默认值 -->
</Category>
<Category title="游戏">
<!-- 填入插槽 center -->
<ul slot="center">
<li v-for="(g,index) in games"
:key="index">{{g}}</li>
</ul>
<!-- 填入插槽 footer -->
<div class="foot"
slot="footer">
<a href="https://blog.fan223.cn">单机游戏</a>
<a href="https://blog.fan223.cn">热门游戏</a>
</div>
</Category>
<Category title="电影">
<video slot="center"
controls
src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
<!-- <template v-slot:footer> -->
<template slot="footer">
<div class="foot">
<a href="https://blog.fan223.cn">经典</a>
<a href="https://blog.fan223.cn">热门</a>
<a href="https://blog.fan223.cn">推荐</a>
</div>
<h4 align="center"> 欢迎观看!</h4>
</template>
</Category>
</div>
</template>
<script>
import Category from './components/Category'
export default {
name: 'App',
components: { Category },
data() {
return {
games: ['红色警戒', '穿越火线', '劲舞团', '超级玛丽'],
}
},
}
</script>
<style scoped></style>
16.3 作用域插槽
数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(games 数据在 Category 组件中,但使用数据所遍历出来的结构由 App 组件决定)
Category.vue,数据在里面,通过数据绑定,让 App.vue 可以获取到数据,:name=‘xxx’,name 表示绑定的名字,xxx 为表达式
<template>
<div class="category">
<h3>{{title}}分类</h3>
<!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
<slot :games="games"
:msg="hello">我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
</div>
</template>
<script>
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'Category',
props: ['title'],
data() {
return {
games: ['红色警戒', '穿越火线', '劲舞团', '超级玛丽'],
hello: 'hello'
}
},
}
</script>
<style scoped></style>
App.vue,使用 scope=‘xxx’ 或 slot-scope=‘xxx’ 来获取数据对象,然后用该数据对象 xxx.name 获取 Category 组件的数据,或者直接 scope={name} 或 slot-scope={name} 直接获取传过来的数据
<template>
<div class="container">
<Category title="游戏">
<!-- <template scope="fan"> -->
<!-- 先获取数据对象,名字自定义,然后用该数据对象.name 获取数据,name 为数据绑定时的名字 -->
<template slot-scope="fan">
<ul>
<li v-for="(g,index) in fan.games" :key="index">{{g}}</li>
</ul>
<h4> {{fan.msg}} </h4>
</template>
</Category>
<Category title="游戏">
<!-- 直接通过 {name} 获取数据 -->
<template slot-scope="{games, msg}">
<ol>
<li v-for="(g,index) in games" :key="index">{{g}}</li>
</ol>
<h4> {{msg}} </h4>
</template>
</Category>
<Category title="游戏">
<template slot-scope="fan">
<h4 v-for="(g,index) in fan.games" :key="index">{{g}}</h4>
</template>
</Category>
</div>
</template>
<script>
import Category from './components/Category'
export default {
name: 'App',
components: { Category },
}
</script>
<style scoped></style>
17. Vuex
17.1 概念
在 Vue 中实现集中式状态(数据)管理的一个 Vue 插件,对 Vue 应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信
- 多个组件依赖于同一状态
- 来自不同组件的行为需要变更同一状态
17.2 Vuex 核心概念和 API
17.2.1 state
-
Vuex 管理的状态对象
-
它应该是唯一的
const state = { xxx: initValue }
17.2.2 actions
-
值为一个对象,包含多个响应用户动作的回调函数
-
在组件中使用:$store.dispatch('对应的 action 回调名', 参数) 触发 actions 中的回调
-
通过 commit() 来触发 mutation 中函数的调用, 间接更新 state
-
可以包含异步代码(定时器, ajax 等等)
const actions = { yyy(context, value){ if (context.state.xxx % 2) { console.log(context, value); context.commit('YYY', value) } }, }
-
context: 上下文,包含多个可能会用到的方法,如 dispatch,可以分发 Action
const actions = { yyy(context, value){ context.dispatch('yyyy', value); }, yyyy(context, value){ ...... context.commit('YYY', value) }, }
17.2.3 mutations
- 值是一个对象,包含多个直接更新 state 的方法
- 在 action 中使用:commit('对应的 mutations 方法名', 参数) 触发 mutations 中的回调
- 不能写异步代码、只能单纯的操作 state
const mutations = { YYY(state, value){ state.xxx += value; }, }
17.2.4 getters
当 state 中的数据需要经过加工后再使用时,使用 getters 加工
- 值为一个对象,包含多个用于返回数据的函数
- 如何使用?—— $store.getters.xxx
const getters = { zzz(state){ return state.xxx * 10 }, }
17.2.5 modules
- 包含多个 module,用于模块化
- 一个 module 是一个 store 的配置对象
- 与一个组件(包含有共享数据)对应
17.3 搭建 Vuex 环境
17.3.1 安装
vue2 中要使用 vuex3 版本,vuex4 只能在 vue3 中使用
npm install vuex@3
17.3.2 准备 store
在 src 目录下创建 store/index.js
// 该文件用于创建 Vuex 中最为核心的 store
// 引入 Vue
import Vue from 'vue'
// 引入 Vuex
import Vuex from 'vuex'
// 应用 Vuex 插件
Vue.use(Vuex)
// 准备 actions——用于响应组件中的动作
const actions = {}
// 准备 mutations——用于操作数据(state)
const mutations = {}
// 准备state——用于存储数据
const state = {}
// 准备getters——用于将state中的数据进行加工
const getters = {}
//创建并暴露 store
export default new Vuex.Store({
//actions: actions,
actions,
mutations,
state,
getters
})
17.3.3 在 main.js 引入 store
// 引入 Vue
import Vue from 'vue'
// 引入 App 组件
import App from './App.vue'
// 关闭 Vue 的生产提示
Vue.config.productionTip = false
// 引入 store,假如 store 文件夹下为 index.js,则默认会去找,可以不写
// import store from './store/index'
import store from './store'
new Vue({
render:h => h(App),
store, // 使用
beforeCreate() {
Vue.prototype.$bus = this;
},
}).$mount('#app')
使用时用 this.$store 来使用 store
17.4 工作流程/原理
- 按照流程,数据在 state 中,sum = 0
- 首先在 Count.vue 中用
this.$store.dispatch('addOdd', this.num)
传递函数名和参数给 store 的 actions - actions 接收到后进行处理(逻辑判断等)后再用
context.commit('ADD', value)
传递建议大写的函数名和参数给 mutations - 在 mutations 中对 state 中的数据进行操作
- 假如不用在 actions 进行处理(逻辑判断等),可以直接在 Count.vue 中用
this.$store.commit('REDUCE', this.num)
传递给 mutations
17.5 使用
Count.vue,用于计算的组件,组件中读取 Vuex 中的数据(state):$store.state.sum
,读取加工后的数据(getter):$store.getters.bigSum
<template>
<div>
<h1>当前求和为:{{$store.state.sum}}</h1>
<h1>放大 10 倍后的值:{{$store.getters.bigSum}}</h1>
<select v-model.number="num">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="incrementOdd">当前求和为奇数再加</button>
<button @click="incrementWait">等一等再加</button>
</div>
</template>
<script>
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'Count',
data() {
return {
num: 1, // 用户选择的数字
}
},
methods: {
increment() {
// this.$store.dispatch('add', this.num)
this.$store.commit('ADD', this.num) // 直接 commit 给 mutations
},
decrement() {
// this.$store.dispatch('reduce', this.num)
this.$store.commit('REDUCE', this.num) // 直接 commit 给 mutations
},
incrementOdd() {
// 先 dispatch给 actions 进行处理,然后再 commit 给 mutations
this.$store.dispatch('addOdd', this.num)
},
incrementWait() {
this.$store.dispatch('addWait', this.num)
},
},
}
</script>
<style lang="css"></style>
store,即 src/store/index.js
// 该文件用于创建 Vuex 中最为核心的 store
// 引入 Vue
import Vue from 'vue'
// 引入 Vuex
import Vuex from 'vuex'
// 应用 Vuex 插件
Vue.use(Vuex)
// 准备 actions——用于响应组件中的动作
const actions = {
// add(context, value) {
// context.commit('ADD', value);
// },
// reduce(context, value){
// context.commit('REDUCE', value)
// },
addOdd(context, value){
if (context.state.sum % 2) {
context.commit('ADD', value)
}
},
addWait(context, value){
setTimeout(() => {
context.commit('ADD', value)
}, 500);
}
}
// 准备 mutations——用于操作数据(state)
const mutations = {
ADD(state, value){
state.sum += value;
},
REDUCE(state, value){
state.sum -= value;
},
}
// 准备state——用于存储数据
const state = {
sum: 0 // 和
}
// 准备getters——用于将state中的数据进行加工
const getters = {
bigSum(state){
return state.sum*10
}
}
// 创建并暴露 store
export default new Vuex.Store({
actions,
mutations,
state,
getters
})
17.6 map 方法
- mapState方法:用于帮助我们映射 state 中的数据为计算属性
computed: { // ...Obj,将 Obj 里的每一组 key:value 展开放到该位置 // 借助 mapState 生成计算属性:sum、school、subject(对象写法) ...mapState({sum:'sum',school:'school',subject:'subject'}), // 借助 mapState 生成计算属性:sum、school、subject(数组写法),名字相同可简写 ...mapState(['sum','school','subject']), },
- mapGetters方法:用于帮助我们映射 getters 中的数据为计算属性
computed: { // 借助mapGetters 生成计算属性:bigSum(对象写法) ...mapGetters({bigSum:'bigSum'}), // 借助mapGetters 生成计算属性:bigSum(数组写法),名字相同可简写 ...mapGetters(['bigSum']) },
- mapActions方法:用于帮助我们生成与 actions 对话的方法,即:包含
$store.dispatch(xxx)
的函数methods:{ // 靠 mapActions 生成:incrementOdd、incrementWait(对象形式) ...mapActions({incrementOdd:'addOdd',incrementWait:'addWait'}) // 靠 mapActions 生成:incrementOdd、incrementWait(数组形式),名字相同可简写 ...mapActions(['addOdd','addWait']) }
- mapMutations方法:用于帮助我们生成与 mutations 对话的方法,即:包含
$store.commit(xxx)
的函数methods:{ // 靠 mapActions 生成:increment、decrement(对象形式) ...mapMutations({increment:'ADD',decrement:'REDUCE'}), // 靠 mapMutations 生成:JIA、JIAN(对象形式),名字相同可简写 ...mapMutations(['ADD','REDUCE']), }
备注:mapActions与 mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象
<template>
<div>
<h1>当前求和为:{{sum}}</h1>
<h1>放大 10 倍后的值:{{$store.getters.bigSum}}</h1>
<h1>学校:{{school}}</h1>
<h1>学科:{{subject}}</h1>
<select v-model.number="num">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<!-- 需要传参 -->
<button @click="increment(num)">+</button>
<button @click="decrement(num)">-</button>
<button @click="incrementOdd(num)">当前求和为奇数再加</button>
<button @click="incrementWait(num)">等一等再加</button>
</div>
</template>
<script>
import { mapState, mapGetters, mapActions, mapMutations } from 'vuex'
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'Count',
data() {
return {
num: 1, // 用户选择的数字
}
},
methods: {
...mapMutations({ increment: 'ADD', decrement: 'REDUCE' }),
...mapActions({ incrementOdd: 'addOdd', incrementWait: 'addWait' })
},
computed: {
...mapState(['sum', 'school', 'subject']),
...mapGetters(['bigSum'])
}
}
</script>
<style lang="css"></style>
17.7 模块化+命名空间
修改 store
const countAbout = {
namespaced:true, // 开启命名空间
state:{x:1},
mutations: { ... },
actions: { ... },
getters: {
bigSum(state){
return state.sum * 10
}
}
}
const personAbout = {
namespaced:true, // 开启命名空间
state:{ ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
countAbout,
personAbout
}
})
- 开启命名空间后,组件中读取 state 数据
// 方式一:自己直接读取 this.$store.state.personAbout.list // 方式二:借助 mapState读取,在前面加上模块的名字 ...mapState('countAbout', ['sum','school','subject']),
- 开启命名空间后,组件中读取 getters 数据
// 方式一:自己直接读取 this.$store.getters['personAbout/firstPersonName'] // 方式二:借助 mapGetters 读取 ...mapGetters('countAbout', ['bigSum'])
- 开启命名空间后,组件中调用 dispatch
// 方式一:自己直接 dispatch this.$store.dispatch('personAbout/addPersonWang', person) // 方式二:借助 mapActions ...mapActions('countAbout', {incrementOdd:'addOdd',incrementWait:'addWait'})
- 开启命名空间后,组件中调用commit
// 方式一:自己直接 commit this.$store.commit('personAbout/ADD_PERSON', person) // 方式二:借助 mapMutations ...mapMutations('countAbout', {increment:'ADD',decrement:'REDUCE'}),
18. vue-router 路由
一个路由(route)就是一组映射关系(key - value),key 为路径, value 可能是 function 或 componen,多个路由需要路由器(router)进行管理。vue-router 是 vue 的一个插件库,专门用来实现 SPA 应用
18.1 SPA
- 单页 Web 应用(single page web application,SPA)
- 整个应用只有一个完整的页面。
- 点击页面中的导航链接不会刷新页面,只会做页面的局部更新
- 数据需要通过 ajax 请求获取
18.2 路由分类
- 后端路由:
- 理解:value 是 function, 用于处理客户端提交的请求
- 工作过程:服务器接收到一个请求时, 根据请求路径找到匹配的函数来处理请求, 返回响应数据
- 前端路由:
- 理解:value 是 component,用于展示页面内容
- 工作过程:当浏览器的路径改变时, 对应的组件就会显示
18.3 基础使用
- 安装:
npm install vue-router@3
,当前 vue-router 默认版本为 vue-router4,用在 Vue3 中,Vue2 只能用 vue-router3,安装时需要指定版本 - 创建 router 的组件,路由组件通常存放在 src/pages 文件夹,一般组件通常存放在 src/components 文件夹
- 创建路由器,在 src 目录下创建 router/index.js
// 该文件专门用于创建整个应用的路由器 // 引入VueRouter import VueRouter from 'vue-router' // 引入路由组件 import Home from '../pages/Home.vue' import About from '../pages/About.vue' // 创建 router 实例对象(路由器),去管理一组一组的路由规则,并暴露出去 export default new VueRouter({ // 路由配置 routes: [ { path: '/about', component: About }, { path: '/home', component: Home }, ] })
- main.js,引入 vue-router,使用该插件
// 引入 Vue import Vue from 'vue' // 引入 App 组件 import App from './App.vue' // 引入 VueRouter import VueRouter from 'vue-router' // 引入路由器 import router from './router/index.js' // 关闭 Vue 的生产提示 Vue.config.productionTip = false // 应用插件 Vue.use(VueRouter) //创建vm new Vue({ el:'#app', render: h => h(App), router:router })
- App.vue,使用 <router-link to="/xxx"> 标签实现路由的切换,to 表示路由路径,使用 <router-view> 标签指定组件的呈现位置
<template> <div> <h2>Vue Router Demo</h2> <div> <!-- 原始 html 中使用a标签实现页面的跳转 --> <!-- <a class="list-group-item active" href="./about.html">About</a> --> <!-- <a class="list-group-item" href="./home.html">Home</a> --> <!-- Vue中借助 router-link 标签实现路由的切换 --> <router-link to="/about">About</router-link> <router-link to="/home">Home</router-link> </div> <div> <!-- 指定组件的呈现位置 --> <router-view></router-view> </div> </div> </template> <script> export default { name: 'App', } </script>
点击页面中的导航链接不会刷新页面,只会做页面的局部更新
- 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载
- 每个组件都有自己的 $route 属性,里面存储着自己的路由信息
- 整个应用只有一个 router,可以通过组件的 $router 属性获取到
18.4 多级(嵌套)路由
-
再准备两个路由组件 Message.vue 和 News.vue
-
在路由器里配置路由规则,src/router/index.js,使用 children 属性来配置多级路由
// 该文件专门用于创建整个应用的路由器 // 引入VueRouter import VueRouter from 'vue-router' // 引入路由组件 import Home from '../pages/Home.vue' import About from '../pages/About.vue' import News from '../pages/News.vue' import Message from '../pages/Message.vue' // 创建一个路由器 export default new VueRouter({ // 路由配置 routes: [ { path: '/about', component: About }, { path: '/home', // 默认为一级路由 component: Home, children: [ // children 配置二级路由 { path: 'news', // 此处前面不要加 /,如一定不要写:/news component: News, }, { path: 'message', component: Message }, ] }, ] })
-
在 Home.vue 路由组件添加子路由
<template> <div> <h2>Home组件内容</h2> <div> <ul class="nav nav-tabs"> <li> <!-- 跳转(要写完整路径)--> <router-link to="/home/news">News</router-link> </li> <li> <router-link to="/home/message">Message</router-link> </li> </ul> <router-view></router-view> </div> </div> </template> <script> export default { // eslint-disable-next-line vue/multi-word-component-names name: 'Home' } </script>
18.5 路由传参
18.5.1 路由的query参数
- 传递参数
<template> <div> <ul> <li v-for="message in messageList" :key="message.id"> <!-- 跳转路由并携带 query 参数,to 的字符串写法 --> <!-- <router-link :to="`/home/message/detail?id=${message.id}&title=${message.title}`"> {{ message.title }} </router-link> --> <!-- 跳转路由并携带 query 参数,to 的对象写法 --> <router-link :to="{ path:'/home/message/detail', query:{ id: message.id, title: message.title } }"> {{message.title}} </router-link> </li> </ul> <router-view></router-view> </div> </template> <script> export default { // eslint-disable-next-line vue/multi-word-component-names name: 'Message', data() { return { messageList: [ { id: '001', title: 'message001' }, { id: '002', title: 'message002' }, { id: '003', title: 'message003' }, ] } }, } </script>
- 接收参数
<template> <ul> <!-- 接收参数 --> <li>消息编号:{{$route.query.id}}</li> <li>消息标题:{{$route.query.title}} </li> </ul> </template> <script> export default { // eslint-disable-next-line vue/multi-word-component-names name: 'Detail', } </script>
18.5.2 命名路由
在路由器里(即 src/router/index.js)使用 name 属性给路由命名
// 该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'
// 引入组件
......
// 创建一个路由器
export default new VueRouter({
// 路由配置
routes: [
{ path: '/about', component: About },
{ path: '/home', component: Home, children: [
{ path: 'news', component: News, },
{ path: 'message', component: Message,
children: [
{
name: 'detail', // 给路由命名
path: 'detail',
component: Detail,
}
]
},
]
},
]
})
简化路由的跳转
<template>
<div>
<ul>
<li v-for="message in messageList" :key="message.id">
<!-- 跳转路由并携带query参数,to的对象写法 -->
<router-link :to="{
name: 'detail',
<!-- path:'/home/message/detail', -->
query:{
id: message.id,
title: message.title
}
}"> {{message.title}} </router-link>
</li>
</ul>
<router-view></router-view>
</div>
</template>
18.5.2 路由的 params 参数
路由携带 params 参数时,若使用 to 的对象写法,则不能使用 path 配置项,必须使用命名路由 name 配置
- 配置路由,声明接收 params 参数
// 该文件专门用于创建整个应用的路由器 import VueRouter from 'vue-router' // 引入组件 ...... // 创建一个路由器 export default new VueRouter({ // 路由配置 routes: [ { path: '/about', component: About }, { path: '/home', component: Home, children: [ { path: 'news', component: News, }, { path: 'message', component: Message, children: [ { name: 'detail', // 给路由命名 path:'detail/:id/:title', // 使用占位符声明接收 params 参数 component: Detail, } ] }, ] }, ] })
- 传递参数
<template> <div> <ul> <li v-for="message in messageList" :key="message.id"> <!-- 跳转并携带 params 参数,to 的字符串写法 --> <!-- <router-link :to="/home/message/detail/${message.id}/${message.title}">{{message.title}}</router-link> --> <!-- 跳转路由并携带 params 参数,to 的对象写法 --> <router-link :to="{ name: 'detail', params:{ id: message.id, title: message.title } }"> {{message.title}} </router-link> </li> </ul> <router-view></router-view> </div> </template>
- 接收参数
<template> <ul> <li>消息编号:{{$route.params.id}}</li> <li>消息标题:{{$route.params.title}} </li> </ul> </template>
18.6 路由的 props 配置
在路由器里(即 src/router/index.js)使用 name 属性给路由命名
// 该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'
// 引入组件
......
// 创建一个路由器
export default new VueRouter({
// 路由配置
routes: [
{ path: '/about', component: About },
{ path: '/home', component: Home, children: [
{ path: 'news', component: News, },
{ path: 'message', component: Message,
children: [
{
name: 'detail', // 给路由命名
path: 'detail/:id/:title',
component: Detail,
// 第一种写法:props 值为对象,该对象中所有的 key-value 的组合最终都会通过 props 传给 Detail 组件
// props:{a:900}
// 第二种写法:props 值为布尔值,布尔值为 true,则把路由收到的所有 params 参数通过 props 传给 Detail 组件
// props:true
//第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
props($route){ // 或者写为 route、{route:{id, title}}
return {
id: $route.params.id,
title: $route.params.title
}
}
}
]
},
]
},
]
})
让路由组件更方便的收到参数
<template>
<ul>
<li>消息编号:{{id}}</li>
<li>消息标题:{{title}} </li>
</ul>
</template>
<script>
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'Detail',
props: ['id', 'title'],
}
</script>
18.7 <router-link>的 replace 属性
控制路由跳转时操作浏览器历史记录的模式
- 浏览器的历史记录有两种写入方式:分别为 push 和 replace,push 是追加历史记录,replace 是替换当前记录。路由跳转时候默认为 push
- 开启 replace 模式:
<router-link replace .......>News</router-link>
,完整写法为:<router-link :replace="true" .......>News</router-link>
18.8 编程式路由导航
不借助 <router-link>
实现路由跳转,让路由跳转更加灵活
<template>
<div>
<ul>
<li v-for="message in messageList" :key="message.id">
<!-- 跳转路由并携带 params 参数,to的对象写法 -->
<router-link :to="{
name: 'detail',
params:{
id: message.id,
title: message.title
}
}"> {{message.title}} </router-link>
<button @click="pushShow(message)">push查看</button>
<button>replace查看</button>
</li>
</ul>
<router-view></router-view>
</div>
</template>
<script>
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'Message',
methods: {
pushShow(message) {
this.$router.push({
name: 'detail',
params: {
id: message.id,
title: message.title
}
})
}
}
}
</script>
使用 push 和 replace 方法来进行跳转操作,可用于 button 按钮
控制前进和后退
this.$router.forward() // 前进
this.$router.back() // 后退
this.$router.go() // 可前进也可后退,传入参数为前进或后退的步数
18.9 缓存路由组件
让不展示的路由组件保持挂载,不被销毁,在组件的呈现位置包上 <keep-alive>
标签,include 属性表示组件名
<template>
<div>
<h2>Home组件内容</h2>
<div>
<ul class="nav nav-tabs">
<li>
<router-link class="list-group-item" active-class="active" to="/home/news">News</router-link>
</li>
<li>
<router-link class="list-group-item" active-class="active" to="/home/message">Message</router-link>
</li>
</ul>
<!-- 缓存多个路由组件 -->
<!-- <keep-alive :include="['News','Message']"> -->
<!-- 缓存一个路由组件 -->
<keep-alive include="News">
<router-view></router-view>
</keep-alive>
</div>
</div>
</template>
切换其他组件再回来,内容还在
18.10 路由组件的生命周期钩子
路由组件所独有的两个钩子,用于捕获路由组件的激活状态
- activated :路由组件被激活时触发
- deactivated :路由组件非激活时触发
<script>
export default {
name:'News',
data() {
return {
opacity:1
}
},
activated() {
console.log('News组件被激活了')
this.timer = setInterval(() => {
this.opacity -= 0.01
if(this.opacity <= 0) this.opacity = 1
},16)
},
deactivated() {
console.log('News组件失活了')
clearInterval(this.timer)
},
}
</script>
18.11 路由(导航)守卫
对路由进行权限控制,分为全局守卫、独享守卫、组件内守卫
18.11.1 meta 路由元信息
将任意信息附加到路由上,如过渡名称、谁可以访问路由等。这些事情可以通过接收属性对象的 meta 属性来实现,并且它可以在路由地址和导航守卫上都被访问到:
const router = new VueRouter({
routes:[
{
name:'guanyu',
path:'/about',
component:About,
meta:{isAuth:true, title:'关于'}
},
]
})
18.11.2 全局守卫
在路由器 src/router/index.js 定义全局守卫,分为全局前置守卫和后置守卫,可以通过路由元信息存储的内容来进行验证
// 该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'
// 引入组件
......
// 创建并暴露一个路由器
const router = new VueRouter({
routes:[
{ name:'guanyu', path:'/about', component:About, meta:{title:'关于'} },
{ name:'zhuye', path:'/home', component:Home, meta:{title:'主页'},
children:[
{ name:'xinwen', path:'news', component:News, meta:{isAuth:true,title:'新闻'} },
{ name:'xiaoxi', path:'message', component:Message, meta:{isAuth:true,title:'消息'},
children:[
{ name:'xiangqing', path:'detail', component:Detail, meta:{isAuth:true,title:'详情'}, props:true
}
]
}
]
}
]
})
// 全局前置路由守卫————初始化的时候被调用、每次路由切换之前被调用
router.beforeEach((to, from, next)=>{
if(to.meta.isAuth){ // 判断是否需要鉴权
if(localStorage.getItem('school')==='vue'){
next()
}else{
alert('学校名不对,无权限查看!')
}
}else{
next()
}
})
//全局后置路由守卫————初始化的时候被调用、每次路由切换之后被调用
router.afterEach((to, from)=>{
document.title = to.meta.title || '系统'
})
export default router
参数 To 和 From
点击 News,进行权限判断
18.11.3 独享守卫
只有前置守卫,没有后置
const router = new VueRouter({
routes:[
{
name:'zhuye', path:'/home', component:Home, meta:{title:'主页'},
children:[
{
name:'xinwen',
path:'news',
component:News,
meta:{isAuth:true,title:'新闻'},
beforeEnter: (to, from, next) => {
if(to.meta.isAuth){ // 判断是否需要鉴权
if(localStorage.getItem('school')==='vue'){
next()
}else{
alert('学校名不对,无权限查看!')
}
}else{
next()
}
}
},
]
}
]
})
// 全局后置路由守卫————初始化的时候被调用、每次路由切换之后被调用
router.afterEach((to,from)=>{
document.title = to.meta.title || '系统'
})
export default router
18.11.4 组件内守卫
<script>
export default {
name:'About',
// 通过路由规则,进入该组件时被调用
beforeRouteEnter (to, from, next) {
if(to.meta.isAuth){ //判断是否需要鉴权
if(localStorage.getItem('school')==='vue'){
next()
}else{
alert('学校名不对,无权限查看!')
}
}else{
next()
}
},
// 通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next) {
next()
}
}
</script>
18.12 路由器的两种工作模式
// 创建并暴露一个路由器
export default new VueRouter({
// mode: 'history'
mode: 'hash',
routes:[
{
name:'guanyu',
path:'/about',
component:About,
meta:{title:'关于'}
},
]
})
- hash 模式:
- # 及其后面的内容就是 hash 值,但地址中永远带着 # 号,不美观
- hash 值不会包含在 HTTP 请求中,即:hash 值不会带给服务器,兼容性较好
- 若以后将地址通过第三方手机 app 分享,若 app 校验严格,则地址会被标记为不合法
- history 模式:
- 地址干净,美观
- 兼容性和 hash 模式相比略差
- 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题
19. Vue UI 组件库
19.1 移动端常用 UI 组件库
- Vant :https://youzan.github.io/vant
- Cube UI :https://didi.github.io/cube-ui
- Mint UI :http://mint-ui.github.io
19.2 PC 端常用 UI 组件库
- Element UI :https://element.eleme.cn
- IView UI :https://www.iviewui.com
19.3 Element UI
详见:https://blog.csdn.net/ACE_U_005A/article/details/124464590