Vue复习笔记
一、脚手架创建Vue项目
1.1 首先需要node环境
-
去node官网下载node应用,然后安装就行了,版本就选最新的,node自带npm包管理器
-
在npm中安装淘宝依赖(cnpm)会比npm快速的安装插件
-
npm install -g cnpm --registry=https://registry.npm.taobao.org
-
1.2 使用npm全局安装脚手架
-
npm install -g @vue/cli
1.3 使用脚手架初始化vue项目(新版本)
-
vue create 项目名 **新版脚手架命令 //接下来可选择vue3.x还是vue2.x
二、vue的基础语法
0 项目代码参考:
- 01vue初体验,
- 02插值的操作,
- 03动态绑定属性,
- 04计算属性,
- 05事件监听v-on,
- 06条件判断,
- 07v-for的使用,
- 08购物车案例,
- 09v-model的使用
2.1 Hello Vue
<div id="app">
{{message}}<br />
{{name}}
</div>
<script src="../vue/vue.js"></script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'Hello Vue!',
name:'spa'
}
});
</script>
代码解读:当使用vue的时候,需要创建一个vue的实例,new Vue时需要传入一个对象对象中的属性有el(用来挂载管理的标签#app,它的类型可以是string也可以是HTMLElement节点),data(用来定义数据,可以在受管理的标签中直接使用这些定义好的数据,它的类型是Object或者Function),methods(定义方法,它的类型是Object,里面可以存放多种方法)
2.2 计数器案例
<div id="app">
<p>当前计数:{{count}}</p>
<!-- 普通用法 -->
<!-- <button v-on:click="count++">加1</button>
<button v-on:click="count--">减1</button> -->
<!-- 绑定函数 -->
<button v-on:click="increment">加1</button>
<button v-on:click="decrement">减1</button>
</div>
<script src="../vue/vue.js"></script>
<script>
const app = new Vue({
el:'#app',
data:{
count:0
},
methods:{
increment(){
console.log('我加了');
this.count++;
},
decrement(){
console.log('我减了');
this.count--;
}
}
});
</script>
代码解读:通过本案例学习到了methods属性(用来定义方法),还学习到了v-on指令的使用,v-on指令的语法糖为@符号
2.3 mvvm概念
view:视图,其实就是我们的DOM节点
model:就是我们抽离出来的数据,存放在data属性里
viewmodel:我们实例化出来的vue对象,它就像是一个处理器一样,连接view和model俩端
2.4 v-if和v-show
<div id="app">
<p v-show="isShow">是否显示</p>
<button @click="changeH">切换</button>
</div>
<script src="../vue/vue.js"></script>
<script>
const app = new Vue({
el:'#app',
data:{
isShow:true
},
methods: {
changeH(){
this.isShow = !this.isShow;
}
},
});
</script>
1. if是vue的一个内部指令,作用于HTML中,根据表达式的值的真假条件,销毁或重建元素;
2. v-show根据表达式之真假值,切换元素的 display CSS 属性
2.5 v-for
<div id="app">
<ul>
<li v-for="item in movies">{{item}}</li>
<button @click="changeArr">更新</button>
</ul>
</div>
<script src="../vue/vue.js"></script>
<script>
const app = new Vue({
el:'#app',
data:{
movies:['海王','星际穿越','大话西游','肖申克的救赎']
},
methods:{
changeArr(){
//1.通过arr[]的方式更新数组的值,不是响应式的
//this.arr[0] = 'dddd'
//2.push,pop,sheft,unsheft,splice,reverse等方法都是响应式的
//Vue自己实现的set(更新的对象,更新的下标,更新的值)
//Vue.set(this.arr,0,'ddd')
}
}
});
</script>
代码解读:v-for指令是循环渲染一组data中的数组,需要以 item in movies形式items是源数据数组并且item是数组元素迭代的别名。通过数组的下标(arr[index])去改变数组的值时,vue不是响应式的,也就是说vue不会监听这种改变
2.6 v-once
<div id="app">
<p>{{message}}</p>
<!-- 即使改变message变量的值,加了v-once指令的标签也只渲染一次,从此不再发生改变 -->
<p v-once>{{message}}</p>
</div>
<script src="../vue/vue.js"></script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'hello world'
}
});
</script>
2.7 v-text和v-html
当我们网速很慢或者javascript出错时,会显示{{xxx}};Vue提供的v-text、v-html可以解决这个问题;
<script src="/assets/js/vue.js"></script>
<div id="app">
<p v-text="message">hello</p>
<span v-html="msgHtml">abc </span>
</div>
<script>
var app = new Vue({
el:'#app',
data:{
message:'hello vue',
msgHtml:'<h2>wellcome to vue world!</h2>'
}
})
</script>
2.8 v-pre
<div id="app">
<!-- 原封不动的解析,而不会触发mustache语法 -->
<h2 v-pre>{{message}}</h2>
</div>
<script src="../vue/vue.js"></script>
<script>
const app = new Vue({
el:'#app',
data:{
message:'{{message}}'
}
});
</script>
2.9 v-on
v-on 就是监听事件,可以用v-on指令监听DOM事件来触发一些javascript代码;
还可以使用@click代替v-on
<html> <head> <title>Add</title> <script src="/assets/js/vue.js"></script> </head> <body> <div id="von"> <p>number:{{num}}</p> <button v-on:click="add">+</button> <button v-on:click="sub">-</button><br/> <button @click="add">加</button> <button @click="sub">减</button> </div> <script>
new Vue({
el:'#von',
data:{
num:100
},
methods:{
add:function(){
this.num = this.num + 1;
},
sub:function(){
this.num --;
}
}
});
</script> </body> </html>
2.10 v-on的修饰符
<div id="app">
<!-- 1.阻止事件冒泡(.stop) -->
<div @click="clickDiv">
aaaaassss
<button @click.stop="btn">按钮</button>
</div>
<!-- 2.阻止默认事件(.prevent) -->
<form action="baidu">
<input type="submit" value="提交" @click.prevent="submitClick">
</form>
<!-- 3.监听键盘的按键(enter:回车键) -->
<input @keyup.enter="press" type="text" />
<!-- 4.once:只触发一次 -->
<button @click.once="ddd">dddd</button>
</div>
<script src="../vue/vue.js"></script>
<script>
const app = new Vue({
el:'#app',
data:{
count:0
},
methods: {
clickDiv(){
console.log('div');
},
btn(){
console.log('btn');
},
submitClick(){
console.log('1111');
},
press(){
console.log('抬起');
},
ddd(){
console.log('once');
}
},
});
</script>
2.11 v-model
绑定数据源:就是把数据绑定在特定的表单元素上,可以很容易的实现双向数据绑定;我们需要vue实例可以帮我们渲染数据并响应式的监听数据修改,同时我们还需要监听用户行为,如果用户在标签上面修改了数据我们需要获取到数据可以使用v-mode指令;
<div id="app">
<!-- 主要是inpute事件和v-bind的结合就能实现v-model -->
<!-- <input type="text" :value="val" @input="changeInput"> -->
<!-- 上面的简写 -->
<input type="text" :value="val" @input="e => val = e.target.value">
<h3>{{val}}</h3>
</div>
<script src="../vue/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
val:''
},
methods: {
changeInput(e){
this.val = e.target.value;
}
},
});
</script>
2.12 v-model的修饰符
<div id="app">
<!-- 1.修饰符:lazy -->
<!-- lazy修饰符只有在你失去焦点或按下回车键的时候才会,把值绑定,这样可以避免频繁的触发双向绑定 -->
<input type="text" v-model.lazy="message">
<h2>你的值是:{{message}}</h2>
<!-- 2.修饰符:number -->
<!-- 控制绑定值数字输入,但是好像有bug,先输入数字可以只记录数字,先输入字符则会变成字符串(v-model会把值转换成String类型) -->
<input type="text" v-model.number="age">
<h2>您的数字是:{{age}}--{{typeof age}}</h2>
<!-- 3.修饰符:trim -->
<!-- 去除绑定值左右俩边的空格 -->
<input type="text" v-model.trim="name">
<h2>您的名字是:{{name}}</h2>
</div>
<script src="../vue/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
message:'',
age:0,
name:''
}
});
</script>
2.13 v-bind
v-bind是处理HTML中的标签属性的
在html中用v-bind:src=”imgSrc”的动态绑定了src的值,这个值是在vue构造器里的data属性中找到的;
v-bind的语法糖为:冒号(😃
<div id = "vbind">
<!-- v-bind图片 -->
<p><img v-bind:src="imgSrc" @click="change"/></p>
</div>
<script>
new Vue({
el:'#vbind',
data:{
imgSrc:'assets/img/01_af.jpg'
},
methods:{
change:function(){
this.imgSrc = 'assets/img/01_csd.jpg'
}
}
}) ;
</script>
2.14 v-bind动态绑定class
<div id="app">
<!-- 动态绑定的方式 -->
<!-- <h2 :class="{class名:布尔值}">class绑定</h2> -->
<!-- 对象形式 -->
<h2 :class="{active:isactive,lice:islice}">class绑定</h2>
<button @click="changeColor">点我变色</button>
<!-- 数组形式 -->
<h2 :class="[active,lice]">class绑定</h2>
</div>
<script src="../vue/vue.js"></script>
<script>
const app = new Vue({
el:'#app',
data:{
isactive:true,
islice:true
},
methods: {
changeColor(){
this.isactive = !this.isactive;
}
},
});
</script>
2.15 计算属性(computed)
<div id="app">
<h2>{{firstName+' '+lastName}}</h2>
<h2>{{firstName}} {{lastName}}</h2>
<!-- 使用计算属性 -->
<h2>{{fullName}}</h2>
</div>
<script src="../vue/vue.js"></script>
<script>
const app = new Vue({
el:'#app',
data:{
firstName:'spa',
lastName:'ll'
},
computed:{
fullName(){
return this.firstName+' '+this.lastName
}
}
});
</script>
注意:我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的响应式依赖进行缓存的。
三、Vue的生命周期
生命周期:某个事物从诞生到消亡的过程
3.1 生命周期图示
3.2 生命周期函数
beforeCreate:实例刚在内存中被创建出来,此时,还没有初始化好 data 和 methods 属性
created:实例已经在内存中创建OK,此时 data 和 methods 已经创建OK,此时还没有开始 编译模板
beforeMount:此时已经完成了模板的编译,生成了虚拟节点,但是还没有挂载到页面中,此时还不可操作dom节点
mounted:此时,已经将编译好的模板,挂载到了页面指定的容器中显示
beforeUpdate:状态更新之前执行此函数, 此时 data 中的状态值是最新的,但是界面上显示的 数据还是旧的,因为此时还没有开 始重新渲染DOM节点
updated:实例更新完毕之后调用此函数,此时 data 中的状态值 和 界面上显示的数据,都已经完成了更新,界面已经被重新渲染好 了!
beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
destroyed:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会 被销毁。
四、组件化开发
0 项目代码参考:10组件化开发
4.1 创建组件
<div id="app">
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h1>{{title}}</h1>
<h2>haha</h2>
<h3>xixixixixixi</h3>
</div>
</template>
<script src="../vue/vue.js"></script>
<script>
Vue.component('cpn', {
template: '#cpn',
data() {
return {
title: 'cpn组件'
}
},
})
const app = new Vue({
el: '#app'
});
</script>
代码解读:通过Vue的component方法,注册组件,参数1:组件名,参数2:组件实例对象
组件实例对象:
1.template:定义组件的模板,可以通过template标签定义,这里传入标签的id号
2.data:必须为函数,且返回一个新的实例对象
3.其它参数,参考Vue实例…
4.2 组件中的data为什么是函数
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h2>当前计数:{{count}}</h2>
<button @click="increment">+</button>
<button @click="decrement">-</button>
</div>
</template>
<script src="../vue/vue.js"></script>
<script>
Vue.component('cpn', {
template: '#cpn',
//如果不是函数,每次调用组件都不会返回新的对象,就无法使得每个组件都有自己的数据.
//这样导致的后果是每个组件都用同一份数据,这样组件的灵活性就没了.
data() {
return {
count: 0,
}
},
methods: {
increment(){
this.count++;
},
decrement(){
this.count--;
}
},
})
const app = new Vue({
el: '#app'
});
</script>
4.3 组件间通信
组件之间通信也叫,组件间的状态传递,它是联系组件与组件的桥梁
4.3.1 父传子
<div id="app">
<cpn :cmovies="movies" :cmessage="message"></cpn>
</div>
<template id="cpn">
<div>
{{cmovies}}
<hr>
{{cmessage}}
</div>
</template>
<script src="../vue/vue.js"></script>
<script>
const cpn = {
template:'#cpn',
data() {
return {
}
},
//1.普通传递
//props:['cmovies'],
//2.传递的时候进行:类型限制
props:{
cmovies:Array,
cmessage:{
type:String, //类型为字符串
default:'aaaaa', //设置默认值
required:true //必须传入,否则报错
}
}
}
const app = new Vue({
el:'#app',
data:{
movies:['海王','看不见的客人','秘密访客'],
message:'hello world!'
},
components:{
cpn
}
});
</script>
代码解读:
1.通过prop传递,在子组件添加自定义属性,通过v-bind绑定此属性,然后将要传的数据作为属性的值
2.子组件通过props属性接收,接收后可以作为data数据一样使用
3.不要在子组件中直接修改父组件传过来的值,如要修改可以将父组件传来的值作为初始值,赋值给data,作为组件内部的数据使用
4.3.2 子传父
<div id="app">
<!--
子传父:
1.在子组件中通过$emit()方法定义事件,参数1:'自定义事件名',参数2:要传给父组建的参数
2.在父组件中通过v-on指令监听自定义事件,然后在监听的回调中取得数据
-->
<cpn @itemclick="getChild"></cpn>
</div>
<template id="cpn">
<div>
<button v-for="item in categroies" :key="item.id" @click="getData(item)">{{item.name}}</button>
</div>
</template>
<script src="../vue/vue.js"></script>
<script>
//定义子组件
const cpn = {
template:'#cpn',
data() {
return {
//类别
categroies:[
{
id:'aaa',
name:'热门推荐'
},
{
id:'bbb',
name:'手机数码'
},
{
id:'ccc',
name:'电脑办公'
}
]
}
},
methods: {
getData(item){
//子传父:1.通过自定义事件
this.$emit('itemclick',item);
}
},
}
const app = new Vue({
el:'#app',
data:{
},
// 注册子组件
components:{
cpn
},
methods: {
//2.在父组件中取得数据
getChild(item){
console.log(item);
}
},
});
</script>
代码解读:
1.在子组件中通过$emit()方法定义事件,参数1:‘自定义事件名’,参数2:要传给父组建的参数
2.在父组件中通过v-on指令监听自定义事件,然后在监听的回调中取得数据
4.3.3 父访问子
<div id="app">
<cpn ref="cpn"></cpn>
<button @click="getChildren">按钮</button>
</div>
<template id="cpn">
<div>
<h2>我是子组件</h2>
</div>
</template>
<script src="../vue/vue.js"></script>
<script>
const cpn = {
template:'#cpn',
data() {
return {
}
},
props:{
},
methods: {
showMessage(){
console.log('message');
}
},
}
const app = new Vue({
el:'#app',
data:{
},
components:{
cpn
},
methods: {
getChildren(){
//通过$children获取到的是所有的子组件,它是数组
//this.$children[0].showMessage();
//这种方法用的很多
//通过$refs:需要在子组件上加上ref属性值为子组件名字,然后通过$refs.设置的名字获取到相应的子组件
this.$refs.cpn.showMessage();
}
},
});
</script>
代码解读:
1.通过 r e f s : 需 要 在 子 组 件 上 加 上 r e f 属 性 值 为 子 组 件 名 字 , 然 后 通 过 refs:需要在子组件上加上ref属性值为子组件名字,然后通过 refs:需要在子组件上加上ref属性值为子组件名字,然后通过refs.设置的名字获取到相应的子组件
4.3.4 子访问父
<div id="app">
<cpn ref="cpn"></cpn>
</div>
<template id="cpn">
<div>
<h2>我是子组件</h2>
<button @click="getParent">按钮</button>
</div>
</template>
<script src="../vue/vue.js"></script>
<script>
const cpn = {
template: '#cpn',
data() {
return {
}
},
props: {},
methods: {
getParent() {
//使用较少
//通过$parent访问父组件
console.log(this.$parent);
//访问根组件:通过$root
console.log(this.$root);
}
},
}
const app = new Vue({
el: '#app',
data: {
},
components: {
cpn
},
methods: {
},
});
</script>
代码解读:
1.访问父组件:使用$parent
2.访问根组件:使用$root
4.4 插槽(slot)
4.4.1 插槽的使用
插槽的使用:
1.基本使用:
2.默认值:
4.4.2 具名插槽
<div id="app">
<cpn>
<button slot="left">按钮</button>
<span slot="center">标题</span>
<span slot="right">右边</span>
</cpn>
</div>
<template id="cpn">
<div>
<!-- 定义具名插槽:
1.给slot添加name属性
2.使用的时候,传入要替换的标签,然后标签上添加slot属性它的值为name的值
-->
<slot name="left"></slot>
<slot name="center"></slot>
<slot name="right"></slot>
</div>
</template>
<script src="../vue/vue.js"></script>
<script>
const cpn = {
template:'#cpn',
data() {
return {
}
},
methods:{
}
}
const app = new Vue({
el:'#app',
data:{
},
methods: {
},
components:{
cpn
}
});
</script>
代码解读:
1.在组件的slot标签上设置name为插槽取名字
2.在使用时,通过slot属性和第一步的name值,指定要替换的插槽
4.4.3 作用域具名插槽
<div id="app">
<cpn>
<div slot-scope="slot">
<p v-for="item in slot.data">{{item}}</p>
</div>
</cpn>
<cpn>
<p slot-scope="slot">{{slot.data.join('-')}}</p>
</cpn>
</div>
<template id="cpn">
<div>
<!-- 作用域具名插槽:
1.给slot添加v-bind绑定的自定义属性,它的值为要传给父组件使用的变量名
2.使用的时候,在组件的根标签上设置slot-scope属性,它的值为slot(可以自定义字符串),然后使用的时候,通过 slot.自定义属性,取得数据
3.总结:父组件替换插槽的标签,但内容由子组件来提供
-->
<slot :data="movies"></slot>
</div>
</template>
<script src="../vue/vue.js"></script>
<script>
const cpn = {
template:'#cpn',
data() {
return {
movies:['海王','肖申克的救赎','霸王别姬','真三国演绎']
}
},
methods:{
}
}
const app = new Vue({
el:'#app',
data:{
},
methods: {
},
components:{
cpn
}
});
</script>
代码解读:
作用域具名插槽:
1.给slot添加v-bind绑定的自定义属性,它的值为要传给父组件使用的变量名
2.使用的时候,在组件的根标签上设置slot-scope属性它的值为slot(可以自定义字符串),然后使用的时候,通过slot.自定义属性取得值
3.总结:父组件替换插槽的标签,但内容由子组件来提供
五、Vue-cli
创建项目:vue create 项目名
六、Vue-router
0 项目代码参考:12vue-router
Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得轻松;这里的路由并不是指我们平时所说的硬件路由器,而是SPA(单页应用)的路径管理器也就是说vue-router就是我们WebApp的链接路径管理系统;
6.1 vue-route的安装
npm install vue-router
yarn add vue-router
6.2 建立router文件夹和index.js文件
//路由配置
import Vue from 'vue';
import VueRouter from 'vue-router';
/* 路由懒加载(lazy):用import的方式导入哦*/
const Home = () => import('../components/Home.vue');
const About = () => import('../components/About.vue');
const User = () => import('../components/User.vue');
/* 非懒加载方式
import Home from '../components/Home.vue';
import About from '../components/About.vue';
import User from '../components/User.vue'; */
//源码中执行install方法,我想大概是在初始化插件吧
Vue.use(VueRouter);
const routes = [
{
path:'/',
redirect:'/home'
},
{
path:'/home',
component:Home
},
{
path:'/about',
component:About
},
{
path:'/user/:uname',
component:User
}
];
const router = new VueRouter({
mode:'history',
routes,
linkActiveClass:'active'
});
export default router;
6.3 将导出的router文件挂载到Vue实例身上
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount('#app')
6.4 使用
<template>
<div id="app">
<h1>我是App组件</h1>
<!-- 1.声明式导航
<router-link>:
to属性:对应跳转的路径
tag属性:将<router-link>标签,渲染成指定的标签,默认渲染为<a></a>标签(已被弃用,但还没被移除)
replace属性:相当于调用了history.replaceState({},"","url")方法,使得网页的后退失效
router-link-active:组件唤醒时自动添加的class,这个默认class可以通过linkActiveClass属性在router里面配置
-->
<router-link to="/home" replace tag="button">首页</router-link>
<router-link to="/about" replace tag="button">关于</router-link>
<!-- 2.函数式导航
通过$router对象:
push(url)
replace(url)
-->
<!-- <button @click="toHome">首页</button>
<button @click="toAbout">关于</button> -->
<!--
3.动态路由:
1.需要在路由(routes)里面的path后面,接一个 /:user
2.跳转到定义了动态路由的组件时,需要在地址后面跟上参数,以确保路由正确匹配
3.一般来说后面的参数需要使用v-bind绑定上去
-->
<router-link :to="'/user/'+uname" replace tag="button">用户</router-link>
<!--
4.关于$router和$route的区别:
1.$router对象是vue-router在定义时new出来的全局对象,它已经挂载到了Vue实例的身上了,在任何组件中都可以访问到
2.$route对象是当前处于活跃状态的组件的路由实例对象,它里面记载了params(动态路由传参),query(地址栏传参),命名组件(name),等参数信息
3.这俩个对象非常重要
-->
<!-- 5.组件占位符 -->
<router-view></router-view>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
uname:'spa'
}
},
methods: {
//编程式导航的测试代码
toHome() {
this.$router.push("/home");
},
toAbout() {
this.$router.push("/about");
},
},
};
</script>
<style>
/* 测试活跃状态的组件样式 */
.router-link-active {
color: red;
}
.active {
color: blue;
}
</style>
6.5 路由的嵌套使用
在需要嵌套的组件下面添加children属性,然后在父组件中添加标签占位
children:[
//注意:子路由的path前面无需加 /
{
path:'',
redirect:'/home/news'
},
{
path:'news',
component:HomeNews
},
{
path:'message',
component:HomeMessage
}
]
6.6 传递参数的方式
6.6.1 params类型:
-
配置路由格式:/router/:id
-
传递的方式:在path后面跟上对应的值
-
传递后形成的路径:/router/123,/router/abc
-
获取:this.$route.params取得包含所有的动态路由传来的参数
6.6.2 query类型:
- 配置路由格式:/router,也就是普通配置
- 传递的方式:对象中使用query的key作为传递的方式
- 传递后形成的路径:/router?id=123,/router?id=abc
- 获取:this.$route.query取得包含所有参数的对象
6.7 导航守卫
正如其名,vue-router
提供的导航守卫主要用来通过跳转或取消的方式守卫导航。有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的。
记住参数或查询的改变并不会触发进入/离开的导航守卫。你可以通过观察 $route
对象变化,或使用 beforeRouteUpdate
的组件内守卫。
7.7.1 全局守卫
前置守卫
你可以使用 router.beforeEach
注册一个全局前置守卫:
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
})
当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。
每个守卫方法接收三个参数:
to: Route
: 即将要进入的目标 路由对象from: Route
: 当前导航正要离开的路由next: Function
: 一定要调用该方法来 resolve 这个钩子。执行效果依赖next
方法的调用参数。next()
: 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。next(false)
: 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到from
路由对应的地址。next('/')
或者next({ path: '/' })
: 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向next
传递任意位置对象,且允许设置诸如replace: true
、name: 'home'
之类的选项以及任何用在router-link
的to
prop 或router.push
中的选项。next(error)
: (2.4.0+) 如果传入next
的参数是一个Error
实例,则导航会被终止且该错误会被传递给router.onError()
确保 next
函数在任何给定的导航守卫中都被严格调用一次。它可以出现多于一次,但是只能在所有的逻辑路径都不重叠的情况下,否则钩子永远都不会被解析或报错。
示例:
//全局前置路由守卫(其实就是一个钩子函数)
router.beforeEach(function (to,from,next) {
//从from到to
//通过document.title设置网页的标题
document.title = to.matched[0].meta.title;
console.log(to,from);
//执行下一步,不写的话会导致路由在此终断
next();
});
后置守卫
你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next
函数也不会改变导航本身:
router.afterEach((to, from) => {
// ...
})
7.7.2 路由独享的守卫
你可以在路由配置上直接定义 beforeEnter
守卫:
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
这些守卫与全局前置守卫的方法参数是一样的。
7.7.3 组件内的守卫
最后,你可以在路由组件内直接定义以下路由导航守卫:
beforeRouteEnter
beforeRouteUpdate
(2.2 新增)beforeRouteLeave
const Foo = {
template: `...`,
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave(to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
beforeRouteEnter
守卫 不能 访问 this
,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。
不过,你可以通过传一个回调给 next
来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。
beforeRouteEnter (to, from, next) {
next(vm => {
// 通过 `vm` 访问组件实例
})
}
注意 beforeRouteEnter
是支持给 next
传递回调的唯一守卫。对于 beforeRouteUpdate
和 beforeRouteLeave
来说,this
已经可用了,所以不支持传递回调,因为没有必要了。
beforeRouteUpdate (to, from, next) {
// just use `this`
this.name = to.params.name
next()
}
这个离开守卫通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false)
来取消。
beforeRouteLeave (to, from, next) {
const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
if (answer) {
next()
} else {
next(false)
}
}
7.7.4 完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用
beforeRouteLeave
守卫。 - 调用全局的
beforeEach
守卫。 - 在重用的组件里调用
beforeRouteUpdate
守卫 (2.2+)。 - 在路由配置里调用
beforeEnter
。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter
。 - 调用全局的
beforeResolve
守卫 (2.5+)。 - 导航被确认。
- 调用全局的
afterEach
钩子。 - 触发 DOM 更新。
- 调用
beforeRouteEnter
守卫中传给next
的回调函数,创建好的组件实例会作为回调函数的参数传入。
6.8 keep-alive标签
6.8.1 使用场景
在搭建 vue 项目时,有某些组件没必要多次渲染,所以需要将组件在内存中进行‘持久化’,此时 便可以派上用场了。 可以使被包含的组件状态维持不变,即便是组件切换了,其内的状态依旧维持在内存之中。在下一次显示时,也不会重现渲染。
6.8.2 生命周期钩子
被包含在 中创建的组件,会多出两个生命周期的钩子: activated 与 deactivated
activated
在组件被激活时调用,在组件第一次渲染时也会被调用,之后每次keep-alive激活时被调用。
deactivated
在组件被停用时调用。
注意:只有组件被 keep-alive 包裹时,这两个生命周期才会被调用,如果作为正常组件使用,是不会被调用,以及在 2.1.0 版本之后,使用 exclude 排除之后,就算被包裹在 keep-alive 中,这两个钩子依然不会被调用!另外在服务端渲染时此钩子也不会被调用的。
6.8.3 配合router-view使用
有些时候可能需要将整个路由页面一切缓存下来,也就是将 进行缓存。这种使用场景还是蛮多的
keep-alive 新加入了两个属性: include(包含的组件缓存生效) 与 exclude(排除的组件不缓存,优先级大于include)
include 和 exclude 属性允许组件有条件地缓存。二者都可以用逗号分隔字符串、正则表达式或一个数组来表示。
当使用正则或者是数组时,一定要使用 v-bind !
注意:
先匹配被包含组件的 name 字段,如果 name 不可用,则匹配当前组件 components 配置中的注册名称。
不会在函数式组件中正常工作,因为它们没有缓存实例。
当匹配条件同时在 include 与 exclude 存在时,以 exclude 优先级最高(当前vue 2.4.2 version)。比如:包含于排除同时匹配到了组件A,那组件A不会被缓存。
包含在 中,但符合 exclude ,不会调用activated 和 deactivated。
七、tabbar插件(slot)
0 项目代码参考:13vue-tabbar
本案例主要使用到vue的插槽技术,自定义了一个功能较为灵活的TabBar组件
TabBar组件可以通过传入icon和text文本来生成自己想要的tabbar,还能传入选中文字的颜色来控制tabbar的风格
组件的设计思路:
1.首先把样式在一个组件里面先写好,然后再一步步抽取出来
2.然后按层级抽取子组件,比如一个完整的TabBar包含TabBarItem(也就是一个选项,一个选项对应一个页面)
3.一个TabBarItem里面又包含,icon和text,这样又抽出来一个子组件
4.在TabBar组件中,可以使用占位,这样就能接收n个TabBarItem组件了,然后TabBarItem组件中,可以使用具名插槽(),因为TabBarItem中的icon和text是分别固定的,这时只需要在TabBarItem组件中,定义俩个插槽并分别命名
5.如果要使得TabBar组件的TabBarItem,在选中的时候文本和icon高亮,就需要在TabBarItem中定义第三个插槽,用来显示选中时的TabBarItem,并且每个插槽都需要包裹一个div用来绑定v-if和click事件(slot会被传入的元素替换掉,所以无法绑定属性)
八、vuex
0 项目代码参考:14vuex
8.1 vuex的概念
vuex就是一个全局的单例模式,把需要共享的状态(数据)交给vuex管理,当vuex管理的时候,存取状态都需要按照vuex的格式来操作,这样才能保证数据的准确性
8.2 vuex数据流程图
数据流图解读:
State-----通过Render渲染到组件-----VueComponent-----组件内通过Dispatch分发异步操作-----Actions(处理异步操作)-----通过context的commit方法提交到------Mutations(可以被Devtools插件监听到)-----然后Mutate修改State中的数据
8.3 各个属性的作用
- state:它是vuex的状态定义存储的地方,vuex的任何状态都在这定义
- mutations:这是修改state中状态的唯一途径,任何修改操作都需要经过这
- actions:这是所有vuex异步操作的地方,只有在这执行完异步操作后,再通过context.commit()方法提交到mutations中才能把最终的结果给到state
- getters:这相当与vue的计算属性,在用户取得数据时如果还需对数据进行其它操作,可以在这里定义一个函数,把操作完的值return出去
- modules:这里是专门分模块的地方,可以把各个大型组件的状态分模块来管理,这样比较清晰明了
8.4 vuex代码演练
-
vuex目录划分
modules:存放模块文件
actions:存放actions对象
getters:存放getters对象
index:除了state对象不分离出来,其它的都分离出来
mutations-types:定义各种方法名的常量
mutation:存放mutation对象
-
getters的使用
export default { powerCounter(state) { return state.count * state.count; }, //getters可以用来当计算属性使用 get20Age(state) { return state.students.filter((student) => { return student.age > 20 }); }, //在getters中还能使用getters中的其他值 ageLength(state, getters) { return getters.get20Age.length; }, //如果要传值,则要返回一个函数 moreAgeStu(state) { return function (age) { return state.students.filter(stu => stu.age > age); } } }
-
mutation的使用
import {DECREMENT,INCREMENT} from './mutations-types'; export default { //定义函数的方式可以用:[变量名](){}这样的方式定义 [INCREMENT](state) { state.count++; }, [DECREMENT](state) { state.count--; }, incrementCount(state, num) { state.count += num; }, addStudent(state,stu){ state.students.push(stu); } }
-
actions的使用
import { INCREMENT } from './mutations-types'; export default { /* outTimeCount(context){ setTimeout(()=>{ context.commit(INCREMENT); },2000); } */ //上面的函数还可以这么写: outTimeCount(context) { return new Promise((resolve) => { setTimeout(() => { context.commit(INCREMENT); resolve('加一完成'); }, 2000); }); } }
-
modules的使用
import moduleA from './modules/moduleA'; //用来划分模块 modules: { moduleA }
//导出模块 export default { state: { name: 'zhangsan' }, mutations: { upName(state, newName) { state.name = newName; } }, actions: { asyncUpdateName(context, newName) { setTimeout(() => { context.commit('upName', newName); }, 2000); } }, getters: { updateName(state, getters, rootState) { return state.name + rootState.count; } } }
-
前端vue代码
<template> <div id="app"> <h2>我是App组件</h2> <p>当前数量:{{$store.state.count}}</p> <!-- 测试getters --> <h2>测试getters</h2> <p>count的平方:{{$store.getters.powerCounter}}</p> <p>获取年龄大于20的学生:{{$store.getters.get20Age}}</p> <table style="border:1px solid black;"> <thead> <tr> <th>name</th> <th>id</th> <th>age</th> </tr> </thead> <tbody> <tr v-for="stu in $store.getters.get20Age" :key="stu.id"> <td>{{stu.id}}</td> <td>{{stu.name}}</td> <td>{{stu.age}}</td> </tr> </tbody> </table> <p>年龄大于20的个数:{{$store.getters.ageLength}}</p> <p>返回年龄大于自定义参数的学生:{{$store.getters.moreAgeStu(19)}}</p> <!-- 加减操作:通过mutations实现数据更新 --> <button @click="increment">+</button> <button @click="decrement">-</button> <button @click="incrementCount(5)">+5</button> <button @click="incrementCount(10)">+10</button> <button @click="addStudent">添加学生</button> <button @click="outTimeCount">延迟2秒加一</button> <!-- HelloVuex组件 --> <hello-vuex></hello-vuex> <h2>测试modules代码</h2> <div> <h3>{{$store.state.moduleA.name}}</h3> <h3>{{$store.getters.updateName}}</h3> <button @click="asyncUpdateName">异步修改名字</button> </div> <!-- vue的响应式的方法: vuex的初始化属性是会自动添加到vue响应式系统中的,但是如果我们后面手动的添加进去的属性,如果不用响应式的操作,会导致数据改变了,但是界面不会变 Vue.set(数组/对象,下标/key,值); Vue.delete(数组/对象,下标/key); --> </div> </template> <script> import HelloVuex from './components/HelloVuex'; import {DECREMENT,INCREMENT} from './store/mutations-types'; export default { name: 'App', components: { HelloVuex }, data() { return { } }, methods: { increment(){ this.$store.commit(INCREMENT); }, decrement(){ this.$store.commit(DECREMENT); }, incrementCount(num){ this.$store.commit('incrementCount',num); }, addStudent(){ let stu = {id:115,name:'陈建亮',age:25} this.$store.commit('addStudent',stu); }, outTimeCount(){ //this.$store.dispatch('outTimeCount'); this.$store.dispatch('outTimeCount').then(msg=>{ console.log(msg); }); }, asyncUpdateName(){ this.$store.dispatch('asyncUpdateName','lisi'); } }, } </script> <style> </style>
九、项目准备
9.1 项目创建和git托管
使用vue-cli创建项目:vue create 项目名
使用码云托管代码(github没翻墙软件,太慢了所以没用):
1.先在云端建立仓库
2.使用vue-cli在本地初始化项目
3.在本地项目中使用git init初始化项目
3.复制码云仓库的地址,通过git remote add origin 仓库地址 关联远程仓库
4.推送代码到远程仓库
9.2 引入css格式化文件
在github中可以下载到:normalize.css文件,然后导入app.vue中
<style>
@import url('assets/css/normalize.css');
</style>
9.3 创建vue.config文件
vue.config文件中可以配置路径别名,就像vue中默认别名@代表src路径一样,本文件会在webpack打包时,跟已被脚手架隐藏的配置文件合并.
ps:在html标签或组件中使用别名时,需要在前面加上符号,如:assets/img/1.jpg
//vue.config
module.exports = {
configureWebpack:{
resolve:{
alias:{
'assets':'@/assets',
'nerwork':'@/network',
'utils':'@/utils',
'components':'@/components',
'views':'@/views'
}
}
}
}
9.4 创建 .editorconfig 文件
.editorconfig文件是专门用来规定代码风格(如代码缩进,字符编码等)的配置文件,在团队开发时很有用,由于我没有团队,所以这个项目没有配置,但是在此做了记录,以防以后遇见不认识
t(){
this.KaTeX parse error: Expected 'EOF', got '}' at position 33: …EMENT); }̲, increm…store.commit(‘incrementCount’,num);
},
addStudent(){
let stu = {id:115,name:‘陈建亮’,age:25}
this.KaTeX parse error: Expected 'EOF', got '}' at position 40: …',stu); }̲, outTim…store.dispatch(‘outTimeCount’);
this.KaTeX parse error: Expected 'EOF', got '}' at position 93: … }); }̲, asyncU…store.dispatch(‘asyncUpdateName’,‘lisi’);
}
},
}
## 九、项目准备
### 9.1 项目创建和git托管
> 使用vue-cli创建项目:vue create 项目名
> 使用码云托管代码(github没翻墙软件,太慢了所以没用):
>
> 1.先在云端建立仓库
>
> 2.使用vue-cli在本地初始化项目
>
> 3.在本地项目中使用git init初始化项目
>
> 3.复制码云仓库的地址,通过git remote add origin 仓库地址 关联远程仓库
>
> 4.推送代码到远程仓库
### 9.2 引入css格式化文件
> 在github中可以下载到:normalize.css文件,然后导入app.vue中
### 9.3 创建vue.config文件
> vue.config文件中可以配置路径别名,就像vue中默认别名@代表src路径一样,本文件会在webpack打包时,跟已被脚手架隐藏的配置文件合并.
>
> ps:在html标签或组件中使用别名时,需要在前面加上~符号,如:~assets/img/1.jpg
```vue
//vue.config
module.exports = {
configureWebpack:{
resolve:{
alias:{
'assets':'@/assets',
'nerwork':'@/network',
'utils':'@/utils',
'components':'@/components',
'views':'@/views'
}
}
}
}
9.4 创建 .editorconfig 文件
.editorconfig文件是专门用来规定代码风格(如代码缩进,字符编码等)的配置文件,在团队开发时很有用,由于我没有团队,所以这个项目没有配置,但是在此做了记录,以防以后遇见不认识