本文为 Vue 系列笔记第三篇。参考:>> 黑马程序员 Vue 全套视频教程
系列文章阅读 📑📑
>> Vue「一」—— 前端工程化 、webpack 的基本使用及常用配置
>> Vue「二」—— vue 基本使用 、vue 指令 、vue 过滤器
>> Vue「三」—— vue 侦听器、vue 计算属性、vue-cli、vue 组件
🐾 文章内容预览
一、watch 侦听器
1. 侦听器基本语法
watch 侦听器 允许开发者监视数据的变化,从而针对数据的变化做特定的操作。也就是 当数据发生变化时,及时做出响应处理 。
下面是采取方法格式来定义侦听器,这种方法比较简便常用。此外,如果遇到某些特殊需求时,也可采取对象格式来定义侦听器(后文介绍)。
<div id="app">
<input type="text" v-model="username">
</div>
<script>
const vm = new Vue({
el: "#app",
data: {
username: ''
},
// 侦听器定义在 watch 节点下
watch: {
username(newVal, oldVal) {
console.log('👍👍👍', newVal, oldVal);
}
}
})
</script>
文本框中连续输入 1
、2
、3
,打印效果如下:
>> 几个注意点
- 所有的侦听器,都应该定义在 watch 节点下。
- 侦听器本质上是一个函数,要监听哪个数据的变化,只需把对应数据名作为方法名即可。
- 注意新值在前面,旧值在后面。
- 此外,这种方法默认不会被自动调用,也就是刚打开页面时不会触发侦听器。
>> 应用场景:使用 watch 检测用户名是否可用
需求:监听 username 值的变化,并使用 axios 发起 Ajax 请求,从服务器获取数据来判断,检测当前输入的用户名是否可用
import axios from 'axios'
export default {
name: 'MyWatch',
data() {
return {
username: ''
}
},
watch: {
async username(newVal, oldVal) {
const { data: res } = await axios.get(`https://www.escook.cn/api/finduser/${newVal}`)
console.log(res)
}
}
}
2. 对象格式的侦听器
此前侦听器的写法是方法格式的写法,也就是直接将对应数据名作为方法名来写函数。这种方法是最常用的,因为它比较简便。
你也可以利用对象格式来定义 watch 侦听器,如下:
watch: {
username: {
// handler 是固定写法,表示当 username 的值变化时,自动调用 handler 处理函数
handler(newVal, oldVal) {
// 。。。
}
}
}
这种语法下,需要将侦听器的响应处理写在 handler(){}
方法内。
3. immediate 选项
默认情况下,组件在初次加载完毕后不会调用 watch 侦听器。如果想让 watch 侦听器 立即被调用,则需要使用 immediate 选项 。
watch: {
username: {
handler(newVal, oldVal) {
console.log(newVal);
},
// 默认 false, 控制侦听器是否自动触发
imediate: true
}
}
4. deep 选项
如果 watch 侦听的是一个对象,如果对象中的 属性值发生了变化,则 无法被监听 到。此时需要使用 deep 选项 。
watch: {
info: {
handler(newVal, oldVal) {
console.log(newVal.username);
},
// 开启深度监听,只要对象中任何属性变化,都会触发
// deep 默认值为 false
deep: true
}
}
注意:这里的 newVal 是 info 对象,而不是变化的属性值。
5. 监听对象单个属性的变化
如果只想监听对象中 单个属性 的变化,则可以按照如下的方式定义 watch 侦听器。
watch: {
'info.username': {
handler(newVal, oldVal) {
console.log(newVal);
}
}
}
二、计算属性
计算属性指的是通过一系列运算之后,最终得到一个属性值。这个动态计算出来的属性值可以被模板结构或 methods 方法使用。
所有的计算属性都要定义在 computed 节点之下,计算属性在定义时要定义成方法格式。如下所示:
<!-- <div class="box" :style="{ backgroundColor: `rgb(${r}, ${g}, ${b})` }">
{{ `rgb(${r}, ${g}, ${b})` }}
</div> -->
<div class="box" :style="{ backgroundColor: rgb }">
{{ rgb }}
</div>
var vm = new Vue({
el: '#app',
data: {
r: 0, g: 0, b: 0
},
computed: {
rgb() {
return `rgb(${this.r},${this.g},${this.b})`;
}
}
});
两点好处:
- 计算属性提高了代码的复用。
- 当所依赖数据发生变化(data 中数据变化)时,它会自动重新运算。
三、vue-cli
1. 单页面应用程序
单页面应用程序 (Single Page Application)简称 SPA,指的是一个 Web 网站只有唯一的一个 HTML 页面,所有的功能与交互都在这唯一的一个页面内完成。
如上图所示,整个项目都是通过唯一一个 HTML 页面 index.html 来呈现的,这就叫做单页面应用程序。
2. 什么是 vue-cli
vue-cli 是 Vue.js 开发的标准工具。它简化了程序员基于 webpack 创建工程化 Vue 项目的过程。
vue-cli 官网 是这样介绍的:Vue CLI 致力于将 Vue 生态中的工具基础标准化。它确保了各种构建工具能够基于智能的默认配置即可平稳衔接,这样你可以专注在撰写应用上,而不必花好几天去纠结配置的问题。
3. vue-cli 的安装
vue-cli 是 npm 上的一个 全局包,可以通过 npm 进行安装:
npm install -g @vue/cli
4. vue-cli 的使用
- 在终端下运行如下的命令,创建指定名称的项目
vue create 项目名称
- 手动选择要安装的模式,建议初学时选择第三项,可以自定义很多功能。
- 手动选择要安装哪些功能,建议如下配置(空格:选中/ 取消):
- 选择 vue 的版本,目前先选择 2.x。
- 选择 css 预处理器,目前只学过 Less,因此选择 Less 即可。
- 选择第三方插件配置文件如何放置,默认选择第一项,各种文件配置信息独立放置。
- 是否将此前是选择作为预设,方便以后不用每次都重复同样配置。
- 项目成功创建完成。
>> 分析一下 vue 项目下的 src 目录的构成
assets 文件夹
:存放项目中用到的静态资源文件,例如:css 样式表、图片资源。
components 文件夹
:存放封装的、可复用的组件。
main.js
:项目的入口文件。整个项目的运行,要先执行 main.js。
App.vue
: 项目的根组件。
5. vue 项目的运行流程
在工程化的项目中,vue 要做的事情很单纯:通过 main.js 把 App.vue 渲染到 index.html 的指定区域中。
- App.vue 用来编写待渲染的 模板结构
- index.html 中需要预留一个 el 区域
- main.js 把 App.vue 渲染到了 index.html 所预留的区域
四、vue 组件
1. vue 中的组件化开发
组件化开发 指的是:根据封装的思想,把页面上 可重用的 UI 结构 封装为组件,从而方便项目的开发和维护。
vue 是一个 支持组件化开发 的前端框架。vue 中规定:组件的后缀名是 .vue。之前接触到的 App.vue 文件本质上就是一个 vue 的组件。
2. vue 组件的三个组成部分
每个 .vue 组件都由 3 部分构成,分别是:
template
:组件的模板结构script
:组件的 JavaScript 行为style
:组件的样式
>> template
vue 规定:每个组件对应的 模板结构,需要定义到 <template>
节点中。如下:
<!--组件的 UI 结构模板-->
<template>
<!--此处定义当前组件的 DOM 结构-->
</template>
❗❗ 注意:
template
是 vue 提供的容器标签,只起到 包裹 性质的作用,它不会被渲染为真正的 DOM 元素template
中只能包含 唯一的根节点
>> script
vue 规定:开发者可以在 <script>
节点中封装组件的 JavaScript 业务逻辑 。如下:
<script>
// 默认导出,固定写法
export default {
data() {
return {
// return 中定义数据
};
},
// 组件中的方法
methods: {},
// 组件中的侦听器
watch: {},
// 组件中的计算属性
computed: {},
// 组件中的过滤器
filters: {},
};
</script>
❗❗ 注意:
- 组件相关的 data、methods 等等都需要放在
export default {}
所导出的对象中。 - .vue 组件中的 data 必须是一个函数,不能直接指向一个数据对象。否则会导致多个组件实例共用同一份数据的问题。
>> style
组件内的 <style>
节点是可选的,开发者可以在 <style>
节点中 编写样式 美化当前组件的 UI 结构。如下:
<!--组件的样式-->
<style>
/* 定义样式 */
</style>
>> style 中支持 less 语法,可以这么做:
在 <style>
标签上添加 lang="less"
属性,即可使用 less 语法编写组件的样式:
<style lang="less">
/* 定义 less 语法的样式 */
</style>
3. 组件之间的父子关系
>> 使用组件的三个步骤
在 App.vue 文件中进行如下操作,即可将 Left 和 Right 组件嵌套在 App 组件中:
- 使用 import 语法导入需要的组件
- 使用 components 节点 注册组件
- 以 标签形式 使用刚才注册的组件
其中,各组件关系如下:
4. 私有子组件与全局组件
>> 通过 components 注册的是 私有子组件
例如,在组件 A 的 components 节点下,注册了组件 F。则组件 F 只能用在组件 A 中;不能被用在组件 C 中。
可以看出,如果有一个组件频繁的被用到,就变得很麻烦,需要每次引入。这时,我们可以使用下面所要介绍的 全局组件 。
>> 注册 全局组件
在 vue 项目的 main.js 入口文件中,通过 Vue.component()
方法,可以注册全局组件。这样以后每次使用时,不再需要重复注册,一劳永逸。
例如,这里打算将 Count.vue 设为全局组件,可以在 main.js 入口文件中这样设置:
此后,在其他组件中使用到这个全局组件 Count.vue 时,只需写标签 <MyCount></MyCount>
即可。
5. 组件的 props
props 是组件的 自定义属性,在封装通用组件的时候,合理地使用 props 可以极大的提高组件的复用性。
在 Count.vue 组件中设置自定义属性 init
:
export default {
// props 是"自定义属性",允许使用者通过自定义属性,为当前组件指定初始值
// 自定义属性可以定义多个,可以取任意合法名称
props: ['init'],
data() {
return {
// 数据
}
},
}
当使用到 MyCount 组件(上面的 Count.vue)时,就可以这样定义自定义属性:
<MyCount init="6"></MyCount>
❗❗ 注意:上面的 init
被赋值了一个字符串 "6"
,而不是数字 6
。这时,如果你想要的赋值是数字型,可以结合 v-bind
来使用,写成如下形式:
<MyCount :init="6"></MyCount>
>> 此外,再强调几点 props 使用事项
1. props 是只读的
vue 规定:组件中封装的 自定义属性是只读的,程序员不能直接修改 props 的值,否则会直接报错。
<button @click="init += 1">+1</button>
如上代码,点击按钮使得自定义属性 init
完成自增时,发现网页给出报错:
要想修改 props 的值,可以 把 props 的值转存到 data 中,因为 data 中的数据都是可读可写的。
data() {
return {
// 把 props 中的 init 值,转存到 count 上
count: this.init,
};
},
2. props 的其他可选项
在声明自定义属性时,还可以将其指定为 对象形式 而非数组形式,同时指定如下可选项:
- default:定义属性的默认值
- type:定义属性的值类型
- required:将属性设置为必填项,强制用户必须传递属性的值
props: {
init: {
// 如果外界使用 Count 组件的时候,没有传递 init 属性,则默认值生效
default: 0,
// init 的值类型必须是 Number 数字
type: Number,
// 必填项校验
required: true
}
},
6. 组件之间的样式冲突问题
默认情况下,写在 .vue 组件中的 样式会全局生效,因此很容易造成多个组件之间的样式冲突问题。
导致组件之间样式冲突的根本原因是:
- 单页面应用程序中,所有组件的 DOM 结构,都是基于唯一的 index.html 页面进行呈现的
- 每个组件中的样式,都会影响整个 index.html 页面中的 DOM 元素
>> 如何解决样式冲突问题呢?
为了提高开发效率和开发体验,vue 为 style 节点 提供了 scoped 属性,从而防止组件之间的样式冲突问题。
<style lang="less" scoped>
h3 {
color: red;
}
</style>
而它的原理就是,scoped 属性自动为每个组件分配了唯一的 “ 自定义属性 ”。该组件中的 DOM 标签和 style 样式都应用了这个自定义属性,这样就解决了样式冲突的问题。
>> /deep/ 样式穿透
如果给当前组件的 style 节点添加了 scoped 属性,则当前组件的样式 对其子组件是不生效的。如果想让某些样式对子组件生效,可以使用 /deep/ 深度选择器。
<style>
/* 当使用第三方组件库的时候,如果有修改第三方组件默认样式的需求,需要用到 /deep/ */
/deep/ h5 {
color: pink;
}
</style>
👍👍 应用场景:通过父组件直接修改子组件样式。一般在引入第三方库时,需要修改第三方库中元素样式时使用,从而避免了查找第三方库源码修改的困难。