0. Vue 2 常用插件
-
Vetur:
-
功能:提供 Vue 2 的语法高亮、智能感知、代码片段、Emmet 支持等。
-
适用场景:Vue 2 项目开发。
-
注意:Vetur 在 Vue 3 中已逐渐被 Volar 替代。
-
-
Vue Router:
-
功能:用于 Vue 2 的路由管理。
-
适用场景:单页面应用(SPA)的页面跳转和管理。
-
-
Vuex:
-
功能:状态管理工具。
-
适用场景:管理应用中的全局状态。
-
-
Element UI:
-
功能:基于 Vue 2 的 UI 组件库。
-
适用场景:快速搭建高质量的 Vue 2 项目界面。
-
-
Axios:
-
功能:用于 HTTP 请求。
-
适用场景:与后端 API 交互。
-
1. vue简介
构建用户界面,用 vue 往 html 页面中填充数据
1.1 vue 的两个特性
1.1.1 数据驱动视图
-
数据的变化会驱动视图自动更新
-
好处:程序员只管把数据维护好,那么页面结构会被 vue 自动渲染出来!
1.1.2 双向数据绑定
在网页中,form 表单负责采集数据,Ajax 负责提交数据。
-
js 数据的变化,会被自动渲染到页面上
-
页面上表单采集的数据发生变化的时候,会被 vue 自动获取到,并更新到 js 数据中
注意:数据驱动视图和双向数据绑定的底层原理是 MVVM(Mode 数据源、View 视图、ViewModel 就是 vue 的实例)
ViewModel:new Vue()的整个块
View:script外body内
Mode:data指向的对象
1.2 vue的使用
<!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 --> <script src="./lib/vue-2.6.12.js"></script> <!-- 2. 创建 Vue 的实例对象 --> <script> // 创建 Vue 的实例对象 const vm = new Vue({ // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器 el: '#app', // data 对象就是要渲染到页面上的数据 data: { username: 'zhangsan' } }) </script>
注:el挂载点可使用$mount
在实例化后手动挂载vue实例
const vm = new Vue({ data: { username: 'zhangsan' } }) vm.$mount('#app')
1.3 vue 项目结构解析
-
node_modules:这个目录包含了项目的所有依赖包,这些依赖包是通过npm(Node Package Manager)安装的。开发者通常不需要手动修改这个目录下的文件。
-
public:存放一些公共的静态资源,如
favicon.ico
、index.html
等。这些文件通常不会被Webpack处理。 -
src:项目的源代码目录,包含所有的组件、页面、样式、脚本等资源。
-
assets:存放项目中需要用到的资源文件,如CSS、JavaScript、图片等。
-
components:存放Vue组件,这些组件是可复用的Vue实例,通常用于构建页面的各个部分。
-
router(如果存在):存放Vue Router的配置文件,用于定义应用的路由规则。
-
store(如果使用Vuex):存放Vuex的状态管理配置文件,用于管理应用的全局状态。
-
views(或page,取决于项目结构):存放Vue页面组件,这些组件通常对应于应用的不同页面或视图。
-
App.vue:项目的根组件,定义了整个应用的UI结构。
-
main.js:项目的入口文件,负责初始化Vue实例,并挂载到DOM上。
-
-
build(在某些版本中可能不存在):用于存放Webpack相关的配置和脚本。这些配置定义了如何打包和构建项目。
-
config(在某些版本中可能不存在):主要存放配置文件,用于区分开发环境和生产环境的不同。这些配置包括端口号、是否开启热加载、静态资源的相对路径等。
-
dist(通过构建命令生成):默认通过
npm run build
命令打包生成的静态资源文件目录,用于生产部署。 -
babel.config.js:Babel的配置文件,用于配置Babel转译JavaScript代码的规则。
-
package.json:项目的配置文件,包含了项目的依赖信息、脚本命令、版本信息等。通过
npm
命令可以管理项目的依赖和脚本。 -
README.md(可选):项目的说明文档,通常包含项目的介绍、安装方法、使用指南等信息。
-
其他文件:如
.gitignore
(指定git忽略跟踪的文件)、vue.config.js
(Vue CLI的配置文件,用于自定义Webpack配置等)等。
1.4 vue项目运行流程
(1)App.vue用来编写待渲染的模板结构
(2)index.html中预留一个el区
(3)main.js把App.vue渲染到index.html所预留区域
2. vue 指令
2.1 内容渲染指令
2.1.1 v-text
缺点:会覆盖元素内部原有的内容!
{{ }}
插值表达式:在实际开发中用的最多,只是内容的占位符,不会覆盖原有的内容!
注意:插值表达式只能用在元素的内容节点中,不能用在元素的属性节点中!
<div id="app"> //表达式拼接 单双引号都可以使用 //在标签上设置 h2内全部内容都被替换 <h2 v-text="message+'!'"></h2> //插值表达式 只替换大括号的位置 <h2>一个叫{{ message+"!" }}的人</h2> </div>
2.1.2 v-html
在标签上设置 全部内容都被替换 html标签会被解析
<div id="app"> <h2 v-html="content"></h2> </div>
2.2 属性绑定指令
-
在 vue 中,可以使用
v-bind:
指令,为元素的属性动态绑定值; -
简写是英文的
:
-
在使用 v-bind 属性绑定期间,如果绑定内容需要进行动态拼接,则字符串的外面应该包裹单引号,例如:
<div :title="'box' + index">这是一个 div</div> <div v-bind:title="'box' + index">这是一个 div</div>
2.3 事件绑定
v-on:
简写是 @
<button @click="add"></button> <input type="text" @keyup.enter="Add"> methods: { add() { // 如果在方法中要修改 data 中的数据,可以通过 this 访问到 this.count += 1 } }
$event
的应用场景:$event
是一个特殊变量,它代表了原生 DOM 事件对象 , 如果默认的事件对象 e 被覆盖了,则可以手动传递一个 $event。例如:
<button @click="add(3, $event)"></button> methods: { add(n, e) { // 如果在方法中要修改 data 中的数据,可以通过 this 访问到 this.count += 1 } }
2.3.1 修饰符
事件修饰符:
-
.prevent
阻止事件默认行为//阻止链接默认跳转 <a @click.prevent="xxx">链接</a>
-
.stop
阻止事件冒泡<div @click="xxx"> <button @click.stop="xxx">按钮</button> </div>
按键修饰符:.
后直接跟要点击的按键
<div id="app"> <input type="text" @keyup.esc="clearInput" @keyup.enter="commitAjax"> </div>
2.4 v-model 指令
获取和设置表单元素的值(双向数据绑定)
<body> <div id="app"> <input type="button" value="修改message" @click="setM"> <input type="text" v-model="message" @keyup.enter="getM"> <h2>{{ message }}</h2> </div> <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> <script> var app=new Vue({ el:"#app", data:{ message:"你好" }, methods:{ getM:function(){ alert(this.message) } setM:function(){ this.message="你你好好" } } }) </script> </body>
v-model修饰符
number将绑定值转换为数字类型
trim自动去除输入值的首尾空白字符
lazy只有输入框失去焦点或按下回车才会更新数据
2.5 条件渲染指令
2.5.1 v-show
原理是:动态为元素添加或移除 display: none
样式,来实现元素的显示和隐藏
-
如果要频繁的切换元素的显示状态,用 v-show 性能会更好
2.5.2 v-if
原理是:每次动态创建或移除元素,实现元素的显示和隐藏
-
如果刚进入页面的时候,某些元素默认不需要被展示,而且后期这个元素很可能也不需要被展示出来,此时 v-if 性能更好
在实际开发中,绝大多数情况,不用考虑性能问题,直接使用 v-if 就好了!!!
//指令值为布尔值 true 或 false <p v-if="true">被 v-if 控制的元素</p> <p v-if="type === 'A'">良好</p> <div v-if="type === 'A'">优秀</div> <div v-else-if="type === 'B'">良好</div> <div v-else-if="type === 'C'">一般</div> <div v-else>差</div>
2.6 循环渲染指令
v-for
根据数据生成列表结构
把作为母版的标签还有其内部所有内容根据数据的个数拷贝若干份
如果数据长度有改变 网页会动态改变
key的作用:列表数据变化时vue会尽可能的复用已存在的DOM元素,这种默认的性能优化策略会导致列表无法被正确的更新,key可以让vue跟踪每个节点身份,保证正确更新下提高渲染性能。
<!-- 官方建议:只要用到了 v-for 指令,那么一定要绑定一个 :key 属性 --> <!-- 没有key在.vue文件会报错 --> <!-- 尽量把 id 作为 key 的值 --> <!-- 官方对 key 的值类型,是有要求的:字符串或数字类型 --> <!-- key 的值是千万不能重复的,否则会终端报错:Duplicate keys detected --> <tr v-for="(item, index) in list" :key="item.id"> <td>{{ index }}</td> <td>{{ item.id }}</td> <td>{{ item.name }}</td> </tr> <div> <!-- 这里使用v-for来遍历genderOptions数组 --> <div v-for="option in genderOptions" :key="option.value"> <!-- 生成复选框,id和value都设置为option.value --> <input type="checkbox" :id="option.value" :value="option.value"> <!-- 生成标签,for属性设置为option.value,文本内容为option.text --> <label :for="option.value">{{ option.text }}</label> </div> </div>
2.7 自定义指令
2.7.1 私有自定义指令
directives节点下声明
directives: { color: { //为绑定的HTML元素设置颜色 bind(el) { el.style.color='red' } } }
使用自定义指令需要加上v-前缀
为自定义指令动态绑定参数:template使用自定义指令,通过等号方式
data(){ return{ color:'red' } } <h1 v-color="color">内容</h1>
通过binding获取指令的参数值:自定义指令形参的第二个参数
bind函数只调用一次:当指令第一次被绑定到元素时,DOM更新时bind不被触发
update会在每次DOM更新时被调用
directives: { color: { bind(el,binding) { //通过binding对象的.value属性,获取动态的参数值 el.style.color=binding.value }, update(el,binding) { el.style.color=binding.value } } }
如果bind和update函数中的逻辑完全相同,则对象格式的自定义指令可以简写为函数格式
directives: { color:(el,binding) { el.style.color=binding.value } }
2.7.2 全局自定义指令
通过Vue.directive()声明
Vue.directive('color',function(el,binding){ el.style.color=binding.value })
3. 过滤器Filters
常用于文本的格式化,本质是一个js函数,定义于js尾部,由管道符’|
‘调用
在过滤器函数中,一定要有 return 值
如果全局过滤器和私有过滤器名字一致,此时按照“就近原则”,调用的是”私有过滤器“
3.1 私有过滤器
定义:
const vm = new Vue({ el: '#app', data: { message: 'hello vue.js' }, //私有过滤器 filters: { //在过滤器的形参中,可以获取到“管道符”前面待处理的那个值 capi(val) { // 字符串有 charAt 方法,这个方法接收索引值,表示从字符串中把索引对应的字符,获取出来 // val.charAt(0) const first = val.charAt(0).toUpperCase() // 字符串的 slice 方法,可以截取字符串,从指定索引往后截取 const other = val.slice(1) return first + other } } })
使用方法
//方法1:插值表达式 <p>message 的值是:{{ message | capi }}</p> //方法2:v-bind属性绑定 <div v-bind:id="message | capi"></div>
串联调用
<p>message 的值是:{{ message | capi | cap }}</p>
应用于日期格式化时可以js库的day.js
3.2 全局过滤器
// 使用 Vue.filter() 定义全局过滤器 Vue.filter('capi', function (str) { const first = str.charAt(0).toUpperCase() const other = str.slice(1) return first + other + '~~~' })
4. watch 侦听器
允许开发者监视数据的变化从而针对数据变化做特定操作
侦听器本质上是一个函数,要监视哪个数据的变化,就把数据名作为方法名即可 ,侦听器参数新值在前,旧值在后
带参的侦听器第一个参数固定是侦听的数据,其余参数项才是传递的参数
4.1 方法格式的侦听器
-
缺点1:无法在刚进入页面的时候,自动触发!!!
-
缺点2:如果侦听的是一个对象,如果对象中的属性发生了变化,不会触发侦听器!!!
const vm = new Vue({ el: '#app', data: { username: 'admin', info: {age: 18} }, // 所有的侦听器,都应该被定义到 watch 节点下 watch: { //1.数据侦听-方法格式 username(newVal,oldVal) { console.log(newVal) } })
4.2 对象格式的侦听器
-
好处1:可以通过 immediate 选项,让侦听器自动触发!!!
-
好处2:可以通过 deep 选项,让侦听器深度监听对象中每个属性的变化!!!
//2.对象侦听-对象格式 //handler是固定写法,表示当username值变化时自动调用handler处理函数 watch: info: { handler(newVal){ console.log(newVal.age) }, //表示页面初次渲染好之后,立即触发当前watch侦听器 immediate: true //不加此属性侦听不到对象值的变化 deep: true } } //3.侦听对象单个属性的变化 watch:{ 'info.age': { handler(newVal){ console.log(newVal.age) } } }
5. 计算属性computed
在method和template都可以使用,需要return一个计算结果
特点:
-
定义的时候,要被定义为“方法”, 但本质是一个属性
-
在使用计算属性的时候,当普通的属性使用即可
-
计算属性会缓存计算的结果,只有其依赖数据变化时,才会重新进行运算
好处:
-
实现了代码的复用
-
只要计算属性中依赖的数据源变化了,则计算属性会自动重新求值!
var vm = new Vue({ el: '#app', data: { // 红色 r: 0, // 绿色 g: 0, // 蓝色 b: 0 }, methods: { // 点击按钮,在终端显示最新的颜色 show() { console.log(this.rgb) } }, // 所有的计算属性,都要定义到 computed 节点之下 // 计算属性在定义的时候,要定义成“方法格式” computed: { // rgb 作为一个计算属性,被定义成了方法格式 rgb() { return `rgb(${this.r}, ${this.g}, ${this.b})` } } })
6. vue-cli 的使用
vue-cli脚手架是Vue.js开发的标准工具,简化基于webpack创建工程化的Vue项目的过程
安装vue-cli&创建vue项目
在终端下运行如下的命令安装
npm install -g @vue/cli
创建指定名称的项目:
vue create 项目的名称
注:因系统上禁止运行脚本,此时可能报错,此时以管理员身份打开powershell
执行语句:get-ExecutionPolicy,得到回复是Restricted,表示状态是禁止的。 执行:set-ExecutionPolicy RemoteSigned回车后,输入Y。 执行:set-ExecutionPolicy RemoteSigned回车后,输入A 若执行set-ExecutionPolicy RemoteSigned回车后,输入A报错, 然后按照提示输入:Set-ExecutionPolicy -Scope CurrentUser 然后再输入:RemoteSigned ,就会出现如上图的选择,最后输入A则成功。
7. vue组件
组件后缀名.vue
7.1 安装与使用
注册私有组件:a的components内注册了c,那么c就不能用在除a以外的其他组件
//组件App.vue
<template> <div> 111 <!-- 3.使用组件 --> <HelloWorld/> </div> </template> <script> // 1.导入子组件 import HelloWorld from './components/HelloWorld.vue' // 2.注册子组件 export default { components: { HelloWorld } } </script> <style> </style>
注册全局组件
//main.js
import Vue from 'vue' import App from './App.vue' // 1.引入组件 import HelloWorld from './components/HelloWorld.vue' Vue.config.productionTip = false // 2.Vue.component()方法注册全局组件 //参数1:字符串格式 表示组件的注册名称 //参数2:需要被全局注册的那个组件 Vue.component('HelloWorld',HelloWorld) new Vue({ render: h => h(App), }).$mount('#app')
//组件App.vue
<template> <div> 111 <!-- 3.使用组件 --> <HelloWorld/> </div> </template> <script> export default { } </script> <style> </style>
7.2 组成
template(模板结构)、script(组件js行为)、style(组件样式)
-
template:容器标签,只有包裹性质作用,不会被渲染成真正的DOM元素,template只能包含唯一的根节点
-
script:封装组件的js业务逻辑,vue中的data必须是方法
-
style:可选,编写美化组件的UI解构,此标签上添加
lang="less"
可用less语法编写
7.3 属性
7.3.1 props
组件自定义属性,提高组件复用性
//数组格式定义 props:['init'] //对象格式定义 props: { init: { //default定义属性默认值 //type定义属性值类型,传来的值若不符则会在终端报错 //required属性设置为必填项,强制用户必须传递属性的值 default: 0, type: Number, required: true } }
父组件为props提供值
//1.静态值 <ChildComponent title="Hello, World!" /> //2.动态值 <template> <div> <ChildComponent :title="parentTitle" /> </div> </template> <script> export default { data() { return { parentTitle: 'Dynamic Title from Parent' }; } }; </script> //3.传递对象,数组 <template> <div> <ChildComponent :user="{ name: 'John Doe', age: 30 }" /> </div> </template> //4.传递计算属性 <template> <div> <ChildComponent :isActive="isActiveComputed" /> </div> </template> <script> export default { computed: { isActiveComputed() { // 根据某些条件返回true或false return this.someCondition; } } }; </script>
注:此属性可读,不能直接修改,可通过转存到data中,data可读可写
props: {'init'}, data(){ return { count: this.init } }
7.3.2 scoped
为style节点提供,可以解决样式冲突问题
vue组件中的样式会全局生效,会发生多个组件样式冲突问题
//解决方法1:可以为子组件分配唯一的自定义属性,通过属性选择器控制样式作用域 <template> <div class="container" data-v-001> <h3 data-v-001>轮播图组件</h3> </div> </template> <style> .container[data-v-0001]{ border: 1px solid red; } </style>
//解决方法2:子组件添加scoped <template> <div class="container"> <h3>轮播图组件</h3> </div> </template> <style scoped> .container { border: 1px solid red; } </style>
/deep/样式穿透:当父组件需要修改子组件内部的样式,但子组件的样式被封装在组件内部,无法直接通过父组件的样式进行修改时,可以使用样式穿透技术
因为node-sass的版本过高或者sass-loader的版本不匹配导致的/deep/会报错,这时换成vue3的::v-deep就可以了
<style lang="less" scoped> //不加时,生成的选择器样式为.title[data-v-052242de] .title { color:blue } //加时,生成的选择器样式为[data-v-052242de] .title /deep/ .title { color:blue } </style>
7.4 组件生命周期
生命周期:一个组件从创建->运行->销毁的整个阶段,强调的是一个时间段
生命周期函数:vue框架提供的内置函数,会伴随着组件的生命周期,自动按次序执行,强调的是一个时间点
生命周期函数分类:
组件生命周期的第一个阶段(组件创建阶段):beforeCreate->created->beforeMount->mounted
组件生命周期的第二个阶段(组件运行阶段):beforeUpdate->updated
组件生命周期的第三个阶段(组件销毁阶段):beforeDestroy->destroyed
Created最早发Ajax请求,Mounted最早操纵DOM节点,Updated数据和结构都是最新的
一个组件创建阶段只执行一次,运行阶段0-n次,销毁阶段也只执行一次生命周期函数与data、method同级
7.5 组件之间的数据共享
组件常见关系:父子关系、兄弟关系
7.5.1 父子组件之间的数据共享
父->子:使用自定义属性
//父组件 //2.通过动态绑定属性,在父组件新值传给子组件的属性 <son :msg="message" :user="userinfo"><son> data(){ return{ message:'hello vue.js', userinfo:{name:'zs',age:20} } } //子组件 <template> <div> <h5>son</h5> <p>{{ msg }}</p> <p>{{ user }}</p> </div> </template> //1.在子组件自定义属性,并在template放入 props:['msg','user']
子->父:使用自定义事件
//子组件 //1.子组件通过$emit提交值 export default { data(){ return {count:0} }, methods:{ add(){ this.count +=1 //修改数据时通过$emit触发自定义事件 this.$emit('numchange',this.count) } } } //父组件 //2.父组件自定义事件,通过参数获取值 <Son @numchange="getNewCount"></son> export default { data(){ return {countFromSon:0} }, methods:{ getNewCount(Val){ this.countFromSon=val } } }
7.5.2 兄弟组件之间的数据共享
vue2中共享方案为EventBus
1.创建EventBus.js模块。并向外共享一个Vue实例对象
2.在数据发送方,调用bus.$emit(‘事件名称’,要发送的数据)方法触发自定义事
3.在数据接收方,调用bus.$on(‘事件名称’,事件处理函数)方法注册一个自定义事件
//兄弟组件A(数据发送方) //2.引入eventBus.js,通过emit触发自定义事件 import bus from './eventBus.js' export default{ data(){ return { msg:'hello vue.js' } }, methods:{ sendMsg(){ bus.$emit('share',this.msg) } } }
//eventBus.js //1.创建eventBus.js import Vue from 'vue' //向外共享vue的实例对象 export default new Vue()
//兄弟组件C(数据接收方) //3.引入eventBus.js,created生命周期函数接收数据 import bus from './eventBus.js' export default{ data(){ return { msgFromLeft:'' } }, created() { bus.$on('share',val=>{ this.msgFromLeft=val }) } }
7.6 ref引用
在不依赖jQuery情况下获取DOM元素或组件的引用
每个vue组件实例都包含一个$refs对象,里面存着对应的DOM元素或组件的引用,默认指向一个空对象
<h3 ref="myh3">组件</h3> <button @click="getRef">获取</button> <my-counter ref="counterRef">组件</my-counter> methods: { getRef(){ //引用页面上的DOM元素 console.log(this.$refs.myh3) //引用页面上的组件实例 console.log(this.$refs.counterRef) } }
应用
通过布尔值inputVisible来控制组件中的文本框与按钮的按需切换
文本框自动获取焦点可添加ref引用,并调用原生DOM的focus()
this.$nextTick(cb)方法会把cb回调推迟到下一个DOM更新周期之后执行,即等组件的DOM更新完成后在执行cb回调函数,从而保证cb回调函数可以操作到最新DOM元素上
<template> <input type="text" v-if="inputVisible" ref="ipt"> <button v-else @click="showInput">显示input输入框</button> </template> <script> export default{ data(){ return{ inputVisible:false } }, methods:{ showInput(){ this.inputVisible=true //把对input的操作推迟到下次DOM更新之后,否则页面上根本不存在文本框内元素 this.$nextTick(()=>{ this.$refs.ipt.focus() }) } } } </script>
7.7 动态组件
动态切换组件的显示与隐藏
7.7.1 动态组件渲染
vue提供内置的component组件来实现
默认情况下,切换动态组件无法保持组件状态,此时可以使用vue内置keep-alive组件来保持动态组件状态(假设每个组件 都有自增一的效果,每次切换组件时,计数器都会重置为0)
keep-alive的include属性用来指定只有名称匹配的组件会被缓存,多个组件用英文逗号隔开
keep-alive生命周期函数:
组件被缓存时自动触发deactived生命周期函数
组件被激活时自动触发activited生命周期函数
<template> <div> <!--2.当前要渲染的组件名--> <button @click="currentComponent = 'ComponentA'">显示组件 A</button> <button @click="currentComponent = 'ComponentB'">显示组件 B</button> <!-- 使用 is 属性动态绑定组件 --> <!--1.通过is属性动态指定要渲染的组件,component起到占位符的作用--> <component :is="currentComponent"></component> </div> </template> <script> import ComponentA from './ComponentA.vue'; import ComponentB from './ComponentB.vue'; export default { data() { return { // 3.当前要显示的组件名称 currentComponent: 'ComponentA' // 默认显示组件 A }; }, components: { // 注册局部组件 ComponentA, ComponentB } }; </script>
7.8 插槽slot
封装组件时希望把不确定的,由用户指定的部分定义为插槽,可以把插槽认为组件封装期间,为用户预留的内容的占位符
若封装组件没有预留任何<slot>插槽,则用户提供的自定义内容会被丢弃
组件使用者若没有为插槽提供内容,则后备生效
//组件 <template> <p>内容1</p> <slot>后备内容</slot> <p>内容n</p> </template> //使用页面 <my-com-1> <p>用户自定义内容</p> </my-com-1>
7.8.1 具名插槽
封装组件时需要预留多个插槽节点,则需要为每个插槽指定具体的name名称
//子构件 <div class="container"> <header> <slot name="header"></slot> </header> <main> <slot></slot> </main> <footer> <slot name="footer"></slot> </footer> </div>
为具名参数提供内容可以在一个<template>元素上使用v-slot(简写为#),并以v-slot的参数的形式提供其名称
默认值default,只能用在template身上
//父构件 <my-com-2> <template #header> <h1></h1> </template> <template #default> <h2></h2> </template> <template #footer> <h3></h3> </template> </my-com-2>
7.8.2 作用域插槽
为预留的插槽绑定props数据的为作用域插槽
<tbody> <slot v-for="item in list" :user="item"></slot> </tbody>
使用:v-slot接收作用域插槽对外提供的数据
<my-com-3> //1.接收作用域插槽对外提供的数据 <template #default="data"> //2.使用作用域插槽的数据 <h2>{{ data.user }}</h2> </template> </my-com-3>
作用域插槽对外提供的数据对象,使用解构赋值简化数据接收过程
//在Vue中,如果有多个默认插槽,会导致无法正确分发内容,因为默认插槽只能有一个。这可能导致了父组件的作用域插槽绑定到了错误的插槽上。
<my-com-3> <template #default="{user}"> <h2>{{ user.id }}</h2> <h2>{{ user.name }}</h2> <h2>{{ user.state }}</h2> </template> </my-com-3>
8.路由
8.1 前端路由的概念与原理
路由:对应关系
前端路由:Hash地址与组件之间的对应关系
SPA:一个web网站只有唯一的一个HTML页面,所有组件的展示与切换都在这个唯一的页面完成,此时不同组件的切换要通过前端路由来实现
SPA与前端路由:SPA项目中,不同功能的切换要通过前端路由来实现
8.1.1 前端路由的工作方式
1.用户点击了页面上的路由链接
2.导致URL地址栏中的Hash值发生变化
3.前端路由监听到了Hash地址的变化
4.前端路由把当前Hash地址对应的组件渲染到浏览器中
8.1.2 实现简单的前端路由
<template> <div class="box"> <!--2.在App.vue组件中,为a链接添加对应的hash值--> <a href="#/home">首页</a> <a href="#/movie">电影</a> <a href="#/Small">关于</a> <!--1.通过conponent标签,结合comName动态渲染组件--> <component :is='comName'></component> </div> </template> <script> import Small from './components/Small.vue' import Home from './components/Home.vue' import Movie from './components/Movie.vue' export default { data(){ return{ comName:'Home' } }, components:{ Small, Home, Movie }, //3.在created生命周期函数中监听到浏览器地址栏中hash地址的变化 created(){ window.addEventListener('hashchange',()=>{ switch(location.hash){ case '#/home': this.comName='Home' break; case '#/movie': this.comName='Movie' break; case '#/Small': this.comName='Small' break; } }) } } </script scoped> <style> </style>
8.2 vue-router的基本使用
vue.js的官方路由解决方案,结合VUE管理SPA项目中组件的切换
8.2.1 安装与配置
1.安装vue-router包
npm i vue-router@3.5.2 -s
2.创建路由模块:新建src/router/index.js路由模块
//1.导入Vue和VueRouter的包 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
3.导入并挂载路由模块:在src/main.js入口文件中
import Vue from 'vue' import App from './App.vue' //1.导入路由模块 import router from '@/router' new Vue({ render:h=>(App), //2.挂载路由模块 router:router }).$mount('#app')
4.声明路由链接和占位符:src/App.vue组件中使用vue-router提供的<router-link>和<router-view>
<template> <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>
8.2.2 声明路由的匹配规则
src/router/index.js中通过routes数组声明
//1.导入需要使用路由切换展示的组件 import Home from '@/components/Home.vue' import Movie from '@/components/Movie.vue' import About from '@/components/About.vue' //2.创建路由的实例对象 const router = new VueRouter({ routes:[ {path:'/home',component:Home}, {path:'/movie',component:Movie}, {path:'/about',component:About} ] })
8.3 vue-router的常见用法
8.3.1 路由重定向
用户在访问地址A时强制用户跳转到地址C,通过路由规则的redirect属性指定一个新的路由地址
const router = new VueRouter({ routers:[ {path:'/',redirect:'/home'}, {path:'/home',component:Home} ] })
8.3.2 嵌套路由
通过路由实现组件的嵌套
例如:点击父级路由<router-link>显示模板内容<router-view>,模板内容中又有子级路由链接,点击子级路由链接显示子级模板内容
8.3.3 声明子路由链接和子路由占位符
1.About.vue组件中声明子路由链接和子路由占位符
<template> <div class="about-container"> <h3>about组件</h3> //在关于页面中,声明两个子路由链接 <router-link to="/about/tab1">tab1</router-link> <router-link to="/about/tab2">tab2</router-link> //在关于页面中,声明子路由占位符 <router-view></router-view> </div> </template>
在src/router/index.js路由模块中导入需要的插件,使用children属性声明子路由规则
import Tab1 from '@/components/Tab1.vue' import Tab2 from '@/components/Tab2.vue' const router = new VueRouter({ routes:[{ //about的路由规则(父级) path:'/about', component:About, children:[ {path:'tab1',component:Tab1}, {path:'tab2',component:Tab2} //通配符路由 应该放在所有明确的路由定义之后,否则它会捕获所有路径,导致其他路由无法正确匹配。 {path:'*',redirect:'/about/tab2'} ] }] })
8.3.4 动态路由匹配
路由规则的复用性差
概念:把Hash地址中的可变的部分定义为参数项,从而提高路由规则的复用性
vue-router中使用(:)来定义路由参数项
{path:'/movie/1',component:Movie}, {path:'/movie/2',component:Movie}, {path:'/movie/3',component:Movie}, //合并 {path:'/movie/:id',component:Movie}
<router-link to="/movie/1">子组件n</router-link>
1.$route.params参数对象:访问动态匹配的参数值
//index.js
{path:'newMovie/:id',component:NewMovieAll}
//NewMovieAll.vue
<template> <div class="box"> <h1>看新电影n</h1> {{ this.UrlId }} </div> </template> <script> export default { data(){ return{ UrlId: 0 } }, created(){ this.UrlId = this.$route.params.id } } </script>
2.使用props接受路由参数:在路由规则中开启props传参
//1.index.js中在定义路由规则时声明props:true选项 //即可在Movie组件中以props形式接收到路由规则匹配到的参数项 {path:'/movie/:id',component:Movie,props:true} <template> //3.直接用props中接收的路由参数 <h3>{{ id }}</h3> </template> <script> export default { props:'id'//2..vue中使用props接收路由规则中匹配到的参数项 } </script>
8.3.5 声明式导航&编程式导航
在浏览器中,点击链接实现导航的方式,叫声明式导航。
如普通网页点击<a>链接,vue项目中点击<router-link>
常用于静态页面跳转,导航栏侧边栏构建,列表页面的链接
在浏览器中,调用API方法实现导航的方式,叫编程式导航。
如普通网页中调用location.href跳转到新页面
表单提交后的跳转,登录注册的跳转,用户输入或动态数据决定跳转路径,返回上下页,在跳转前执行某些逻辑,例如权限检查、表单验证
vue-router中的编程式导航API
1.this.$router.push(‘hash 地址’):跳转到指定的hash地址,并增加一条历史记录
<template> <div class="home-container"> <h3>Home组件</h3> <button @click="gotoMovie">跳转到Movie页面</button> </div> </template> <script> export default { methods:{ gotoMovie(){this.$router.push('/movie/1')} } } </script>
2.this.$router.replace(‘hash 地址’):跳转到指定的hash地址,并替换当前的历史记录
-
与push的区别:push会增加一条历史记录,replace不会增加历史记录,而是替换掉当前的历史记录
3.this.$router.go(数字 n):实现导航历史前进、后退
-
$router.back()历史记录中后退到上一个页面
-
$router.forward()历史记录中前进到下一个页面
<template> <h3>{{ id }}</h3> <button @click="goBack">后退</button> </template> <script> export default { props:['id'], methods:{ goBack(){this.$router.go(-1)} } } </script>
8.3.6 导航守卫
控制路由的访问权限,如登录
全局前置守卫:导航跳转时触发,此时可以进行访问权限控制
//创建路由实例对象 const router = new VueRouter({...}) //调用路由实例对象的beforeEach方法声明全局前置守卫 //每次路由导航跳转时自动触发fn回调函数 router.beforeEach(fn)
1.守卫方法的3个形参:router.beforeEach(to,from,next)
-
to是将要访问的路由的信息对象
-
from是将要离开的路由的信息对象
-
next是一个函数,调用next()表示放行,允许这次路由导航
2.next函数的3种调用方式
-
当前用户拥有后台主页的访问权限,直接放行:next()
-
当前用户没有后台主页的访问权限,强制其跳转到登陆页面:next(‘/login’)
-
当前用户没有后台主页的访问权限,不允许跳转到后台页面:next(false)
3.控制后台主页的访问权限
router.beforeEach((to,from,next)=>{ if(to.path === '/main'){ const token = localStorage.getItem('token') if(token){ next()//访问的是后台主页,且具有token值 }else{ next('/login')//访问的是后台主页,且没有token值 } {}}else{ next()//访问的不是后台主页,直接放行 } })