共计 20556 个字符,预计需要花费 52 分钟才能阅读完成。
一、项目结构和入口
1. 创建vue项目
# 1. 安装vue-cli
npm install -g vue-cli
# 2. 创建vue项目
vue init webpack demo_vue2
# 接下来会提示输入项目名称、描述等等
(node:12404) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
? Project name demo_vue2
? Project description vue
? Author hausen1012 <9470835+hausen1012@user.noreply.gitee.com>
? Vue build standalone
? Install vue-router? No
? Use ESLint to lint your code? No
? Set up unit tests No
? Setup e2e tests with Nightwatch? No
? Should we run `npm install` for you after the project has been created? (recommended) npm
vue-cli · Generated "demo_vue2".
# Installing project dependencies ...
2. vue 的项目结构
vue2 创建后的项目结构如下:
demo_vue2/
│
├── build/
│ ├── build.js # 生产环境构建脚本
│ ├── buildjs.js # 生成生产环境的 JavaScript
│ ├── check-versions.js # 检查当前环境的 Node 和 npm 版本
│ ├── logo.png # 一个 logo 图片
│ ├── utils.js # 构建过程中的工具函数
│ ├── vue-loader.conf.js # Vue 组件的 webpack loader 配置
│ ├── webpack.base.conf.js # webpack 基础配置
│ ├── webpack.dev.conf.js # webpack 开发环境配置
│ └── webpack.prod.conf.js # webpack 生产环境配置
│
├── config/
│ ├── dev.env.js # 开发环境配置
│ ├── index.js # 项目配置的主要入口
│ └── prod.env.js # 生产环境配置
│
├── node_modules/ # 依赖的 npm 包
│
├── src/
│ ├── assets/ # 静态资源文件,如图片、样式表等
│ │ └── logo.png # 一个 logo 图片
│ ├── components/ # 可复用的 Vue 组件
│ │ └── HelloWorld.vue # HelloWorld 组件
│ ├── App.vue # 应用的根组件
│ └── main.js # 应用的入口文件
|
├── static/ # 静态文件目录
├── .babelrc # Babel 配置文件
├── .editorconfig # 编辑器配置文件,用于统一不同编辑器的代码风格
├── .gitignore # Git 版本控制忽略文件
├── .postcssrc.js # PostCSS 配置文件
├── index.html # 应用的 HTML 入口文件
├── package-lock.json # 锁定依赖包版本的 npm 自动生成文件
├── package.json # 项目的配置文件,包含了依赖信息、脚本等
└── README.md # 项目说明文档
Babel 配置文件通常,
.babelrc
文件会包含一些预设(presets)和插件(plugins),例如@babel/preset-env
,它可以根据指定的目标环境自动确定需要转换的 JavaScript 特性。
3. vue项目的运行
这里的入口文件是 scr/main.js
,它是由 webpack 的构建配置文件决定的,这个文件通常为 webpack.base.conf.js
。
运行流程:
main.js
内容通常为如下:
// 导入vue组件库
import Vue from 'vue'
// 导入根组件 App
import App from './App'
// 这行代码关闭了 Vue.js 在控制台输出生产模式提示的功能。在生产环境中,通常不需要这些提示,因此关闭可以减少输出的噪音。
Vue.config.productionTip = false
// 创建vue实例。
new Vue({
el: '#app', // 指定 vue 实例要挂载的 DOM 元素。(将 vue 模板内容挂载到该容器下)
components: { App }, // 注册App组件才能在模板中使用。否则下面那行不能使用
template: '<App/>' // 指定了 Vue 实例的模板。(将 App 组件渲染的内容挂载到 id 为 app 的元素下)
})
然后可以看看 App.vue
文件里面具体是什么内容。
<!--相当于 html -->
<template>
<div id="app">
<img src="./assets/logo.png">
<HelloWorld/>
</div>
</template>
<!--相当于 js -->
<script>
import HelloWorld from './components/HelloWorld'
export default {
name: 'App',
components: {
HelloWorld
}
}
</script>
<!--相当于 css -->
<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>
以 .vue
为后缀的文件,即 vue 项目中的组件。一般分为三个部分,template
、script
和 style
分别对应网页三元素中的 html
、js
、css
。
这里的 App
组件主要的作用是,显示一个 logo 图片,下面是 HelloWorld
组件的内容。
HelloWorld.vue
内容基本都是一些 vue 相关的链接,内容在这里就不贴了,运行后访问可以看见内容如下:
二、vue指令
Vue 中的指令是一种特殊的语法,用于向 HTML 元素添加特殊行为。指令以 v-
开头,后跟指令名称,例如 v-bind
、v-if
、v-for
等。每个指令都提供了一些特定的功能,可以根据特定的条件或数据来改变 DOM 的结构、属性或其他行为。
1. 内容渲染指令
-
v-text(类似innerText)
- 使用语法:
<p v-text="uname">hello</p>
,意思是将uname值渲染到p标签中 - 类似innerText,使用该语法,会覆盖p标签原有内容
- 使用语法:
-
v-html(类似innerHTML)
- 使用语法:
<p v-html="intro">hello</p>
,意思是将intro值渲染到p标签中 - 类似innerHTML,使用该语法,会覆盖p标签原有内容,能够将HTML标签的样式呈现出来
- 使用语法:
<template>
<div id="app">
<h2>个人信息</h2>
<!-- 指令是 vue 提供的特殊的 html 属性,写的时候就当成属性来用即可 -->
姓名:<span v-text="uname">张三</span><br>
简介:<span v-html="intro">没有简介</span>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data () {
return {
uname: '李四',
intro: '<h2>这是一个<strong>非常优秀</strong>的boy<h2>'
}
}
}
</script>
其中模板内的 张三
和 没有简介
将被替换,并且会有相关提示:
v-text/v-html will override element children.
意思就是即使里面写了内容,也会被 v-text
和 v-html
重写。
2. 条件渲染指令
条件判断指令,用来辅助开发者按需控制DOM的显示与隐藏。条件渲染指令有如下两个,分别是:
-
v-show
- 作用:控制元素显示隐藏
- 语法:v-show="表达式"。表达式值为 true 显示,false 隐藏
- 原理:切换display:none控制显示隐藏
- 场景:频繁切换显示隐藏的场景
-
v-if
- 作用:控制元素显示隐藏
- 语法:v-if="表达式"。表达式值为 true 显示,false 隐藏
- 原理:基于条件判断,是否创建或移除元素节点
- 场景:要么显示,要么隐藏,不频繁切换的场景
v-else
和 v-else-if
是复制 v-if
的指令。
<template>
<!--
v-show 底层原理:切换css的display:none来控制显示隐藏
v-if 底层原理:根据判断条件控制元素的创建和移除
-->
<div id="app">
<div v-show="flag" class="box">我是v-show控制的盒子</div>
<hr>
<div>成绩:{{ score }}</div>
<p v-if="score>=90">成绩评定A:奖励电脑一台</p>
<p v-else-if="score>=70">成绩评定B:奖励周末郊游</p>
<p v-else-if="score>=60">成绩评定C:奖励零食礼包</p>
<p v-else>成绩评定D:惩罚一周不能玩手机</p>
<button @click="addScore()">成绩-10</button>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data () {
return {
flag: true,
score: 90
}
},
methods: {
addScore(){
console.log(this.score)
this.score -= 10
}
}
}
</script>
3. 事件绑定指令
v-on
指令用于向 DOM 注册事件,语法如下:
- <button v-on:事件名="内联语句">按钮
- <button v-on:事件名="处理函数">按钮
- <button v-on:事件名="处理函数(实参)">按钮
语法糖: v-on:
简写为 @
。
3.1 内联语句
<template>
<div id="app">
<button v-on:click="count--">-</button>
<span>{{count}}</span>
<!-- 使用语法糖 -->
<button @click="count++">+</button>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data () {
return {
count: 100
}
},
methods: {
}
}
</script>
3.2 内联语句
<template>
<div id="app">
<button @click="changeStatus">显示/隐藏</button>
<h1 v-show="isShow">你好啊</h1>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data () {
return {
isShow:true
}
},
methods: {
changeStatus(){
//app.isShow=!app.isShow
//methods函数内的this指向Vue实例
this.isShow=!this.isShow
}
}
}
</script>
3.3 给事件处理函数传参
<template>
<div id="app">
<div class="box">
<h3>小黑自动售货机</h3>
<button @click="buy(5)">可乐5元</button>
<button @click="buy(10)">咖啡10元</button>
</div>
<p>银行卡余额:{{money}}元</p>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data () {
return {
money: 100
}
},
methods: {
buy(price) {
this.money -= price
}
}
}
</script>
4. 属性绑定指令
4.1 基本使用
v-bind
指令用于设置 html 的标签属性,如:src、url、title。
语法糖: v-bind:
简写为 :
。
<template>
<div id="app">
<img v-bind:src="pic">
<!-- 简写 -->
<img :src="pic">
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data () {
return {
pic: "https://picsum.photos/960/540"
}
}
}
</script>
4.2 动态参数
从 2.6.0 开始,可以用方括号括起来的 JavaScript 表达式作为一个指令的参数:
<a v-bind:[attributeName]="url"> ... </a>
<!-- 动态参数的缩写 (2.6.0+) -->
<a :[attributeName]="url"> ... </a>
这里的 attributeName 会被作为一个 JavaScript 表达式进行动态求值,求得的值将会作为最终的参数来使用。例如,如果你的 Vue 实例有一个 data property attributeName,其值为 "href",那么这个绑定将等价于 v-bind:href。
<template>
<div id="app">
<img v-bind:[attributeName]="pic">
<!-- 简写 -->
<img :[attributeName]="pic">
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data () {
return {
attributeName: "src",
pic: "https://picsum.photos/960/540"
}
}
}
</script>
同样地,你可以使用动态参数为一个动态的事件名绑定处理函数
<a v-on:[eventName]="doSomething"> ... </a>
<!-- 动态参数的缩写 (2.6.0+) -->
<a @[event]="doSomething"> ... </a>
在这个示例中,当 eventName
的值为 "focus"
时,v-on:[eventName]
将等价于 v-on:focus
。
4.3 样式控制增强
v-bind
对样式控制的增强。Vue 扩展了 v-bind 的语法,可以针对 class 类名和 style 行内样式进行控制。
- 普通形式:
<div :class="对象or数组">这是一个div</div>
- 对象语法:
当 class 动态绑定的是对象时,键就是类名,值就是布尔值,如果值是 true,就有这个类,否则没有这个类
<div class="box" :class="{ 类名1: 布尔值, 类名2: 布尔值 }"></div>
适用场景:一个类名来回切换
<template>
<div id="app">
<h1 class="box" :class="{ 'red-font': flag, 'green-font': !flag }">{{ msg }}</h1>
<button @click="change()">切换字体颜色</button>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data () {
return {
msg: "hello",
flag: true
}
},
methods: {
change() {
console.log(this.flag)
this.flag = !this.flag
}
}
}
</script>
<style scoped>
.red-font {
color: red;
}
.green-font {
color: green;
}
</style>
- 数组语法:
当 class 动态绑定的是数组时,数组中所有的类都会添加到盒子上,本质就是一个 class 列表
<div class="box" :class="[ 类名1, 类名2, 类名3 ]"></div>
使用场景:批量添加或删除类。
<template>
<div id="app">
<h1 class="box" :class=classList >{{ msg }}</h1>
<button @click="addClass()">增加样式</button>
<button @click="removeClass()">去掉样式</button>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data () {
return {
msg: "hello",
classList: []
}
},
methods: {
addClass() {
this.classList.push('red-font', 'green-background');
},
removeClass() {
this.classList = []
}
}
}
</script>
<style scoped>
.red-font {
color: red;
}
.green-background {
background-color: green;
}
</style>
5. 列表渲染指令
v-for
指令用来辅助开发者基于一个数组来循环渲染一个列表结构。
v-for 指令需要使用(item , index) in arr
形式的特殊语法,其中:
- item是数组中的每一项
- index是每一项的索引,不需要可以省略
- arr是被遍历的数组
<template>
<div id="app">
<h3>小黑的书架</h3>
<ul v-for="(item,index) in booksList" :key="item.id">
<li>
<span>{{ index }}</span>
<span>{{item.name}}</span>
<span>{{item.author}}</span>
<button @click="del(item.id)">删除</button>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data () {
return {
booksList: [
{ id: 1, name: '《红楼梦》', author: '曹雪芹' },
{ id: 2, name: '《西游记》', author: '吴承恩' },
{ id: 3, name: '《水浒传》', author: '施耐庵' },
{ id: 4, name: '《三国演义》', author: '罗贯中' }
]
}
},
methods: {
del(id) {
this.booksList = this.booksList.filter(item => item.id != id);
}
}
}
</script>
key 的作用:给列表项添加唯一的标识。便于 Vue 进行列表项的正确排序复用
注意:
- key的值只能是字符串或数字类型
- key的值必须具有唯一性
- 推荐使用id作为key(唯一),不推荐使用index作为key
6. 双向绑定指令
6.1 概述
所谓双向绑定就是:
- 数据改变后,呈现的页面结果会更新
- 页面结果更新后,数据也会随之改变
作用:给表单元素(input、radio、select)使用,双向绑定数据,可以快速获取或设置表单元素内容。
常见的表单元素都可以用v-model绑定关联,快速获取或设置表单元素的值。
它会根据控件类型自动选取正确的方法来更新元素:
输入框 input:text ——> value
文本域 textarea ——> value
复选框 input:checkbox ——> checked
单选框 input:radio ——> checked
下拉菜单 select ——> value
…
<template>
<div id="app">
<h3>小黑学习网</h3>
<div>姓名:{{ username }}</div>
输入姓名:
<input type="text" v-model="username">
<hr>
<div>
是否勾选:
<span v-if="isSingle">是</span>
<span v-else>否</span>
</div>
是否单身:
<input type="checkbox" v-model="isSingle">
<hr>
<!--
前置理解:
1. name: 给单选框加上 name 属性 可以分组 → 同一组互相会互斥
2. value: 给单选框加上 value 属性,用于提交给后台的数据
结合 Vue 使用 → v-model
-->
<div>
你选择的性别:
<span v-if="gender == 1">男</span>
<span v-else>女</span>
</div>
性别:
<input v-model="gender" type="radio" name="gender" value="1">男
<input v-model="gender" type="radio" name="gender" value="2">女
<hr>
<!--
前置理解:
3. option 需要设置 value 值,提交给后台
4. select 的 value 值,关联了选中的 option 的 value 值
结合 Vue 使用 → v-model
-->
<div>
所在的城市:
<span v-if="city == 101">北京</span>
<span v-else-if="city == 102">上海</span>
<span v-else-if="city == 103">成都</span>
<span v-else-if="city == 104">南京</span>
</div>
所在城市:
<select v-model="city">
<option value="101">北京</option>
<option value="102">上海</option>
<option value="103">成都</option>
<option value="104">南京</option>
</select>
<hr>
<div>自我描述:{{ desc }}</div>
<textarea v-model="desc"></textarea>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data () {
return {
username: 'zhangsan',
isSingle: false,
gender: 1,
city: '102',
desc: "暂无"
}
}
}
</script>
6.2 原理
v-model 本质上是一个语法糖。例如应用在输入框上,就是 value 属性和 input 事件的合写。
<template>
<div id="app" >
<input v-model="msg" type="text">
<input :value="msg" @input="msg = $event.target.value" type="text">
</div>
</template>
对于 select:
<select :value="selectedOption" @change="selectedOption = $event.target.value">
<option value="option1">Option 1</option>
<option value="option2">Option 2</option>
</select>
7. 指令修饰符
指令修饰符就是通过 .
指明一些指令后缀,不同的后缀封装了不同的处理操作。
按键修饰符
- @keyup.enter:当点击 enter 键的时候才触发
<template>
<div id="app">
<h3>@keyup.enter → 监听键盘回车事件</h3>
<input @keyup.enter="fn" v-model="username" type="text">
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data () {
return {
username: '张三'
}
},
methods: {
fn(e) {
if (e.key === "Enter") {
alert(this.username);
}
}
}
}
</script>
v-model修饰符
- v-model.trim:去除首位空格
- v-model.number:转数字
<template>
<div id="app">
<h3>v-model修饰符 .trim .number</h3>
姓名:<input v-model.trim="username" type="text"><br>
年纪:<input v-model.number="age" type="text"><br>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data () {
return {
username: '',
age: '',
}
}
}
</script>
事件修饰符
- @事件名.stop:阻止冒泡
- @事件名.prevent:阻止默认行为
- @事件名.stop.prevent:可以连用,即阻止事件冒泡也阻止默认行为
<template>
<div id="app">
<h3>@事件名.stop → 阻止冒泡</h3>
<div @click="fatherFn" class="father">
<!-- 不阻止冒泡会弹窗两次 -->
<div @click.stop="sonFn" class="son">儿子</div>
</div>
<h3>@事件名.prevent → 阻止默认行为</h3>
<!-- 阻止默认行为后无法跳转百度 -->
<a @click.prevent href="http://www.baidu.com">阻止默认行为</a>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
methods: {
fatherFn() {
alert('老父亲被点击了')
},
sonFn() {
alert('儿子被点击了')
}
}
}
</script>
三、计算属性和侦听器
1. 计算属性
1.1 概念
计算属性的概念是基于现有的数据,计算出来的新属性。依赖的数据变化,自动重新计算。
语法
- 声明在 computed 配置项中,一个计算属性对应一个函数
- 使用起来和普通属性一样使用
{{计算属性名}}
优势
- 计算属性会对计算出来的结果缓存,再次使用直接读取缓存
- 依赖项变化了,会自动重新计算并再次缓存
注意
- computed 配置项和 data 配置项是同级的
- computed 中的计算属性虽然是函数的写法,但他仍然是个属性
- computed 中的计算属性不能和 data 中的属性同名
- 使用 computed 中的计算属性和使用 data 中的属性是一样的用法
- computed 中计算属性内部的 this 依然指向的是 Vue 实例
1.2 基本使用
对于任何复杂逻辑,都应当使用计算属性,如下案例:
<template>
<div id="app">
<p>原始内容: "{{ message }}"</p>
<p>反转原始内容: "{{ reversedMessage }}"</p>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data () {
return {
message: 'Hello'
}
},
computed: {
// 计算属性的 getter
reversedMessage: function () {
// `this` 指向 vm 实例
return this.message.split('').reverse().join('')
}
}
}
</script>
1.3 完整写法
计算属性默认的简写,只能读取访问,不能修改,如果要修改,需要写计算属性的完整写法。
<template>
<div id="app">
姓:<input type="text" v-model="firstName"><br>
名:<input type="text" v-model="lastName"><br>
<p>姓名:{{fullName}}</p>
<button @click="changeName">修改姓名</button>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data () {
return {
firstName: '刘',
lastName: '备',
}
},
methods: {
changeName() {
this.fullName = "吕小布"
}
},
computed: {
//完整写法:获取+设置
fullName: {
//当fullName计算属性被获取求值时,执行get(有缓存,优先读缓存)
//会将返回值作为求值的结果
get() {
return this.firstName + this.lastName
},
//当fullName计算属性被修改赋值时,执行set
//修改的值传递给set方法的形参
set(value) {
this.firstName = value.slice(0, 1)
this.lastName = value.slice(1)
}
}
}
}
</script>
2. 侦听器
2.1 概念
侦听器用于监视数据变化,执行一些业务逻辑或异步操作。
语法
- watch 同样声明在跟 data 同级的配置项中
- 简单写法:简单类型数据直接监视
- 完整写法:添加额外配置项
2.2 基本使用
除了以下案例,还可以使用侦听器监听输入框的变化,发送ajax请求,判断用户可能需要输入的关键词,比如百度搜索。
<template>
<div id="app">
<p>计数器: {{ counter }}</p>
<button @click="increment">增加</button>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data () {
return {
counter: 0
}
},
methods: {
increment() {
this.counter++;
}
},
watch: {
counter(newValue, oldValue) {
alert('计数器的值发生变化了:' + oldValue + '→' + newValue);
console.log('计数器的值发生变化了:', oldValue, '→', newValue);
}
}
}
</script>
四、生命周期
1. 简介
Vue 生命周期:就是一个 Vue 实例从创建到销毁的整个过程
生命周期分为四个阶段:创建、挂载、更新、销毁
- 创建阶段:创建Vue实例
- 挂载阶段:渲染模板
- 更新阶段:修改数据,更新视图
- 销毁阶段:销毁Vue实例
2. 生命周期钩子
Vue 生命周期过程中,会自动运行一些函数,被称为生命周期钩子,让开发者可以在特定阶段运行自己的代码。
既然有 4 个阶段,每个阶段都有两个周期,所以一共是八个周期。
<template>
<div>
<h3>计数器:{{ count }}</h3>
<button @click="count++">计数器+1</button>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data() {
return {
count: 100,
};
},
beforeCreate() {
// this.count 为 undefined
console.log('vue 实例创建之前', this.count);
},
created() {
// this.count 为 100
console.log('vue 实例创建之后', this.count);
},
beforeMount() {
console.log('模板挂载之前');
},
mounted() {
console.log('模板挂载之后');
},
// 更新之前和更新之后打印的 count 时一样的,区别只在于数据有没有更新到页面上
beforeUpdate() {
console.log('数据更新之前', this.count);
},
updated() {
console.log('数据更新之后', this.count);
},
// 比如说到路由时,需要展示其他组件,那个这个组件就需要销毁
beforeDestroy() {
console.log('vue 实例销毁之后');
},
destroyed() {
console.log('vue 实例销毁之后');
},
};
</script>
五、组件
1. 概念
在 Vue 中,组件是可复用的 Vue 实例,可以将其看作是自定义元素,用于构建用户界面中的各种功能块。
并且 Vue 中有组件树的概念,其中 App.vue 就是根组件,它包含应用的整体布局和结构,同时它也可以包含其他的子组件。
使用组件化开发有利于维护,并且便于复用。
2. 构成
组件是由三部分构成
- template:结构(有且只有一个根元素)
- script:js 逻辑
- style:样式(可支持 less)
<template>
<div>
<h3>计数器:{{ count }}</h3>
<button @click="count++">计数器+1</button>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data() {
return {
count: 100,
};
}
}
</script>
<style scoped>
</style>
- data 返回函数
这里需要注意的是,除了根组件,其他组件定义的 data 数据都是返回一个函数。目的是为了:保证每个组件实例,维护独立的一份数据对象。
- scoped 解决样式冲突
通过添加 scoped
属性到 <style>
标签,可以使得样式仅对当前组件起作用。这意味着在一个组件中定义的样式规则将不会影响到其他组件,即使它们使用了相同的类名或选择器。
3. 组件注册
组件需要使用到另外一个组件,需要进行组件注册,否则无法使用。其中,组件注册又分为全局组件注册和局部组件注册。
- 全局组件注册
全局组件注册是指在 Vue 对象中直接注册组件,这种方式注册的组件在任何地方都可以使用,而不需要再次注册。
import Vue from 'vue'
import App from './App'
Vue.component('HelloWorld', HelloWorld)
new Vue({
el: '#app',
components: { App },
template: '<App/>'
})
- 局部注册
局部注册指,某个组件需要哪个组件,就在这个组件中注册需要的组件。
<template>
<div id="app">
<HelloWorld/>
</div>
</template>
<script>
import Test from './components/HelloWorld'
export default {
name: 'App',
components: {
HelloWorld
}
}
</script>
像上面这样,局部注册了 HelloWorld
组件,那么在 App.vue
中就可以直接使用 HelloWorld
组件。
六、组件通信
1. 简介
组件通信,就是指组件与组件之间的数据传递
- 组件的数据是独立的,无法直接访问其他组件的数据。
- 想使用其他组件的数据,就需要组件通信
组件关系分类
- 父子关系
- 非父子关系
通信解决方案
2. 父子组件通信
2.1 父传子
父组件通过 props 将数据传递给子组件,props 是组件上注册的一些自定义属性。
特点:
- 可以传递任意数量的 prop
- 可以传递任意类型的 prop
props 和 data 都可以给组件提供数据,区别是 data 的数据是自己的,可以随便改;props 的数据是外部的,不能直接改,要遵循单向数据流。(父级props的数据更新,会向下流动,影响子组件。这个数据流动是单向的)
三个步骤:
- 给子组件以添加属性的方式传值
- 子组件内部通过props接收
- 模板中直接使用props接收的值
2.2 子传父
子传父需要通过事件来进行修改。
三个步骤:
- 父组件监听 $emit 触发的事件
- $emit 触发事件,给父组件发送消息通知
- 提供处理函数,在函数的形参中获取传过来的参数
3. 非父子组件通信
3.1 event bus
event bus
事件总线可以在非父子组件之间,进行简易的消息传递。
四个步骤:
- 创建全局事件总线
- 设置监听事件
- 触发监听事件
- 提供处理函数,在函数的形参中获取传过来的参数
3.2 provide&inject
语法
- 父组件provide提供数据
export default {
provide () {
return {
// 普通类型【非响应式】
color: this.color,
// 复杂类型【响应式】
userInfo: this.userInfo,
}
}
}
- 子/孙组件 inject获取数据
export default {
inject: ['color','userInfo'],
}
注意:
- provide提供的简单类型的数据不是响应式的,复杂类型数据是响应式的。(推荐实验复杂类型的数据)
- 子/孙组件通过inject获取的数据,不能在自身组件内修改
七、自定义指令
1. 概念
自定定义的指令,可以封装一些 DOM 操作,扩展额外的功能。
- 内置指令:
v-html
、v-if
、v-bind
、v-on
… 这都是 Vue 内置的一些指令,可以直接使用 - 自定义指令:同时Vue也支持让开发者自己注册一些指令。这些指令被称为自定义指令。每个指令都有自己各自独立的功能。
2. 基本语法
- 全局指令注册
//全局注册指令
Vue.directive('focus', {
//inserted 会在指令所在的元素被插入到页面中时触发
inserted(el) {
//el 就是指令所绑定的元素
el.focus();
}
})
- 局部指令注册
directives:{
//指令名:指令的配置项
focus: {
inserted(el){
el.focus()
}
}
}
注意:
- 使用指令之前一定要注册
- 注册指令时不加
-v
,使用时一定要加-v
3. 实现自定义指令
实现一个color指令,传入不同的颜色,给标签设置文字颜色。
<template>
<div id="app" >
<h1 v-color="color1">指令的值1测试</h1>
<h1 v-color="color2">指令的值2测试</h1>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data: function () {
return {
color1: 'red',
color2: 'green'
}
},
directives: {
color: {
//1.inserted 提供的是元素被添加到页面中的逻辑
inserted(el, binding) {
//el.style.color='red'
el.style.color = binding.value
},
//2.update 指令的值修改的时候触发,提供值变化后,dom 更新的逻辑
update(el, binding) {
el.style.color = binding.value
}
}
}
}
</script>
八、插槽
1. 概念
Vue 的插槽(slot)是一种机制,允许在父组件中向子组件传递内容。它允许你在子组件的特定位置插入任意内容,而不受子组件内部结构的限制。这对于创建可复用的组件以及在不同场景下灵活地组合内容非常有用。
插槽可分为默认插槽、具名插槽和作用域插槽。
- 默认插槽:默认插槽是一种简单的插槽,其实是一种特殊的具名插槽,名称为
default
- 具名插槽:具名插槽允许子组件定义多个插槽位置,并且可以在父组件中指定要插入的内容到哪个插槽中
- 作用域插槽:作用域插槽允许子组件将数据传递到父组件,使父组件可以在插槽中使用子组件的数据。(作用域可以这么理解,子组件的数据有作用域限制,但是作用域插槽可以将其传递到父组件使用)
2. 默认插槽
使用默认插槽非常简单,只需要预留一个插槽位置即可,然后引用组件,组件中间的内容就会插入到插槽中。
HelloWorld.vue
<template>
<div id="app">
<h3>父组件</h3>
<div class="content">
<Card title="游戏列表">
<ul>
<li v-for="item in gameList" :key="item.id">{{ item.name }}</li>
</ul>
</Card>
<Card title="图片展示">
<img :src="picUrl">
</Card>
<Card title="消息通知">
{{ msg }}
</Card>
</div>
</div>
</template>
<script>
import Card from './Card.vue';
export default {
name: 'HelloWorld',
components: {
Card
},
data () {
return {
gameList: [
{ id: 1, name: "王者荣耀"},
{ id: 2, name: "原神"},
{ id: 3, name: "英雄联盟"}
],
picUrl: "https://fastly.picsum.photos/id/77/960/540.jpg?hmac=idkrsBSL_IbzHXXXS3HYS-ZmT3_P4cq0hWPynF1llk8",
msg: "明天放假!"
}
}
}
</script>
<style scoped>
#app {
background-color: #ccc;
padding: 20px;
}
.content {
display: flex;
justify-content: space-evenly;
}
img, video {
width: 100%;
}
</style>
Card.vue
<template>
<div class="category">
<h2> {{ title }}</h2>
<slot></slot>
</div>
</template>
<script>
export default {
name: 'Card',
props: ['title']
}
</script>
<style scoped>
.category {
background-color: skyblue;
border-radius: 10px;
box-shadow: 0 0 10px;
padding: 10px;
width: 200px;
height: 300px;
}
</style>
效果:
需要注意的是,很多时候可能需要插槽有一个默认内容,那么将默认内容放在插槽内即可。
<template>
<div class="category">
<h2> {{ title }}</h2>
<slot>默认展示的内容</slot>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: ['title'],
data () {
return {
}
}
}
</script>
<style scoped>
.category {
background-color: skyblue;
border-radius: 10px;
box-shadow: 0 0 10px;
padding: 10px;
width: 200px;
height: 300px;
}
</style>
3. 具名插槽
3.1 基本使用
上面的案例,其实 title 没必要传递,将其定义为一个插槽即可。但是如果有多个插槽,父组件在插入时就不知道哪个对应哪个,所以需要给插槽设置名称。
HelloWorld.vue
<template>
<div id="app">
<h3>父组件</h3>
<div class="content">
<Card>
<template v-slot:content>
<ul>
<li v-for="item in gameList" :key="item.id">{{ item.name }}</li>
</ul>
</template>
<template v-slot:title>
<h2>游戏列表</h2>
</template>
</Card>
<Card>
<template v-slot:title>
<h2>图片展示</h2>
</template>
<template v-slot:content>
<img :src="picUrl">
</template>
</Card>
<Card>
<template v-slot:title>
<h2>消息通知</h2>
</template>
<template v-slot:content>
{{ msg }}
</template>
</Card>
</div>
</div>
</template>
<script>
import Card from './Card.vue';
export default {
name: 'HelloWorld',
components: {
Card
},
data () {
return {
gameList: [
{ id: 1, name: "王者荣耀"},
{ id: 2, name: "原神"},
{ id: 3, name: "英雄联盟"}
],
picUrl: "https://fastly.picsum.photos/id/77/960/540.jpg?hmac=idkrsBSL_IbzHXXXS3HYS-ZmT3_P4cq0hWPynF1llk8",
msg: "明天放假!"
}
}
}
</script>
<style scoped>
#app {
background-color: #ccc;
padding: 20px;
}
.content {
display: flex;
justify-content: space-evenly;
}
img, video {
width: 100%;
}
</style>
Card.vue
<template>
<div class="category">
<slot name="title"></slot>
<slot name="content"></slot>
</div>
</template>
<script>
export default {
name: 'Card'
}
</script>
<style scoped>
.category {
background-color: skyblue;
border-radius: 10px;
box-shadow: 0 0 10px;
padding: 10px;
width: 200px;
height: 300px;
}
</style>
其中游戏列表故意将标题放在下面,是为了表示具名插槽与位置无关,实现的效果一样。
3.2 动态插槽名
动态指令参数也可以用在 v-slot
上,来定义动态的插槽名:
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
</base-layout>
3.3 具名插槽的缩写
2.6.0 新增
跟 v-on
和 v-bind
一样,v-slot
也有缩写,即把参数之前的所有内容 (v-slot:
) 替换为字符 #
。例如 v-slot:title
可以被重写为 #title
:
<Card>
<template #content>
<ul>
<li v-for="item in gameList" :key="item.id">{{ item.name }}</li>
</ul>
</template>
<template #title>
<h2>游戏列表</h2>
</template>
</Card>
4. 作用域插槽
作用域插槽主要是用于数据在子组件,但是父组件需要使用这个数据的情况。需要在 slot
组件中将数据传递给插槽,然后在父组件通过 v-slot
指令拿到数据。
HelloWorld.vue
<template>
<div id="app">
<h3>父组件</h3>
<div class="content">
<Card>
<template v-slot="params">
<ul>
<li v-for="item in params.gameList" :key="item.id">{{ item.name }}</li>
</ul>
</template>
</Card>
<Card>
<template v-slot="params">
<ol>
<li v-for="item in params.gameList" :key="item.id">{{ item.name }}</li>
</ol>
</template>
</Card>
<Card>
<template v-slot="params">
<h3 v-for="item in params.gameList" :key="item.id">{{ item.name }}</h3>
</template>
</Card>
</div>
</div>
</template>
<script>
import Card from './Card.vue';
export default {
name: 'HelloWorld',
components: {
Card
}
}
</script>
<style scoped>
#app {
background-color: #ccc;
padding: 20px;
}
.content {
display: flex;
justify-content: space-evenly;
}
</style>
Card.vue
<template>
<div class="category">
<h2>游戏列表</h2>
<slot :gameList="gameList"></slot>
</div>
</template>
<script>
export default {
name: 'Card',
props: ['title'],
data () {
return {
gameList: [
{ id: 1, name: "王者荣耀"},
{ id: 2, name: "原神"},
{ id: 3, name: "英雄联盟"}
],
}
}
}
</script>
<style scoped>
.category {
background-color: skyblue;
border-radius: 10px;
box-shadow: 0 0 10px;
padding: 10px;
width: 200px;
height: 300px;
}
</style>
注意:
- 当只有默认插槽时,可以将
v-slot
放在子组件上。(上面第二种情况) - 当只有默认插槽时,可以忽略插槽名称。上面的
v-slot="params"
为v-slot:default="params"
的缩写。
注意默认插槽的缩写语法不能和具名插槽混用,因为它会导致作用域不明确。
效果:
作用域插槽还可以进行结构,具体可参考 解构插槽 Prop。
还有被废弃的 slot
和 slot-scope
属性,参考 废弃了的语法。
提醒:本文发布于145天前,文中所关联的信息可能已发生改变,请知悉!