1、什么是vue
Vue是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。Vue.js是以数据驱动和组件化的思想构建的;动态构建用户界面:把后台的数据动态显示在前端页面中;渐进式框架:Vue的库由核心库和一系列相关的插件组成;核心库比较轻量,可以实现一些基础的功能;我们可以只使用Vue的核心功能,如果需要一些其他的功能,我们可以在核心库的基础上添加进来其他的插件;
2、bootstrap和vue的区别
Bootstrap 是一个干净、直观、健壮的基于 HTML、CSS 和 JavaScript 的前端开发框架,而 vue 是一个用于构建用户界面的渐进式 JavaScript 框架。
3、vue父子通信
3.1 父组件到子组件通讯
父组件到子组件的通讯主要为:子组件接受使用父组件的数据,这里的数据包括属性和方法(String, Number, Boolean, Object, Array , Function)。vue提倡单向数据流,因此在通常情况下都是父组件传递数据给子组件使用,子组件触发父组件的事件,并传递给父组件所需要的参数。
通过props接收数据
父子通讯中最常见的数据传递方式就是通过props传递数据,就好像方法的传参一样,父组件调用子组件并传入数据,子组件接受到父组件传递的数据进行验证使用。
props可以接受function,所以function也可以以这种方式传递到子组件使用。
<!-- 父组件 -->
<template>
<div class="fatherView">
<p>父组件</p>
<p>接收子组件的值<span style="color: blueviolet;">{{ childName }}</span></p>
<!-- 插入子组件 -->
<!-- :绑定要传递给子组件的数据 -->
<Child :fatherMassage="fatherName"></Child>
</div>
</template>
<script>
import Child from './Child.vue'
export default {
name: "Father",
// 组件传值
props: [],
data() {
return {
// 传递给子组件的数据
fatherName: 'father',
}
},
components: {
// 注册子组件
Child
}
}
</script>
<style lang="less" scoped>
.fatherView {
padding: 10px;
background-color: aquamarine;
}
</style>
-------------------------------------------------------------------------------
<!--子组件-->
<template>
<div class="childView">
<p>子组件</p>
<p>来自父组件的消息<span style="color: yellowgreen;">{{ fatherMassage }}</span></p>
</div>
</template>
<script>
export default {
name: "Child",
// 接收父组件的数据
props: ['fatherMassage'],
data() {
return {
}
},
}
</script>
<style lang="less" scoped>
.childView{
padding: 10px;
background-color: antiquewhite;
}
</style>
3.2 子组件到父组件通讯
子组件到父组件的通讯主要为父组件如何接受子组件之中的数据。这里的数据包括属性和方法(String,Number,Boolean,Object, Array ,Function)。
通过$emit传递父组件数据
与父组件到子组件通讯中的$on配套使用,可以向父组件中触发的方法传递参数供父组件使用。
<!-- 父组件 -->
<template>
<div class="fatherView">
<p>父组件</p>
<p>接收子组件的值<span style="color: blueviolet;">{{ childName }}</span></p>
<!-- 插入子组件 -->
<!-- @自定义事件(@事件名要与子组件定义的一致)接收子组件的数据 -->
<Child @childFun="childRes"></Child>
</div>
</template>
<script>
import Child from './Child.vue'
export default {
name: "Father",
// 组件传值
props: [],
data() {
return {
fatherName: 'father',
childName: ''
}
},
methods: {
// 接收并使用子组件数据
childRes(childName) {
this.childName = childName
}
},
created() {
this.childRes()
},
components: {
Child
}
}
</script>
<style lang="less" scoped>
.fatherView {
padding: 10px;
background-color: aquamarine;
}
</style>
<!-- 子组件 -->
<template>
<div class="childView">
<p>子组件</p>
<!-- 点击触发子传父事件 -->
<button @click="childEvent">子传父</button>
</div>
</template>
<script>
export default {
name: "Child",
props: [],
data() {
return {
childName: 'child',
}
},
methods: {
// 子传父
// 需要传递给父组件的数据this.$emit('函数名', 参数)
childEvent(){
this.$emit('childFun', this.childName)
}
},
created() {
},
}
</script>
<style lang="less" scoped>
.childView{
padding: 10px;
background-color: antiquewhite;
}
</style>
4、computed 与 watch 的区别
功能上:computed是计算属性,watch是监听一个值的变化,然后执行对应的回调。
是否调用缓存:computed中的函数所依赖的属性没有发生变化,那么调用当前的函数的时候会从缓存中读取,而watch在每次监听的值发生变化的时候都会执行回调。
是否调用return:computed中的函数必须要用return返回,watch中的函数不是必须要用return。
computed默认第一次加载的时候就开始监听;watch默认第一次加载不做监听,如果需要第一次加载做监听,添加immediate属性,设置为true(immediate:true)
使用场景:computed----当一个属性受多个属性影响的时候,使用computed-----购物车商品结算。watch–当一条数据影响多条数据的时候,使用watch-----搜索框。
5、Vue中ref和($refs)的用法
在Vue中一般很少会用到直接操作DOM,但不可避免有时候需要用到,这时我们可以通过ref和$refs这两个来实现
5.1 ref
ref 被用来给元素或子组件注册引用信息, 引用信息将会注册在父组件的 $refs 对象上,如果是在普通的DOM元素上使用,引用指向的就是 DOM 元素,如果是在子组件上,引用就指向组件的实例。
ref 加在普通的元素上,用this.$refs.name获取到的是dom元素
ref 加在子组件上,用this.$refs.name获取到的是组件实例,可以使用组件的所有方法。
当v-for用于元素或组件的时候,引用信息将是包含DOM节点或组件实例的数组。关于ref注册时间的重要说明:因为ref本身是作为渲染结果被创建的,在初始渲染的时候你不能访问它们-它们还不存在!$refs也不是响应式的,因此你不应该试图用它在模板中做数据绑定。
利用 v-for 和 ref 获取一组数组或者dom 节点:
<template>
<div class="refView">
<ul>
<li v-for="item in 6" ref="list">{{ item }}</li>
</ul>
</div>
</template>
<script>
export default {
name: "Ref",
mounted(){
console.log(this.$refs.list);
}
}
</script>
<style lang="less" scoped>
ul{
display: flex;
}
li{
list-style: none;
margin-right: 5.3333vw;
}
</style>
5.2 $refs
$refs是一个对象,持有已注册过 ref的所有的子组件。
6、vue单向数据流
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。
额外的,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。
7、Vuex
vuex中一共有五个状态 State Getter Mutation Action Modules
7.1 State:
Vuex使用单一状态树,用一个对象就包含了全部的应用层级状态。
vuex 管理的状态对象,可以把这个看成是data,专门用来存储数据;
它应该是唯一的;
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
//数据,相当于data
state: {
name:"张三",
age:12,
count:0
},
})
在组件中想访问这里面的数据:
标签中:
<p>{{ $store.state.name }}</p>
函数中:
console.log(this.$store.state.name);
从vuex中按需导入mapstate函数
当前组件需要的全局数据,映射为当前组件computed属性
<template>
<div class="">
<p>{{ $store.state.name }}</p>
<!-- 直接渲染计算属性中导入的数据 -->
<p>{{ name }}</p>
</div>
</template>
<script>
// 从vuex中按需导入mapstate函数
import { mapState } from "vuex";
export default {
computed: {
...mapState(['name', 'age', 'count'])
}
}
</script>
<style lang="less" scoped></style>
7.2 Mutations:
更改Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type)和 一个回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数
包含多个直接更新 state 的方法(回调函数)的对象
谁来触发: action 中的commit('mutation 名称')
只能包含同步的代码, 不能写异步代码
在mutations中的方法,最多传递两个参数。其中参数1:是state状态,参数2:通过commit提交过来的参数。如果传递多个参数,可以传递一个对象。
// 定义方法,操作state里面的数据
mutations: {
addcount(state, num){
state.count = state.count + num
},
reduce(state){
state.count--
}
},
在组件中使用:
<template>
<div class="">
<!-- 直接渲染计算属性中导入的数据 -->
<p>count:{{ count }}</p>
<button @click="btnAdd">增加count的值</button>
<button @click="btnReduce">减少count的值</button>
</div>
</template>
<script>
import { mapState, mapMutations } from "vuex";
export default {
methods: {
// 1、通过commit执行mutations中的方法(方法名,参数)
// btnAdd(){
// // 加10
// this.$store.commit('addcount', 10)
// },
// btnReduce(){
// this.$store.commit('reduce')
// },
// 2、通过...mapMutations把mutations里的函数导入
...mapMutations(['addcount', 'reduce']),
btnAdd(){
this.addcount(10)
},
btnReduce(){
this.reduce()
},
},
computed: {
...mapState(['name', 'age', 'count'])
}
}
</script>
<style lang="less" scoped></style>
7.3 Actions:
Action 类似于 mutation,不同在于:Action 提交的是 mutation,而不是直接变更状态。Action 可以包含任意异步操作。
包含多个间接更新state的方法的对象
通过执行: commit()来触发mutation 的调用, 间接更新 state
谁来触发: 组件中:$store.dispatch('action 名称', data1)
在 action 内部执行异步操作
// 异步操作mutations
actions: {
asyncReduce(content){
// 模拟异步
setTimeout(() => {
console.log(content);
content.commit('reduce')
}, 1000)
}
},
在组件中使用:
<template>
<div class="">
<!-- 直接渲染计算属性中导入的数据 -->
<p>count:{{ count }}</p>
<button @click="btnAsync">异步减少count的值</button>
</div>
</template>
<script>
import { mapState, mapMutations, mapActions } from "vuex";
export default {
methods: {
// 1、直接使用 dispatch触发Action函数
// btnAsync(){
// this.$store.dispatch("asyncReduce")
// }
// 2、通过...mapActions把actions里的函数导入
...mapActions(['asyncReduce']),
btnAsync(){
this.asyncReduce()
}
},
computed: {
...mapState(['name', 'age', 'count'])
}
}
</script>
<style lang="less" scoped></style>
7.4 Getters:
getters只负责对外提供数据,不负责修改数据。如果想要修改state中的数据,请去找mutations。
包含多个计算属性(get)的对象
谁来读取: 组件中:$store.getters.xxx
7.5 Modules:
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter,甚至是嵌套子模块——从上至下进行同样方式的分割
包含多个 module
一个 module 是一个 store 的配置对象
与一个组件(包含有共享数据)对应
8、路由守卫
比如说,当点击商城的购物车的时候,需要判断一下是否登录,如果没有登录,就跳转到登录页面,如果登陆了,就跳转到购物车页面,相当于有一个守卫在安检
路由守卫写在main.js文件,或者写在router文件夹下的index.js文件
路由守卫有三种:
全局钩子: beforeEach、 afterEach
独享守卫(单个路由里面的钩子): beforeEnter、 beforeLeave
组件内守卫:beforeRouteEnter、 beforeRouteUpdate、 beforeRouteLeave
全局守卫:
router.beforeEach() // 进入之前触发
router.afterEach() // 进入之后触发
每个守卫方法接收三个参数:
to: Route: 即将要进入的目标路由对象(to是一个对象,是将要进入的路由对象,可以用to.path调用路由对象中的属性)
from: Route: 当前导航正要离开的路由
next: Function: 这是一个必须需要调用的方法,执行效果依赖 next 方法的调用参数。
8.1 前置路由守卫(每次切换前被调用)
首先先在需要配置路由守卫的地方加上 meta: { isAuth: true }
{
path: '/',
name: 'Home',
component: () => import('../views/Home.vue'),
meta: { isAuth: true, title:'主页' },
},
//全局前置路由守卫————初始化的时候被调用、每次路由切换之前被调用
router.beforeEach((to, from, next) => {
//如果路由需要跳转
if (to.meta.isAuth) {
//判断 如果school本地存储是qinghuadaxue的时候,可以进去
if (localStorage.getItem('school') === 'qinghuadaxue') {
next() //放行
} else {
alert('抱歉,您无权限查看!')
}
} else {
// 否则,放行
next()
}
})
8.2 后置路由守卫(每次切换后被调用)
是路由跳转之后执行的事件,可以用作跳转路由后更改网页名
首先路由的meta需要配置title的名字
{
path: '/',
name: 'Home',
component: () => import('../views/Home.vue'),
meta: { isAuth: true, title:'主页' },
},
//全局后置路由守卫————初始化的时候被调用、每次路由切换之后被调用
router.afterEach((to, from) => {
document.title = to.meta.title || '默认名' //修改网页的title
})
8.3 独享路由守卫(某一个路由所单独享用的路由守卫)
独享路由守卫只有前置没有后置
{
path: '/',
name: 'Home',
component: () => import('../views/Home.vue'),
meta: { isAuth: true },
beforeEnter: (to, from, next) => {
if (to.meta.isAuth) { //判断是否需要授权
if (localStorage.getItem('school') === 'qinghuadaxue') {
next() //放行
} else {
alert('抱歉,您无权限查看!')
}
} else {
next() //放行
}
}
},
8.4 组件内守卫(某一个路由所单独享用的路由守卫)
独享路由守卫只有前置没有后置,直接写在.vue文件中
//通过路由规则,进入该组件时被调用
beforeRouteEnter(to,from,next) {
if(toString.meta.isAuth){
if(localStorage.getTime('school')==='qinghuadaxue'){
next()
}else{
alert('学校名不对,无权限查看!')
}
} else{
next()
}
},
//通过路由规则,离开该组件时被调用
beforeRouteLeave(to,from,next) {
next()
}
9、hash路由与history的区别
9.1 hash模式
hash模式的url会在尾巴后面带上#号,hash值的变化不会导致浏览器向服务器发出请求,不会导致重新加载页面,hash的改变的会触发hashchange时间,可以监测浏览器的前进后退,hash的传参会有体积的限制
优点
实现简单,兼容性好(兼容到ie8)
绝大多数前端框架均提供了给予hash的路由实现
不需要服务器端进行任何设置和开发
除了资源加载和ajax请求以外,不会发起其他请求
缺点
对于部分需要重定向的操作,后端无法获取hash部分内容,导致后台无法取得url中的数据,典型的例子就是微信公众号的oauth验证
服务器端无法准确跟踪前端路由信息
对于需要锚点功能的需求会与目前路由机制冲突
9.2 history模式
history模式不仅可以在url里放参数,还可以将数据存放在一个特定的对象中
需要与后端配合、后端可以拿到路由信息
有history.go()、history.back()、history.forward()、history.pushState()、history.replaceState()
优点
对于重定向过程中不会丢失url中的参数。后端可以拿到这部分数据
绝大多数前段框架均提供了browser的路由实现
后端可以准确跟踪路由信息
可以使用history.state来获取当前url对应的状态信息
缺点
兼容性不如hash路由(只兼容到IE10)
需要后端支持,每次返回html文档
10、插槽(slot)
插槽就是子组件中用slot标签定义的预留位置,有name属性叫具名插槽,不设置name属性的叫不具名插槽,使用插槽主要是为了在父组件中使用子组件标签的时候可以往子组件内写入html代码。
10.1 具名插槽
父组件:
<template>
<div class="fatherView">
<p>父组件</p>
<!-- 子组件 -->
<Child>
<!-- 往插槽里写内容 -->
<template #childSlot>插槽内容</template>
</Child>
</div>
</template>
子组件:
<template>
<div class="childView">
<p>子组件</p>
<!-- 具名插槽 -->
<slot name="childSlot"></slot>
</div>
</template>
效果:
10.2 作用域插槽
父组件:
<template>
<div class="fatherView">
<p>父组件</p>
<!-- 插入子组件 -->
<Child>
<!-- 往插槽里写内容 -->
<!-- #childSlot="{ arr }接收子组件arr数据 -->
<template #childSlot="{ arr }">
<p style="color: red;">插槽内容</p>
<p v-for="item in arr" :key="index" style="color: red;">{{ item }}</p>
</template>
</Child>
</div>
</template>
子组件:
<template>
<div class="childView">
<p>子组件</p>
<!-- 插槽 -->
<!-- :arr="arr"绑定data中arr数据 -->
<slot name="childSlot" :arr="arr"></slot>
</div>
</template>
<script>
export default {
name: "Child",
data() {
return {
arr: ['a', 'b', 'c']
}
},
}
</script>
效果: