黑马vue2笔记

Vue介绍

vue概念

vue:一套用于构建用户界面的前端框架

  1. 构建用户界面
    • 用vue往html页面添加数据
  2. 框架
    • 框架是一套现成的解决方案,程序员只能遵守框架的规范,去编写自己的业务功能!
    • vue 的指令、组件(是对 UI 结构的复用)、路由、Vuex、vue 组件库

vue两个特性

  1. 数据驱动视图:

    • 数据的变化会驱动视图自动更新
  2. 双向绑定数据:

    在网页中,form表单负责采集数据,Ajax负责提交数据

    • js数据的变化,会被自动渲染到页面
    • 页面上表单采集的数据发生变化时,会被vue自动获取到,并更新到js数据中

MVVM

MVVM 是 vue 实现数据驱动视图双向数据绑定的核心原理。MVVM 指的是 Model、View 和 ViewModel, 它把每个 HTML 页面都拆分成了这三个部分,如图所示:

在这里插入图片描述

工作原理

在这里插入图片描述

基本使用

//被 Vue 控制的 DOM 区域
<div id="app">{{ }}</div>
//导入 Vue 的库文件,可以使用 Vue 这个构造函数 
<script src="./lib/vue-2.6.12.js"></script>
<script>
	//创建 Vue 实例对象
    const vm = new Vue({
    	//el 属性固定写法,表示当前 vm 实例要控制的区域,接收值为选择器
    	el: '#app',
    	//data 对象就是需要渲染的数据
        data: {
            username: 'zhangsan'
        }
    })
</script>

在这里插入图片描述

Vue指令

1.内容渲染指令

  1. v-text 缺点:会覆盖元素内部原有的内容

    //从 data 对象中获取 username 渲染到内容中
    <p v-text="username"></p>
    
  2. {{ }} 插值表达式:在实际开发中用的最多,只是内容的占位符,不会覆盖原有的内容!

    <p>姓名:{{ username }}</p>
    
  3. v-html 作用:可以把带有标签的字符串,渲染成真正的 HTML 内容!

2.属性绑定指令

注意:插值表达式只能用在元素的内容节点中,不能用在属性节点

  • 在 Vue 中,可以使用v-bind:指令,为元素的属性动态绑定值

    <input type="text" v-bind:placeholder="tips">//tips为 data 对象中属性
    

    v-bind: 可以简写成:

    <input type="text" :placeholder="tips">
    
  • 在使用 v-bind:属性绑定期间,如果绑定内容需要动态拼接,则字符串需要用单引号包裹

    <div :title="'box' + index">div标签</div>
    

3.事件绑定指令

  1. v-on:简写@

  2. 语法格式:

    <button @click="add(2)">点击加2</button>// add 为function, 需要在methods对象中定义
    //methods 的作用:定义事件处理函数
    methods: {
    	add(n) {
    		//this === vm 结果为 true
    		//如果要修改 data 中的数据,可以通过 this 来访问
    		this.count += n
    	}
    }
    
  3. $event : 如果默认的事件对象e被覆盖了,则可以手动传递一个$event

    <button @click="add(3, $event)"></button>
    
    methods: {
       add(n, e) {
    			//三元运算符 count 为偶数则将 backgroundCOlor 改成 red
    			e.target.style.backgroundColor = count % 2 === 0 ? 'red' : ''
    			this.count += 1
       }
    }
    
  4. 事件修饰符:

    • .prevent: 阻止默认行为(如:阻止 a 链接跳转,阻止表单提交等)

      <a href="https://www.baidu.com" @click.prevent="onLinkClick">百度</a>
      
    • .stop: 阻止冒泡

      <button @click.stop="xxx">按钮</button>
      
  5. 按键修饰符:在监听键盘事件时,我们经常需要判断详细的按键。

    //只有在 key 是 Esc 时调用 vm.clearInput()
    //只有在 key 是 Enter 时调用 vm.commitAjax()
    <input type="text" @keyup.esc="clearInput" @keyup.enter="commitAjax">
    

4.双向绑定指令

Vue 提供了 v-model 双向数据绑定指令,用来辅助开发者在不操作 DOM 的前提下,快速获取表单的数据

//通过修改文本内容从而修改数据源内容
//v-model 绑定覆盖了默认属性value
<input type="text" v-model="username">

v-model修饰符

修饰符作用
.number自动将用户的输入值转为数值类型
.trim自动过滤用户输入的首尾空白字符
.lazy在“change”时而非“input”时更新

.number

<input v-model.number="age" />

.trim

<input v-model.trim="msg" />

.lazy

<input v-model.lazy="msg" />

5.条件渲染指令

条件渲染指令用来辅助开发者按需控制 DOM 的显示与隐藏。条件渲染指令有如下两个

  1. v-show 的原理是:动态为元素添加或移除 display: none 样式,来实现元素的显示和隐藏
  • 如果要频繁的切换元素的显示状态,用 v-show 性能会更好
  1. v-if 的原理是:每次动态创建或移除 DOM 元素,实现元素的显示和隐藏
    • 如果刚进入页面的时候,某些元素默认不需要被展示,而且后期这个元素很可能也不需要被展示出来,此时 v-if 性能更好

在实际开发中,绝大多数情况,不用考虑性能问题,直接使用 v-if 就好了!!!

//直接给定 true(显示) 或 false(不显示)
<p v-if="true">被 v-if 控制的元素</p>

<p v-if="type === 'A'">良好</p>

6.列表渲染指令

vue 提供了v-for 列表渲染指令,用来辅助开发者基于一个数组来循环渲染一个列表结构。

v-for 指令需要使用 (item, index) in list形式的特殊语法,其中:

  • item: 被循环数组的每一项
  • index:当前项索引
  • list:待循环的数组

建议使用 v-for 指令时一定要指定 key 的值(既提升性能、又防止列表状态紊乱)

key 的注意事项

  1. key 的值只能是字符串数字类型
  2. key 的值必须具有唯一性(即:key 的值不能重复)
  3. 建议把数据项 id 属性的值作为 key 的值(因为 id 属性的值具有唯一性)
  4. 使用 index 的值当作 key 的值没有任何意义(因为 index 的值不具有唯一性,可能发生改变)
data: {
	list: [ //待循环数组
		{id: 1, name: 'zs'},
		{id: 2, name: 'ls'}
	]
}

<ul>
    <li v-for="(item, index) in list" :key="item.id">索引:{{ index }},姓名:{{ item.name}}</li>
</ul>

v-for 指令中的 item 和 index 都是形参

Vue过滤器

过滤器(Filters)是 vue 为开发者提供的功能,常用于文本的格式化

适用场景:

  • 插值表达式
  • v-bind属性绑定
//通过“ | ”调用 capitalize 过滤器,对 message 值格式化,结果为函数返回值
<p>{{ message | capitalize }}</p>

私有过滤器

在 filters 节点下定义的过滤器,称为“私有过滤器”,只能在当前 vm 实例所控制的 el 区域内使用。

const vm = new Vue({
	el: '#app',
	data: {
		username: 'zs'
	}
	//在 filters 节点下定义过滤器
	filters: { 
		//把首字母转换为大写的过滤器
		capitalize(value) { 
			return value.charAt(0).toUpperCase() + value.slice(1)
		}
	} 
})

全局过滤器

可以在多个 Vue 实例之间共享过滤器

Vue.filter() 方法接收两个参数:

  1. 参数1:全局过滤器的"名字"
  2. 参数2:全局过滤器的"处理函数"
Vue.filter('capitalize', (str) => {
	return value.charAt(0).toUpperCase() + value.slice(1)
})

可以调用多个过滤器:

//依次调用过滤器 filterA 和 过滤器 filterB
{{ message | filterA | filterB }

过滤器传参

过滤器处理函数参数中:

  • 第一个永远是" | "前面待处理的值
  • 从第二个参数起才是处理函数中参数
//将arg1 和 arg2 作为参数传到 filterA 中
<p>{{ message | filterA(arg1, arg2) }}</p>

Vue.filter('filterA', (value, arg1, arg2) => {
	//......
})

过滤器注意点

  1. 本质是一个函数,要定义在 filters 节点下
  2. 过滤器函数中,一定要有 return 返回值
  3. 如果全局过滤器和私有过滤器名字一致,此时按照“就近原则”,调用的是”私有过滤器“

watch侦听器

watch 侦听器允许开发者监视数据的变化,从而针对数据的变化做特定的操作。

本质上是一个函数,都应该被定义到watch节点下

侦听器格式

  1. 方法格式的侦听器
    • 缺点1:无法在刚进入页面时刷新
    • 缺点2:如果侦听的是一个对象,当对象中属性发生变化时,不会触发侦听器
  2. 对象格式的侦听器
    • 优点1:可以通过 immediate 选项,让侦听器自动触发!!!
    • 优点2:可以通过 deep 选项,让侦听器深度监听对象中每个属性的变化!!!

方法格式的侦听器

<script>
	const vm = new Vue({
        el: '#app',
        data: {
            username: 'zs'
        }
        watch: {
        	//需要把监视的数据名,作为方法名
        	//新值在前,旧值在后
        	username(newValue, oldValue) {
        		//.....
    		}
    	}
    })
</script>

对象格式的侦听器

<script>
	const vm = new Vue({
        el: '#app',
        data: {
          info: {
          	username: 'admin',
          	address: {
            	city: '北京'
          	}
          }
        },
        watch: {
        	info: {
        		//侦听器的处理函数
        		handler(newValue, oldValue){
        			console.log(newValue, oldValue)
    			},
                // immediate 选项的默认值是 false
          		// immediate 的作用是:控制侦听器是否自动触发一次!
                immediate: true,
    			//开启深度监听,只要对象中任何一个属性变化了,都会触发“对象的侦听器”
    			deep: true
    		}
    	}
    })
</script>

注意:如果要侦听对象的子属性,则必须用单引号包裹(方法格式)

'info.username'(newVal, oldVal){
	console.log(newVal)
}

计算属性

实时监听 data 中数据的变化,并 return 一个计算后的新值, 供组件渲染 DOM 时使用。

这个动态计算出来的属性值可以被模板结构(插值,v-bind)methods 方法使用。

特点:

  1. 所有的计算属性,都要定义到 computed 节点之下
  2. 计算属性在定义的时候,要定义成**“方法格式”,但是计算属性的本质是一个属性**

用法

<div id="app">
  <h2>总价格:{{ totalPrice }}</h2>
</div>


const vm = new Vue({
    el: '#app',
    data: {
      books:[
        {id: 1001, name: 'Unix编程艺术',price: 119},
        {id: 1002, name: '代码大全',price: 105},
        {id: 1003, name: '深入理解计算机原理',price: 99},
        {id: 1004, name: '现代操作系统',price: 109}
      ]
    },
    computed: {
      totalPrice () {
        
        let totalPrice = 0;
        
        for (let i in this.books) {
          totalPrice += this.books[i].price;
        }
          
        // 也可以使用 for of 
        for (let book of this.books) {
          totalPrice += book.price;
        }
        return totalPrice;
      }
    }
	methods: {
		test: {
			//将计算属性 totalprice 看成属性来访问
			console.log(this.totalprice)
		}
	}
  })

计算属性 vs 方法

methodscomputed 看起来都可以实现我们的功能,那么为什么还要多一个计算属性

methods: 每次使用都会调用方法
computed: 计算属性会缓存计算的结果, 不变的情况下只调用一次, 除非原属性发生改变,才会重新调用.

计算属性 vs 侦听器

侧重的应用场景不同侧重的应用场景不同:

计算属性侧重于监听多个值的变化,最终计算并返回一个新值
侦听器侧重于监听单个数据的变化,最终执行特定的业务处理,不需要有任何返回值。

Vue-cli

vue-cli 是 Vue.js 开发标准工具,它简化了程序员基于 webpack 创建工程化的 Vue 项目的过程

  1. 在终端下运行如下的命令,创建指定名称的项目:

    vue create 项目的名称

  2. vue 项目中 src 目录的构成:

    assets 文件夹:存放项目中用到的静态资源文件,例如:css 样式表、图片资源
    components 文件夹:程序员封装的、可复用的组件,都要放到 components 目录下
    main.js 是项目的入口文件。整个项目的运行,要先执行 main.js
    App.vue 是项目的根组件。
    

vue 项目工作流程:

在工程化项目中,vue 通过 main.jsApp.vue 渲染到 index.html 的指定区域(用 App.vue 内容替换 index.hmtl 指定区域)

  1. App.vue 用来编写待渲染的模板模块
  2. index.html 中需要预留一个 el 区域
  3. main.js 把 App.vue 渲染到 index.html 预留区域

main.js 文件:

//导入 vue 这个包,得到 Vue 构造函数
import Vue from 'vue'  

//导入 App.vue 根组件,将来要把 App.vue 中的模板结构渲染到 HTML 中
import App from './App.vue'

Vue.config.productionTip = false

//创建 Vue 的实例对象
new Vue({
    //把 render 函数指定组件(App),渲染到 HTML 页面中
    render: h => h(App)
}).$mount('#app') //Vue 实例的$mount()方法 指定了 index.html 中预留区域 作用与 el 属性一样

执行 npm run serve 时,会将 main.js 转换成 bundle.js 自动导入到 index.html 中

vue组件

组件化开发指的是:根据封装的思想,把页面上可重用的 UI 结构封装为组件,从而方便项目的开发和维护

vue 中规定:组件的后缀是 .vue

组件构成

每个 .vue 组件都由三个部分构成:

  1. template:组件的模板结构
  2. script:组件的 JavaScript 行为
  3. style:组件的样式

注意:每个组件中必须包含 template 模板结构,而 script 行为和 style 样式是可选的组成部分

template:

  • template 是 vue 提供的容器标签,只起到包裹性质的作用,它不会被渲染为真正的 DOM 元素

  • template 中只能包含唯一的根节点

script:

  • vue 组件的 data 必须是函数
<script>
	//组件相关的 data 数据,methods 方法等,都需要定义到 export default 所导出的对象中
    export default {
        //data 数据源
        data() {
            //return 出去的{ }中,可以定义数据
            return {
                username: 'zs'
            }
        },
        methods: {},   //事件相关函数
        watch: {},     //侦听器
        computed: {},  //计算属性
        filters: {}    //过滤器
        //this 表示当前 vue 组件实例对象
    }
</script>

style:

  • 可以在 style 结点中编写样式美化当前组件的 UI 结构
  • style 标签上添加 lang=“less” 属性,即可使用 less 语法编写组件的样式
<style lang="less">
    h1 {
        font-weight: normal;
        span{
            color: red;
        }
    }
</style>

组件的使用

组件在被封装好之后,彼此之间是相互独立的,不存在父子关系

使用组件的时候,根据彼此的嵌套关系,形成了父子关系、兄弟关系

注册私有组件

通过 components 注册的是私有组件,被注册的组件只能在当前组件使用

  1. 使用 import 导入 需要的组件

    import left from '@/components/Left.vue'
    
  2. 在 script 标签中使用 components 节点注册组件

    <script>
    	export default {
            components: {
                Left
            }
        }
    </script>
    
  3. 标签形式使用刚才注册的组件

    <template>
    	<div>
            <Left></Left>
        </div>
    </template>
    
注册全局组件

在 vue 项目的 main.js 入口文件中,通过 Vue.component()方法,可以注册全局组件

import Vue from 'vue'
import App from './App.vue'

// 导入需要被全局注册的那个组件
import Count from '@/components/Count.vue'

// 参数一: 组件的 '注册名称',将来以标签形式使用时要求和这个名称一样。
// 参数二: 需要被全局注册的那个组件。
Vue.component('MyCount', Count)

// 消息提示的环境配置,设置为开发环境或者生产环境
Vue.config.productionTip = false

new Vue({
  // render 函数中,渲染的是哪个 .vue 组件,那么这个组件就叫做 “根组件”
  render: h => h(App)
}).$mount('#app')

组件的props

props 是组件的自定义属性,允许使用者通过自定义属性,为当前组件指定初始值

优点:不同组件使用同一个注册的组件时,可以传不同的初始值

注意: props 数组里面是字符串

组件封装者:

<script>
	export default {
        props: ['initA', 'initB'],
        data() {
            return {
                count: this.initA
            }
        }
    }
</script>

组件使用者:

<template>
	//初始化 initA 得到的 initA 值是字符’9‘
	<MyCount initA="9"></MyCount>
</template>

v-bind 动态绑定自定义属性

动态绑定自定义属性后,双引号里面是JS语句,因此初始化的结果不带双引号

<template>
	//初始化 initA 得到的 initA 值是数值 9
	<MyCount :initA="9"></MyCount>
</template>
props只读

vue 规定:组件中封装的自定义属性是只读的,程序员不能直接修改 props 的值。否则会直接报错

如果要修改 props 的值,可以把 props 的值转存到 data 中

default默认值

组件使用者有可能没有对自定义属性初始化,那么会显示 undefined ,需要用 default 来定义属性的默认值

props 指向对象

export default{
	props: {
		init: {
			//设定默认值
			default: 0
		}
	}
}
type值类型

在自定义属性时,可以通过type来定义属性的值类型

export default {
	props: {
		init: {
			//用 default 属性定义属性默认值
			default: 1,
			//用 type 属性定义属性的值类型
			//如果传递的值不符合该类型,终端报错
			type: Number
		}
	}
}
required必填项

在声明自定义属性时,可以通过 required 属性,将自定义属性设置成必填项

export default {
	props: {
		init: {
			required: true
		}
	}
}

组件间样式冲突

默认情况下,.vue 组件中的样式会全局生效,会造成多个组件间样式冲突

原因:

  1. 单页面应用程序,所有组件的 DOM 结构,都基于唯一的 index.html 页面进行呈现
  2. 每个组件的样式,都会影响整个 index.html 页面的 DOM 元素

解决办法:Vue 提供在 style 节点中添加 scoped 属性

让当前组件样式对其他组件不生效

原理:添加 scoped 后,Vue 底层会为组件中每个标签添加自定义属性(data-v-xxx),同一个组件的自定义属性一样。在编写组件样式时,通过属性选择器来控制样式的作用域。

示例:

<style scoped>
    //当查找当前组件标签添加样式,通过 h3[data-v-xxx],既要满足是 h3 ,又要满足是自定义属性 [data-v-xxx]
    h3 {
        color: red;
    }
</style>

通过浏览器查看:

在这里插入图片描述

在这里插入图片描述

/deep/ 样式穿透

样式穿透目的:

通过在当前组件 style 节点下添加样式,让子组件生效

如果给当前组件的 style 节点添加了 scoped 属性,当前组件的样式不会对子组件生效,所以需要添加 /deep/ 进行样式穿透

示例:

<style lang="less" scoped>
    
/*不加 /deep/ 时,生成的选择器格式为 .title[data-v-052242de]*/
//既要求是 title 类,又要求是自定义属性[data-v-052242de]
.title{
    color: blue;
}

/*加/deep/ 时,生成的选择器格式为 [data-v-052242de] .title*/
//自定义属性父元素 [data-v-052242de] 下的子元素 title
/deep/ .title {
  color: pink;
}
</style>

生命周期

在这里插入图片描述

created() {
	//经常在里面,调用 methods 的方法,用 Ajax 向服务器请求数据
	//并且把请求的数据,转存到 data 中,供 template 模板渲染使用
	//由于模板还未创建好,所以不能创建 DOM 
}

在这里插入图片描述

组件的数据共享

在这里插入图片描述

父传子

父组件通过 v-bind 绑定属性向子组件共享数据,子组件通过使用 props 接收数据

在这里插入图片描述

//父组件
<template>
	//使用子组件
	<div>
        <Son :msg="message" :user="userinfo"></Son>
    </div>
</template>

<script>
	//导入子组件
    import Son from '@/components/Son.vue'
    export default {
        data() {
            return {
                message: 'hello vue.js',
                userinfo: {
                    name: 'zs',
                    age: 18
                }
            }
        },
        //注册子组件
        components: {
       		Son 
    	}
    }
</script>

//子组件
<template>
	<div>
        <p>父组件传过来的msg值: {{ msg }}</p>
		<p>父组件传过来的user值: {{ user }}</p>
    </div>
</template>

<script>
	export default {
        props: ['msg', 'user']

    }
</script>

父组件的 userinfo 跟子组件的 user 指向同一个对象

子传父

自定义事件

//子组件
<template>
	<div>
    <p>
        count 值:{{ count }}
    </p>
    <button @click="add">
        按钮
    </button>
    </div>
</template>
<script>
export default{
    methods: {
        add() {
            this.count += 1
            //修改数据时,通过 $emit() 触发自定义事件
            this.$emit('numchange', this.count)
        }
    },
    data() {
        return {
            count: 0
        }
    }
} 
</script>

//父组件
<template>
	<div>
        <Son @numchange="getNew"></Son>
    	<p>
         子组件传的值:{{ countFromSon }}   
    	</p>
    </div>
</template>
<script>
    import Son from '@/components/Son.vue'  
    export default {
        data() {
            return {
                countFromSon: ''
            }
        },
        methods: {
			getNew(val) {
                this.countFromSon = val
            }
        },
        components: {
            Son
        }
    }
</script>

兄弟组件共享

在 vue2 中,兄弟组件之间数据共享的方案是 EventBus

在这里插入图片描述

注意:A 和 C 共享同一个 bus

EventBus 的使用步骤:

  1. 创建 eventBus.js模块,并向外共享一个 Vue 的实例对象
  2. 在数据发送方,调用 bus.$emit('事件名称', 要发送的数据)方法触发自定义事件 。
  3. 在数据接收方,调用 bus.$on('事件名称', 事件处理函数)方法注册一个自定义事件。

ref引用操作 DOM

ref 用来辅助开发者在不依赖 jQuery 和 DOM api 的情况下 ,获取 DOM 元素或组件的引用

每个 vue 的组件实例上,都包含一个 $refs 对象,里面存储着对应的 DOM 元素或组件的引用。默认情况下,组件的 $refs 指向一个空对象。

<template>
	<div>
    	<h1 ref="color1">App</h1>
        <button @click="changeColor">this</button>
    </div>
</template>
<script>
export default {
  methods: {
    changeColor() {
      this.$refs.color1.lstyle.color = 'red'
    }
  }
}
</script>

注意 ref 也可以加在组件的标签上

this.$nextTick(cb)方法

this.$nextTick(cb) 将 cb 回调函数推迟到下一个 DOM 更新周期之后执行(即组件 DOM 更新完成后执行)

this.$nextTick(() => {
	console.log('hello')
})

动态组件

动态组件指的是 动态切换组件的显示与隐藏

基本使用

Vue 提供了一个内置的 <component> 组件,专门实现动态组件的渲染

  1. component 标签时 Vue 内置的,作用:组件占位符

  2. 通过 :is 属性,动态指定要渲染的组件

    • is 属性值,表示要渲染的组件名字
  3. 使用 keep-alive 标签保持组件状态(避免切换时需要重新创建)

    • keep-alive 会把内部的组件进行缓存,而不是销毁
    • include 属性指定哪些组件需要被缓存(多个组件用英文逗号隔开,不能加空格)
    • exclude 属性指定哪些组件不被缓存

    注意: include 跟 exclude 属性不能同时出现

示例:

<template>
<div class="app-container">
  <div class="box">
    <keep-alive include="Left,Right">
      <component :is="isName"></component>
    </keep-alive>
    <button @click="tarnsLeft">切换成Left</button>
    <button @click="tarnsRight">切换成Right</button>
  </div>
</div>
</template>

<script>
import Left from '@/components/Left.vue'
import Right from '@/components/Right.vue'
export default {
  components: {
    Left,
    Right
  },
  data() {
    return {
      // 表示要展示组件名称
      isName: 'Left'
    }
  },
  methods: {
    tarnsLeft() {
      this.isName = 'Left'
    },
    tarnsRight() {
      this.isName = 'Right'
    }
  }
}
</script>

插槽slot

概念

封装组件中将不确定的内容称为插槽

在这里插入图片描述

可以把插槽认为是组件封装期间,为用户预留的内容的占位符

用法

在封装组件期间,通过 <slot> 标签定义插槽,从而为用户预留内容占位符

<!-- Count 组件 -->
<template>
	<!-- 通过 slot 标签,为用户预留占位符 -->
	<slot></slot>
</template>

<!-- 在App.vue 中使用Count组件标签 -->
<Count>
    <p>组件标签里面内容会插入到 slot 标签中</p>
</Count>

后备内容

slot 标签内添加的内容会被作为后备内容。

// 子组件 Count
<template>
	<slot>这是后备内容</slot>
</template>


// 使用插槽
	<Count>
       // 如果用户没有提供内容,上面 slot 标签内定义的内容会生效,此时页面会有 "这是后备内容"
       // 如过提供了,下面的 img 将会被渲染到页面上
        <img src="../assets/log.png" alt="">
	</Count>

具名插槽

每个 slot 插槽,都要有一个 name 属性(指定插槽名称),如果省略了 slot 的 name 属性,会有一个默认名称 “default”

<slot name="default"></slot>

v-slot指令

为指定插槽提供内容

  • v-slot: 后面跟上插槽的名称

  • v-slot:可以简写 #。例如 v-slot:header 可以被重写为 #header。

  • v-slot 属性只能放在 组件标签<template> 元素上 (否则会报错)。

<Count>
    <template #default>
    	<p>组件标签里面内容会插入到指定名称 slot 标签中</p>
    </template>
</Count>

作用域插槽

在封装组件的过程中,可以为预留的 插槽绑定 props 数据的 <slot>,这种带有 props 数据的 叫做“作用 域插槽”

//message 是自定义属性,通过 v-bind 进行绑定
<slot name="content" :message="str" :user="user"></slot>
//.....
export default {
	data() {
		return {
			str: 'hello vue.js',
			user: {
				name: 'zs',
				age: 18
			}
		}
	}
}

可以使用 v-slot: 的形式,接收作用域插槽对外提供的数据。

<Count>
	// content 表示指定的插槽名称,scope 表示接收作用域插槽提供的数据
	<template #content="scope">
	//注意 scope 是一个对象,可以通过对象解构获取其中内容
		<p>{{ scope }}</p>
	</template>
</Count>

进行解构

<template #content="{message, user}">
		<p>传过来的str: {{ msg }}</p>
		<p>传过来的user: {{ user }}</p>
</template>

自定义指令

  • 使用自定义组件时需要加 v-指令前缀

    即:声明成 color ,使用时要写成 v-color

私有自定义指令

在每个 Vue 组件中,可以在 directives 节点下声明私有自定义指令

directives: {
	//定义名为 color 的指令,指向一个配置对象
	color: {
		//当指令第一次被绑定到元素上时,会触发 bind 函数
		bind(el) {
			// el 固定写法,是指绑定了此指令的原生 DOM 对象
			el.style.color = 'red'
		}
	}
}

注意:使用需要写成 v-xxx

为自定义指令绑定动态参数

template 结构中 使用自定义指令时,可以通过等号 = 方式,为当前指令动态绑定参数值

<template>
    
<!--在使用指令时,动态为当前指令绑定参数值 color-->
<h1 v-color="color">App组件</h1>

</template>

data(){
	return{
	color:'red’
}

通过 binding 获取指令参数值:

通过 binding 对象的 value 属性,动态获取参数值

//传的 color 是变量,需要在 data 数据中找
<h1 v-color="color">App组件</h1>

// 此时传入的是字符串,不会在 data 数据中查找
<p v-color="'blue'" ></p>

directives:{
	color:{
		bind(el,binding){
            //通过binding对象的.value属性,获取动态的参数值
			el.style.color=binding.value
}

注意:当 data 中的 color 值发生变化时,bind 函数不会再次触发,因此el.style.color 的值不会改变

update 函数:

bind 函数只调用一次:当指令第一次绑定到元素时调用,当 DOM 更新时,bind 函数不会触发

update 函数会在每次 DOM 更新时调用

directives: {
	color: {
		bind(el, binding) {
			el.style.color = binding.value
		},
		update(el, binding) {
			el.style.color = binding.value
		}
	}
}

bind 函数 需要跟 update 函数一起使用

简写形式:

如果 bindupdate 函数中的 逻辑完全相同,则对象格式的自定义指令可以简写成函数格式:

directives:{
    //在 bind 和 update 时,会触发相同的业务逻辑
    color( el,binding ){
        el.style.color = binding.value
}
// color 后面是 function 函数,表明函数里面在 bind 跟 update 一样

全局自定义指令

通过 Vue.directive() 进行声明

Vue.directive('color', function(el, binding) {
	el.style.color = binding.value
})
//参数1:字符串,自定义指令名称
//参数2:对象(里面包含 bind 跟 update 函数),用来接收指令的参数值(上面写的函数时简写形式)

axios

在 Vue-cli 中使用 axios:
// 在组件内
<template>
    <button @click="postInfo">发起 POST 请求</button>
</template>


<script>
// 1. 导入 axios 
import axios from 'axios' 
export default {
 // 2. 在 methods 定义 axios请求方法
  methods: {
  // 如果调用某个方法的返回值是 Promise 实例,则前面可以添加 await!
  // await 只能用在被 async “修饰”的方法中
  // 调用 axios 后,用 async/await 进行简化
  // { data: res} 是采用了对象解构
  // { name: 'zs', age: 20} 是传的body
    async postInfo() {
      const { data: res } = await axios.post('http://http://www.liulongbin.top:3006/api/post', { name: 'zs', age: 20 })
      console.log(res)
    }
  }
}
</script>

把 axios 挂载到 Vue原型上并配置请求根路径

避免重复导入axios 和重复写入完整请求地址。

main.js 中配置

import Vue from 'vue'
import App from './App.vue'
import axios from 'axios'

Vue.config.productionTip = false

// 全局配置 axios 的请求根路径 (官方提供配置项)
axios.defaults.baseURL = 'http://www.liulongbin.top:3006'

// 把 axios 挂载到 Vue.prototype 上,供每个 .vue 组件的实例直接使用
Vue.prototype.$http = axios

// 今后,在每个 .vue 组件中要发起请求,直接调用 this.$http.xxx
// 但是,把 axios 挂载到 Vue 原型上,有一个缺点:不利于 API 接口的复用!!!

new Vue({
  render: h => h(App)
}).$mount('#app')

使用:

<template>
     <button @click="btnGetBooks">
     	获取图书列表的数据			
     </button>
</template>


<script>
export default {
  methods: {
    // 点击按钮,获取图书列表的数据
    async btnGetBooks() {
      const { data: res } = await this.$http.get('/api/getbooks')
      console.log(res)
    }
  }
}
</script>

缺点:

无法实现API接口的复用 : 在多个组件想使用同一个请求方法(API)的时候,只能在每个组件重新定义一次。

路由

前端路由:

Hash 地址组件间的对应关系

hash 地址:#后面的地址

前端路由工作原理

  1. 点击路由链接
  2. 导致了 URL 地址栏中 Hash 值发生变化
  3. 前端路由监听 Hash 地址变化
  4. 前端路由把当前 Hash 地址对应组件渲染到浏览器

在这里插入图片描述

Vue-router

vue-router 是 vue.js 官方给出的路由解决方案,它只能结合 vue 项目进行使用,能够轻松的管理 SPA 项目中组件的切换。

基本使用
  1. 安装 vue-router 包
npm i vue-router@3.5.2

此时在 src 目录下新增一个 router 文件夹

  1. 创建路由模块

    在 router 文件夹中新建 index.js 路由模块

    // 1.导入 vue 和 vue-router 包
    import Vue from 'vue'
    import VueRouter from 'vue-router'
    
    // 2.调用 Vue.use() 函数,把VueRouter 安装为 Vue 的插件
    Vue.use(VueRouter)
    
    // 3.创建路由对象实例对象
    const router = new VueRouter()
    
    //4.向外共享路由的实例对象
    export default router
    
  2. 导入并挂载路由模块

main.js 文件中,导入并挂载路由:

import Vue from 'vue'
import App from '@/App.vue'

// 导入路由模块
import router from '@router/index.js'
// index.js 文件可以省略,因为会自动查找 router 文件夹下的 .js 文件

new Vue({
render: h => h(App),

// 挂载路由模块
router: router
}).$mount('#app')
  1. 声明路由链接和占位符

在 src/App.vue 组件中,使用 vue-router 提供的 <router-link><router-view> 声明路由链接和占位符

<tcmplate>
    <div class="app-container">
        <h1>App组件</h1>
        
        <!--1.定义路由链接 不用加#-->
        <router-link to="/home">首页</router-link>
        <router-link to="/movie">电影</router-link>
        <router-link to="/about">关于</router-link>
        
        <!--2.定义路由的占位符-->
        <router-view></router-view>
    </div>
</template>

  1. 声明路由匹配规则

src/router/index.js 路由模块中,通过 routes 数组声明路由的规则:

// 导入需要使用路由切换展示的组件
import Home from '@/components/Home.vue'
import Movie from '@/components/Movie.vue'
import About from '@/components/About.vue'

// 创建路由实例对象
const router = new VueRouter({
    //routes 数组中,声明路由匹配规则
    routes: [
        // path 表示要匹配的 hash 地址;component 表示要展示的路由
        { path: '/home', component: Home},
        { path: '/movie', component: Movie},
        { path: '/about', component: About}
    ]
})
路由重定向

当访问地址 A 的时候,强制用户跳转 到地址C, 从而展示特定的组件页面。

  • 通过路由规则的 redirect 属性,指定一个新的路由地址来实现路由的重定向。
const router = new VueRouter({
    //routes 数组中,声明路由匹配规则
    routes: [
        // path 表示要匹配的 hash 地址;component 表示要展示的路由
        { path: '/', redirect: '/home'},
        { path: '/home', component: Home},
        { path: '/movie', component: Movie},
        { path: '/about', component: About}
    ]
})
嵌套路由

通过路由实现组件的嵌套

在这里插入图片描述

  1. 声明子路由链接和子路由占位符

    我们在 About.vue 组件中套娃了 tab1 和 tab2 组件 ,如果我们想展示它,则需要声明子路由链接以及子路由占位符 :

    <template>
    	<div class="about-container">
    	 	<h3>About 组件</h3>
            
    		<!--1.在关于页面中,声明两个子路由链接-->
    		<router-link to="/about/tab1">tab1</router-link>
        	<router-link to="/about/tab2">tab2</router-link>
    
        	<hr/>
     		<!--2.在关于页面中,声明子路由的占位符-->
     		<router-view></router-view>
    	</div>
    </template>
    
    
  2. 通过 children 属性声明子路由规则

    src/router/index.js 路由模块中,导入需要的组件,并使用 children 属性声明子路由规则:

    const router = new VueRouter({
      routes: [
        // path 表示要匹配的 hash 地址;component 表示要展示的路由
        { path: '/', redirect: '/home' },
        { path: '/home', component: Home },
        { path: '/movie', component: Movie },
        { 
          path: '/about',
          component: About,
          children: [
            { ptah: '/', redirect: 'tab1'} //重定向
            { path: 'tab1', component: Tab1 }, // 访问/about/tab1时,展示Tab1组件
            { path: 'tab2', component: Tab2} // 访问/about/tab2时,展示Tab2组件
          ]
        }
      ]
    })
    

    注意:使用 children 属性嵌套声明子级路由规则时,path 名称不需要加 /

动态路由

动态路由指的是:把 Hash 地址中可变的部分定义为参数项,从而提高路由规则的复用性

  • 在 vue-router 中使用英文冒号(:)来定义路由动态参数项
// src/router/index.js 文件

// : 后面是动态参数名称
{ path: '/movie/:id', component: Movie}

$route.params 访问动态匹配参数值

<template>
	<div class="movie-container">
		<!--this.$route是路由的“参数对象”-->
		<h3> Movie 组件-- {{this.$route.params.id}} </h3>
	</div>
</template>

<script>
	export default{
		name:'Movie'
	}
</script>

使用 props 接收路由参数

为了简化路由参数获取形式,在路由规则时,声明 props: true 选项

// 在 router/index.js 文件
{ path: '/movie/:id', component: Movie, props: true}

定义好后,即可在Movie组件中,以props的形式接收到路由规则匹配到的参数项。

<template>
	<!-- 3、直接使用props中接收的路由参数 -->
    <h3> MyMovie组件--{{id}}</h3>
</template>

<script>
export default{
    //2、使用props接收路由规则中匹配到的参数项
	props:['id']
</script>

编程式导航 API

vue-router 提供了许多编程式导航的 API,其中最常用的导航 API 分别是:

  1. this.$router.push(‘hash 地址’)
    • 跳转到指定 hash 地址,并增加一条历史记录 。
  2. this.$router.replace(‘hash 地址’)
    • 跳转到指定的 hash 地址,并替换掉当前的历史记录 。
  3. this.$router.go(数值 n)
    • 实现导航历史前进、后退。
    • $router.back(),后退到上一个页面。
    • $router.forward() ,前进到下一个页面。

调用 this.$router.push() 或者 $router.replace()方法,可以跳转到指定的 hash 地址,展示对应的组件页面 :

<template>
	<div class="home-container">
		<h3> Home组件 </h3>
		<button @click="gotoMovie">跳转到Movie页面</button>
	</div>
</template>

<script>
export default{
	methods:{
		gotollovie(){
        this.$router.push("/movie/1")
    }
}
</script>

push 和 replace 的区别:

  • push 会 增加一条历史记录
  • replace 不会增加历史记录,而是替换掉当前的历史记录

调用 this.$router.go() 方法,可以在浏览历史中前进和后退:

<template>
	<h3> MyMovie组件---{{id}}</h3>
	<button @click="goBack" >后退</button>
</template>

<script>
export default{
	props:['id'],
    methods:{
		goBack(){
            this.$router.go(-1) //后退到之前的组件页面
        }
},
</script>

导航守卫

在这里插入图片描述

每次发生路由的导航跳转时,都会触发全局前置守卫。因此,在全局前置守卫中,我们可以对每个路由进行访问权限的控制:

//创建路由实例对象
const router = new VueRouter({..…})

//调用路由实例对象的beforeEach方法,即可声明“全局前置守卫"
//每次发生路由导航跳转的时候,都会自动触发这个“回调函数”

router.beforeEach((to,from,next) =>{
    /* 必须调 next 函数 */   
})

守卫方法的 3 个形参:

  • to 是将要访问的路由的信息 (对象)
  • from 是将要离开的路由的信息对象
  • next 是一个函数,一定要调用 next () 才表示放行,允许这次路由导航 。

注意:

  1. 在守卫方法中如果不声明 next 形参,则默认允许用户访问每一个路由
  2. 在守卫方法中如果声明了 next 形参,则必须调用 next() 函数,否则不允许用户访问任何一个路由!
next 函数的 3 种调用方式

在这里插入图片描述

  1. 直接放行:next()
  2. 强制其停留在当前页面:next(false)
  3. 强制其跳转到登录页面:next(’/login’)
router.beforeEach((to,from,next) => {
     //获取浏览器缓存的用户信息
    const userInfo = window.localStorage.getItem('token');
	if (to.path === '/main' && !userInfo ){ //访问的是 main 页面且 token 不存在
            // 访问的是后台主页,但是没有token的值,跳转到登入页
			next('/login')  
	} else{
	   next()//访问的不是后台主页,直接放行
})

拓展

在这里插入图片描述

  1. 在 hash 地址中,/后面的参数项,叫做 “路径参数
    • 在路由“参数对象”中,需要使用 this.$route.params 来访问路径参数
    • 但是我们一般会使用 props 传参来接收路径参数。
  2. 在 hash 地址中,? 后面的参数项,叫做**“查询参数”**
    • 在路由“参数对象”中,需要使用 this.$route.query 来访问查询参数
  3. this.$route 中,path 只是路径部分;fullPath 是完整的地址。

要想从一个地址跳到另一个地址 (如: 登录后立即跳转到首页,并且首页里设置默认的展示的页面)

this.$router.push(‘hash地址’)

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值