一、组件的创建和使用
1.1 组件概念
- 组件 :
- 可复用的Vue实例,把 标签 、样式 和 JS 封装成一个单独的
.vue
文件; - 代码里面体现为一个独立的
.vue
文件;
- 可复用的Vue实例,把 标签 、样式 和 JS 封装成一个单独的
- 组件化 :
- 封装的思想:
- 把页面上 可复用的部分 封装为组件,从而方便项目的开发和维护;
- 一个页面可以拆分成多个组件,一个组件就是一个整体,每个组件有自己独立的结构样式行为。
- 封装的思想:
- 使用场景 :
- 遇到标签可复用的时候;
- 组件的优点 :
- 各自独立:每个组件都有自己独立的结构样式和行为;
- 便于复用;
1.2 组件的创建和使用
1.2.1 创建组件
- 创建一个
.vue
文件,封装想要复用的html、js、css
;
1.2.2 导入组件
-
全局导入 :
-
语法 :
- 目标文件:
msin.js
;
jsimport Vue from 'vue' import 组件对象 from '组件路径(.vue文件路径)
- 目标文件:
-
-
局部导入 :
-
语法 :
jsimport 组件对象 from '组件路径(.vue文件路径)'
-
1.2.3 注册组件
- 全局注册
-
在
main.js
中注册; -
语法:
jsimport Vue from 'vue' // 组件名 - 字符串 // 组件对象 - 变量 Vue.component('组件名', 组件对象)
-
注意:该组件在所有的
.vue
文件中都可以使用;
-
- 局部注册
-
在
需要组件的文件
中注册; -
语法:
jsexport default { components: { 组件名: 组件对象 } }
-
❗ 注意 :
- 根据ES6的新特性,属性名和属性值一样,可以简写,所以可以将 组件名 和 组件对象 写成一样 的;
- 该组件仅限当前文件使用;
-
1.2.4 使用组件
-
将 组件名 当作 自定义标签 去 使用;
-
语法 :
js<template> <div id="app"> <组件名 /> <组件名></组件名> </div> </template>
-
注意 :
- 单双标签都可以,单标签 需要 自闭合;
- 该组件是 块元素 还是 行内元素 ,取决于组件的根标签 ,既
template
下面唯一的根标签; - 组件运行之后的样子:
- 使用 组件里的 唯一根标签 替换 组件标签;
-
组件命名规范 :
- 使用 大驼峰 命名法;
.vue
文件名 = 组件对象 = 组件名 = 自定义标签名
-
示例展示:
-
全局导入 + 全局注册 + 使用步骤:
js// 该行代码已经有,不需重复写 import Vue from 'vue' // 全局导入组件 // 语法: import 组件对象 from '组件路径' import PanelX from './components/Panel.vue' // 全局注册组件 // 语法:Vuw.component('组件名', 组件对象) // 组件名 - 字符串 // 组件对象 - 变量 Vue.component("PanelX", PanelX);
-
局部导入 + 局部注册 + 使用步骤
js<template> <div id="app"> <h3>案例:折叠面板</h3> <!-- 4.使用组件 - 直接将组件名当作标签使用 --> <!-- 单双标签都可以 --> <!-- 单标签需要自闭和 --> <!-- 局部注册的组件 --> <Panel /> <Panel></Panel> <!-- 全局注册的组件 --> <PanelX /> <PanelX></PanelX> </div> </template> <script> // 1.封装组件 - 抽取可复用标签 + 对应JS代码 + 对应样式 // 2.导入组件 // 语法:import 组件对象 from '组件路径' import Panel from "./components/Panel.vue"; export default { // 3.注册组件 // components: { // "组件名": 组件对象, // } components: { // Panel: Panel, // 简写 Panel, }, }; </script> <style lang="less"> body { background-color: #ccc; #app { width: 400px; margin: 20px auto; background-color: #fff; border: 4px solid blueviolet; border-radius: 1em; box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.5); padding: 1em 2em 2em; h3 { text-align: center; } } } </style>
-
1.3 scoped 的作用
- 用
scoped
修饰style
标签之后,webpack
在打包的时候,会尝试去给当前vue文件里的标签添加随机哈希值; - 准备:当前 组件内标签 都被添加
data-v-8位哈希值
的属性; - 获取:css选择器 都被添加
[data-v-8位哈希值]
的 属性选择器; - 注意:
- 每个组件的自定义属性都是相互独立的;
- 给当前所有标签都添加自定义属性,并且自定义属性是相同的;
- css选择器,就会形成一个交集选择器
- 原有的选择器 + 属性选择器
二、组件通信
- 为什么组件之间需要通信?
- vue写代码思想:
- 组件化,代码不会写在一个文件中,而是拆分成一个一个的组件,最后再进行组装,得到一个完整界面,由于代码分散在不同的文件中中,最后是通过这些组件之间的配合来实现需求,这时候难免它们之间必须要传数据。
- vue写代码思想:
2.1 父组件向子组件传值(props机制)
- 在 父组件 里面引入 子组件;
- 步骤:
- 1️⃣ 子组件内,声明固定变量(
props
)接收传递的数据 :- ❌ 数组型 的 props :
- 在
props
数组里面,准备 字符串型 作为 接收数据 的 变量,接收父组件传递过来的数据; - 若 父组件 未传值 ,则接收到的数据就是 undefined;
- 在
- ✅ 对象型 的 props :
- 每个变量 都是 一个对象,作为
props
的 子对象; - 每个对象都有三个属性:
type
:- 属性值:
Number、String、Boolean、Array、Object、Function、Promise、......
- 规定传入数据的类型;
- 如果传入的数据不是规定的类型,就会报错;
- 注意:
null
和undefined
会通过任何类型验证;
- 属性值:
required
:- 属性值:
true / false
- 是否必传(不传数据会报错);
required
default
两个属性二选一;
- 属性值:
default
:- 默认值(不传数据就用默认值,传了就用传递的数据);
- 注意:
- 对象或数组默认值必须从一个工厂函数获取
- 每个变量 都是 一个对象,作为
- ❌ 数组型 的 props :
- 2️⃣ 引入组件,注册组件,使用组件,传值进去 ;
- 在 自定义标签上 (组组件标签),使用
v-bind
动态绑定属性,子组件内 声明的 接收变量 作为 属性名 ,要 传递的数据(变量) 作为 属性值 传递过去。
- 在 自定义标签上 (组组件标签),使用
- 1️⃣ 子组件内,声明固定变量(
- 这块东西还是比较多的,具体的一些可以去Vue2官网的prop中看看。
- 两种
props
接收数据方式的对比:- 数组:
- 方式很单一,也不能做限制;
- 对象:
- 可以对传递的数据进行限制;
- 可以设置默认值;
- 在子组件使用频率很是频繁的情况下,子组件中的某块数据大多数情况下是一样的,这时候,我们就可以设置默认值;
- 数组:
- 示例展示:
-
父组件:
html<template> <div> <!-- 使用组件 --> <组件名 :子组件props中的变量名="父组件要向子组件传递的数据"></组件名> </div> </template> <script> // 导入组件 import 组件对象 from '组件路径' // .vue文件名 = 组件对象名 = 组件名 = 自定义标签名 export default { // 注册组件 components: { 组件名: 组件对象 } } </script>
-
子组件:
html<script> export default { // 1.声明固定变量,接收父组件传递过来的数据属性 // 2.父组件内,子组件上,通过自定义属性传数据属性 // 注意:props在根data、methods等平级 // props第一种写法 props: ['变量名1', '变量名2', '变量名3', ......], // props第二种写法 props: { 变量1: { type: Number(简单数据类型), required: true }, 变量2: { type: Number(简单数据类型), default: 写成对应类型的数据, }, 变量3: { type: Array / Object, dafault: function () { // type = Array return [对应的数据] // type = Object return { 对应数据 } } } } } </script>
-
2.2 子组件向父组件传值($emit机制)
-
1️⃣ 父组件内 给 子组件标签 绑定 自定义事件 和 事件处理函数 :
-
语法:
js<组件标签 @自定义事件名 = "父组件methods中定义的事件名" />
-
-
2️⃣ 子组件内 ,在恰当的时机,触发 父组件 给 子组件 绑定的 自定义事件 ,进而触发父组件methods中对应的事件处理函数执行;
-
语法:
jsthis.$emit('自定义事件名', 要向父组件传递的数据)
-
2.3 单向数据流
- 为什么不建议 子组件修改父组件传过来的值?
- 父子数据不一致,而且子组件是依赖于父组件传入的值;
- 导致数据的不一致。
- 单向数据流:
- 从 父组件 到 子组件 的 数据流向;
- ❗❗ Vue规定 :
props
里的 变量,本身是 只读 的;
- 这里只是简单说了一下,想要了解的更深可以去问度娘。
❌ 2.4 跨组件通信(了解即可)
-
跨组件通信:两个 没有任何关系 的 组件 进行 通信;
-
语法:
js// 1. src/EventBus/index.js - 创建空白Vue实例并导出 // 2. 在要 接收值 的 组件 - eventBus.$on("事件名", 事件处理函数) // 3. 在要 传递值 的 组件 - eventBus.$emit("事件名", 事件处理函数的实参1, 事件处理函数的实参2, ......)
-
示例展示:
- (图片看不清可以右键,在新标签页中打开图片)
三、Vue生命周期
- 生命周期 :
- 从创建到销毁的整个过程称为组件的生命周期;
3.1 钩子函数
- 钩子函数 :
- Vue框架 内置函数 ,随着组件的生命周期 自动执行;
- 监测Vue生命周期到达了什么阶段;
- 作用 :
- 在特定的时间点,执行特定的操作;
- 使用场景 :
- 组件创建完毕后,可以在
created
生命周期函数中 发起Ajax请求 ,从而 初始化data
数据;
- 组件创建完毕后,可以在
- 分类:
- 四个阶段 ,八个方法
3.2 复习axios使用步骤
-
下包:
yarn add axios -S
;
-
导入包:
import axios from 'axios'
;
-
调用
axios
函数,对接接口文档,获取相关数据:js// 1. 创建 axios 实例并导出 // 一般都是在 src/utils/request.js 下写 import axios from 'axios' const request = axios.create({ baseURL: process.env.VUE_APP_BASE_API, timeout: 5000 }) export default request // 2. 在 src/api/对应的js文件 导出 request(axios实例) import request from '@/utils/request' // @ 表示 src 文件夹 /* * 参数根据需求填写 * method = GET ➡ params(get方式传参) * method = POST ➡ data(post方式传参) */ export function 方法名(data 或 params) { return request({ method: '请求方式', url: '请求地址', data 或 params(根据请求方式填写,此处使用了对象的简写,属性值和属性名一样) }) } // 3. 在需要使用接口的地方,导入接口,使用即可
3.3 初始化阶段
- 1️⃣
new Vue()
➡ Vue实例化 :- 在
main.js
中实例化Vue,从此刻起,Vue就诞生了; - 组件也是一个小的Vue实例;
- 在
- 2️⃣
Init Event & Lifecyle
➡ 给 Vue实例 添加 属性 和 方法 :- 给Vue上户口,给Vue实例添加属性和方法,这些属性和方法我们暂时用不了,无需关心;
- 3️⃣ ❌
beforeCreate
➡ 执行钩子函数,当前钩子函数 访问不了 data数据 和 methods方法- 我们在组件中声明的data数据和methods方法还没有加到Vue实例上;
- 这个钩子中,无法访问数据和方法,开发中不用,但个别源码会用;
- 4️⃣
Init injections&reactivity
➡ 开始把data
数据 和mothods
方法 变成 响应式,并且加到vue实例上; - 5️⃣ ✅
created
➡ 开始执行钩子函数,现在 可以访问 data数据 和 methods方法 ;- 使用场景 :
- 发起Ajax请求、开启定时器;
- 当我们一进入某个组件,就需要获取后台数据展示界面。可以在这个时候发起Ajax请求,早点拿数据,早点渲染界面;
- 使用场景 :
- 示例展示:
- 小结:
- Vue实例 从 创建 到 编译模板 执行了 哪些 钩子函数 ?
beforeCreate、created
;
created
函数 触发 能获取data
吗?- 可以获取,但是 不能获取真实DOM;
- Vue实例 从 创建 到 编译模板 执行了 哪些 钩子函数 ?
3.4 挂载阶段
Has el option?
➡ 判断new Vue({})
的参数对象中是否有el
选项 - 检查要挂到那里;el
选项 :- 决定Vue实例 编译好模板 以后,要将编译后的 标签结构挂载到哪里;
- NO:
- 脚手架生成的代码没有
el
选项; - new出来的Vue实例调用
$mount()
方法(脚手架生成的代码就是调用$mount()
方法);
- 脚手架生成的代码没有
- YES:
- 继续检查
template
选项;
- 继续检查
Has "template" option?
=> 判断参数对象中是否有template
选项;- YES - 编译
template
返回render
函数:- 插入到 el 或 $mount() 指定的位置;
- NO:
- 编译
el
的外部HTML内容(自己本身+内部的标签)作为模板; - 用
App.vue
的根标签及其子元素把public/index.html
中<div id="app"></div>
整体给替换掉(真实DOM展示的就是App.vue
的所有标签);
- 编译
- YES - 编译
- 虚拟DOM 挂载成 真实DOM 之前;
- ❌
beforeMount
=> 把App.vue
的所有标签(插值,指令等)编译完毕之后,开始执行beforeMount()
钩子函数,此时,template
中的标签知识编译完毕,但还没有变成真实DOM,也就是说,此时我们无法获取真实DOM(这个钩子是个废物); Crate vm.$el ...
- 把 虚拟DOM 和 渲染的数据 一并挂到 真实DOM 上;- 用
App.vue
中的根标签及其子元素替换掉<div id="app"></div>
盒子,此时,我们在template
中写的标签(虚拟DOM)才会变成真实DOM,挂载到DOM树上;
- 用
- 真实DOM挂载完毕;
- ✅
mounted
- 虚拟DOM变成真实DOM 之后,才执行该函数,因此此时可以获取任何一个真实DOM ,进而可以去操作DOM;- 虚拟DOM变成真实DOM之后,才执行mounted钩子,此时可以获取真实DOM;
- 使用场景:获取真实DOM,进而通过Web API进行操作;
- 示例展示:
- 小结:
- Vue实例 从 创建 到 显示 都经历了哪些 钩子函数 ?
beforeCrate、created、beforeMount、mounted
;
created
函数里,能获取真实DOM吗?- 不能获取真实DOM(模板都还没编译);
- 在什么钩子函数里面可以获取真实DOM?
mounted
钩子中可以获取真实DOM;- 挂载之前 的是 虚拟DOM ,挂载之后 的是 真实DOM;
- Vue实例 从 创建 到 显示 都经历了哪些 钩子函数 ?
3.5 更新阶段
- 前提 :
- 更新阶段的前提是 随着 data数据 的 变化 而 执行的;
- 如果数据不变 ,这个阶段的钩子是不会执行的 ,只要数据变化 了,每次都会执行该钩子函数;
- 当
data
里数据改变,更新DOM之前; - ❌
beforeUpdate
➡ 生命周期钩子函数被执行;- 数据变了之后,紧接着执行
beforeUpdate()
钩子,此时能 获取到 最新 的 数据 ,但是DOM还没更新,此时获取 不到 最新的 DOM内容(这个钩子基本上是个废物);
- 数据变了之后,紧接着执行
Virtual DOM......
➡ 虚拟DOM重新渲染,打补丁到真实DOM(diff算法的比对);- 当数据变了,Vue内部要开始diff算法对比,找出变化的地方,进行打补丁,重新渲染;
- ❌
updated
➡ 生命周期钩子函数被执行;- 当组件重新渲染完毕之后,开始执行
updated()
钩子,此时可以获取最新的DOM内容;
- 当组件重新渲染完毕之后,开始执行
- 当有
data
数据改变 ➡ 重复这个循环; - ❗ 注意 :
- 程序刚开始运行,不执行这两个钩子函数;
- 只有当数据改变的时候,才会执行;
- 示例展示:
- 小结 :
- 什么时候执行
updated
钩子函数?- 当数据发生变化并更新页面后;
- 在哪可以获取更新后的DOM?
- 在
updated
钩子函数里面;
- 在
- 什么时候执行
3.6 销毁阶段
- 当组件实例的
$destroy()
被调用,就会触发组件的销毁(除了调用$destroyed()
来销毁组件,还可以通过v-if
来 销毁组件); beforeDestroy
➡ 生命周期钩子函数被执行;- 拆卸当前组件的所有侦听器 、所有组件的事件监听器(把当前组件占用的资源释放掉,做内存回收工作);
- 内存回收处理完毕后,此时执行最后一个钩子函数(组件的销毁后钩子);
destroyed
➡ 生命周期钩子函数被执行(Vue实例生命周期结束);- 示例展示:
- 小结 :
- 一般在
beforeDestory、destoryed
里做什么?- 手动 消除计时器、定时器、解绑事件;
- 生命周期中常用的钩子函数:
- 一般在
四、axios的使用
4.1 axios 基本介绍
- 特点 :
- 支持客户端发起Ajax请求;
- 支持服务端Node.js发起请求;
- 支持Promise相关用法;
- 支持请求和响应的拦截器功能;
- 自动转换JSON数据;
- Ajax如何给后台传参?
- 在url拼接 ==> 查询字符串;
- 在url路径 上 ==> 需要后端处理;
- 在请求体 / 请求头 ==> 传参给后台;
4.2 axios 基本使用
-
这里只做基本展示,想要完整的
axios
配置对象,大家可以去官网看看Axios官网 - 请求配置; -
前置工作:
js// 下载 axios yarn add axios -S // 导入axios import axios from 'axios'
4.2.1 GET请求
-
语法:
js// 默认就是 GET 请求,可以不写 // 请求方式的 大小写 都可以 axios({ method: 'GET', url: '请求地址', params: { // 要携带的参数 } }) .then(res => console.log(res)) // 得到请求成功的数据 .catch(err => console.log(err)) // 捕获错误 ----------------------------------------------- async get() { const res = await axios({ method: 'GET', url: '请求地址', params: { // 要携带的参数 } }) console.log(res.data) }
4.2.2 POST请求
-
语法:
jsaxios({ method: 'POST', url: '请求地址', data: { // 要传递的参数 } }) .then(res => console(res)) .catch(err => console.log(err)) ----------------------------------- async post() { const res = await axios({ method: 'POST', url: '请求地址', data: { // 要写带的数据 } }) console.log(res.data) }
4.3 axios 小结
axios
返回的是一个Promise
实例;- 可以使用
async
和await
简化Promise
操作; - 只要函数里面使用了
await
,则函数就必须被async
修饰;- 是最近一级的函数使用
async
;
- 是最近一级的函数使用
- 发起
GET
请求,使用params
配置项 传递参数 ;- 参数是拼接在 url地址 上面;
- 发起
POST
请求,使用data
配置项 传递参数 ;- 参数在 请求体 中;
- axios 默认发给 后台 请求体数据格式 是 JSON字符串 ;
- 除了
JSON
格式外,还有FormData
格式; - 关于
FormData
大家可以参考我的另一篇文章前端开发小技巧 - 【JavaScript】 - FormData的使用 + 将QS插件的请求改写成普通的请求
- 除了
五、$ref
和 $nextTick
5.1 获取DOM / 组件对象
5.1.1 获取DOM
-
通过 原生JS获取DOM的方法 或
ref属性
获取 原生DOM; -
在
mounted
生命周期中 ➡ 有两种方式获取DOM:-
目标标签 ➡ 添加id、class、...... 或 ref属性:
html<标签 ref="" id="" class=""></标签>
-
恰当时机,通过id、class 或 ref属性 获取目标元素:
js// 挂载后(虚拟DOM变成真实DOM) mounted() { // 只要是原生JS获取DOM元素的方法都可以获取 // document.querySelect/document.querySelectAll...... console.log(document.getElementById('id名称')) console.log(this.$refs.ref属性值) }
-
-
注意 :
- ref属性 不展示 在 标签上;
- 可以在Vue调式面板()看到:
-
示例展示:
html<template> <div> <!-- 1.给要获取的标签添加ref属性,并设置相应的属性值 --> <h4 ref="diJia">迪迦奥特曼</h4> </div> </template> <script> export default { // 2.在恰当的实际,获取DOM元素 mounted() { // 方法一:Web API方法 // 方法二:this.$refs.ref属性值 console.log(this.$refs.diJia); this.$refs.diJia.style.color = "red"; }, }; </script>
5.1.2 获取组件对象
- 实现步骤 :
*- 在 组件标签 上 添加 ref属性;
-
- 在恰当时机(挂载后:
mounted
),获取组件对象;
- 恰当时机 ➡ 虚拟DOM挂载到DOM树上,既虚拟DOM变成真实DOM (
mounted
); - 使用
this.$refs.ref名称
;
- 在恰当时机(挂载后:
- 总结 :
- 作用 :
- 既可以获取原生DOM,也可以获取组件对象;
- 使用步骤 (2步):
- 给需要获取的原生DOM或组件标签添加ref属性,并设置属性值;
- 在恰当时机(挂在后:mounted):同故宫
this.$refs.ref属性值
获取;
- 获取到的组件对象就是该组件中的this;
- 作用 :
5.2 Vue - 异步更新DOM
- Vue更新DOM 是 异步 的、并且是 批量的 ;
- 这样做的优点 :
- 减少 重绘 和 回流;
- 这样做的优点 :
- Vue 监测 数据更新 ,开启一个DOM更新队列(异步任务);
- 示例展示:
5.3 $nextTick
-
等 DOM更新后 ,触发 此方法里的 函数执行;
-
语法:
jsthis.$nextTick(回调函数)
-
返回值 :
$nextTick()
返回的是一个Promise
,可以配合 async 和 await 使用;
-
示例展示:
-
代码展示:
html<template> <div> <h4>4.$nextTick应用场景</h4> <button @click="search" v-if="flag">点击搜索</button> <input ref="input" type="text" placeholder="请输入关键字" v-else /> </div> </template> <script> export default { data() { return { flag: true, }; }, methods: { async search() { this.flag = false; console.log(this.$refs.input); // undefined // undefined 的原因 // data数据变化,需要更新DOM,Vue更新DOM是异步操作 // data数据更新完后,input还没有变成真实DOM // 等待DOM更新完毕后,再去执行this.$nextTick回调函数的函数体 this.$nextTick(() => { console.log(this.$refs.input); // input标签 this.$refs.input.focus(); }); // $nextTick原地返回一个Promise实例,可以async和await简化Promise操作 // 这个Promise实例里面保存着异步操作的结果 // await可以得到这个异步操作的结果 // 也就是说,程序会停止到这一行,直到await等待到这个异步操作结果,才会去执行下面的代码 await this.$nextTick(); this.$refs.input.focus(); }, }, }; </script>