环境准备
#1 准备 Node 环境
由于大部分 Vue 项目都是依赖于 Node 环境. 在开始之前, 我们需要先准备好 Node 环境
访问Node 中文官网 (opens new window)下载最新版的Node.js
找到下载的 node-v12.16.1-x64.msi, 双击安装
一路Next
, 到安装完成.
安装完成后, 按win+R
键打开 cmd 命令窗口
在 windows 命令行中, 使用如下命令测试
# 查看node的版本
node -v
# 查看npm的版本
npm -v
node 是一个 js 的运行环境, 这里我们安装 node 主要是需要其中的 npm 包管理工具
这里, 我的电脑上 node 的版本不是最新的, 是10.16.3
的版本.
如果大家按下载的版本安装, 应该是更新的版本
#2 配置 npm 镜像
npm 默认的仓库地址是在国外网站,速度较慢,建议大家设置到淘宝镜像。但是切换镜像是比较麻烦的。推荐一款切换镜像的工具:nrm
我们首先安装 nrm,这里-g
代表全局安装
npm install nrm -g
全局安装后, 会在如下目录, 产生如下文件
然后, 我们就可以使用 nrm 命令了
通过nrm ls
命令查看 npm 的仓库列表,带*的就是当前选中的镜像仓库:
通过nrm use taobao
来指定要使用的镜像源:
注意
有教程推荐大家使用 cnpm 命令,但是我使用发现 cnpm 有时会有 bug,不推荐
#3 创建项目
在 D 盘, 新建一个文件夹 ==code== . 打开, 按住shift+鼠标右键
, 打开 powerShell 窗口
注意
路径中不要出现中文
#4 初始化
使用如下指令初始化
npm init -y
发现在目录下会多一个文件 package.json, 这个文件用来管理该项目使用了哪些包
#5 安装 vue
执行如下命令安装 vue
npm install vue --save
以上命令可以简写为
npm i vue
在项目目录会产生一个文件夹 node_modules 和一个文件 package-lock.json
在 package.json 文件中, 会多如下内容
在 node_modules 里就是 vue 了
#6 小结
总结
使用 Npm 安装 Vue 分为两步
- 项目初始化:
npm init -y
- 安装 vue:
npm install vue
=====================
起步案例
做为第一个案例, 主要给大家介绍 vue 的最基本使用.
vue 使用的 3 步曲
- 引入 vue.js
- 编写页面(视图)
- 编写 vue 实例
#1 引入 vue.js
在 html 的头部, 通过<script src>
引入 vue.js
这里我用到了 VSCode 插件: Path Intellisense 快速补全路径
示例
<!-- 1. 引入vue.js -->
<script src="../node_modules/vue/dist/vue.js"></script>
演示
#2 编写页面(视图)
在 boby 中, 编写一个div
元素, id 为 app, 所有视图部分将在这部分渲染
示例
<!-- 2. 编写页面 -->
<div id="app"></div>
演示
#3 编写 vue 实例
在 body 的最末尾, 使用<script>
, 编写 vue 实例
示例
<!-- 3. 编写vue实例 -->
<script>
const vm = new Vue({
el: '#app', // 对应操作的元素, 必填参数
data: {}, // 数据部分
methods: {}, // 方法部分
})
</script>
- el(element 元素): 表示 vue 实例操作页面元素
- data(数据): 数据部分
- methods(方法): 方法部分
演示
#4 渲染数据
需求
将数据渲染到页面中
#1) 定义数据
在 data 中定义数据
示例
<!-- 3. 编写vue实例 -->
<script>
const vm = new Vue({
el: '#app', // 对应操作区的元素, 必填参数
data: {
msg: 'hello world',
}, // 数据部分
methods: {}, // 方法部分
})
</script>
使用键值对方式定义数据. 其中
- msg: 相当于变量名
- 'hello world': 相当于变量值
演示
#2) 在页面中渲染
示例
<div id="app">
{
{ msg }}
</div>
- 通过
{ {}}
(插值表达式)使用在 data 部分定义的变量
演示
======================================
MVVM模型
MVVM 由 Model(数据模型) View(视图) 和 VM(ViewModel) 组成
核心思想
实现 数据 与 视图 的 双向绑定
各自的作用
- Model(数据模型): 操作数据
- View(视图): 显示数据
- VM: 模型与视图间的双向操作
在 MVVM 之前, 开发人员需要从后端获取数据, 然后通过 DOM 操作 Model 渲染到 View 中.
当用户操作了视图, 再通过 DOM 获取 View 中的数据, 同步到 Model 中
而 VM 的作用就是把 DOM 操作完全的封装起来, 开发人员不用再关心 Model 和 View 之间如何影响
- 只要 Model 发生改变, View 就可以自然的表现出来
- 只要用户操作了 View, Model 中的数据也会跟着变化
案例
实现如下效果, 当在输入框中输入字符时, 同步显示到页面中
#1 完成 Vue 的三步曲
在 src 目录下创建一个新的文件: 02_MVVM模型.html
#1) 引入 vue.js
示例
<!-- 1. 引入vue.js -->
<script src="../node_modules/vue/dist/vue.js"></script>
演示
#2) 编写 div 元素
示例
<!-- 2. 编写div元素 -->
<div id="app"></div>
演示
#3) 编写 vue 实例
示例
<!-- 3. 编写vue实例 -->
<script>
const vm = new Vue({
el: '#app',
data: {},
})
</script>
演示
#2 编写页面
分析
页面由文字, input 框, 内容显示三部分组成
示例
<!-- 2. vue的主操作区 -->
<div id="app">
在这里输入的内容会在下面显示: <input v-model="msg" type="text">
<h3>{
{ msg }}</h3>
</div>
- v-model 表示双向绑定, 将输入框中的内容和 vue 实例中的 msg 属性绑定
{ {}}
是插值表达式, 用来显示 msg 的数据
#3 编写逻辑
这里的逻辑比较简单, 只需要在 data 部分, 添加一个msg
变量就可以
示例
<script>
const vm = new Vue({
el: '#app',
data: {
msg: '',
},
})
</script>
#4 测试
使用 Open In Default Browser 测试(使用到的插件: open in browser)
#5 调试工具
为了方便调试 vue, 可以在 chrome 浏览器中安装 vue-devtools.
#6 小结
小结
- vue 使用的三步曲(引入 vue.js div 元素 vue 实例)
- data 中的内容就是 M(模型), div 中的内容是 V(视图), vue 实例是 VM
============================
Vue实例
#1 属性和方法
Vue 应用的核心就是 Vue 实例 , 一个基本的 Vue 实例包括如下图所示:
new Vue({
el: '#app',
data: {},
methods: {},
})
- el: 表示要操作的页面元素
- data: 数据, 可以理解为面向对象中类的"属性"
- methods: 方法, 可以理解为面向对象中类的"方法"
属性
当 Vue 实例创建时, 会获取 data 中的数据, 并渲染到视图中, 并监视 data 中数据的变化, 当 data 中的数据改变时, 重新渲染视图
方法
方法就是: 响应视图中的事件, 并修改 data 里的数据
重要结论
- 属性就是用来 保存 数据的
- 方法就是用来 修改 数据的
案例
实现一个简单的加法器
两个输入框,填写两个数字, 点击=号时, 计算结果
#1) 完成 Vue 的三步曲
在 src 目录下创建03_加法器.html
示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>03_加法器</title>
<!-- 1. 引入vue.js -->
<script src="../node_modules/vue/dist/vue.js"></script>
</head>
<body>
<!-- 2. 编写div元素 -->
<div id="app"></div>
<!-- 3. 编写vue实例 -->
<script>
const vm = new Vue({
el: '#app',
})
</script>
</body>
</html>
#2) 编写页面
分析
页面由两个 input 框和一个按钮, 再加一个 input 框组成
示例
<div id="app">
<input type="text" />
+
<input type="text" />
<button>=</button>
<input type="text" />
</div>
#3) 编写逻辑
#i 绑定属性
html 部分
<div id="app">
<input v-model="first" type="text" />
+
<input v-model="second" type="text" />
<button>=</button>
<input v-model="result" type="text" />
</div>
使用 v-model 绑定属性
js 部分
const vm = new Vue({
el: '#app', // 元素
data: {
first: 0,
second: 0,
result: 0,
}, // 属性
methods: {}, // 方法
})
#ii 绑定方法
html 部分
<button v-on:click="add">=</button>
使用 v-on 绑定 click 方法
js 部分
methods: {
add() {
this.result = parseInt(this.first) + parseInt(this.second)
}
} // 方法
4) 小结
小结
- data: 保存 数据
- methods: 改变 数据
=========================
模板语法
模板语法主要用于视图的编写,以v-
指令. 常见的有
- 属性绑定: v-bind, 简写为
:
- 方法绑定: v-on, 简写为
@
- 双向绑定: v-model
- 条件渲染: v-if
- 列表渲染: v-for
#1 属性绑定和双向绑定
在 src 目录下, 创建04_属性绑定.html
, 完成 vue 的 3 步曲
示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>04_属性绑定</title>
<!-- 1. 引入vue.js -->
<script src="../node_modules/vue/dist/vue.js"></script>
</head>
<body>
<!-- 2. 编写div元素 -->
<div id="app"></div>
<!-- 3. 编写vue实例 -->
<script>
const vm = new Vue({
el: '#app',
})
</script>
</body>
</html>
属性绑定
将 非表单标签 的一个属性和 data 中的一个变量绑定
html 部分
<a :href="url"></a>
js 部分
const vm = new Vue({
el: '#app',
data: {
url: 'http://baidu.com',
},
})
- 将 a 标签
href
属性和 data 中的 url 变量绑定 - 相当于
<a href="http://baidu.com"></a>
双向绑定
将 表单标签 的 value 值和 data 中的变量双向绑定
html 部分
<input type="text" v-model="uName" />
js 部分
const vm = new Vue({
el: '#app',
data: {
uName: 'xiaoming',
},
})
1
2
3
4
5
6
- 将 input 标签的 value 属性和 data 中的 uName 变量绑定
- 相当于
<input type="text" value="xiaoming" />
#2 条件渲染
条件渲染
当条件满足时, 渲染到页面
主要指令: v-if
和v-show
#1) 完成 vue 的 3 步曲
在 src 目录下, 创建05_条件渲染.html
, 编写 vue 的基础模板
示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>05_条件渲染</title>
<!-- 1. 引入vue.js -->
<script src="../node_modules/vue/dist/vue.js"></script>
</head>
<body>
<!-- 2. 编写div元素 -->
<div id="app"></div>
<!-- 3. 编写vue实例 -->
<script>
const vm = new Vue({
el: '#app',
})
</script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#2) 编写页面与逻辑
示例
html 部分
<!-- 2. 编写div元素 -->
<div id="app">
<div v-if="flag">这是用v-if渲染的元素</div>
<div v-show="flag">这是用v-show渲染的元素</div>
</div>
1
2
3
4
5
js 部分
<script>
const vm = new Vue({
el: '#app',
data: {
flag: true,
},
})
</script>
1
2
3
4
5
6
7
8
- 当 flag 为 true 时, 两个元素都可以显示
- 当 flag 为 false 时, 两个元素都不显示, 区别是
- v-if: 不会创建元素
- v-show: 创建元素, 但是 display=none
#3) 表达式
除了使用变量外, 还可以使用表达式
比如
<div v-if="flag == true">这是用v-if渲染的元素</div>
1
案例
通过按钮控制元素的显示/隐藏
示例
html 部分
<div id="app">
<button @click="flag = !flag">切换</button>
<div v-if="flag">这是用v-if渲染的元素</div>
<div v-show="flag">这是用v-show渲染的元素</div>
</div>
1
2
3
4
5
- 绑定按钮的点击事件
- 当 flag==true 时点击, flag 先取反, 再保存, 此时 flag 为 false
- 当 flag==false 时点击, flag 先取反, 再保存, 此时 flag 为 true
#3 列表渲染
列表渲染也称"循环渲染", 通过v-for
遍历数组或者对象
#1) 遍历数组
如果只希望得到每个数组元素的值, 不需要得到下标
语法
v-for="item in items"
1
示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>06_列表渲染</title>
<!-- 1. 引入vue.js -->
<script src="../node_modules/vue/dist/vue.js"></script>
</head>
<body>
<!-- 2. 编写div元素 -->
<div id="app">
<ul>
<li v-for="item in items">{
{ item }}</li>
</ul>
</div>
<!-- 3. 编写vue实例 -->
<script>
const vm = new Vue({
el: '#app',
data: {
items: ['test1', 'test2', 'test3'],
},
})
</script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
如果只希望得到每个数组元素的值和下标
语法
v-for="(item, index) in items"
1
示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>06_列表渲染</title>
<!-- 1. 引入vue.js -->
<script src="../node_modules/vue/dist/vue.js"></script>
</head>
<body>
<!-- 2. 编写div元素 -->
<div id="app">
<ul>
<li v-for="(item, index) in items">{
{ index }}--{
{ item }}</li>
</ul>
</div>
<!-- 3. 编写vue实例 -->
<script>
const vm = new Vue({
el: '#app',
data: {
items: ['test1', 'test2', 'test3'],
},
})
</script>
</body>
</html>
#2) 遍历对象
- 只取值:
v-for="value in obj"
- 取键和值:
v-for="(value, key) in obj"
- 取键和值和索引:
v-for="(value, key, index) in obj"
===========================
计算属性与侦听器
#1 计算属性
计算属性用于对原始数据的再次加工
需求
实现如下效果
#1) 完成 Vue 的三步曲
在 src 目录下创建07_计算属性.html
, 编写基础的 vue 模板
示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>07_计算属性</title>
<!-- 1. 引入vue.js -->
<script src="../node_modules/vue/dist/vue.js"></script>
</head>
<body>
<!-- 2. 编写div元素 -->
<div id="app"></div>
<!-- 3. 编写vue实例 -->
<script>
const vm = new Vue({
el: '#app',
})
</script>
</body>
</html>
#2) 使用传统方式
分析
页面由一个输入框和一个提示组成
示例
html 部分
<!-- 2. 编写div元素 -->
<div id="app">
输入金额:
<input type="text" v-model="total" />
<h3>您一共消费了{
{ total }}元</h3>
</div>
js 部分
<!-- 3. 编写vue实例 -->
<script>
const vm = new Vue({
el: '#app',
data: {
total: 0,
},
})
</script>
#3) 使用计算属性
示例
html 部分
<!-- 2. 编写div元素 -->
<div id="app">
输入金额:
<input type="text" v-model="total" />
<h3>{
{ msg }}</h3>
</div>
js 部分
<!-- 3. 编写vue实例 -->
<script>
const vm = new Vue({
el: '#app',
data: {
total: 0,
},
computed: {
msg() {
return `您一共消费了${this.total}元`
},
},
})
</script>
#4) 小结
举个极端的案例, 我们写了一万个页面, 您一共消费了元
这段代码就要写一万次
如果, 突然有一天, 需求改变了, 提示信息是: 您本次消费xx元
, 那么就要修改一万次
设计原则
视图业务无关性原则: 尽量将视图显示和业务处理解耦
#2 侦听器
侦听器用于侦听数据的改变, 进而自动触发相应的方法
需求
实现如下效果:
![05_watch](http://image.brojie.cn// images05_watch.gif)
#1) 完成 Vue 的三步曲
在 src 目录下创建08_侦听器.html
, 编写基础的 vue 模板
示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>08_侦听器</title>
<!-- 1. 引入vue.js -->
<script src="../node_modules/vue/dist/vue.js"></script>
</head>
<body>
<!-- 2. 编写div元素 -->
<div id="app"></div>
<!-- 3. 编写vue实例 -->
<script>
const vm = new Vue({
el: '#app',
})
</script>
</body>
</html>
#2) 使用侦听器实现
示例
html 部分
<!-- 2. 编写div元素 -->
<div id="app">
单价:10元
<br />
数量:
<input type="text" v-model="number" />
<h3>总价: {
{ total }}元</h3>
</div>
js 部分
<!-- 3. 编写vue实例 -->
<script>
const vm = new Vue({
el: '#app',
data: {
number: 0,
total: 0,
},
watch: {
number() {
this.total = 10 * this.number
},
},
})
</script>
watch: {
// 监听data中已经存在的变量, 在watch中写的函数跟变量是同名
// 是一对多, 当变量值发生改变时, 执行一系列的操作
number(newValue, oldValue) {
// number被改变, 就会执行对应watch函数
// 操作一: 计算total的值
this.total = this.number * 10
// 操作二: 可以记录旧的值, 新的值
console.log(newValue, oldValue)
},
#3) 使用计算属性实现
示例
html 部分
<!-- 2. 编写div元素 -->
<div id="app">
单价:10元
<br />
数量:
<input type="text" v-model="number" />
<h3>总价: {
{ total }}元</h3>
</div>
js 部分
<!-- 3. 编写vue实例 -->
<script>
const vm = new Vue({
el: '#app',
data: {
number: 0,
},
computed: {
total() {
return this.number * 10
},
},
})
</script
#3 总结
两者的区别:
- 计算属性用于对原始数据的再次加工, 计算属性是有缓存的. 相当于在 vue 实例中添加了一个新的属性, 可以同时依赖多个值, 只要其中依赖的一个值发生改变就会重新计算
- 侦听器用于侦听数据的改变, 进而自动触发相应的方法. 不会缓存, 每次重新计算, 可以获取新旧数据, 主要用于一个值的改变引起的一系列的操作
#4 过滤器
对数据进行格式化操作
#1) 完成 vue 的三步曲
<!DOCTYPE html>
<html lang="en">
<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" />
<title>Document</title>
<script src="../node_modules/vue/dist/vue.js"></script>
</head>
<body>
<div id="app"></div>
<script>
const vm = new Vue({
el: '#app',
})
</script>
</body>
</html>
#2) 定义过滤器
过滤器分为
- 全局过滤器
- 局部过滤器
全局过滤器
可以所有的 vue 实例中使用
局部过滤器
只能在某一个 vue 实例中使用
示例
<!DOCTYPE html>
<html lang="en">
<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" />
<title>Document</title>
<script src="../node_modules/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">{
{price | priceFormat}}</div>
<script>
const vm = new Vue({
el: '#app',
data: {
price: 88,
},
filters: {
// 格式化价格. 保留两位小数, 前面加¥
// 88 -> ¥88.00
priceFormat(value) {
return '¥' + value.toFixed(2)
},
},
})
</script>
</body>
</html>
作业
实现如下效果
=============================
组件
组件可以理解成项目的零件
一个 项目 就是由多个 组件 构成的
举例
一个房子是一个 Vue 应用, 那么客厅/卧室/厨房/卫生间就是组件
一个电脑是一个 Vue 应用, 那么硬盘/内存/主板/显示器/键盘就是组件
组件分为
- 全局组件
- 局部组件
#1 全局组件
顾名思义, 全局都可以使用的组件
#1) 完成 Vue 三步曲
在 src 目录下创建09_全局组件.html
, 编写基础的 vue 模板
示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>09_全局组件</title>
<!-- 1. 引入vue.js -->
<script src="../node_modules/vue/dist/vue.js"></script>
</head>
<body>
<!-- 2. 编写div元素 -->
<div id="app"></div>
<!-- 3. 编写vue实例 -->
<script>
const vm = new Vue({
el: '#app',
})
</script>
</body>
</html>
#2) 定义全局组件
语法
Vue.component('组件名', { 组件参数 })
示例
Vue.component('com1', {
template: '<button @click="count++">你点了我{
{count}}次</button>',
data() {
return {
count: 0,
}
},
})
- 组件没有 el 参数, 原因是组件不会和具体的页面元素绑定
- 组件必须有 template 参数, 原因是组件需要渲染页面, template 就是需要渲染的 html
- 组件也是一个 Vue 的实例, 所以在组件中也有 data/methods 等
- data 必须是一个函数, 并返回一个对象
#3) 引用组件
在 html 中, 通过组件名引用组件
<!-- 2. 编写div元素 -->
<div id="app">
<!-- 引用组件 -->
<com1></com1>
<com1></com1>
<com1></com1>
</div>
我们发现每个组件互不干扰,都有自己的 count 值。怎么实现的?
重点
组件中的 data 属性必须是函数!
当我们定义这个 <com1>
组件时,它的 data 并不是像这样直接提供一个对象:
data: {
count: 0
}
取而代之的是,一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝:
data: function () {
return {
count: 0
}
}
如果 Vue 没有这条规则,点击一个按钮就会影响到其它所有实例!
#4) 全局使用
创建一个新的 vue 实例 vm2
const vm2 = new Vue({
el: '#app2',
})
创建新的 div 元素 app2
html
<div id="app2">
<com1></com1>
</div>
发现在 app2 中, 也可以引用 com1 组件, 这样定义的就是全局组件, 所有的 vm 实例都可以引用
#5) 小结
全局组件的使用步骤
- 定义组件
- 引用组件
#2 局部组件
一般在单页面应用(SPA)中使用较多的是局部组件
注意
局部组件只属于某一个 Vue 实例, 通过 comopnents 添加(挂载)
- 通常将组件参数单独定义, 方便工程化时管理
- 通常将组件模板单独定义, 方便工程化时管理
#1) 完成 Vue 三步曲
在 src 目录下创建10_局部组件.html
, 编写基础的 vue 模板
示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>10_局部组件</title>
<!-- 1. 引入vue.js -->
<script src="../node_modules/vue/dist/vue.js"></script>
</head>
<body>
<!-- 2. 编写div元素 -->
<div id="app"></div>
<!-- 3. 编写vue实例 -->
<script>
const vm = new Vue({
el: '#app',
})
</script>
</body>
</html>
#2) 定义局部组件
模板部分
<!-- 组件模板 -->
<template id="tmp">
<button @click="count++">
你点了我{
{count}}次
</button>
</template>
js 部分
// 定义组件对象
const com1 = {
template: '#tmp', // 定义组件模板
data() {
// 定义属性
return {
count: 0,
}
},
}
#3) 挂载组件
在 vue 实例中挂载组件
const vm = new Vue({
el: '#app',
components: {
// 组件名: 组件对象
son: com1,
},
})
- 在 vue 实例中, 通过
components
完成挂载
#4) 引用组件
在 html 中, 通过组件名
引用组件
<!-- 2. 编写div元素 -->
<div id="app">
<!-- 引用组件 -->
<son></son>
</div>
#5) 小结
局部组件的使用步骤
- 定义组件模板
- 定义组件对象
- 在 vue 实例中挂载组件
- 引用组件
========================
组件通信
通常一个单页应用(SPA)会以一棵嵌套的组件树的形式来组织
页面首先分成了顶部导航、左侧内容区、右侧边栏三部分
- 左侧内容区又分为上下两个组件
- 右侧边栏中又包含了 3 个子组件
各个组件之间以嵌套的关系组合在一起,那么这个时候不可避免的会有组件间通信的需求
主要分为两种情况:
- 父向子 传递数据
- 子向父 传递数据
#1 父组件向子组件传值
#1) 完成 Vue 三步曲
在 src 目录下创建11_父向子传值.html
, 编写基础的 vue 模板
示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>11_父向子传值</title>
<!-- 1. 引入vue.js -->
<script src="../node_modules/vue/dist/vue.js"></script>
</head>
<body>
<!-- 2. 编写div元素 -->
<div id="app"></div>
<!-- 3. 编写vue实例 -->
<script>
const vm = new Vue({
el: '#app',
})
</script>
</body>
</html>
#2) 定义子组件
在 vue 对象中, 使用 components 定义一个局部组件
components: {
// 组件名: {组件参数}
son: {
template: '<h3></h3>',
}
}
#3) 引用子组件
在 html 中通过组件名, 通过组件名引用组件
<div id="app">
<son></son>
</div>
#4) 传值
父组件向子组件传递数据的步骤
-
修改 html, 在子组件标签中添加一个属性 f2s
<div id="app"> <!-- 1. 在son标签中, 添加一个属性 --> <son f2s="父组件给子组件的数据"></son> </div>
-
修改 js, 在子组件中使用 props 接收
components: { son: { template: '<h3></h3>', props: ['f2s'] } }
-
在子组件的模板中使用
components: { son: { template: '<h3>{ { f2s }}</h3>', props: ['f2s'] } }
#5) 使用属性绑定
也可以和父组件中的一个数据绑定起来使用
<div id="app">
<!-- 1. 在son标签中, 添加一个属性 -->
<son :f2s="msg"></son>
</div>
#6) 小结
父向子传值的步骤
- 在子组件标签中添加一个属性, 发送数据
- 在子组件中通过
props
, 接收数据
#2 子组件向父组件传值
#1) 完成 Vue 三步曲
在 src 目录下创建12_子向父传值.html
, 编写基础的 vue 模板
示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>12_子向父传值</title>
<!-- 1. 引入vue.js -->
<script src="../node_modules/vue/dist/vue.js"></script>
</head>
<body>
<!-- 2. 编写div元素 -->
<div id="app"></div>
<!-- 3. 编写vue实例 -->
<script>
const vm = new Vue({
el: '#app',
})
</script>
</body>
</html>
#2) 定义子组件
在 vue 对象中, 使用 components 定义一个局部组件
components: {
// 组件名: {组件参数}
son: {
template: '<button>点我</button>',
}
}
#3) 引用子组件
在 html 中通过组件名, 通过组件名引用组件
<div id="app">
<son></son>
</div>
#4) 传值
子组件向父组件传递数据的步骤
-
在子组件模板中绑定一个方法
components: { //1. 定义组件 son: { template: '<button @click="sendMsg">点我</button>', } }
-
在方法中向父组件提交(emit)一个事件
components: { //1. 定义组件 son: { template: '<button @click="sendMsg">点我</button>', methods: { sendMsg(){ // 调用$emit, 向父组件提交一个send事件 this.$emit('send', "子组件->父组件") } } } }
-
在子组件标签中监听事件(绑定方法)
<div id="app"> <!-- 2. 引用组件 --> <son @send="handleSend"></son> </div>
-
在父组件中编写处理程序
methods: {
handleSend(msg){
alert('收到子组件的数据'+msg)
}
},
#5) 小结
子向父传值的步骤
- 在子组件中绑定一个方法, 向父组件提交一个事件
- 在子组件标签中绑定一个方法, 监听事件
- 在父组件中编写处理程序
=====================================
Vue中的slot
#1 概述
#1) 什么是 slot
slot 可以理解为预留了一个可替换的地方
游戏卡是可以插拔的, 插游戏卡的地方就是一个插槽
思考
游戏卡插槽有什么作用?
再比如, USB 接口也可以看成一个插槽
. 可以插入 U 盘, 硬盘, 鼠标, 键盘...
还有, CPU 槽, 内存槽. 他们的存在有什么共同点??
#2) 为什么需要 slot
通过上面的例子, 我们可以看出
- 通过插不同的游戏卡, 可以玩不同的游戏
- 通过插不同的外设, 可以扩展电脑的功能
- 通过插不同型号的 CPU(i3/i5/i7/i9), 可以更换 CPU
所以, 插槽最主要的作用是提供扩展性
.
#3) Vue 中的 slot
在 Vue 开发中, slot 主要应用在组件开发中, 通过在组件中预留 slot, 实现不同的功能
示例
<!DOCTYPE html>
<html lang="en">
<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" />
<title>Document</title>
<script src="../node_modules/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<son></son>
</div>
<template id="tmp">
<div>我是子组件</div>
</template>
<script>
const vm = new Vue({
el: '#app',
components: {
son: {
template: '#tmp',
},
},
})
</script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
子组件的模板编译后, 会替换<son>
所在的地方
但是, 不管在<son>
中添加任何内容, 都不起作用~
<div id="app">
<son>
<!-- 在子组件里写的内容不会生效 -->
<h2>我是子组件的标题</h2>
</son>
</div>
1
2
3
4
5
6
这样, 子组件的可扩展性就很不好. 如果希望子组件中的内容可以替换怎么办??
在子组件中预留一个插槽
, 通过给子组件传递不同的内容来改变子组件
#2 具名插槽
#1) 作用
如果需要同时使用多个插槽, 就需要给插槽取名字.
就好比: 主板上同时有 CPU 槽和内存槽, 如何区分这两个插槽, 不至于把内存插到 CPU 中
当然, 现实中肯定不会, 但是程序中就需要使用名字区分开
#2) 使用
- 在子组件中, 定义具名插槽
- 在引用子组件时, 通过
slot属性
指定要替换的插槽
<div id="app">
<son>
<div slot="cpu">我是CPU</div>
<div slot="memery">我是内存</div>
</son>
</div>
<template id="tmp">
<div>
<slot name="cpu"></slot>
<slot name="memery"></slot>
</div>
</template>
没有指定的内容会全部放到<slot>
中, 也就是默认插槽
<div id="app">
<son>
<div slot="cpu">我是CPU</div>
<div slot="memery">我是内存</div>
<hr />
<div>我是剩余的内容</div>
<p>我也是...</p>
</son>
</div>
<template id="tmp">
<div>
<slot name="cpu"></slot>
<slot name="memery"></slot>
<!-- slot其实也有名字, 名字是default -->
<slot></slot>
<slot name="default"></slot>
</div>
</template>
#3 作用域插槽
#1) 编译作用域
在 Vue 编译的过程中, 如果父子组件中定义的相同的状态
, 会不会冲突呢?
如果不会冲突, 具体访问的是哪个状态
呢
示例
<!DOCTYPE html>
<html lang="en">
<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" />
<title>Document</title>
<script src="../node_modules/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<button v-show="isShow">按钮</button>
<son></son>
</div>
<template id="tmp">
<div>
<h3>我是子组件</h3>
<button v-show="isShow">子组件按钮</button>
</div>
</template>
<script>
const vm = new Vue({
el: '#app',
data: {
isShow: false,
},
components: {
son: {
template: '#tmp',
data() {
return {
isShow: true,
}
},
},
},
})
</script>
</body>
</html>
父组件和子组件中都存在isShow
.
- 如果在父模板中使用 isShow, 访问的是父组件
data
中的值 - 如果在子模板中使用 isShow, 访问的是子组件
data
中的值
通过上述示例, 我们可以发现, 在父组件中是不能直接访问子组件中的状态的.
需求
- 在父模板中可定制子组件的内容
- 同时使用子组件中的数据
#2) 为什么需要作用域插槽
为了解决上述问题, 引入了作用域插槽的概念, 其核心是在父模板中访问子组件的数据
示例
<div id="app">
<button v-show="isShow">按钮</button>
<!-- 通过v-slot指令找名字为default的插槽, 并指定prop对象对应default插槽 -->
<son v-slot:default="prop">
<!-- 通过prop对象访问show属性, 相当于访问了子组件的isShow -->
<button v-show="prop.show">子组件按钮</button>
</son>
</div>
<template id="tmp">
<div>
<h3>我是子组件</h3>
<!-- 在slot:default对象中, 定义自定义属性show -->
<slot :show="isShow"></slot>
</div>
</template>
其中, v-slot
可以使用#
简写
<!-- 通过v-slot指令找名字为default的插槽, 并指定prop对象对应default插槽 -->
<son #default="prop">
<!-- 通过prop对象访问show属性, 相当于访问了子组件的isShow -->
<button v-show="prop.show">子组件按钮</button>
</son>
通过同时有多个插槽, 需要借用<template>
语法
示例
<!DOCTYPE html>
<html lang="en">
<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" />
<title>Document</title>
<script src="../node_modules/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<button v-show="isShow">按钮</button>
<!-- 通过v-slot指令找名字为default的插槽, 并指定prop对象对应default插槽 -->
<son>
<template #default="prop">
<!-- 通过prop对象访问show属性, 相当于访问了子组件的isShow -->
<button v-show="prop.show">子组件按钮</button>
</template>
<template #left="left">
<h3>{
{left.info.name}}</h3>
</template>
</son>
</div>
<template id="tmp">
<div>
<h3>我是子组件</h3>
<!-- 在slot:default对象中, 定义自定义属性show -->
<slot :show="isShow"></slot>
<slot name="left" :info="stu"></slot>
</div>
</template>
<script>
const vm = new Vue({
el: '#app',
data: {
isShow: false,
},
components: {
son: {
template: '#tmp',
data() {
return {
isShow: true,
stu: {
name: 'xiaoming',
age: 18,
},
}
},
},
},
})
</script>
</body>
</html>