Vue2 基础

423次阅读
没有评论

共计 31049 个字符,预计需要花费 78 分钟才能阅读完成。

Vue 基础用法

一、Vue 的指令与过滤器

一、指令的概念

指令是 vue 为开发者提供的 模板语法,用于 辅助开发者渲染页面的基本结构。

vue 中的指令 按照不同的用途 分为6大类:

  1. 内容渲染 指令
  2. 属性绑定 指令
  3. 事件绑定 指令
  4. 双向绑定 指令
  5. 条件渲染 指令
  6. 列表渲染 指令

1.内容渲染指令

内容渲染指令 用来辅助开发者 渲染 DOM 元素的文本内容。常用内容渲染指令有三个:

  • v-text
  • {{}}
  • v-html
v-text

用法示例:

 <!-- 把 username 对应的值,渲染到第一个 P 标签中-->
 <p v-text="username"></p>
 ​
 <!-- 把 gender 对应的值,渲染到第二个 p 标签中-->
 <!-- 注意:第二个 p 标签中,默认的文本 “性别” 会被 gender 的值覆盖掉-->
 <p v-text="gender">性别</p>
{{}} 插值表达式

{{}} 语法专门用来解决 v-text 覆盖原本内容的问题:

 <!-- 使用 {{}} ,将对应的值渲染到元素的内容节点中-->
 <p>姓名: {{ username }}</p>
 <p>性别: {{ gender }}</p>

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

v-html

v-text 指令和插值表达式只能渲染 纯文本内容,若要把 包含 HTML 标签的字符串 渲染为页面的 HTML 元素,则需要用到 v-html:

 <!-- data 中定义了名为 discription 的数据:
 <h5 style="color: red;">我在学习 Vue</h5> -->
 <p v-html="discription"></p>

2.属性绑定指令

v-bind

如果要为 元素的属性 动态绑定 属性值,则需要用到 v-bind 属性绑定指令:

 <!-- 假设有如下的 data 数据:
 data: {
   inputValue: '请输入内容',
   imgSrc:'https://cn.vue.js.org/images/logo.png'
 }
 -->
 ​
 <!-- 使用 v-bind 指令,为 input 的 placeholder 动态绑定属性值 -->
 <input type="text" v-bind:placeholder="inputValue" />
 <br/>
 <!-- 使用 v-bind 指令,为 img 的 src 动态绑定属性值 -->
 <img v-bind:src="imgSrc" alt=""/>

属性绑定指令的简写形式:(简写为 ‘:’ )

 <!-- 假设有如下的 data 数据:
 data: {
   inputValue: '请输入内容',
   imgSrc:'https://cn.vue.js.org/images/logo.png'
 }
 -->
 ​
 <!-- 使用 v-bind 指令,为 input 的 placeholder 动态绑定属性值 -->
 <input type="text" :placeholder="inputValue" />
 <br/>
 <!-- 使用 v-bind 指令,为 img 的 src 动态绑定属性值 -->
 <img :src="imgSrc" alt=""/>

使用 JavaScript 表达式

在 vue 提供的模板渲染语法中,除了支持 绑定简单的数据值,还支持 JavaScript 表达式的运算。

3.事件绑定指令

vue 提供了 v-on 事件绑定 指令,用来辅助为 DOM 元素绑定事件监听:

 <h3>count 的值为:{{count}}</h3>
 <button v-on:click="addCount">+1</button>

注意: 原生 DOM 对象有 onclick、oninput、onkeyup 等,替换为 vue 的事件绑定形式后,分别为:v-on:click、v-on:input、v-on:keyup。

通过 v-on 绑定的事件处理函数,需要在 methods 节点 中进行声明:

 const vm = new Vue({
   el: '#app',
   data: { count: 0},
   methods: {  // v-on 绑定的事件处理函数,需声明在 method 节点中
         addCount() {
             // this 表示当前 new 出来的 vm 实例对象,
             // 通过 this 可以访问到 data 中的数据
             this.count += 1
         }
     },
 })

事件绑定的简写形式(简写为 ‘@’ ):

 <div id="app">
     <h3>count 的值为:{{count}}</h3>
     <button @click="addCount"></button>
 </div>
事件对象 event

在 v-on 指令所绑定的事件处理函数中,可以接收到事件对象 event:

<h3>count 的值为:{{count}}</h3>
<button @click="addCount">+1</button>
----------------------------------------------------
methods: {
	addCount(e) {	// 接收事件参数对象 event,简写为 e
		const nowBgColor = e.target.style.backgroundColor
		e.taget.style.backgroundColor = nowBgColor === 'red'?'' : 'red'
		this.count += 1
	}
}
绑定事件并传参

在使用 v-on 指令绑定事件时,可以使用 ‘ () ’ 进行传参:

<h3>count 的值为:{{count}}</h3>
<button @click="addNewCount(2)">+2</button>
-----------------------------------------------------
methods: {
	// 在形参处用 step 接收传递过来的参数值
	addNewCount(step) {
		this.count += step
	}
}
$event 自定义事件

$event 是 Vue 提供的 特殊变量,用来表示 原生的事件参数对象 event。$event 可以解决事件参数对象 event 被覆盖的问题:

<h3>count 的值为: {{count}}</h3>
<buttom @click="addNewCount(2,$event)">+2</buttom>
-------------------------------------------------------
methods: {
	// 在形参出用 e 接收传递过来的原生事件参数对象 $event
	addNewCount(step,e) {
		const nowBgColor = e.target.style.backgroundColor
		e.target.style.backgroundColor = nowBgColor === 'cyan'?'' : 'cyan'
		this.count += step
	}
}
事件修饰符

在事件处理函数中调用 preventDefault() 或 stopPropagation() 是很常见的需求。因此 vue 提供了 事件修饰符,来辅助 对事件的触发进行控制。常用的 5 个事件修饰符如下:

事件修饰符 说明
.prevent 阻止默认行为(例如:阻止 a链接的跳转,表单的提交等)
.stop 阻止事件冒泡
.capture 以捕获模式触发当前的事件处理函数
.once 绑定的事件只触发一次
.self 只有在 event.target 是当前元素自身时触发事件处理函数
<!-- 触发 click 点击事件时,阻止 a链接的默认跳转行为 -->
<a href="https://www.baidu.com" @click.prevent="onLinkClick">百度首页</a>
按键修饰符

在监听 键盘事件 时,我们需要 判断详细的按键,此时,可以为 键盘相关的事件 添加 按键修饰符,例如:

<!-- 只有在 'key' 是 'Enter' 时调用 'vm.submit()' -->
<input @keyup.enter="submit">
<!-- 只有在 'key' 是 'Esc' 时调用 'vm.clearInput()' -->
<input @keyup.esc="clearInput">

4.双向绑定指令

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

<p>用户名是: {{username}}</p>
<input type="text" v-model="username" />

<p>选中的省份是: {{province}}</p>
<select v-model="province">
    <option value="">请选择</option>
    <option value="1">北京</option>
    <option value="2">河北</option>
    <option value="3">黑龙江</option>    
</select>

注意: v-model 指令 只能配合表单元素 一起使用。

为了方便对用户输入的内容进行处理, vue 为 v-model 指令提供了 3 个修饰符:

修饰符 作用 示例
.number 自动将用户的输入值转为数值类型 <input v-model.number="age" />
.trim 自动过滤用户输入的首位空白字符 <input v-model.trim="msg" />
.lazy 在 ‘change’ 时而非 ‘input’ 时更新 <input v-model.lazy="msg" />

5.条件渲染指令

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

  • v-if
  • v-show
v-if 和 v-show 的区别

实现原理不同:

  • v-if 指令会 动态的创建或移除 DOM 元素,从而控制元素在页面上的显示与隐藏
  • v-show 指令会动态为元素 添加或移除 style=“diplay:none;” 样式,从而控制元素的显示与隐藏

性能消耗不同:

v-if 有更高的 切换开销,而 v-show 有更高的 初始渲染开销。

  • 如果需要 非常频繁的切换,则使用 v-show
  • 如果在 运行时条件很少改变,则使用 v-if
v-else

v-if 可以单独使用,或配合 v-else 指令一起使用:

<div v-if="Math.random() > 0.5">随机数大于 0.5</div>
<div v-else>随机数小于或等于 0.5</div>
v-else-if

v-else-if 指令,充当 v-if 的 “else-if 块” ,可以连续使用:

<div v-if="type ===  'A'">优秀</div>
<div v-else-if="type ===  'B'">良好</div>
<div v-else-if="type ===  'C'">一般</div>
<div v-else>差</div>

6.列表渲染指令

vue 提供了 v-for 指令,用来辅助开发者 基于一个数值来循环渲染相似的 UI 结构。

v-for 指令需要使用 item in items 的特殊语法:

  • items 是 待循环的数组
  • item 是 当前的循环项
data: {
	list: [  // 列表数据
		{ id: 1, name: 'zs'},
		{ id: 2, name: 'ls'}
	]
}
------------------------------------------------------
<ul>
    <li v-for="item in list">姓名是:{{item,name}}</li>
</ul>
v-for 中的索引

v-for 指令还支持一个 可选的 第二个参数,即 当前项的索引。

data: {
	list: [  // 列表数据
		{ id: 1, name: 'zs'},
		{ id: 2, name: 'ls'}
	]
}
------------------------------------------------------
<ul>
    <li v-for="(item, index) in list">索引是: {{index}}, 姓名是: {{item,name}}</li>
</ul>

注意: v-for 指令中的 item 项 和 index 索引 都是形参,可以根据需要进行 重命名。

使用 key 维护列表的状态

当 列表的数据变化时,默认下,vue 会 尽可能的复用 已存在的 DOM 元素,从而 提升渲染的性能。但这默认的优化策略,会导致 有状态的列表无法被正确更新。

为了给 vue 一个提示,以便它能跟踪每个节点的身份,从而在保证 有状态的列表被正确更新的前提下,提示渲染的性能。此时需要为每项提供一个 唯一的 key 属性。

<!-- 用户列表区域 -->
<ul>
    <li v-for="user in userlist" :key="user.id">
        <input type="checkbox" />
        姓名: {{user.name}}
    </li>
</ul>

key 的注意事项

  1. key 的值只能是 字符串 或 数字 类型。
  2. key 的值 必须具有唯一性。
  3. 建议把 数据项的 id 属性值 作为 key 的值。
  4. 建议使用 v-for 指令时 一定要指定 key 的值。

二、过滤器(仅限 Vue 2)

过滤器 常用于 文本的格式化,可以用于 插值表达式 和 v-bind 属性绑定。

过滤器应该被添加在 JavaScript 表达式的尾部,由 管道符 进行调用:

<!-- 在 {{}} 中通过 “管道符” 调用 capitalize 过滤器,对 message 的值进行格式化 -->
<p>{{ message | capitalize }}</p>
<!-- 在 v-bind 中通过 “管道符” 调用 formatId 过滤器,对 rawId 的值进行格式化-->
<div v-bind:id="rawId | formatId"></div>

1.定义过滤器

可以在 filters 节点 中定义过滤器:

const vm = new Vue({
	el: '#app',
	data: {
        message: 'hello vue.js',
        info: 'title info'
    },
    filters: {		// 在 filters 节点下定义过滤器
        capitalize(str) {	// 把首字母转为大写的过滤器,形参(str),为管道符前面的值
            // 一定要有返回值
            return str.charAt(0).toUpperCase() + str.slice(1)
        }
    }
})

2.私有过滤器 和 全局过滤器

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

如果希望在 多个 vue 实例之间共享过滤器, 则可以按照如下的格式定义全局过滤器:

// 全局过滤器 独立于每个 vm 实例之外
/* Vue.filter() 方法接收两个参数:
	第一个参数为全局过滤器的 名字,
	第二个参数为全局过滤器的 处理函数*/
Vue.filter('capitalize', (str) => {
    return str.charAt(0).toUpperCase() + str.slice(1) + '~~'
})

3.连续调用多个过滤器

过滤器可以 串联的 进行调用:

/* 把 message 的值,交给 filterA 进行处理 
	把 filterA 处理的结果,再交给 filterB 进行处理
	最后把 filterB 处理的结果,作为最终值渲染到页面上 */
{{ message | filterA | filterB }}

4.过滤器传参

过滤器本质是 JavaScript 函数:

<!-- 调用 maxLength 过滤器时传参 -->
<p>{{ text | capitalize | maxLength(5) }}</p>

Vue.filter('capitalize', (str) => {
	return str.charAt(0).toUpperCase() + str.slice(1) + '~~'
})
// 第一个参数为管道符前面待处理的值,第二个参数之后,才是调用过滤器时传递的参数。
Vue.filter('maxLength', (str,len = 10) => {
    if(str.length <= len) return str
    return str.slice(0,len) + '...'
})

二、侦听器与计算属性

一、侦听器

1.什么是 watch 侦听器

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

const vm = new Vue({
    el: '#app',
    data: { username: ''},
    watch: {
        // 监听 username 值的变化
        // newVal 是变化后的新值,oldVal 是变化之前的旧值
        username(newVal,oldVal) {
            console.log(newVal,oldVal)
        }
    }
})

2.侦听器的格式

1.方法格式的侦听器
  • 缺点:
    1. 无法在刚进入页面的时候自动触发
    2. 如果侦听的是一个对象,如果对象中的属性发生了变化,则不会触发侦听器
2.对象格式的侦听器
  • 好处:
    1. 可以通过 immediate 选项,自动触发
    2. 可以通过 deep 选项,让侦听器深度监听对象中每个属性的变化
const vm = new Vue({
    el: '#app',
    data: {
        info: {
            username: 'admin'
        }
    },
    watch: {
        // 定义对象格式的侦听器
        username: {
            // 侦听器的处理函数
            handler(newVal,oldVal) {
                console.log(newVal,oldVal)
            },
            // immediate 选项的默认值为 false
            immediate: true,
            // 开启深度监听,对象中任何一个属性变化都会触发侦听器
            deep: true
        }
        // 如果要侦听的是对象的子属性的变化,则必须包裹一层单引号
        'info.username'(newVal) {
    		console.log(newVal)
		}
    }
})

二、计算属性

1.什么是计算属性

计算属性指 通过一系列运算 之后,最终得到一个 属性值。

这个 动态计算出来的属性值 可以被模板结构或 methods 方法使用:

var vm = new Vue({
    el: '#app',
    data: {
        r: 0, g: 0, b: 0
    },
    computed: {
        rgb() {
            return `rgb( ${this.r}, ${this.g}, ${this.b} )`
        }
    },
    methods: {
        show() { console.log(this.rgb) }
    },
})

特点:

  1. 定义的时候,要被定义为 方法
  2. 在 使用 计算属性时,当 普通属性 使用即可

好处:

  1. 实现了代码的复用
  2. 只要计算属性中依赖的 数据源变化 了,则 计算属性会自动重新求值

三、axios

  1. 调用 axios 之后,使用 async / await 进行简化
  2. 使用解构赋值,从 axios 封装的大对象中,把 data 属性解构出来
  3. 把解构出来的 data 属性,使用冒号进行重命名,一般重命名为 (data : res)

一、axios 的基础语法

axios({
    method: '请求的类型',
    url: '请求的 URL 地址'
}).then((result) => {
    // .then 用来指定请求成功之后的回调函数
    // 形参中的 result 是请求成功之后的结果
})

注意: axios 在请求到数据之后,在外面会套一层其他数据:

// 接口服务器
{
    status: '',
    msg: '',
    data: []
}

// axios
{
    config: {},
    data: { 真实的数据 },
    headers: {},
    request: {},
    status: xxx,
    statusText: ''
}

二、结合 async 和 await 调用 axios

axios({
    // 请求方式
    method: 'GET',
    // 请求地址
    url: 'http://www.xxx',
    // URL 中的查询参数 (GET)
    params: {
        id: 1
    },
    // 请求体参数 (POST)
    data: {}
}).then(function(result) {
    console.log(result)
})
<button id="btnPost">发起POST请求</button>
<script>
    document.querySelector('#btnPost').addEventListener('click', async function() {
        // 如果调用某个方法的返回值是 Promise 实例,则前面可以添加 await
        // await 只能用在被 async 修饰的方法中
        const result = await axios({
            method: 'POST',
            url: 'http://www.xxx',
            data: {
                name: 'zs',
                age: 20
            }
        })
        console.log(result)
    })
</script>

三、使用解构赋值

<button id="btnPost">发起POST请求</button>
<script>
    document.querySelector('#btnPost').addEventListener('click', async function() {
        // 解构赋值的时候,使用 : 进行重命名
        const {data : res} = await axios({
            method: 'GET',
            url: 'http://www.xxx'
        })
        console.log(res.data)
    })
</script>

四、基于 axios.get 和 axios.post 发起请求

<body>
    <button id="btnGET">GET</button>
    <button id="btnPOST">POST</button>
    
    <script src="axios文件路径"></script>
    <script>
        document.querySelector("#btnGET").addEventListener('click', async function() {
            const { data : res } = await axios.get("http://www.xxx", {
                params: { id: 1 }
            })
            console.log(res)
        })
        
        document.querySelector('#btnPOST').addEventListener('click',  async function() {
            const { data : res } = await axios.post('url', { name: 'zs', gender: '女' })
            console.log(res)
        })
    </script>
</body>

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

Left 组件

<template>
	<button @click="getInfo">发起 GET 请求</button>
</template>
<script>
    export default {
        methods: {
            async getInfo() {
                const { data: res } =await this.$http.get('/api/get')
                console.log(res)
            }
        }
    }
</script>

main.js

import axios from 'axios'
import .....

// 全局配置 axios 的请求根路径
axios.defaults.baseURL = 'http://www.xxxx:8000'
// 把 axios 挂载到 Vue.prototype 上,供每个 .vue 组件实例直接使用
Vue.prototype.$http = axios

// 今后在每个 .vue 组件中要发起请求,直接调用 this.$http.xxx

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

缺点: 无法实现 api 接口的复用

四、vue-cli

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

中文官网:https://cli.vuejs.org/zh/

一、安装和使用

vue-cli 是 npm 上的一个 全局包,使用 npm install 命令,即可安装到自己的电脑上:

npm install -g @vue/cli

1.基于 vue-cli 快速生成工程化的 Vue 项目:

vue create 项目名 (路径最好全英文)

2.选择安装项目

Please pick a preset: (Use arrow keys)
Default([Vue2] babel, eslint)		// Vue 2	立即创建
Default(Vue 3) ([Vue 3] babel, eslint)		// Vue 3	立即创建
Manually select features	// 手动选择
// 选择第三项后
Please pick a preset: Manually select features
Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to invenrt selection)	// 
(*) Chooose Vue version		// 选择 Vue 版本
(*) Babel		// 安装 Babel (用于解决 js 兼容性)
() TypeScript	// 微软的脚本语言 (ts)
() Progressive	// 渐进式框架
() Router		// 路由
() Vuex
(*) CSS Pre-processors	// CSS 预处理器
() Linter / Formatter	// 强制规范化化代码 (不规范不给运行且报错)
() Unit Testing		// 单元测试
() E2E Testing		// E2E 测试
// 版本选择 略
// CSS 预处理器 略

// 将文件放入哪
Where do you prefer placing config for Babel, ESLint, etc.?
>In dedicated config files		// 独立文件 (选择)
In package.json			// 放入 package.json 包里

// 将选择存储作为下一次预设? 略
// 预设名字

最后使用 
cd 项目名
npm run serve
运行
2.介绍项目的目录结构
  • node modules // 第三方包
  • public
    • favicon.ico // 网页图标
    • index.html // 首页
  • src // 我们写的源码
  • .browerslistrc
  • .gitignore
  • babel.config.js
  • package-lock.json
  • package.json // 包管理配置文件
  • README.md

src 源代码目录构成

  • src
    • assets // 一些第三方静态资源例如图片,样式表
    • components // 程序员封装的、可复用的组件
    • main.js // 项目的入口文件,整个项目的运行要先执行 main.js
    • App.vue // 定义 UI 结构

3. vue 项目的运行流程

vue 通过 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({
  //el: '#app',
  // 把 render 函数指定的组件,渲染到 HTML 页面中
  render: h => h(App),
}).$mount('#app')    // 与 el 作用相同
  • App.vue
<template>
  <div id="app">    <!-- 替换index.html 中的app div -->
    <img alt="Vue logo" src="./assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js App"/>
  </div>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue'

export default {
  name: 'App',
  components: {
    HelloWorld
  }
}
</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>
  • index.html
<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <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 %></title>
  </head>
  <body>
    <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>

其中:

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

五、vue 组件

一、组件的三个组成部分

1.什么是组件化开发

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

2.vue 项目中的组件化开发

vue 是一个 支持组件化开发 的前端框架。

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

3.vue 组件的三个组成部分

  • template -> 组件的 模板结构
  • script -> 组件的 JavaScript 行为
  • style -> 组件的 样式
// .vue 文件中的 script
// 默认导出,固定写法
export default {
    // data 数据源
    // 注意: .vue 组件中的 data 不能指向对象,必须是一个函数
    /*data {
    username: 'zs'     错误
	}*/
    data() {
        // 这个 return 出去的 { } 中,可以定义数据
        return {
            username: 'zs'
        }
    }
}

4.在组件中定义 methods

export default {
    // data 数据源
    // 注意: .vue 组件中的 data 不能指向对象,必须是一个函数
    /*data {
    username: 'zs'     错误
	}*/
    // name 组件的名称,在使用 vue-devtools 调试时,自定义组件名称可以区分每个组件
    name: 'Myapp',
    data() {
        // 这个 return 出去的 { } 中,可以定义数据
        return {
            username: 'zs'
        }
    },
    methods: {
        changeName() {
            // 在组件中,this 表示当前组件的实例对象
            console.log(this)
            this.username = 'admin'
        }
    },
    // 当前组件中的侦听器
    watch: {},
    // 当前组件中的计算属性
    computed: {},
    // 当前组件中的过滤器
    filters: {}
}

5.启用 less 语法以及唯一根节点

在 .vue 文件中,<template></template>中不能含有两个根节点例如两个 <div>。

注意:vue 3.0 可以使用多个根节点。

一般采取:

<template>
 	<div>
        <div>
            123
        </div>
        
        <div>
            456
        </div>
    </div>
</template>

要使用 less 语法,则需:

<style lang="less">
    h3 {
        color: red;
    }
</style>

二、组件的基本使用

1.注册组件

1.注册组件的两种方式

vue 中注册组件的方式分为 “全局注册” 和 “局部注册 两种:

  • 被 全局注册 的组件,可以在全局任何一个组件内使用
  • 被 局部注册 的组件,只能在当前注册的范围内使用

应用场景:

  • 若组件在开发期间 使用频率较高,则进行 全局注册
  • 若组件 只在特定的情况下用到,则进行 局部注册
全局注册组件

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

import { createApp } from 'vue'
import App from './App.vue'
// 导入 Swiper 和 Test 两个组件
import Swiper from './components/MySwiper.vue'
import Test from './components/MyTest.vue'

const app =  createApp(App)

// 调用 app 实例的 component() 方法,在全局注册 my-swiper 和 my-test 两个组件
// 参数1: 字符串格式,表示组件的 “注册名称”
// 参数2: 需要被全局注册的组件
app.component('my-swiper', Swiper)
app.component('my-test', Test)

app.mount('#app')

使用 app.component() 方法注册的全局组件,直接以标签的形式进行使用:

<template>
  <h1>这是 App 根组件</h1>
  <my-swiper></my-swiper>
  <my-test></my-test>
</template>
局部注册组件
<template>
  <h1>这是 App 根组件</h1>
  <my-swiper></my-swiper>
  <my-search></my-search>
</template>

<script>
    import Search from './components/MySearch.vue'
    export default {
        components: {	// 通过 components 节点,为当前组件注册私有子组件
            'my-search': Search,
        },
    }
</script>
2.通过 name 属性注册组件

在注册组件期间,除了可以 直接提供组件的注册名称 之外,还可以把 组件的 name 属性 作为注册后 组件的名称:

<template>
  <h3>轮播图组件</h3>
</template>

<script>
    export default {
        name: 'MySwiper'    // name 属性为当前组件的名字
    }
</script>

import Swiper from './components/MySwiper.vue'
app.component(Swiper.name, Swiper)    // 相当于 app.component('MySwiper', Swiper)
3.使用组件的三个步骤
  1. 使用 import 语法 导入需要的组件
import Left from "@/components/Left.vue"
  1. 使用 components 节点注册组件(私有子组件)
export default {
  components: {
    Left
  }
}
  1. 以 标签形式 使用刚才注册的组件
<div> class='box'>
  <Left></Left>
</div>

2.组件之间的样式冲突问题

默认情况下,写在 .vue 组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题。导致组件

之间样式冲突的根本原因是:

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

为每个组件 分配唯一的自定义组件,在编写组件样式时,通过 属性选择器 来控制 样式的作用域:

<template>
  <div class="container" data-v-001>
    <h3 data-v-001>轮播图组件</h3>    
  </div>
</template>

<style>
    /* 通过中括号“属性选择器”,来防止组件之间的样式冲突问题 */
    .container[data-v-001] {
        border: 1px solid red;
    }
</style>
2.style 节点的 scoped 属性

为了提高开发效率和体验, vue 为 style 节点 提供了 scoped 属性,从而防止组件之间的样式冲突问题:

<template>
  <div class="container">
    <h3>轮播图组件</h3>    
  </div>
</template>
<style scoped>
    /* style 节点的 scoped 属性,用来自动为每个组件分配唯一的 “自定义属性” */
    .container[data-v-001] {
        border: 1px solid red;
    }
</style>

3./deep/ 样式穿透

如果给当前组件的 style 节点添加了 scoped 属性,则 当前组件的样式对其子组件是不生效的。如果想让某些样

式对子组件生效,可以使用 /deep/ 深度选择器。

<style lang="less" scoped>
    .title {
        color: blue; /* 不加 /deep/ 时,生成的选择器格式为 .title[data-v-052242de] */
    }
    /deep/ .title {
        color: blue; /* 加上 /deep/ 时,生成选择器格式为 [data-v-052242de] .title */
    }
</style>

注意: /deep/ 是 vue2.x 中实现样式穿透的方案。在 vue3.x 中推荐使用 :deep() 替代。

当使用第三方组件库时,若有修改组件样式的需求,则需要用到 /deep/。

三、组件的 props (自定义属性)

为了提高组件的 复用性,在封装 vue 组件时需要遵守如下的原则:

  • 组件的 DOM 结构、Style 样式 要尽量复用
  • 组件中要 展示的数据,尽量由组件的使用者提供

为了方便 使用者 为组件提供要展示的数据,vue 组件提供了 props 的概念。

1.什么是组件的 props

props 是组件的 自定义属性,组件的使用者可以通过 props 把数据传递到子组件内部,供子组件内部进行使

用:

<!-- 通过自定义 props,把文章的标题和作者,传递到 my-article 组件中 -->
<my-article title="面朝大海,春暖花开" author="海子"></my-article>

props 的 作用: 父组件通过 props 向子组件传递要展示的数据

props 的 好处: 提高了组件的 复用性

2.在组件中声明 props

在封装 vue 组件时,可以把 动态的数据项 声明为 props 自定义属性,自定义属性可以在当前组件的模板结构中被直接使用:

<!-- my-article 组件的定义如下 -->
<template>
  <h3>标题:{{ title }}</h3>
  <h5>作者:{{ author }}</h5>
</template>

<script>
    export default {
        props: ['title', 'author'],	// 父组件传递给 my-article 组件的数据,必须在 props 节点中声明。
    }
</script>

注意: 如果父组件给子组件 传递了未声明的 props 属性,则这些属性会被忽略,无法被子组件使用。

3.动态绑定 props 的值

可以使用 v-bind 属性绑定 的形式,为组件动态绑定 props 的值:

<!-- 通过 v-bind 属性绑定,为 title 动态赋予一个变量的值 -->
<!-- 通过 v-bind 属性绑定,为 author 动态赋予一个表达式的值-->
<my-article :title="info.title" :author="'post by' + info.author"></my-article>

4.props 是只读的

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

想修改 props 的值,可以 把 props 的值转存到 data 中。

props: ['init'],
data() {
  return {
	count: this.init  // 把 this.init 的值转存到 count
  }
}

5.props 的 default 默认值

在声明自定义属性时,可以通过 default 来 定义属性的默认值:

export default {
    props: {
        init: {
            default: 0
        }
    }
}

6.props 的 type 值类型

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

export default {
    props: {
        init: {
            default: 0,
            type: Number
        }
    }
}

7.props 的 required 必填项

export default {
    props: {
        init: {
            default: 0,
            type: Number,
            // 必填项校验(若不填则报错)
            required: true
        }
    }
}

四、 Class 与 Style 绑定

在实际开发中经常会遇到 动态操作元素样式 的需求。因此,vue 允许开发者通过 v-bind 属性绑定指令,为元

素动态绑定 class 属性的值 和 行内的 style 样式。

1.动态绑定 HTML 的 class

可以通过 三元表达式,动态的为元素绑定 class 的类名:

<h3 class="thin" :class="isItalic ? 'italic' : ''">MyDeep 组件</h3>
<button @click="isItalic=!isItalic">Toggle Italic</button>

data() {
  return { isItalic: true }
}
.thin {	// 字体变细
  font-weight: 200;
}
.italic {	// 倾斜字体
  font-style: italic;
}

2.以数组语法绑定 HTML 的 class

如果元素需要动态 绑定多个 class 的类名,此时可以使用 数组的语法格式:

<h3 class="thin" :class="[isItalic ? 'italic' : '', isDelect ? 'delect' : '']">MyDeep 组件</h3>

<button @click="isItalic=!isItalic">Toggle Italic</button>

data() {
  return {
	isItalic: true, 
	isDelete: false,
  }
}

3.以对象语法绑定 HTML 的 class

使用数组语法动态绑定 class 会导致 模板结构臃肿 的问题,此时可以使用 对象语法 进行 简化:

<h3 class="thin" :class="classObj">MyDeep 组件</h3>
<button @click="classObj.italic = !classObj.italic">Toggle Italic</button>
<button @click="classObj.delete = !classObj.delete">Toggle Delete</button>

data() {
  return {
    classObj: { // 对象中,属性名是 class 类名,值是布尔值
		italic: true,
		delete: false,
	}
  }
}

4.以 对象语法绑定内联的 style

:style 的 对象语法 十分直观,看着像 CSS ,其实是一个 JavaScript 对象:

<div :style="{color: active, fontSize: fsize + 'px', 'background-color': bgcolor}">文本</div>
<button @click="fsize += 1">字号 + 1</button>
<button @click="fsize -= 1">字号 - 1</button>

data() {
  return {
	active: 'red',
	fsize: 30,
	bgcolor: 'pink',
  }
}

五、组件的生命周期

1.生命周期 & 生命周期函数

生命周期 是指一个组件从 创建 -> 运行 -> 销毁 整个阶段,强调的是一个时间段。

生命周期函数:是由 vue 框架提供的 内置函数,会伴随着组件的生命周期 自动按次序执行。

例:

  • 当组件 在内存中被创建完毕 之后,会自动调用 created 函数
  • 当组件被成功的 渲染到页面上 之后,会自动调用 mounted 函数
  • 当组件 被销毁完毕 之后,会自动调用 unmounted 函数
生命周期函数 执行时机 所属阶段 执行次数 应用场景
beforeCreate 在内存中开始创建组件前 创建阶段 唯一 1 次 –
create 组件在内存中创建完毕后 创建阶段 唯一 1 次 发送 ajax 请求初始数据
beforeMount 把组件初次渲染到页面之前 创建阶段 唯一 1 次 –
mounted 组件初次在页面渲染完毕之后 创建阶段 唯一 1 次 操作 DOM 元素
beforeUpdate 在组件被重新渲染之前 运行阶段 0 或多次 –
updated 组件在页面中被重新渲染完毕之后 运行阶段 0 或多次 –
beforeUnmount 在组件被销毁之前 销毁阶段 唯一 1 次 –
unmounted 组件销毁后 销毁阶段 唯一 1 次 –

六、组件之间的数据共享

1.组件之间的关系

组件关系分为 3 种:

  • 父子关系
  • 兄弟关系
  • 后代关系

2.父子组件之间的数据共享

父组件向子组件共享数据

父组件通过 v-bind 属性绑定 向子组件共享数据。同时,子组件需要用 props 接收数据:

// 父组件  在子组件标签上绑定要传递的数据
<my-test-1 :msg="message" :user="userinfo"></my-test-1>

data() {
  return {
	massage: 'hello vue',
	userinfo: { name: 'zs', age: 20 },
  }
}
// 子组件
<template>
  <h3>测试父子传值</h3>
  <p>{{ msg }}</p>
  <p>{{ user }}</p>
  <button @click="user.name='zs'">修改user</button> // 修改父组件(不建议)
</template>

<script>
    export default {
        props: ['msg', 'user'], // 不可更改
    }
</script>
子组件向父组件共享数据

子组件通过 自定义事件 的方式向父组件共享数据:

// 子组件
<script>
    export default {
        emits: ['nichange'],  // 1.声明自定义事件
        data () { return { n1: 0 } },
        methods: {
            addN1() {
                this.n1++
                // 2.数据变化时,触发自定义事件
                this.$emit('n1change', this.n1) // this 为传参,到父组件
            },
        },
}
</script>
// 父组件
// 1.监听子组件的自定义事件 n1change
<my-test-1 @nichange='getn1'></my-test-1>

export default {
  data() {
	methods: {
	  getn1(n1) {	// 2.通过形参,接收子组件传递过来的数据 n1 即 this.n1
		this.n1FromSon = n1
	  },
	},
  }
}

3.兄弟组件之间的数据共享

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

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

七、ref 引用

在 vue 中,不需要操作 DOM, 只需维护数据即可,不建议安装和使用 jQuery。

1.什么是 ref 引用

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

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

2.使用 ref 引用 DOM 元素
<template>
    <div>
        <h1 ref="myh1">App 根组件</h1>
        <button @click="showThis">打印this</button>
    </div>
</template>
<script>
    export default {
        methods: {
            showThis() {
                console.log(this.$refs.myh1)
            }
        }
    }
</script>
3.使用 ref 引用组件实例
<!-- 使用 ref 属性,为对应的组件添加引用名称 -->
<my-counter ref="counterRef"></my-counter>
<button @click="getRef">获取 $refs 引用</button>

methods: {
  getRef() {
	// 通过 this.$refs.引用名   可以引用组件的实例
	console.log(this.$refs.counterRef)
	// 引用到组件的实例后,可以调用组件上的 methods 方法
	this.$refs.counterRef.add()
  },
}
4.控制文本框和按钮的按需切换

通过布尔值 inputVisible 来控制组件中的文本框与按钮的按需切换:

<template>
	<input type="text" v-if="inputVisible">
	<button v-else @click="showInput">展示 Input 输入框</button>
</template>
<script>
    export default {
        data() {
            return {
                // 控制文本框和按钮的按需切换
                iniputVisible: false,
            }
        },
        methods: {
            showInput() {	// 切换布尔值,显示文本框
                this.inputVisible = true
            },
        },
    }
</script>
5.让文本框自动获得焦点

当文本框展示出来之后,如果希望它立即获得焦点,则可以为其添加 ref 引用,并调用原生 DOM 对象的

.focus() 方法即可:

<template>
	<input type="text" v-if="inputVisible" ref="ipt">
	<button v-else @click="showInput">展示 Input 输入框</button>
</template>
<script>
    methods: {
        showInput() {
            this.inputVisible = true
            // 获取文本框的 DOM 引用,并调用 .focus() 使其自动获得焦点
            this.$refs.ipt.focus()
        },
    }
</script>
6.this.$nextTick(cb) 方法

组件的 $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>
    methods: {
        showInput() {
            this.inputVisible = true
            // 把对 input 文本框的操作,推迟到下次 DOM 更新之后,否则页面上不存在文本框元素(生命周期)
            this.$nextTick(() => {
                this.$refs.ipt.focus()
            })
        },
    }
</script>

八、动态组件

1.什么是动态组件

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

2.如何实现动态组件的渲染

<component> 组件 专门用来实现动态组件的渲染:

data() {
    // 1.当前要渲染的组件名称
    return { comName: 'Left' }
}

<!-- 2.通过 is 属性,动态指定要渲染的组件 -->
<!-- is 属性的值,表示要渲染的组件的名字 -->
<component :is='comName'></component>  <!-- 占位 -->

<!-- 3.点击按钮,动态切换组件的名称 -->
<button @click="comName = 'Left'">展示 Left 组件</button>
<button @click="comName = 'Right'">展示 Right 组件</button>
3.使用 keep-alive 保持状态

默认情况下,切换动态组件时 无法保持组件的状态。此时可以用 <keep-alive> 组件保持组件的状态。

keep-alive 可以防止组件在动态切换时被销毁:

<keep-alive>
    <component :is="comName"></component>
</keep-alive>
4. keep-alive 对应的生命周期函数

当组件 被缓存 时,会自动触发组件的 deactivated 生命周期函数。

当组件 被激活 时,会自动触发组件的 activated 生命周期函数。

当组件 第一次被创建时 会执行 created 和 avtivated 生命周期。

5. keep-alive 的 include 属性

include 属性用来指定,只有名称匹配的组件会被缓存,

exclude 属性指定哪些不需要被缓存,注意:不要同时使用 include 和 exclude :

<keep-alive include="MyLeft, MyRight">
    <component :is="comName"></component>
</keep-alive>

九、插槽

1.什么是插槽

插槽 (slot) 是 vue 为 组件的封装者 提供的能力。允许开发者在封装组件时,把 不确定的、希望由用户指定的部分 定义为插槽。

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

2.插槽的基础用法

在封装组件时,可以通过 <slot> 元素 定义插槽,从而 为用户预留内容占位符:

<template>
  <p>这是 MyCom1 组件的第 1 个 p 标签</p>
  <!-- 通过 slot 标签,为用户预留内容占位符(插槽) -->
  <slot></slot>
  <p>这是 MyCom1 组件的最后一个 p 标签</p>
</template>
<my-com-1>
  <!-- 在使用 MyCom1 组件时,为插槽指定具体的内容 -->
    <p>---用户自定义的内容---</p>
</my-com-1>
2.1 没有预留插槽的内容会被丢弃

如果在封装组件时 没有预留任何的 <slot> 插槽,则用户提供的任何 自定义内容都会被丢弃:

<template>
  <p>这是 MyCom1 组件的第 1 个 p 标签</p>
  <!-- 封装组件时,没有预留任何插槽 -->
  <p>这是 MyCom1 组件的第 2 个 p 标签</p>
</template>
<my-com-1>
  <!-- 自定义内容会被丢弃 -->
  <p>---用户自定义内容---</p>
</my-com-1>
2.2 后备内容

封装组件时,可以为预留的 <slot> 插槽提供 后备内容(默认内容),如果组件没有为插槽提供任何内容,则后备内容会生效:

<template>
  <p>这是 MyCom1 组件的第 1 个 p 标签</p>
  <slot>这是后备内容</slot>
  <p>这是 MyCom1 组件的最后一个 p 标签</p>
</template>
3.具名插槽

如果在封装组件时 需要预留多个插槽节点,则需要为每个 <slot> 插槽指定 具体的 name 名称。这种 带具体名称的插槽 叫做 具名插槽:

<div class="container">
    <header>
        <!-- 我们希望把页头放这里 -->
        <slot name="header"></slot>
    </header>
    <main>
        <!-- 我们希望把主要内容放这里 -->
        <slot></slot>
    </main>
    <footer>
        <!-- 我们希望把页脚放这里 -->
        <slot name="footer"></slot>
    </footer>
</div>

注意: 没有指定 name 名称的插槽默认名称为 default。

3.1 为具名插槽提供内容

在向具名插槽提供内容时,可以在 <template> 元素上使用 v-slot 指令,并以 v-slot 的参数形式提供其名称:

<my-com-2>
    <template v-slot:header>
	  <h1>滕王阁序</h1>
    </template>
    
    <template v-slot:default>
	  <p>豫章故郡,洪都新府。</p>
    </template>
    
    <template v-slot:footer>
	  <p>落款:王勃</p>
    </template>
</my-com-2>
3.2 具名插槽的简写形式

把参数前的内容(v-slot:) 替换为字符 #,例 v-slot:header= #header

4.作用域插槽

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

<tbody>
  <!-- 下面的 slot 是一个作用域插槽 -->
  <slot v-for="item in list" :user="item"></slot>
</tbody>
4.1 使用作用域插槽

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

<my-com-3>
  <!-- 1.接收作用域插槽对外提供的数据 -->
  <template v-slot:default="scope">
	<tr>
      <!-- 2.使用作用域插槽的数据 -->
      <td>{{scope}}</td>
    </tr>
  </template>
</my-com-3>
4.2 解构插槽 Prop

作用域插槽对外提供的数据对象,可以使用 解构赋值 简化数据的接收过程:

<my-com-3>
	<!-- v-slot: 可以简写成 # -->
    <!-- 作用域插槽对外提供的数据对象,可以通过 解构赋值 简化接收的过程 -->
    <template #default="{user}">
		<tr>
            <td>{{user.id}}</td>
            <td>{{user.name}}</td>
		   <td>{{user.state}}</td>
        </tr>
    </template>
</my-com-3>

十、自定义指令

1.自定义指令的分类
  • 私有 自定义指令
  • 全局 自定义指令
2.私有自定义指令

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

directives: {
  color: {
	//为绑定到的 HTML 元素设置红色的文字
    // 当指令第一次被绑定到元素上的时候,回立即触发 bind 函数
	bind(el) {
	  //形参中的 el 是绑定了此指令的、原生的 DOM 对象
	  el.style.color = 'red'
	}
  }
}
3.使用自定义指令

在使用自定义指令时,需要加上 v- 前缀:

<!-- 声明自定义时,指令的名字是 color-->
<!-- 使用自定义指令时,需要加上 v- 指令前缀 -->
<h1 v-color>App 组件</h1>
4.为自定义指令 动态绑定参数值

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

<script>
    data() {
        return {
            color: 'red'  // 定义 color 颜色值
        }
    }
</script>
<!-- 在使用指令时,动态为当前指令绑定参数值 color -->
<h1 v-color="color">App 组件</h1>
<!-- 若在双引号中包含单引号,则是传递一个值 -->
<p v-color="'red'">测试</p>
5.通过 binding 获取指令的参数值

在声明自定义指令时,可以通过形参中的 第二个参数,来接收指令的参数值:

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

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

directives: {
    color: {
        //当指令第一次被绑定到元素时被调用
        bind(el, binding) {
            el.style.color = binding.value
        },
        // 每次 DOM 更新时被调用
        update(el, binding) {
            el.style.color = binding.value
        }
    }
}
7.函数简写

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

directives: {
    // 在 insert 和 update 时,会触发相同的业务逻辑
    color(el, binding) {
        el.style.color = binding.value
    }
}
8.全局自定义指令

全局共享的自定义指令需要通过 Vue.directive() 进行声明:

// 参数1:字符串,表示全局自定义指令的名字
// 参数2:对象,用来接收指令的参数值
Vue.directive('color', function(el, binding) {
    el.style.color = binding.value
})

复习:数组中的方法

一、some 循环

foreach 循环一旦开始就无法在中间停止,性能较低。

const arr = ['a', 'b', 'c', 'd']

arr.some((item,  index) => {
    console.log('ok')
    if (item === 'c') {
        console.log(index)
        // 在找到对应的项之后,可以通过 return true 来停止 some 循环
        return true
    }
})

二、every 循环

const arr = [
    { id: 1, name: '西瓜', state: true },
    { id: 2, name: '榴莲', state: true },
    { id: 3, name: '草莓', state: true },
]

// 需求:判断数组中,水果是否被全选,可用于全选按钮
const result = arr.every(item => item.state)
console.log(result)

三、 reduce 循环基本用法

reduce 循环 即累加

// 语法: .reduce((累加的结果, 当前循环值) => {}, 初始值)
const arr = [
    { id: 1, name: '西瓜', state: true, price: 10, count: 1 },
    { id: 2, name: '榴莲', state: true, price: 80, count: 2 },
    { id: 3, name: '草莓', state: true, price: 20, count: 3 },
]

// 需求:把购物车数组中,已勾选的水果,总价累加起来
/*let amt = 0	// 总价
arr.filter(item => item.state).forEach(item => {
    amt += item.price * item.count
})
console.log(amt) */

const result = arr.filter(item => item.state).reduce((amt, item) => {
    return amt += item.price  * item.count
}, 0) // 这里的 0 为 amt 的初始值

console.log(result)

六、路由

一、前端路由的概念与原理

1.什么是路由

路由 (router) 就是 对应关系。

2.SPA 与前端路由

SPA 指的是一个 web 网站只有唯一的一个 HTML 页面, 所有组件的展示与切换 都在这唯一的一个页面内完成。

此时,不同组件之间的切换 都需要 前端路由 来完成。

前端路由: Hash 地址与组件之间的对应关系。

3.前端路由的工作方式

  1. 用户 点击了 页面上的路由链接
  2. 导致了 URL 地址栏 中的 Hash 值 发生了变化
  3. 前端路由监听到了 Hash 地址的变化 (URL 中 # 后都属于 Hash 地址)
  4. 前端路由把当前 Hash 地址对应的组件 渲染到浏览器中

4.实现简易的前端路由

  1. 通过 <component> 标签,结合 comName 动态渲染组件:<!– 通过 is 属性,指定要展示的组件的名称 –> <component :is=”comName”></component> <script> export default { name: ‘App’, data() { return { //要展示的组件的名称 comName: ‘Home’ } } } </script>
  2. 在 App.vue 组件中,为 <a> 链接 添加对应的 hash 值:<a href=”#/home”>Home</a>&nbsp; <a href=”#/movie”>Movie</a>&nbsp; <a href=”#/about”>About</a>
  3. 在 created 生命周期函数中,监听浏览器地址栏中 hash 地址的变化,动态切换要展示的组件的名称:<script> export default { created() { window.onhashchange = () => { switch (location.hash) { case ‘#/home’: // 点击了‘首页’的链接 this.comName = ‘Home’ break case ‘#/movie’: // 点击了‘电影’的链接 this.comName = ‘Movie’ break } } } } </script>

二、vue-router 的基本使用

1.什么是 vue-router

vue-router 是 vue.js 给出的 路由解决方案。能够管理 SPA 项目中组件的切换。

官方文档:https://router.vuejs.org/zh/

2.vue-router 安装和配置

2.1 在项目中安装 vue-router

在 vue2 项目中,安装 router 命令如下:

npm i vue-router@3.5.2 -S
2.2 创建路由模块

在 src 源代码目录下,新建 router/index.js 路由模块,并初始化如下代码:

// 导入 Vue 和 VueRouter 的包
import Vue from 'vue'
import VueRouter from 'vue-router'

// 调用 Vue.use() 函数, 把 VueRouter 安装为 Vue 的插件
Vue.use(VueRouter)

// 创建路由的实例对象
const router = new VueRouter()

// 向外共享路由的实例对象
export default router
2.3 导入并挂载路由模块

在 src/main.js 入口文件中,导入并挂载路由模块:

import Vue from 'vue'
import App from './App.vue'
// 导入路由模块
import router from './router'

new Vue({
  render: h => h(App),
  // 挂载路由模块
  router: router
}).$mount('#app')
2.4 声明 路由链接 和 占位符

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

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

3.声明路由的 匹配规则

在 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:[
    // 在 routers 数组中,声明路由的匹配规则
    // path 表示要匹配的 hash 地址,component 表示要展示的路由组件
    { path: '/home', component: Home },
    { path: '/movie', component: Movie },
    { path: '/about', component: About }
  ]
})

三、vue-router 的常见用法

1.路由重定向

路由重定向 指的是:用户在访问 地址 A 的时候, 强制用户跳转 到地址 C ,从而展示特定的组件页面。

通过路由规则的 redirect 属性,指定一个新的路由地址,可以方便的设置路由的重定向:

const router = new VueRouter({
  // 在 routes 数组中,声明路由的匹配规则
  routes: [
    // 当用户访问 / 的时候,通过 redirect 属性跳转到 /home 对应的路由规则
    { path: '/', redirect: '/home'},
    { path: '/home', component: Home },
    { path: '/movie', component: Movie },
    { path: '/about', component: About }
  ]
})

2.嵌套路由

通过路由实现 组件的嵌套展示,叫做嵌套路由

3.子路由

3.1 声明 子路由链接 和 子路由占位符

在 About.vue 组件中,声明 tab1 和 tab2 的 子路由链接 以及 子路由占位符:

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

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

import Tab1 from '@/components/tabs/Tab1.vue'
import Tab2 from '@/components/tabs/Tab2.vue'

const router = new VueRouter({
  routes: [
    {		// about 页面的路由规则 (父级路由规则)
      path: '/about',
      component: About,
      children: [  // 通过 children 属性,嵌套声明子级路由规则
        // 官方建议path 部分不加斜线 
        { path: 'tab1', component: Tab1 }, // 访问 /about/tab1 时,展示 Tab1 组件
        { path: 'tab2', component: Tab2 }  // 访问 /about/tab2 时,展示 Tab2 组件
      ]
    }
  ]
})

如果 children 数组中,某个路由规则的 path 值为空字符串,则这条路由规则叫做 默认子路由。可在一定程度上替代重定向。

4.动态路由匹配

4.1动态路由的概念

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

在 vue-router 中使用 : 来定义路由的参数项:

{ path: '/movie/:id', component: Movie }

// 将以下 3 个路由规则合并为一个,提高了路由规则的复用性
{ path: '/movie/1', component: Movie }
{ path: '/movie/2', component: Movie }
{ path: '/movie/3', component: Movie }
4.2 $route.params 参数对象

在 动态路由 渲染出来的组件中,可以使用 this.$route.params 对象访问到 动态匹配的参数值:

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

<script>
  export default {
    name: 'Movie'
  }
</script>
4.3 使用 props 接收路由参数

为了简化路由参数的获取形式, vue-router 允许在 路由规则 中 开启 props 传参 :

// 在定义路由规则时,声明 props: true 选项
// 即可在 Movie 组件中,以 props 的形式接收到路由规则匹配到的参数项
{ path: '/movie/:id', component: Movie, props: true }

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

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

5. 声明式导航 & 编程式导航

在浏览器中, 点击链接 实现导航的方式,叫做 声明式导航:

  • 普通网页中点击 <a> 链接、vue 项目中点击 <router-link> 都属于声明式导航

在浏览器中,调用 API 方法 实现导航的方式看,叫做 编程式导航:

  • 普通网页中调用 location.href 跳转到新页面的方式,属于编程式导航
5.1 vue-router 中的编程式导航 API

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

  1. this.$router.push('hash 地址')跳转到指定 hash 地址,并 增加 一条历史记录
  2. this.$router.replace('hash 地址')跳转到指定 hash 地址,并 替换掉当前的 历史记录
  3. this.$router.go(数值n)实现导航历史前进、后退
5.2 $router.push

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

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

<script>
  export default {
    methods: {
      getoMovie() m{ this.$router.push('/movie/1') }
    }
  }
</script>
5.3 $router.replace

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

push 和 replace 的区别:

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

调用 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>
5.5 $router.go 的简化用法

在实际开发中,一般只会前进和后退一层页面,因此提供了两个便捷方法:

  1. $router.back()在历史记录中,后退 到上一个页面
  2. $router.forward()在历史记录中,前进 到下一个页面

6. 导航守卫

导航守卫 可以 控制路由的访问权限:

6.1 全局前置守卫

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

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

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

全局前置守卫 的回调函数中接收 3 个形参,格式为:

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

// 全局前置守卫
router.beforeEach((to, from, next) => {
  // to 是将要访问的路由的信息对象
  // from 是将要离开的路由的信息对象
  // next 是一个函数,调用 next() 表示放行,允许这次路由导航
})
6.3 next 函数的 3 种调用方式
  • 当前用户 拥有 后台主页的访问权限,直接放行: next()
  • 当前用户 没有 后台主页的访问权限,强制其跳转到登录页面: next(‘/login’)
  • 当前用户 没有 后台主页的访问权限,不允许跳转到后台主页: next(false)
6.4 控制后台主页的访问权限
router.beforeEach((to, from, next) => {
  if(to.path === '/main') {
    const token = localStorage.getItem('token')
    if (token) {
      next() // 访问的是后台主页,且有 token 的值
    } else {
      next('/login') // 访问的是后台主页,但是没有 token 的值
    }
  } else {
    next() // 访问的不是后台主页,直接放行
  }
})
正文完
 0
Yichend
版权声明:本文于2022-09-24转载自bilibili黑马程序员,共计31049字。
转载提示:此文章非本站原创文章,若需转载请联系原作者获得转载授权。
评论(没有评论)