Vue

Vue

cccs7 Lv5

Vue.js 是什么?

Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链 以及各种支持类库 结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。

QuickStart

尝试 Vue.js 最简单的方法是使用 Hello World 例子 。你可以在浏览器新标签页中打开它,跟着例子学习一些基础用法。或者你也可以创建一个 .html 文件,然后通过如下方式引入 Vue:

直接下载并用 <script> 标签引入,Vue 会被注册为一个全局变量。

在开发环境下不要使用压缩版本,不然你就失去了所有常见错误相关的警告

1
2
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

或者:

1
2
<!-- 生产环境版本,优化了尺寸和速度 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>

不推荐新手直接使用 vue-cli,尤其是在你还不熟悉基于 Node.js 的构建工具时。

声明式渲染


Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统:

1
2
3
4
5
6
7
8
9
<div id="app">
{{ message }}
</div>
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})

image-20230711161810160

我们已经成功创建了第一个 Vue 应用!看起来这跟渲染一个字符串模板非常类似,但是 Vue 在背后做了大量工作。现在数据和 DOM 已经被建立了关联,所有东西都是响应式的。我们要怎么确认呢?打开你的浏览器的 JavaScript 控制台 (就在这个页面打开),并修改 app.message 的值,你将看到上例相应地更新。

image-20230711161904786

注意我们不再和 HTML 直接交互了。一个 Vue 应用会将其挂载到一个 DOM 元素上 (对于这个例子是 #app) 然后对其进行完全控制。那个 HTML 是我们的入口,但其余都会发生在新创建的 Vue 实例内部。

除了文本插值,我们还可以像这样来绑定元素 attribute:

1
2
3
4
5
6
7
8
9
10
11
<div id="app-2">
<span v-bind:title="message">
鼠标悬停几秒钟查看此处动态绑定的提示信息!
</span>
</div>
var app2 = new Vue({
el: '#app-2',
data: {
message: '页面加载于 ' + new Date().toLocaleString()
}
})

这里我们遇到了一点新东西。你看到的 v-bind attribute 被称为指令。指令带有前缀 v-,以表示它们是 Vue 提供的特殊 attribute。它们会在渲染的 DOM 上应用特殊的响应式行为。在这里,该指令的意思是:“将这个元素节点的 title attribute 和 Vue 实例的 message property 保持一致”。

如果你再次打开浏览器的 JavaScript 控制台,输入 app2.message = '新消息',就会再一次看到这个绑定了 title attribute 的 HTML 已经进行了更新。

条件与渲染


控制切换一个元素是否显示也相当简单:

1
2
3
4
5
6
7
8
9
<div id="app-3">
<p v-if="seen">现在你看到我了</p>
</div>
var app3 = new Vue({
el: '#app-3',
data: {
seen: true
}
})

image-20230711163311370

继续在控制台输入 app3.seen = false,你会发现之前显示的消息消失了。

这个例子演示了我们不仅可以把数据绑定到 DOM 文本或 attribute,还可以绑定到 DOM 结构。此外,Vue 也提供一个强大的过渡效果系统,可以在 Vue 插入/更新/移除元素时自动应用过渡效果

还有其它很多指令,每个都有特殊的功能。例如,v-for 指令可以绑定数组的数据来渲染一个项目列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div id="app-4">
<ol>
<li v-for="todo in todos">
{{ todo.text }}
</li>
</ol>
</div>
var app4 = new Vue({
el: '#app-4',
data: {
todos: [
{ text: '学习 JavaScript' },
{ text: '学习 Vue' },
{ text: '整个牛项目' }
]
}
})

image-20230711163345478

在控制台里,输入 app4.todos.push({ text: '新项目' }),你会发现列表最后添加了一个新项目。

处理用户输入


为了让用户和你的应用进行交互,我们可以用 v-on 指令添加一个事件监听器,通过它调用在 Vue 实例中定义的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div id="app-5">
<p>{{ message }}</p>
<button v-on:click="reverseMessage">反转消息</button>
</div>
var app5 = new Vue({
el: '#app-5',
data: {
message: 'Hello Vue.js!'
},
methods: {
reverseMessage: function () {
this.message = this.message.split('').reverse().join('')
}
}
})

image-20230711163456008

image-20230711163510989

注意在 reverseMessage 方法中,我们更新了应用的状态,但没有触碰 DOM——所有的 DOM 操作都由 Vue 来处理,你编写的代码只需要关注逻辑层面即可。

Vue 还提供了 v-model 指令,它能轻松实现表单输入和应用状态之间的双向绑定。

1
2
3
4
5
6
7
8
9
10
11
<div id="app-6">
<p>{{ message }}</p>
<input v-model="message">
</div>

var app6 = new Vue({
el: '#app-6',
data: {
message: 'Hello Vue!'
}
})

image-20230711163552530

组件化应用构建


组件系统是 Vue 的另一个重要概念,因为它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用。仔细想想,几乎任意类型的应用界面都可以抽象为一个组件树:

Component Tree

在 Vue 里,一个组件本质上是一个拥有预定义选项的一个 Vue 实例。在 Vue 中注册组件很简单:

1
2
3
4
5
6
// 定义名为 todo-item 的新组件
Vue.component('todo-item', {
template: '<li>这是个待办项</li>'
})

var app = new Vue(...)

现在你可以用它构建另一个组件模板:

1
2
3
4
<ol>
<!-- 创建一个 todo-item 组件的实例 -->
<todo-item></todo-item>
</ol>

但是这样会为每个待办项渲染同样的文本,这看起来并不炫酷。我们应该能从父作用域将数据传到子组件才对。让我们来修改一下组件的定义,使之能够接受一个 prop

1
2
3
4
5
6
7
Vue.component('todo-item', {
// todo-item 组件现在接受一个
// "prop",类似于一个自定义 attribute。
// 这个 prop 名为 todo。
props: ['todo'],
template: '<li>{{ todo.text }}</li>'
})

现在,我们可以使用 v-bind 指令将待办项传到循环输出的每个组件中:

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
<div id="app-7">
<ol>
<!--
现在我们为每个 todo-item 提供 todo 对象
todo 对象是变量,即其内容可以是动态的。
我们也需要为每个组件提供一个“key”,稍后再
作详细解释。
-->
<todo-item
v-for="item in groceryList"
v-bind:todo="item"
v-bind:key="item.id"
></todo-item>
</ol>
</div>
Vue.component('todo-item', {
props: ['todo'],
template: '<li>{{ todo.text }}</li>'
})

var app7 = new Vue({
el: '#app-7',
data: {
groceryList: [
{ id: 0, text: '蔬菜' },
{ id: 1, text: '奶酪' },
{ id: 2, text: '随便其它什么人吃的东西' }
]
}
})

image-20230711163942067

尽管这只是一个刻意设计的例子,但是我们已经设法将应用分割成了两个更小的单元。子单元通过 prop 接口与父单元进行了良好的解耦。我们现在可以进一步改进 <todo-item> 组件,提供更为复杂的模板和逻辑,而不会影响到父单元。

在一个大型应用中,有必要将整个应用程序划分为组件,以使开发更易管理。在后续教程 中我们将详述组件,不过这里有一个 (假想的) 例子,以展示使用了组件的应用模板是什么样的:

1
2
3
4
5
6
7
<div id="app">
<app-nav></app-nav>
<app-view>
<app-sidebar></app-sidebar>
<app-content></app-content>
</app-view>
</div>

与自定义元素的关系

你可能已经注意到 Vue 组件非常类似于自定义元素——它是 Web 组件规范 的一部分,这是因为 Vue 的组件语法部分参考了该规范。例如 Vue 组件实现了 Slot API is attribute。但是,还是有几个关键差别:

  1. Web Components 规范已经完成并通过,但未被所有浏览器原生实现。目前 Safari 10.1+、Chrome 54+ 和 Firefox 63+ 原生支持 Web Components。相比之下,Vue 组件不需要任何 polyfill,并且在所有支持的浏览器 (IE9 及更高版本) 之下表现一致。必要时,Vue 组件也可以包装于原生自定义元素之内。
  2. Vue 组件提供了纯自定义元素所不具备的一些重要功能,最突出的是跨组件数据流、自定义事件通信以及构建工具集成。

虽然 Vue 内部没有使用自定义元素,不过在应用使用自定义元素、或以自定义元素形式发布时,依然有很好的互操作性 。Vue CLI 也支持将 Vue 组件构建成为原生的自定义元素。

Vue 基础

Vue 实例

在 Vue 中,一个实例(Instance)是一个 Vue 应用程序的根实例。可以通过创建一个 Vue 实例来初始化一个 Vue 应用程序。Vue 实例是一个 JavaScript 对象,它包含了应用程序的数据和方法,以及一些 Vue 特定的选项。

创建一个 Vue 实例的方法是使用 Vue 构造函数,并将一个选项对象传递给它。选项对象可以包含 data、methods、computed、watch、生命周期钩子函数等属性,用于定义实例的数据和行为。

创建一个 Vue 实例

核心步骤

  1. 准备容器
  2. 引包 — 开发版本/生产版本
  3. 创建Vue实例 new Vue()
  4. 指定配置项,渲染数据
    1. el:指定挂载点
    2. data提供数据
    3. methods 方法

例如,以下代码创建了一个 Vue 实例:

1
2
3
4
5
6
7
8
9
10
11
var vm = new Vue({
el: '#app',
data: {
message: 'Hello, Vue!'
},
methods: {
reverseMessage: function () {
this.message = this.message.split('').reverse().join('')
}
}
})

使用了 Vue 构造函数来创建一个 Vue 实例,并将一个选项对象传递给它。选项对象包含了一个 el 属性,它指定了实例要挂载到的 DOM 元素。在这个例子中,我们将实例挂载到了一个 id 为 “app” 的元素上。

选项对象还包含了一个 data 属性,它定义了实例的响应式数据。在这个例子中,我们定义了一个名为 message 的数据属性,它的初始值为 “Hello, Vue!”。

选项对象还包含了一个 methods 属性,它定义了实例的方法。在这个例子中,我们定义了一个名为 reverseMessage 的方法,它将 message 的值反转。

在 Vue 中,一个实例是一个 Vue 应用程序的根实例。我们可以通过创建一个 Vue 实例来初始化一个 Vue 应用程序,并使用选项对象来定义实例的数据和行为。

生命周期钩子

每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。每个实例都有一组生命周期钩子函数(Lifecycle Hooks),它们分别在实例的不同阶段被调用,可以用来在不同生命周期阶段执行一些操作

这些生命周期钩子函数包括:

  1. beforeCreate:在实例初始化之后,数据观测和事件配置之前被调用。
  2. created:在实例创建完成后被立即调用,这个阶段完成了实例的数据观测、属性和方法的运算、watch/event 事件回调。
  3. beforeMount:在挂载开始之前被调用,即将模板编译成渲染函数时调用,此时还没有挂载到 DOM。
  4. mounted:实例挂载到 DOM 上后调用,这时候可以进行 DOM 操作。
  5. beforeUpdate:在数据更新之前被调用,发生在虚拟 DOM 重新渲染和打补丁之前。
  6. updated:在数据更新之后被调用,发生在虚拟 DOM 重新渲染和打补丁之后。
  7. beforeDestroy:在实例销毁之前调用。在这一步,实例仍然完全可用。
  8. destroyed:在实例销毁之后调用。这个阶段所有的指令都已解绑,所有的事件监听器都已移除,所有的子实例也已经被销毁。

比如 created 钩子可以用来在一个实例被创建之后执行代码:

1
2
3
4
5
6
7
8
9
10
new Vue({
data: {
a: 1
},
created: function () {
// `this` 指向 vm 实例
console.log('a is: ' + this.a)
}
})
// => "a is: 1"

也有一些其它的钩子,在实例生命周期的不同阶段被调用,如 mountedupdateddestroyed。生命周期钩子的 this 上下文指向调用它的 Vue 实例。

不要在选项 property 或回调上使用箭头函数 ,比如

created: () => console.log(this.a)vm.$watch('a', newValue => this.myMethod())

因为箭头函数并没有 thisthis 会作为变量一直向上级词法作用域查找,直至找到为止

经常导致 Uncaught TypeError: Cannot read property of undefinedUncaught TypeError: this.myMethod is not a function 之类的错误。

Vue生命周期:就是一个Vue实例从创建 到 销毁 的整个过程。

生命周期四个阶段:① 创建 ② 挂载 ③ 更新 ④ 销毁

1.创建阶段:创建响应式数据

2.挂载阶段:渲染模板

3.更新阶段:修改数据,更新视图

4.销毁阶段:销毁Vue实例

68206593781

生命周期图示

下图展示了实例的生命周期。

Vue 实例生命周期

Vue生命周期过程中,会自动运行一些函数,被称为【生命周期钩子】→ 让开发者可以在【特定阶段】运行自己的代码

68206604029

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
<div id="app">
<h3>{{ title }}</h3>
<div>
<button @click="count--">-</button>
<span>{{ count }}</span>
<button @click="count++">+</button>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
count: 100,
title: '计数器'
},
// 1. 创建阶段(准备数据)


// 2. 挂载阶段(渲染模板)


// 3. 更新阶段(修改数据 → 更新视图)


// 4. 卸载阶段

})
</script>

模板语法

Vue.js 使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据。所有 Vue.js 的模板都是合法的 HTML,所以能被遵循规范的浏览器和 HTML 解析器解析。

在底层的实现上,Vue 将模板编译成虚拟 DOM 渲染函数。结合响应系统,Vue 能够智能地计算出最少需要重新渲染多少组件,并把 DOM 操作次数减到最少。

插值表达式

在 Vue 中,插值表达式(Interpolation)是一种用于将数据绑定到模板中的方式。插值表达式使用双大括号语法(”{{ }}“)将数据包含在内,例如:

1
<div>{{ message }}</div>

Mustache 标签将会被替代为对应数据对象上 message property 的值。无论何时,绑定的数据对象上 message property 发生了改变,插值处的内容都会更新。上述使用插值表达式将一个名为 message 的数据属性绑定到了一个 div 元素中。在实例渲染时,Vue 会将 message 的值替换双大括号的内容,从而显示出来。

插值表达式可以包含任何合法的 JavaScript 表达式,例如:

1
<div>{{ message.toUpperCase() }}</div>

使用 message.toUpperCase() 方法将 message 的值转换为大写字母。

插值表达式也可以用于绑定计算属性和方法的返回值,例如:

1
<div>{{ reversedMessage }}</div>

使用一个名为 reversedMessage 的计算属性,它返回 message 的反转字符串。

在 Vue 中,插值表达式是一种将数据绑定到模板中的方式,使用双大括号语法将数据包含在内。插值表达式可以包含任何合法的 JavaScript 表达式,也可以用于绑定计算属性和方法的返回值。

注意事项
  • 1.在插值表达式中使用的数据 必须在data中进行了提供

    • ```html

      我是P标签

      1
      2
      3
      4
      5
      6
      7



      - 2.支持的是表达式,而非语句,比如:if for ...

      - ```html
      <p>{{if}}</p>

  • 3.不能在标签属性中使用 {{ }} 插值 (插值表达式只能标签中间使用)

    • ```html

      我是P标签

      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
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60



      #### 指令

      在 Vue 中,指令 (Directives) 是带有 `v-` 前缀的特殊 attribute。指令 attribute 的值预期是**单个 JavaScript 表达式** (`v-for` 是例外情况)。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM指令可以用于控制 DOM 元素的显示、隐藏、样式、属性、事件等方面。

      以下列举了一些常用的 Vue 指令:

      1. v-if:根据表达式的值,条件地渲染或销毁元素。
      2. v-show:根据表达式的值,切换元素的显示和隐藏状态。
      3. v-for:根据表达式的值,在元素上循环渲染一组元素。
      4. v-bind:动态地绑定元素的属性或属性值。
      5. v-on:绑定元素的事件监听器。
      6. v-model:在表单元素上创建双向数据绑定。
      7. v-text:渲染文本内容。
      8. v-html:渲染 HTML 内容。



      ##### 基础

      ###### 内容渲染指令

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

      - v-text(类似innerText)


      - - 使用语法:`<p v-text="uname">hello</p>`,意思是将 uame 值渲染到 p 标签中
      - 类似 innerText,使用该语法,会覆盖 p 标签原有内容


      - v-html(类似 innerHTML)


      - - 使用语法:`<p v-html="intro">hello</p>`,意思是将 intro 值渲染到 p 标签中
      - 类似 innerHTML,使用该语法,会覆盖 p 标签原有内容
      - 类似 innerHTML,使用该语法,能够将HTML标签的样式呈现出来。

      代码演示:

      ```js

      <div id="app">
      <h2>个人信息</h2>
      // 既然指令是vue提供的特殊的html属性,所以咱们写的时候就当成属性来用即可
      <p v-text="uname">姓名:</p>
      <p v-html="intro">简介:</p>
      </div>

      <script>
      const app = new Vue({
      el:'#app',
      data:{
      uname:'张三',
      intro:'<h2>这是一个<strong>非常优秀</strong>的boy<h2>'
      }
      })
      </script>
条件渲染指令

条件判断指令,用来辅助开发者按需控制 DOM 的显示与隐藏。条件渲染指令有如下两个,分别是:

  1. v-show

    1. 作用: 控制元素显示隐藏
    2. 语法: v-show = “表达式” 表达式值为 true 显示, false 隐藏
    3. 原理: 切换 display:none 控制显示隐藏
    4. 场景:频繁切换显示隐藏的场景

    68189122828

  2. v-if

    1. 作用: 控制元素显示隐藏(条件渲染)
    2. 语法: v-if= “表达式” 表达式值 true显示, false 隐藏
    3. 原理: 基于条件判断,是否创建 或 移除元素节点
    4. 场景: 要么显示,要么隐藏,不频繁切换的场景

    68189123775

    示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <!-- 
    v-show底层原理:切换 css 的 display: none 来控制显示隐藏
    v-if 底层原理:根据 判断条件 控制元素的 创建 和 移除(条件渲染)
    -->

    <div id="app">
    <div v-show="flag" class="box">我是v-show控制的盒子</div>
    <div v-if="flag" class="box">我是v-if控制的盒子</div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script>
    const app = new Vue({
    el: '#app',
    data: {
    flag: false
    }
    })
    </script>
  3. v-else 和 v-else-if

    1. 作用:辅助v-if进行判断渲染
    2. 语法:v-else v-else-if=”表达式”
    3. 需要紧接着v-if使用

示例代码:

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
<body>

<div id="app">
<p v-if="gender === 1">性别:♂ 男</p>
<p v-else>性别:♀ 女</p>
<hr>
<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>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>

const app = new Vue({
el: '#app',
data: {
gender: 2,
score: 95
}
})
</script>

</body>
事件绑定指令

使用Vue时,如需为DOM注册事件,及其的简单,语法如下:

  • <button v-on:事件名="内联语句">按钮</button>
  • <button v-on:事件名="处理函数">按钮</button>
  • <button v-on:事件名="处理函数(实参)">按钮</button>
  • v-on: 简写为 @
  1. 内联语句

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <div id="app">
    <button @click="count--">-</button>
    <span>{{ count }}</span>
    <button v-on:click="count++">+</button>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script>
    const app = new Vue({
    el: '#app',
    data: {
    count: 100
    }
    })
    </script>
  2. 事件处理函数

    注意:

    • 事件处理函数应该写到一个跟data同级的配置项(methods)中
    • methods中的函数内部的this都指向Vue实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<body>
<div id="app">
<button @click="fn">切换显示隐藏</button>
<h1 v-show="isShow">黑马程序员</h1>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app4 = new Vue({
el: '#app',
data: {
isShow: true
},
methods: {
fn () {
// 让提供的所有methods中的函数,this都指向当前实例
// console.log('执行了fn', app.isShow)
// console.log(app3 === this)
this.isShow = !this.isShow
}
}
})
</script>
</body>

3.给事件处理函数传参

  • 如果不传递任何参数,则方法无需加小括号;methods方法中可以直接使用 e 当做事件对象

  • 如果传递了参数,则实参 $event 表示事件对象,固定用法。

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
<body>

<div id="app">
<div class="box">
<h3>小黑自动售货机</h3>
<button @click="buy(5)">可乐5元</button>
<button @click="buy(10)">咖啡10元</button>
<button @click="buy(8)">牛奶8元</button>
</div>
<p>银行卡余额:{{ money }}元</p>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
money: 100
},
methods: {
buy (price) {
this.money -= price
}
}
})
</script>
</body>
属性绑定指令
  1. 作用:动态设置html的标签属性 比如:src、url、title
  2. 语法:**v-bind:**属性名=“表达式”
  3. **v-bind:**可以简写成 => :

比如,有一个图片,它的 src 属性值,是一个图片地址。这个地址在数据 data 中存储。

则可以这样设置属性值:

  • <img v-bind:src="url" />
  • <img :src="url" /> (v-bind可以省略)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div id="app">
<img v-bind:src="imgUrl" v-bind:title="msg" alt="">
<img :src="imgUrl" :title="msg" alt="">
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
imgUrl: './imgs/10-02.png',
msg: 'hello 波仔'
}
})
</script>
列表渲染指令

Vue 提供了 v-for 列表渲染指令,用来辅助开发者基于一个数组来循环渲染一个列表结构。

v-for 指令需要使用 (item, index) in arr 形式的特殊语法,其中:

  • item 是数组中的每一项
  • index 是每一项的索引,不需要可以省略
  • arr 是被遍历的数组

此语法也可以遍历对象和数字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<div id="app">
<h3>小黑水果店</h3>
<ul>
<li v-for="(item, index) in list">
{{ item }} - {{ index }}
</li>
</ul>

<ul>
<li v-for="item in list">
{{ item }}
</li>
</ul>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
list: ['西瓜', '苹果', '鸭梨', '榴莲']
}
})
</script>

v-for 中的key

语法: key=”唯一值”

作用:给列表项添加的唯一标识。便于Vue进行列表项的正确排序复用

为什么加key:Vue 的默认行为会尝试原地修改元素(就地复用

在 Vue 的 v-for 指令中,key 是一个特殊的属性,它用于帮助 Vue 识别每个节点的身份,以便在更新 DOM 时能够更高效地渲染元素。

在使用 v-for 渲染列表时,Vue 会根据每个元素的索引值来确定它们的身份。如果列表中的元素发生了改变(例如排序、添加或删除元素),Vue 将不得不重新渲染整个列表,这会导致性能问题。

为了解决这个问题,Vue 使用 key 属性来标识每个节点的身份。当节点的 key 值发生改变时,Vue 将会认为这是一个新的节点,而不是原来的节点,从而避免了不必要的 DOM 操作,提高了渲染性能。

在 v-for 中使用 key 的语法如下:

1
<div v-for="(item, index) in items" :key="item.id">{{ item.name }}</div>

在这个例子中,我们使用了 item.id 作为每个元素的 key 值,这个值应该是唯一的。当列表中的元素发生改变时,Vue 将会使用 key 值来识别每个元素的身份,从而更高效地更新 DOM。

如果列表中的元素没有唯一的标识符,可以使用元素索引作为 key 值,但是这会影响到性能,因为当列表中的元素发生变化时,Vue 将不得不重新渲染整个列表。

在 Vue 中,使用 key 属性是 v-for 指令中的一个重要的优化技巧,它用于帮助 Vue 识别每个节点的身份,以便在更新 DOM 时能够更高效地渲染元素。Key 值应该是唯一的,可以使用元素的唯一标识符或者索引作为 key 值。

实例代码:

1
2
3
4
5
6
7
<ul>
  <li v-for="(item, index) in booksList" :key="item.id">
    <span>{{ item.name }}</span>
    <span>{{ item.author }}</span>
    <button @click="del(item.id)">删除</button>
  </li>
</ul>

注意:

  1. key 的值只能是字符串 或 数字类型
  2. key 的值必须具有唯一性
  3. 推荐使用 id 作为 key(唯一),不推荐使用 index 作为 key(会变化,不对应)
双向绑定指令

所谓双向绑定就是:

  1. 数据改变后,呈现的页面结果会更新
  2. 页面结果更新后,数据也会随之而变

作用:表单元素(input、radio、select)使用,双向绑定数据,可以快速 获取设置 表单元素内容

语法:v-model=”变量”

需求:使用双向绑定实现以下需求

  1. 点击登录按钮获取表单中的内容
  2. 点击重置按钮清空表单中的内容
68191312573
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
31
<div id="app">
<!--
v-model 可以让数据和视图,形成双向数据绑定
(1) 数据变化,视图自动更新
(2) 视图变化,数据自动更新
可以快速[获取]或[设置]表单元素的内容
-->
账户:<input type="text" v-model="username"> <br><br>
密码:<input type="password" v-model="password"> <br><br>
<button @click="login">登录</button>
<button @click="reset">重置</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
username: '',
password: ''
},
methods: {
login () {
console.log(this.username, this.password)
},
reset () {
this.username = ''
this.password = ''
}
}
})
</script>
动态参数

从 2.6.0 开始,可以使用 方括号 括起来的 JavaScript 表达式作为一个指令的 参数

1
2
3
4
<!--
注意,参数表达式的写法存在一些约束,如之后的“对动态参数表达式的约束”章节所述。
-->
<a v-bind:[attributeName]="url"> ... </a>

这里的 attributeName 会被作为一个 JavaScript 表达式进行动态求值,求得的值将会作为最终的参数来使用。例如,如果你的 Vue 实例有一个 data property attributeName,其值为 "href",那么这个绑定将等价于 v-bind:href

同样地,你可以使用动态参数为一个动态的事件名绑定处理函数:

1
<a v-on:[eventName]="doSomething"> ... </a>

在这个示例中,当 eventName 的值为 "focus" 时,v-on:[eventName] 将等价于 v-on:focus

对动态参数的值的约束

动态参数预期会求出一个字符串,异常情况下值为 null。这个特殊的 null 值可以被显性地用于移除绑定。任何其它非字符串类型的值都将会触发一个警告。

对动态参数表达式的约束

动态参数表达式有一些语法约束,因为某些字符,如空格和引号,放在 HTML attribute 名里是无效的。例如:

1
2
<!-- 这会触发一个编译警告 -->
<a v-bind:['foo' + bar]="value"> ... </a>

变通的办法是使用没有空格或引号的表达式,或用计算属性替代这种复杂表达式。

在 DOM 中使用模板时 (直接在一个 HTML 文件里撰写模板),还需要避免使用大写字符来命名键名,因为浏览器会把 attribute 名全部强制转为小写:

1
2
3
4
5
<!--
在 DOM 中使用模板时这段代码会被转换为 `v-bind:[someattr]`。
除非在实例中有一个名为“someattr”的 property,否则代码不会工作。
-->
<a v-bind:[someAttr]="value"> ... </a>
修饰符

修饰符 (modifier) 是以半角句号 . 指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。例如,.prevent 修饰符告诉 v-on 指令对于触发的事件调用 event.preventDefault()

1
<form v-on:submit.prevent="onSubmit">...</form>

所谓指令修饰符就是通过“.”指明一些指令后缀 不同的后缀封装了不同的处理操作 —> 简化代码

按键修饰符
  • @keyup.enter —>当点击enter键的时候才触发

代码演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div id="app">
<h3>@keyup.enter → 监听键盘回车事件</h3>
<input v-model="username" type="text">
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
username: ''
},
methods: {

}
})
</script>
v-model修饰符
  • v-model.trim —>去除首位空格
  • v-model.number —>转数字
事件修饰符
  • @事件名.stop —> 阻止冒泡
  • @事件名.prevent —>阻止默认行为
  • @事件名.stop.prevent —>可以连用 即阻止事件冒泡也阻止默认行为
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<style>
.father {
width: 200px;
height: 200px;
background-color: pink;
margin-top: 20px;
}
.son {
width: 100px;
height: 100px;
background-color: skyblue;
}
</style>

<div id="app">
<h3>v-model修饰符 .trim .number</h3>
姓名:<input v-model="username" type="text"><br>
年纪:<input v-model="age" type="text"><br>


<h3>@事件名.stop → 阻止冒泡</h3>
<div @click="fatherFn" class="father">
<div @click="sonFn" class="son">儿子</div>
</div>

<h3>@事件名.prevent → 阻止默认行为</h3>
<a @click href="http://www.baidu.com">阻止默认行为</a>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
username: '',
age: '',
},
methods: {
fatherFn () {
alert('老父亲被点击了')
},
sonFn (e) {
// e.stopPropagation()
alert('儿子被点击了')
}
}
})
</script>

缩写

v- 前缀作为一种视觉提示,用来识别模板中 Vue 特定的 attribute。当你在使用 Vue.js 为现有标签添加动态行为 (dynamic behavior) 时,v- 前缀很有帮助,然而,对于一些频繁用到的指令来说,就会感到使用繁琐。同时,在构建由 Vue 管理所有模板的**单页面应用程序 (SPA - single page application) ** 时,v- 前缀也变得没那么重要了。因此,Vue 为 v-bindv-on 这两个最常用的指令,提供了特定简写:

v-bind 缩写
1
2
3
4
5
6
7
8
<!-- 完整语法 -->
<a v-bind:href="url">...</a>

<!-- 缩写 -->
<a :href="url">...</a>

<!-- 动态参数的缩写 (2.6.0+) -->
<a :[key]="url"> ... </a>
v-on 缩写
1
2
3
4
5
6
7
8
<!-- 完整语法 -->
<a v-on:click="doSomething">...</a>

<!-- 缩写 -->
<a @click="doSomething">...</a>

<!-- 动态参数的缩写 (2.6.0+) -->
<a @[event]="doSomething"> ... </a>

它们看起来可能与普通的 HTML 略有不同,但 :@ 对于 attribute 名来说都是合法字符,在所有支持 Vue 的浏览器都能被正确地解析。而且,它们不会出现在最终渲染的标记中。缩写语法是完全可选的

计算属性和侦听器


计算属性

基于现有的数据,计算出来的新属性依赖的数据变化,自动重新计算。

语法
  1. 声明在 computed 配置项中,一个计算属性对应一个函数
  2. 使用起来和普通属性一样使用
  3. 写在computed配置项中
  4. 作为属性,直接使用
    • js中使用计算属性: this.计算属性
    • 模板中使用计算属性:
注意
  1. computed配置项和data配置项是同级
  2. computed中的计算属性虽然是函数的写法,但他依然是个属性
  3. computed中的计算属性不能和data中的属性同名
  4. 使用computed中的计算属性和使用data中的属性是一样的用法
  5. computed中计算属性内部的this依然指向的是Vue实例

模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。例如:

1
2
3
<div id="example">
{{ message.split('').reverse().join('') }}
</div>

在这个地方,模板不再是简单的声明式逻辑。你必须看一段时间才能意识到,这里是想要显示变量 message 的翻转字符串。当你想要在模板中的多处包含此翻转字符串时,就会更加难以处理。

所以,对于任何复杂逻辑,你都应当使用计算属性

示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div id="example">
<p>Original message: "{{ message }}"</p>
<p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
var vm = new Vue({
el: '#example',
data: {
message: 'Hello'
},
computed: {
// 计算属性的 getter
reversedMessage: function () {
// `this` 指向 vm 实例
return this.message.split('').reverse().join('')
}
}
})

结果:

Original message: “Hello”

Computed reversed message: “olleH”

这里我们声明了一个计算属性 reversedMessage。我们提供的函数将用作 property vm.reversedMessage 的 getter 函数:

1
2
3
console.log(vm.reversedMessage) // => 'olleH'
vm.message = 'Goodbye'
console.log(vm.reversedMessage) // => 'eybdooG'

你可以打开浏览器的控制台,自行修改例子中的 vm。vm.reversedMessage 的值始终取决于 vm.message 的值。

你可以像绑定普通 property 一样在模板中绑定计算属性。Vue 知道 vm.reversedMessage 依赖于 vm.message,因此当 vm.message 发生改变时,所有依赖 vm.reversedMessage 的绑定也会更新。而且最妙的是我们已经以声明的方式创建了这种依赖关系:计算属性的 getter 函数是没有副作用 (side effect) 的,这使它更易于测试和理解。

计算属性缓存 vs 方法

在 Vue.js 中,可以使用计算属性 (Computed Properties) 或方法 (Methods) 来处理视图中的逻辑和计算。计算属性是一个可以缓存的属性,它会根据它的依赖缓存结果并在需要时返回缓存结果。而方法是每次调用时都会执行。

你可能已经注意到我们可以通过在表达式中调用方法来达到同样的效果:

1
2
3
4
5
6
7
<p>Reversed message: "{{ reversedMessage() }}"</p>
// 在组件中
methods: {
reversedMessage: function () {
return this.message.split('').reverse().join('')
}
}

我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要 message 还没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。

这也同样意味着下面的计算属性将不再更新,因为 Date.now() 不是响应式依赖:

1
2
3
4
5
computed: {
now: function () {
return Date.now()
}
}

相比之下,每当触发重新渲染时,调用方法将总会再次执行函数。

我们为什么需要缓存?假设我们有一个性能开销比较大的计算属性 A,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有其他的计算属性依赖于 A。如果没有缓存,我们将不可避免的多次执行 A 的 getter!如果你不希望有缓存,请用方法来替代。

下面是计算属性和方法的区别:

  1. 缓存机制

    计算属性具有缓存机制,只有在依赖的数据发生改变时才会重新计算,而方法在每次调用时都会执行。因此,如果需要频繁调用同一个方法,使用计算属性会比方法更高效。

  2. 依赖关系

    计算属性可以依赖于其他的计算属性或响应式数据,当依赖的数据发生变化时,计算属性会重新计算并缓存结果。方法没有缓存机制,每次调用时需要重新计算。因此,如果需要依赖其他的计算属性或响应式数据,使用计算属性会更方便。

  3. 使用方式

    计算属性和方法的使用方式不同。计算属性是定义在 computed 对象中的函数,可以像普通属性一样在模板中直接使用,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div>{{ fullName }}</div>
</template>

<script>
export default {
data() {
return {
firstName: 'John',
lastName: 'Doe',
};
},
computed: {
fullName() {
return this.firstName + ' ' + this.lastName;
},
},
};
</script>

方法是定义在 methods 对象中的函数,需要在模板中使用方法时,需要在模板中调用该方法,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div>{{ getFullName() }}</div>
</template>

<script>
export default {
data() {
return {
firstName: 'John',
lastName: 'Doe',
};
},
methods: {
getFullName() {
return this.firstName + ' ' + this.lastName;
},
},
};
</script>

计算属性和方法都可以用来处理视图中的逻辑和计算。如果需要频繁调用同一个方法,使用计算属性会比方法更高效。如果需要依赖其他的计算属性或响应式数据,使用计算属性会更方便。使用计算属性和方法的方式不同,计算属性可以像普通属性一样在模板中直接使用,而方法需要在模板中调用。

计算属性 vs 侦听属性

vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性

在 Vue.js 中,计算属性 (Computed Properties) 和侦听属性 (Watch Properties) 都可以用来观察数据的变化并作出响应。但是,它们有不同的使用场景和特点。

当你有一些数据需要随着其它数据变动而变动时,你很容易滥用 watch 。然而,通常更好的做法是使用计算属性而不是命令式的 watch 回调。

细想一下这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div id="demo">{{ fullName }}</div>
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar'
},
watch: {
firstName: function (val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function (val) {
this.fullName = this.firstName + ' ' + val
}
}
})

上面代码是命令式且重复的。将它与计算属性的版本进行比较:

1
2
3
4
5
6
7
8
9
10
11
12
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
}
})

下面是计算属性和侦听属性的区别:

  1. 使用场景

    计算属性通常用于处理视图中的逻辑和计算,例如根据数据的不同属性计算新的值。计算属性具有缓存机制,只有在依赖的数据发生改变时才会重新计算。侦听属性通常用于在数据变化时触发异步或开销较大的操作,例如发送 Ajax 请求或进行复杂计算。

  2. 响应方式

    计算属性是一种声明式的响应式编程方式,它的值会随着依赖的数据变化而自动更新。当计算属性所依赖的数据发生变化时,计算属性会重新计算并更新其值。侦听属性是一种命令式的响应式编程方式,它通过手动监听数据的变化并执行相应的操作来响应数据的变化。

  3. 使用方式

    计算属性是定义在 computed 对象中的函数,可以像普通属性一样在模板中直接使用,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <template>
    <div>{{ reversedMessage }}</div>
    </template>

    <script>
    export default {
    data() {
    return {
    message: 'Hello, World!',
    };
    },
    computed: {
    reversedMessage() {
    return this.message.split('').reverse().join('');
    },
    },
    };
    </script>

    侦听属性是定义在 watch 对象中的函数,需要手动监听数据的变化并执行相应的操作,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <template>
    <div>{{ message }}</div>
    </template>

    <script>
    export default {
    data() {
    return {
    message: 'Hello, World!',
    };
    },
    watch: {
    message(newValue, oldValue) {
    console.log(`message changed from ${oldValue} to ${newValue}`);
    },
    },
    };
    </script>

    计算属性和侦听属性都可以用来观察数据的变化并作出响应。计算属性通常用于处理视图中的逻辑和计算,侦听属性通常用于在数据变化时触发异步或开销较大的操作。计算属性是一种声明式的响应式编程方式,侦听属性是一种命令式的响应式编程方式。计算属性可以像普通属性一样在模板中直接使用,而侦听属性需要手动监听数据的变化并执行相应的操作。

计算属性的 setter

在 Vue.js 中,计算属性 (Computed Properties) 可以定义 getter 和 setter,用于在获取和设置属性值时执行特定的操作。计算属性默认只有 getter,不过在需要时你也可以提供一个 setter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ...
computed: {
fullName: {
// getter
get: function () {
return this.firstName + ' ' + this.lastName
},
// setter
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
// ...

现在再运行 vm.fullName = 'John Doe' 时,setter 会被调用,vm.firstNamevm.lastName 也会相应地被更新。

计算属性的 setter 仅在计算属性被设置时才会被调用。这意味着,如果直接修改 firstName 或 lastName,setter 不会被调用。因此,应该始终使用计算属性来获取和设置相关的属性值,以确保 setter 能够正确地执行。

侦听器

在 Vue.js 中,侦听器 (Watcher) 是一种用于监听数据变化并做出响应的机制。当数据发生变化时,侦听器会自动执行相应的函数或表达式,从而实现对数据变化的响应。

虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。

语法
  1. watch同样声明在跟data同级的配置项中

  2. 简单写法: 简单类型数据直接监视

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    data: { 
      words: '苹果',
      obj: {
        words: '苹果'
      }
    },

    watch: {
    // 该方法会在数据变化时,触发执行
      数据属性名 (newValue, oldValue) {
    一些业务逻辑 或 异步操作。
    },
    '对象.属性名' (newValue, oldValue) {
    一些业务逻辑 或 异步操作。
    }
    }
  3. 完整写法:—>添加额外的配置项

    1. deep:true 对复杂类型进行深度监听
    2. immdiate:true 初始化 立刻执行一次
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    data: {
      obj: {
        words: '苹果',
        lang: 'italy'
      },
    },

    watch: {// watch 完整写法
      对象: {
    deep: true, // 深度监视
    immdiate:true,//立即执行handler函数
        handler (newValue) {
          console.log(newValue)
        }
      }
    }

示例
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<div id="watch-example">
<p>
Ask a yes/no question:
<input v-model="question">
</p>
<p>{{ answer }}</p>
</div>
<!-- 因为 AJAX 库和通用工具的生态已经相当丰富,Vue 核心代码没有重复 -->
<!-- 提供这些功能以保持精简。这也可以让你自由选择自己更熟悉的工具。 -->
<script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.13.1/lodash.min.js"></script>
<script>
var watchExampleVM = new Vue({
el: '#watch-example',
data: {
question: '',
answer: 'I cannot give you an answer until you ask a question!'
},
watch: {
// 如果 `question` 发生改变,这个函数就会运行
question: function (newQuestion, oldQuestion) {
this.answer = 'Waiting for you to stop typing...'
this.debouncedGetAnswer()
}
},
created: function () {
// `_.debounce` 是一个通过 Lodash 限制操作频率的函数。
// 在这个例子中,我们希望限制访问 yesno.wtf/api 的频率
// AJAX 请求直到用户输入完毕才会发出。想要了解更多关于
// `_.debounce` 函数 (及其近亲 `_.throttle`) 的知识,
// 请参考:https://lodash.com/docs#debounce
this.debouncedGetAnswer = _.debounce(this.getAnswer, 500)
},
methods: {
getAnswer: function () {
if (this.question.indexOf('?') === -1) {
this.answer = 'Questions usually contain a question mark. ;-)'
return
}
this.answer = 'Thinking...'
var vm = this
axios.get('https://yesno.wtf/api')
.then(function (response) {
vm.answer = _.capitalize(response.data.answer)
})
.catch(function (error) {
vm.answer = 'Error! Could not reach the API. ' + error
})
}
}
})
</script>

结果:

Ask a yes/no question:

I cannot give you an answer until you ask a question!

在这个示例中,使用 watch 选项允许我们执行异步操作 (访问一个 API),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

除了 watch 选项之外,您还可以使用命令式的 vm.$watch API

vm.$watchAPI

vm.$watch 是 Vue 实例的一个方法,用于监听一个表达式或一个函数计算结果的变化。当被监听的表达式或函数返回的值发生变化时,回调函数会被触发。

以下是 vm.$watch 的语法:

1
vm.$watch(expOrFn, callback, [options])

其中,参数的含义如下:

  • expOrFn:要监听的表达式或函数计算结果。可以是一个字符串表示的表达式,也可以是一个返回值的函数。
  • callback:监听到表达式或函数计算结果变化时触发的回调函数。
  • options:可选参数,包括以下选项:
    • deep:是否深度监听对象或数组的变化,默认为 false
    • immediate:是否在监听开始时立即执行回调函数,默认为 false
    • handler:与 callback 作用相同,为了兼容老版本的写法而存在。

vm.$watch 返回一个取消监听的函数,调用该函数可以停止监听。

以下是一个示例:

1
2
3
4
5
6
7
8
9
10
11
12
// 在 Vue 实例中添加一个 data 属性
new Vue({
data: {
message: 'Hello, Vue!'
},
created() {
// 监听 message 属性的变化
this.$watch('message', (newVal, oldVal) => {
console.log(`message changed from ${oldVal} to ${newVal}`);
});
}
});

在上面的示例中,在 Vue 实例创建时使用 this.$watch 监听 message 属性的变化。当 message 属性发生变化时,回调函数会被触发并打印出变化前后的值。

Class 与 Style 绑定

操作元素的 class 列表和内联样式是数据绑定的一个常见需求。因为它们都是 attribute,所以我们可以用 v-bind 处理它们:只需要通过表达式计算出字符串结果即可。不过,字符串拼接麻烦且易错。因此,在将 v-bind 用于 classstyle 时,Vue.js 做了专门的增强。表达式结果的类型除了字符串之外,还可以是对象或数组。

绑定 HTML Class

对象语法

我们可以传给 v-bind:class 一个对象,以动态地切换 class:

1
<div v-bind:class="{ active: isActive }"></div>

上面的语法表示 active 这个 class 存在与否将取决于数据 property isActivetruthiness

你可以在对象中传入更多字段来动态切换多个 class。此外,v-bind:class 指令也可以与普通的 class attribute 共存。当有如下模板:

1
2
3
4
<div
class="static"
v-bind:class="{ active: isActive, 'text-danger': hasError }"
></div>

和如下 data:

1
2
3
4
data: {
isActive: true,
hasError: false
}

结果渲染为:

1
<div class="static active"></div>

isActive 或者 hasError 变化时,class 列表将相应地更新。例如,如果 hasError 的值为 true,class 列表将变为 "static active text-danger"

绑定的数据对象不必内联定义在模板里:

1
2
3
4
5
6
7
<div v-bind:class="classObject"></div>
data: {
classObject: {
active: true,
'text-danger': false
}
}

渲染的结果和上面一样。我们也可以在这里绑定一个返回对象的计算属性 。这是一个常用且强大的模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
<div v-bind:class="classObject"></div>
data: {
isActive: true,
error: null
},
computed: {
classObject: function () {
return {
active: this.isActive && !this.error,
'text-danger': this.error && this.error.type === 'fatal'
}
}
}
数组语法

我们可以把一个数组传给 v-bind:class,以应用一个 class 列表:

1
2
3
4
5
<div v-bind:class="[activeClass, errorClass]"></div>
data: {
activeClass: 'active',
errorClass: 'text-danger'
}

渲染为:

1
<div class="active text-danger"></div>

如果你也想根据条件切换列表中的 class,可以用三元表达式:

1
<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>

这样写将始终添加 errorClass,但是只有在 isActive 是 truthy 时才添加 activeClass

不过,当有多个条件 class 时这样写有些繁琐。所以在数组语法中也可以使用对象语法:

1
<div v-bind:class="[{ active: isActive }, errorClass]"></div>

绑定内联样式

对象语法

v-bind:style 的对象语法十分直观——看着非常像 CSS,但其实是一个 JavaScript 对象。CSS property 名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用引号括起来) 来命名:

1
2
3
4
5
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
data: {
activeColor: 'red',
fontSize: 30
}

直接绑定到一个样式对象通常更好,这会让模板更清晰:

1
2
3
4
5
6
7
<div v-bind:style="styleObject"></div>
data: {
styleObject: {
color: 'red',
fontSize: '13px'
}
}

同样的,对象语法常常结合返回对象的计算属性使用。

数组语法

v-bind:style 的数组语法可以将多个样式对象应用到同一个元素上:

1
<div v-bind:style="[baseStyles, overridingStyles]"></div>
多重值

2.3.0+

从 2.3.0 起你可以为 style 绑定中的 property 提供一个包含多个值的数组,常用于提供多个带前缀的值,例如:

1
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>

这样写只会渲染数组中最后一个被浏览器支持的值。在本例中,如果浏览器支持不带浏览器前缀的 flexbox,那么就只会渲染 display: flex

事件处理

监听事件

可以用 v-on 指令监听 DOM 事件,并在触发时运行一些 JavaScript 代码。

示例:

1
2
3
4
5
6
7
8
9
10
<div id="example-1">
<button v-on:click="counter += 1">Add 1</button>
<p>The button above has been clicked {{ counter }} times.</p>
</div>
var example1 = new Vue({
el: '#example-1',
data: {
counter: 0
}
})

结果:

image-20230712170057845

事件处理方法

然而许多事件处理逻辑会更为复杂,所以直接把 JavaScript 代码写在 v-on 指令中是不可行的。因此 v-on 还可以接收一个需要调用的方法名称。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<div id="example-2">
<!-- `greet` 是在下面定义的方法名 -->
<button v-on:click="greet">Greet</button>
</div>
var example2 = new Vue({
el: '#example-2',
data: {
name: 'Vue.js'
},
// 在 `methods` 对象中定义方法
methods: {
greet: function (event) {
// `this` 在方法里指向当前 Vue 实例
alert('Hello ' + this.name + '!')
// `event` 是原生 DOM 事件
if (event) {
alert(event.target.tagName)
}
}
}
})

// 也可以用 JavaScript 直接调用方法
example2.greet() // => 'Hello Vue.js!'

结果:

image-20230712170147336

内联处理器中的方法

除了直接绑定到一个方法,也可以在内联 JavaScript 语句中调用方法:

1
2
3
4
5
6
7
8
9
10
11
12
<div id="example-3">
<button v-on:click="say('hi')">Say hi</button>
<button v-on:click="say('what')">Say what</button>
</div>
new Vue({
el: '#example-3',
methods: {
say: function (message) {
alert(message)
}
}
})

结果:

image-20230712170252617

有时也需要在内联语句处理器中访问原始的 DOM 事件。可以用特殊变量 $event 把它传入方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
<button v-on:click="warn('Form cannot be submitted yet.', $event)">
Submit
</button>
// ...
methods: {
warn: function (message, event) {
// 现在我们可以访问原生事件对象
if (event) {
event.preventDefault()
}
alert(message)
}
}

事件修饰符

在事件处理程序中调用 event.preventDefault()event.stopPropagation() 是非常常见的需求。尽管我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。

为了解决这个问题,Vue.js 为 v-on 提供了事件修饰符。之前提过,修饰符是由点开头的指令后缀来表示的。

  • .stop
  • .prevent
  • .capture
  • .self
  • .once
  • .passive
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>

<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>

<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>

<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>

<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 -->
<div v-on:click.capture="doThis">...</div>

<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>

使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 v-on:click.prevent.self 会阻止所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击。

2.1.4 新增

1
2
<!-- 点击事件将只会触发一次 -->
<a v-on:click.once="doThis"></a>

不像其它只能对原生的 DOM 事件起作用的修饰符,.once 修饰符还能被用到自定义的组件事件 上。如果你还没有阅读关于组件的文档,现在大可不必担心。

2.3.0 新增

Vue 还对应 addEventListener 中的 passive 选项提供了 .passive 修饰符。

1
2
3
4
<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成 -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<div v-on:scroll.passive="onScroll">...</div>

这个 .passive 修饰符尤其能够提升移动端的性能。

不要把 .passive.prevent 一起使用,因为 .prevent 将会被忽略,同时浏览器可能会向你展示一个警告。请记住,.passive 会告诉浏览器你想阻止事件的默认行为。

按键修饰符

在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on 在监听键盘事件时添加按键修饰符:

1
2
<!-- 只有在 `key``Enter` 时调用 `vm.submit()` -->
<input v-on:keyup.enter="submit">

你可以直接将 KeyboardEvent.key 暴露的任意有效按键名转换为 kebab-case 来作为修饰符。

1
<input v-on:keyup.page-down="onPageDown">

在上述示例中,处理函数只会在 $event.key 等于 PageDown 时被调用。

按键码

keyCode 的事件用法已经被废弃了 并可能不会被最新的浏览器支持。

使用 keyCode attribute 也是允许的:

1
<input v-on:keyup.13="submit">

为了在必要的情况下支持旧浏览器,Vue 提供了绝大多数常用的按键码的别名:

  • .enter
  • .tab
  • .delete (捕获“删除”和“退格”键)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

有一些按键 (.esc 以及所有的方向键) 在 IE9 中有不同的 key 值, 如果你想支持 IE9,这些内置的别名应该是首选。

你还可以通过全局 config.keyCodes 对象自定义按键修饰符别名

1
2
// 可以使用 `v-on:keyup.f1`
Vue.config.keyCodes.f1 = 112

系统修饰键

2.1.0 新增

可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。

  • .ctrl
  • .alt
  • .shift
  • .meta

注意:在 Mac 系统键盘上,meta 对应 command 键 (⌘)。在 Windows 系统键盘 meta 对应 Windows 徽标键 (⊞)。在 Sun 操作系统键盘上,meta 对应实心宝石键 (◆)。在其他特定键盘上,尤其在 MIT 和 Lisp 机器的键盘、以及其后继产品,比如 Knight 键盘、space-cadet 键盘,meta 被标记为“META”。在 Symbolics 键盘上,meta 被标记为“META”或者“Meta”。

例如:

1
2
3
4
5
<!-- Alt + C -->
<input v-on:keyup.alt.67="clear">

<!-- Ctrl + Click -->
<div v-on:click.ctrl="doSomething">Do something</div>

请注意修饰键与常规按键不同,在和 keyup 事件一起用时,事件触发时修饰键必须处于按下状态。换句话说,只有在按住 ctrl 的情况下释放其它按键,才能触发 keyup.ctrl。而单单释放 ctrl 也不会触发事件。如果你想要这样的行为,请为 ctrl 换用 keyCodekeyup.17

.exact 修饰符

2.5.0 新增

.exact 修饰符允许你控制由精确的系统修饰符组合触发的事件。

1
2
3
4
5
6
7
8
<!-- 即使 AltShift 被一同按下时也会触发 -->
<button v-on:click.ctrl="onClick">A</button>

<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button v-on:click.ctrl.exact="onCtrlClick">A</button>

<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button v-on:click.exact="onClick">A</button>
鼠标按钮修饰符

2.2.0 新增

  • .left
  • .right
  • .middle

这些修饰符会限制处理函数仅响应特定的鼠标按钮。

表单输入绑定

基础用法

你可以用 v-model 指令在表单 <input><textarea><select> 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但 v-model 本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。

v-model 会忽略所有表单元素的 valuecheckedselected attribute 的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data 选项中声明初始值。

v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:

  • text 和 textarea 元素使用 value property 和 input 事件;
  • checkbox 和 radio 使用 checked property 和 change 事件;
  • select 字段将 value 作为 prop 并将 change 作为事件。

对于需要使用输入法 (如中文、日文、韩文等) 的语言,你会发现 v-model 不会在输入法组合文字过程中得到更新。如果你也想处理这个过程,请使用 input 事件。

它会根据 控件类型 自动选取 正确的方法 来更新元素

  • 输入框 input:text ——> value
  • 文本域 textarea ——> value
  • 复选框 input:checkbox ——> checked
  • 单选框 input:radio ——> checked
  • 下拉菜单 select ——> value
文本
1
2
<input v-model="message" placeholder="edit me">
<p>Message is: {{ message }}</p>
image-20230712170942174
多行文本
1
2
3
4
<span>Multiline message is:</span>
<p style="white-space: pre-line;">{{ message }}</p>
<br>
<textarea v-model="message" placeholder="add multiple lines"></textarea>
image-20230712171026497
复选框

单个复选框,绑定到布尔值:

1
2
<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{{ checked }}</label>

image-20230712171113631

多个复选框,绑定到同一个数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<br>
<span>Checked names: {{ checkedNames }}</span>
new Vue({
el: '...',
data: {
checkedNames: []
}
})

image-20230712171119116

单选按钮
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div id="example-4">
<input type="radio" id="one" value="One" v-model="picked">
<label for="one">One</label>
<br>
<input type="radio" id="two" value="Two" v-model="picked">
<label for="two">Two</label>
<br>
<span>Picked: {{ picked }}</span>
</div>
new Vue({
el: '#example-4',
data: {
picked: ''
}
})

image-20230712171213136

选择框

单选时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div id="example-5">
<select v-model="selected">
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<span>Selected: {{ selected }}</span>
</div>
new Vue({
el: '...',
data: {
selected: ''
}
})

如果 v-model 表达式的初始值未能匹配任何选项,<select> 元素将被渲染为“未选中”状态。在 iOS 中,这会使用户无法选择第一个选项。因为这样的情况下,iOS 不会触发 change 事件。因此,更推荐像上面这样提供一个值为空的禁用选项。

多选时 (绑定到一个数组):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div id="example-6">
<select v-model="selected" multiple style="width: 50px;">
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<br>
<span>Selected: {{ selected }}</span>
</div>
new Vue({
el: '#example-6',
data: {
selected: []
}
})

v-for 渲染的动态选项:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<select v-model="selected">
<option v-for="option in options" v-bind:value="option.value">
{{ option.text }}
</option>
</select>
<span>Selected: {{ selected }}</span>
new Vue({
el: '...',
data: {
selected: 'A',
options: [
{ text: 'One', value: 'A' },
{ text: 'Two', value: 'B' },
{ text: 'Three', value: 'C' }
]
}
})

值绑定

对于单选按钮,复选框及选择框的选项,v-model 绑定的值通常是静态字符串 (对于复选框也可以是布尔值):

1
2
3
4
5
6
7
8
9
10
<!-- 当选中时,`picked` 为字符串 "a" -->
<input type="radio" v-model="picked" value="a">

<!-- `toggle` 为 true 或 false -->
<input type="checkbox" v-model="toggle">

<!-- 当选中第一个选项时,`selected` 为字符串 "abc" -->
<select v-model="selected">
<option value="abc">ABC</option>
</select>

但是有时我们可能想把值绑定到 Vue 实例的一个动态 property 上,这时可以用 v-bind 实现,并且这个 property 的值可以不是字符串。

复选框
1
2
3
4
5
6
7
8
9
10
<input
type="checkbox"
v-model="toggle"
true-value="yes"
false-value="no"
>
// 当选中时
vm.toggle === 'yes'
// 当没有选中时
vm.toggle === 'no'

这里的 true-valuefalse-value attribute 并不会影响输入控件的 value attribute,因为浏览器在提交表单时并不会包含未被选中的复选框。如果要确保表单中这两个值中的一个能够被提交,(即“yes”或“no”),请换用单选按钮。

单选按钮
1
2
3
<input type="radio" v-model="pick" v-bind:value="a">
// 当选中时
vm.pick === vm.a
选择框的选项
1
2
3
4
5
6
7
<select v-model="selected">
<!-- 内联对象字面量 -->
<option v-bind:value="{ number: 123 }">123</option>
</select>
// 当选中时
typeof vm.selected // => 'object'
vm.selected.number // => 123

修饰符

.lazy

在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步 (除了上述 输入法组合文字时)。你可以添加 lazy 修饰符,从而转为在 change 事件_之后_进行同步:

1
2
<!-- 在“change”时而非“input”时更新 -->
<input v-model.lazy="msg">
.number

如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符:

1
<input v-model.number="age" type="number">

这通常很有用,因为即使在 type="number" 时,HTML 输入元素的值也总会返回字符串。如果这个值无法被 parseFloat() 解析,则会返回原始的值。

.trim

如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符:

1
<input v-model.trim="msg">

组件基础

在 Vue 中,组件是 Vue 实例的可复用部分。组件可以看作是一个自定义元素,包含了 HTML、CSS 和 JavaScript 等相关的代码,可以在不同的应用程序和页面中重复使用。组件的数据和行为都封装在组件内部,实现了高度的模块化和复用性。

Vue 组件可以分为两种类型:

  • 全局组件:全局组件可以在任何 Vue 实例中使用,通过 Vue.component() 方法来定义。全局组件的定义需要在 Vue 实例创建之前完成。
  • 局部组件:局部组件只能在定义它们的 Vue 实例中使用。局部组件的定义可以在 Vue 实例创建时或之后完成。

以下是一个全局组件的示例:

1
2
3
4
5
6
7
8
9
// 定义全局组件
Vue.component('my-component', {
template: '<div>这是一个全局组件</div>'
});

// 创建 Vue 实例
new Vue({
el: '#app'
});

在上面的代码中,我们使用 Vue.component() 方法定义了一个名为 my-component 的组件,在 Vue 实例中可以使用该组件。

以下是一个局部组件的示例:

1
2
3
4
5
6
7
8
9
// 创建 Vue 实例
new Vue({
el: '#app',
components: {
'my-component': {
template: '<div>这是一个局部组件</div>'
}
}
});

我们在 Vue 实例中定义了一个名为 my-component 的组件,在该 Vue 实例中可以使用该组件。

quickstart

步骤
  1. 创建 .vue 文件 (三个组成部分)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <template>
    <div>

    </div>
    </template>

    <script setup>

    </script>

    <style lang="scss" scoped>

    </style>
  2. 在使用的组件内先导入再注册,最后使用

使用方式

当成 HTML 标签使用即可 <组件名></组件名> —— 组件名规范 大驼峰命名法

语法
1
2
3
4
5
6
7
8
9
10
11
// 导入需要注册的组件
import 组件对象 from '.vue文件路径'
import HmHeader from './components/HmHeader'

export default { // 局部注册
  components: {
   '组件名': 组件对象,
    HmHeader:HmHeaer,
HmHeader
}
}

scope 解决样式冲突

写在组件中的样式会 全局生效 → 因此很容易造成多个组件之间的样式冲突问题。

  1. 全局样式: 默认组件中的样式会作用到全局,任何一个组件中都会受到此样式的影响

  2. 局部样式: 可以给组件加上scoped 属性,可以让样式只作用于当前组件

在 Vue 组件中,使用 scoped 属性解决样式冲突的原理是通过在编译时为组件的样式添加一个唯一的属性选择器来实现的。这个属性选择器包含了一个组件的唯一标识符,从而将样式限定在组件的作用域内,避免了样式冲突的问题。

  1. 当前组件内标签都被添加data-v-hash值 的属性
  2. css选择器都被添加 [data-v-hash值] 的属性选择器

最终效果: 必须是当前组件的元素, 才会有这个自定义属性, 才会被这个样式作用到

68230651737

例如,下面是一个使用 scoped 属性定义的组件样式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<div class="my-component">
<p class="message">{{ message }}</p>
</div>
</template>

<style scoped>
.my-component[data-v-1a2b3c] {
background-color: #f1f1f1;
padding: 20px;
}

.message[data-v-1a2b3c] {
color: red;
font-size: 24px;
}
</style>

在上面的代码中,我们定义了一个组件 my-component,并使用 scoped 属性将其样式限定在组件的作用域内。在样式部分,我们为组件的根元素和子元素添加了一个属性选择器 [data-v-1a2b3c],其中 data-v-1a2b3c 是一个唯一的标识符,用于标识当前组件的样式。这样做会使组件的样式只在当前组件内生效,避免了样式冲突的问题。

scoped 属性只会对当前组件的样式生效,而不会影响其子组件或父组件的样式

data 必须是一个函数

Vue 组件中的 data 选项必须是一个函数。这是因为 Vue 组件可能会被复用多次,如果 data 是一个普通的对象,那么所有实例将共享同一个对象,这会导致状态污染和不可预测的行为。而将 data 定义为一个函数,每次创建组件实例时都会返回一个新的数据对象,从而避免了这个问题。

1
2
3
4
5
<div id="components-demo">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>

当我们定义这个 <button-counter> 组件时,你可能会发现它的 data 并不是像这样直接提供一个对象:

1
2
3
data: {
count: 0
}

取而代之的是,一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝:

1
2
3
4
5
data: function () {
return {
count: 0
}
}

如果 Vue 没有这条规则,点击一个按钮就可能会像如下代码一样影响到其它所有实例

image-20230713173928274

组件的组织

通常一个应用会以一棵嵌套的组件树的形式来组织:

Component Tree

例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。

为了能在模板中使用,这些组件必须先注册以便 Vue 能够识别。这里有两种组件的注册类型:全局注册局部注册。至此,我们的组件都只是通过 Vue.component 全局注册的:

1
2
3
Vue.component('my-component-name', {
// ... options ...
})

全局注册的组件可以用在其被注册之后的任何 (通过 new Vue) 新创建的 Vue 根实例,也包括其组件树中的所有子组件的模板中。

Vue 组件可以通过全局注册和局部注册两种方式来使用。全局注册是指在任何 Vue 实例中都可以使用该组件,而局部注册是指只能在某个 Vue 实例或其子组件中使用该组件。

局部注册

局部注册可以使用组件选项中的 components 选项来定义组件。例如,下面是一个局部注册的组件示例:

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
<template>
<div>
<p>{{ message }}</p>
<button @click="count++">{{ count }}</button>
<my-child-component></my-child-component>
</div>
</template>

<script>
import MyChildComponent from './MyChildComponent.vue';

export default {
data() {
return {
message: 'Hello, World!',
count: 0
}
},

// 局部注册子组件
components: {
'my-child-component': MyChildComponent
}
}
</script>

在上面的代码中,我们首先引入了一个子组件 MyChildComponent,并在组件选项中的 components 选项中将其注册为该组件的子组件,从而使得在该组件的模板中可以使用它。

全局注册

全局注册可以使用 Vue.component() 方法来定义组件。例如,下面是一个全局注册的组件示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<div>
<p>{{ message }}</p>
<button @click="count++">{{ count }}</button>
</div>
</template>

<script>
export default {
data() {
return {
message: 'Hello, World!',
count: 0
}
}
}

// 全局注册组件
Vue.component('my-component', MyComponent);
</script>

在上面的代码中,我们首先定义了一个组件 MyComponent,并在其中声明了数据和模板。然后,我们通过 Vue.component() 方法全局注册了该组件,使得在任何 Vue 实例中都可以使用它。

局部注册的组件只能在定义它们的 Vue 实例或其子组件中使用,不能在其他 Vue 实例中使用。但是,通过递归地将子组件作为父组件使用,我们可以构建出复杂的组件树,从而实现更加灵活和可复用的组件设计。

组件通信

在 Vue 中,组件通信是非常重要的,它允许不同的组件之间共享数据和进行交互。Vue 提供了多种组件通信的方式,包括 props、事件、$emit/$on 和 Vuex 状态管理等。

常见的组件通信方式:

  1. Props:父组件可以通过 props 向子组件传递数据和配置,子组件可以在其 props 中定义属性,并从父组件接收数据。这种方式适用于父子组件之间的数据传递和配置。

  2. 事件:子组件可以通过 $emit 触发事件,并将数据传递给父组件,父组件可以通过 v-on 监听子组件的事件,并在事件处理函数中接收数据。这种方式适用于子组件向父组件传递数据或触发某些操作。

  3. $emit/$on:可以使用 $emit 和 $on 方法在任意两个组件之间进行通信。$emit 方法用于触发事件并传递数据,$on 方法用于监听事件并接收数据。这种方式适用于任意两个组件之间的通信。

  4. Vuex:Vuex 是一个专为 Vue.js 应用程序开发的状态管理库,它可以将共享状态抽离出来,以集中管理。通过在不同的组件中读取和修改共享状态,实现了组件之间的通信。这种方式适用于大型应用程序中的状态管理和组件通信。

什么是组件通信?

组件通信,就是指组件与组件之间的数据传递

  • 组件的数据是独立的,无法直接访问其他组件的数据。
  • 想使用其他组件的数据,就需要组件通信
组件之间如何通信
68230890309
组件关系分类
  1. 父子关系
  2. 非父子关系
68231807380
通信解决方案
68231811109
父子通信流程
  1. 父组件通过 props 将数据传递给子组件
  2. 子组件利用 $emit 通知父组件修改更新
68231844456
父向子通信代码示例

父组件通过props将数据传递给子组件

父组件App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<div class="app" style="border: 3px solid #000; margin: 10px">
我是APP组件
<Son :title="msg"></Son>
</div>
</template>

<script>
import Son from './components/Son.vue'
export default {
name: 'App',
data() {
return {
myTitle: '学前端,就来黑马程序员',
}
},
components: {
Son,
},
}
</script>

<style>
</style>

子组件Son.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div class="son" style="border:3px solid #000;margin:10px">
我是Son组件
</div>
</template>

<script>
export default {
name: 'Son-Child',

}
</script>

<style>

</style>

68231871178

父向子传值步骤

  1. 给子组件以添加属性的方式传值
  2. 子组件内部通过props接收
  3. 模板中直接使用 props接收的值
子向父通信代码示例

子组件利用 $emit 通知父组件,进行修改更新

68231896563

子向父传值步骤

  1. $emit触发事件,给父组件发送消息通知
  2. 父组件监听$emit触发的事件
  3. 提供处理函数,在函数的性参中获取传过来的参数
总结
  1. 组件关系分类有哪两种
  2. 父子组件通信的流程是什么?
    1. 父向子
    2. 子向父

Prop

Vue 组件的 prop 是一种用于传递数据和配置组件的机制,它可以让父组件向子组件传递数据并配置子组件的行为。在组件内部,prop 可以像普通的属性一样使用,但其值是由父组件传递的。

以下是一个示例,展示了在 Vue 组件中如何使用 prop

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
31
32
33
34
35
36
37
<!-- 父组件 -->
<template>
<div>
<child-component :message="myMessage"></child-component>
</div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
data() {
return {
myMessage: 'Hello, World!'
}
},

components: {
'child-component': ChildComponent
}
}
</script>

<!-- 子组件 -->
<template>
<div>
<p>{{ message }}</p>
</div>
</template>

<script>
export default {
props: {
message: String
}
}
</script>

在上面的代码中,我们定义了一个父组件和一个子组件。在父组件中,我们使用 v-bind 指令将 myMessage 绑定到子组件的 message 属性上。在子组件中,我们使用 props 选项来定义 message 属性,其类型为 String

在子组件的模板中,我们使用 message 属性来显示父组件传递过来的消息。当父组件中的 myMessage 发生改变时,子组件中的 message 也会相应地更新。

prop 是单向数据流的,即只能从父组件向子组件传递数据,不能在子组件中直接修改 prop 的值。

单个根元素

在 Vue 的模板中,每个组件必须只有一个根元素,这是因为 Vue 在编译模板时会将其转化为一个 render 函数,而 render 函数必须返回一个单独的根元素。

以下是一个示例,展示了在 Vue 组件中如何使用单个根元素:

1
2
3
4
5
6
<template>
<div>
<h1>{{ title }}</h1>
<p>{{ message }}</p>
</div>
</template>

在上面的代码中,我们定义了一个组件,其中包含了一个 div 根元素和两个子元素 h1p。这个组件符合 Vue 模板的要求,因为它只有一个根元素 div

如果在组件的模板中包含了多个根元素,或者根元素不唯一,则会导致编译错误。例如,下面的代码就是不合法的:

1
2
3
4
<template>
<h1>{{ title }}</h1>
<p>{{ message }}</p>
</template>

在上面的代码中,我们试图将 h1p 作为组件的根元素,这是不合法的,因为 Vue 要求每个组件必须只有一个根元素。

虽然 Vue 要求每个组件必须只有一个根元素,但这个根元素可以是任何合法的 HTML 元素,也可以是一个自定义元素。在实际开发中,我们可以根据需要来选择合适的根元素,从而实现更加灵活和可复用的组件设计。

事件总线

在 Vue.js 中,事件总线(Event Bus)是一种用于实现组件之间通信的模式。它是一个空的 Vue 实例,用于充当中央事件总线,可以让所有组件都能够访问它,并向它注册和触发事件。

事件总线的原理很简单,就是在一个单独的 Vue 实例中,使用 $emit 方法触发事件,并使用 $on 方法监听事件。组件之间通过事件总线来传递消息和数据。

image-20230713222306150

以下是一个简单的事件总线示例:

1
2
3
4
5
6
7
// EventBus.js

import Vue from 'vue'

const EventBus = new Vue()

export default EventBus

在上面的代码中,我们创建了一个名为 EventBus 的事件总线,并将其导出。这里我们使用 Vue.js 的实例来实现事件总线。

可以在任何组件中导入 EventBus 并使用 $emit 方法触发事件:

1
2
3
4
5
6
7
8
9
10
11
// ComponentA.vue

import EventBus from './EventBus'

export default {
methods: {
handleClick() {
EventBus.$emit('my-event', 'Hello from Component A')
}
}
}

在上面的代码中,我们在组件 A 中使用 $emit 方法触发了一个名为 my-event 的事件,并将消息 'Hello from Component A' 作为参数传递。

可以在任何组件中导入 EventBus 并使用 $on 方法监听事件:

1
2
3
4
5
6
7
8
9
10
11
// ComponentB.vue

import EventBus from './EventBus'

export default {
created() {
EventBus.$on('my-event', message => {
console.log(message) // 输出:'Hello from Component A'
})
}
}

在上面的代码中,我们在组件 B 中使用 $on 方法监听了名为 my-event 的事件,并在事件处理函数中打印了消息。

通过这种方式,我们可以在组件之间传递消息和数据,实现组件之间的通信。需要注意的是,事件总线是一种全局通信方式,因此需要避免命名冲突和事件滥用。

非父子通信 - provide & inject

在 Vue.js 中,组件之间的通信通常是通过 props 和事件实现的。但是,在某些情况下,组件之间的关系可能不是父子关系,而是祖先和后代之间的关系。这时就需要使用 provideinject API 实现非父子组件之间的通信。

provideinject 是 Vue.js 提供的一种高级选项,用于在祖先组件中向所有后代组件注入数据。它们的使用方式类似于 props 和 props 的传递,但是并不需要通过 props 将数据一层层地传递下去。

image-20230713222840510

语法
  1. 父组件 provide提供数据
1
2
3
4
5
6
7
8
9
10
export default {
  provide () {
    return {
// 普通类型【非响应式】
color: this.color,
// 复杂类型【响应式】
userInfo: this.userInfo,
    }
  }
}

2.子/孙组件 inject获取数据

1
2
3
4
5
6
export default {
  inject: ['color','userInfo'],
  created () {
    console.log(this.color, this.userInfo)
  }
}

以下是一个简单的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!-- Parent.vue -->
<template>
<div>
<child-a></child-a>
<child-b></child-b>
</div>
</template>

<script>
import ChildA from './ChildA.vue'
import ChildB from './ChildB.vue'

export default {
provide: {
message: 'Hello from Parent'
},
components: {
ChildA,
ChildB
}
}
</script>

在上面的代码中,我们定义了一个名为 Parent 的组件,并使用 provide 选项向其所有后代组件注入了一个名为 message 的数据。这样,所有后代组件都可以通过 inject 选项来访问这个数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- ChildA.vue -->
<template>
<div>
<h2>Child A</h2>
<p>{{ message }}</p>
</div>
</template>

<script>
export default {
inject: ['message']
}
</script>

在上面的代码中,我们定义了一个名为 ChildA 的组件,并使用 inject 选项来访问 Parent 组件中提供的 message 数据。这样,ChildA 组件就可以在其模板中使用 message 属性来显示数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- ChildB.vue -->
<template>
<div>
<h2>Child B</h2>
<p>{{ message }}</p>
</div>
</template>

<script>
export default {
inject: ['message']
}
</script>

在上面的代码中,我们定义了另一个名为 ChildB 的组件,并同样使用 inject 选项来访问 Parent 组件中提供的 message 数据。

  • provide提供的简单类型的数据不是响应式的,复杂类型数据是响应式。(推荐提供复杂类型数据)
  • 子/孙组件通过inject获取的数据,不能在自身组件内修改

通过 provideinject API,我们可以实现非父子组件之间的通信。需要注意的是,这种方式会使组件之间的耦合性增加

v-model 原理

原理

v-model本质上是一个语法糖。例如应用在输入框上,就是value属性 和 input事件 的合写

1
2
3
4
5
6
7
<template>
  <div id="app" >
    <input v-model="msg" type="text">

    <input :value="msg" @input="msg = $event.target.value" type="text">
  </div>
</template>

可以分为以下几个步骤:

  1. 首先,Vue.js 会根据指令中的表达式创建一个计算属性。例如,对于 v-model="message",Vue.js 会创建一个计算属性 message
  2. 然后,Vue.js 会根据表单元素或组件的类型,注册一个适当的事件监听器。例如,对于 <input> 元素,Vue.js 会注册一个 input 事件监听器。
  3. 当用户在表单元素或组件中输入或选择数据时,Vue.js 会触发相应的事件,并将用户输入或选择的数据作为参数传递给事件处理函数。
  4. 在事件处理函数中,Vue.js 会更新计算属性的值。例如,对于 v-model="message",Vue.js 会更新计算属性 message 的值为用户输入或选择的数据。
  5. 最后,当计算属性的值发生变化时,Vue.js 会更新绑定的表单元素或组件的值,从而实现数据的双向绑定。
作用

提供数据的双向绑定

  • 数据变,视图跟着变 :value
  • 视图变,数据跟着变 @input

$event 用于在模板中,获取事件的形参

表单类组件封装

在 Vue.js 中,表单是一个非常常见的应用场景。为了提高代码的复用性,我们可以将表单相关的逻辑封装成一个组件,从而使代码更加清晰和简洁。

下面是一个简单的表单组件的封装示例:

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
31
32
33
34
35
36
<template>
<div>
<label :for="id">{{ label }}</label>
<input :type="type" :id="id" :value="value" @input="handleChange">
<span v-if="error" class="error">{{ error }}</span>
</div>
</template>

<script>
export default {
props: {
label: {
type: String,
required: true
},
value: {
type: String,
required: true
},
type: {
type: String,
default: 'text'
},
id: {
type: String,
required: true
},
error: String
},
methods: {
handleChange(event) {
this.$emit('input', event.target.value)
}
}
}
</script>

在上面的代码中,我们定义了一个名为 FormInput 的表单组件,它包含了一个标签、一个输入框和一个错误提示。组件接受了多个 props,包括标签、输入框的值、类型、ID、错误提示等。

在组件的模板中,我们使用了绑定语法 :for:type:id:value 来绑定标签、输入框的类型、ID 和值。同时,我们在输入框上使用了 @input 监听器来监听用户输入,并在事件处理函数中使用 $emit 方法触发一个名为 input 的事件,从而实现了双向数据绑定。

在组件的脚本中,我们使用了 props 选项来定义了组件接受的 props。同时,我们还定义了一个名为 handleChange 的方法,用于监听用户的输入事件,并触发一个名为 input 的自定义事件,从而实现双向数据绑定。

通过封装表单组件,我们可以大大提高代码的复用性,同时也可以使代码更加清晰和简洁。在使用表单组件时,我们只需要传入相应的 props,就可以快速创建一个表单元素。

v-model 优化

v-model其实就是 :value和@input事件的简写

  • 子组件:props通过value接收数据,事件触发 input
  • 父组件:v-model直接绑定数据

可以使用 v-model 优化上述代码,将双向绑定的逻辑封装进组件中,使得在使用该组件时可以更加简洁。

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
31
<template>
<div>
<label :for="id">{{ label }}</label>
<input :type="type" :id="id" :value="value" @input="$emit('input', $event.target.value)">
<span v-if="error" class="error">{{ error }}</span>
</div>
</template>

<script>
export default {
props: {
label: {
type: String,
required: true
},
value: {
type: String,
required: true
},
type: {
type: String,
default: 'text'
},
id: {
type: String,
required: true
},
error: String
}
}
</script>

在上面的代码中,我们将 @input 监听器替换为 $emit 方法,并使用 v-model 来绑定输入框的值,从而实现了双向数据绑定的逻辑。

使用 v-model 优化后,在使用该组件时,就可以直接使用 v-model 绑定组件的值,而无需显式地使用 @input$emit。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<div>
<form-input v-model="message" label="Message" id="message" />
<p>Message: {{ message }}</p>
</div>
</template>

<script>
import FormInput from './FormInput.vue'

export default {
components: {
FormInput
},
data() {
return {
message: ''
}
}
}
</script>

在上面的代码中,我们使用 v-model="message" 来绑定组件的值,从而实现了双向数据绑定的逻辑。

案例

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div class="app">
<BaseSelect></BaseSelect>
</div>
</template>

<script>
import BaseSelect from './components/BaseSelect.vue'
export default {
data() {
return {
selectId: '102',
}
},
components: {
BaseSelect,
},
}
</script>

<style>
</style>

BaseSelect.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div>
<select>
<option value="101">北京</option>
<option value="102">上海</option>
<option value="103">武汉</option>
<option value="104">广州</option>
<option value="105">深圳</option>
</select>
</div>
</template>

<script>
export default {
}
</script>

<style>
</style>

子组件

1
2
3
4
5
6
7
8
9
<select :value="value" @change="handleChange">...</select>
props: {
  value: String
},
methods: {
  handleChange (e) {
    this.$emit('input', e.target.value)
  }
}

父组件

1
<BaseSelect v-model="selectId"></BaseSelect>

.sync 修饰符

.sync修饰符 就是 :属性名@update:属性名 合写

.sync 是 Vue.js 中一个非常有用的修饰符,用于实现父子组件之间的双向数据绑定。它可以简化父子组件之间数据传递的代码,同时提高代码的可读性和可维护性。

可以实现 子组件父组件数据双向绑定,简化代码

简单理解:子组件可以修改父组件传过来的props值

在 Vue.js 中,通常使用 props 将数据从父组件传递到子组件。当子组件需要修改这些数据时,通常需要触发一个事件来通知父组件,从而更新数据。.sync 修饰符可以自动为我们完成这些操作,从而简化了代码。

场景

封装弹框类的基础组件, visible属性 true显示 false隐藏

语法

父组件

1
2
3
4
5
6
7
8
//.sync写法
<BaseDialog :visible.sync="isShow" />
--------------------------------------
//完整写法
<BaseDialog
:visible="isShow"
@update:visible="isShow = $event"
/>

子组件

1
2
3
4
5
props: {
  visible: Boolean
},

this.$emit('update:visible', false)
示例

.sync 修饰符的使用方式非常简单,只需要在父组件中使用 v-bind 绑定一个子组件的 prop,并在子组件中使用 $emit 方法触发一个名为 update:<prop-name> 的事件,即可实现双向数据绑定。例如:

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<!-- 父组件 -->
<template>
<div>
<child-component :message.sync="message" />
<p>Message: {{ message }}</p>
</div>
</template>

<script>
import ChildComponent from './ChildComponent.vue'

export default {
components: {
ChildComponent
},
data() {
return {
message: ''
}
}
}
</script>

<!-- 子组件 -->
<template>
<div>
<input :value="message" @input="handleChange">
</div>
</template>

<script>
export default {
props: {
message: {
type: String,
required: true
}
},
methods: {
handleChange(event) {
this.$emit('update:message', event.target.value)
}
}
}
</script>

在上面的代码中,我们在父组件中使用 v-bind 绑定了一个名为 message 的 prop,并在子组件中使用 $emit 方法触发一个名为 update:message 的事件来更新 message 的值。同时,在父组件中使用 v-model 也可以实现双向数据绑定的效果。

需要注意的是,.sync 修饰符只能用于父子组件之间的双向数据绑定,且仅适用于 props 名称为单个词语的情况。如果需要传递多个参数或 prop 名称包含多个单词,可以使用对象语法来传递多个参数。例如:

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<!-- 父组件 -->
<template>
<div>
<child-component :user.sync="{ name, age }" />
<p>Name: {{ name }}</p>
<p>Age: {{ age }}</p>
</div>
</template>

<script>
import ChildComponent from './ChildComponent.vue'

export default {
components: {
ChildComponent
},
data() {
return {
name: '',
age: ''
}
}
}
</script>

<!-- 子组件 -->
<template>
<div>
<input :value="name" @input="handleNameChange">
<input :value="age" @input="handleAgeChange">
</div>
</template>

<script>
export default {
props: {
user: {
type: Object,
required: true
}
},
methods: {
handleNameChange(event) {
this.$emit('update:user', { ...this.user, name: event.target.value })
},
handleAgeChange(event) {
this.$emit('update:user', { ...this.user, age: event.target.value })
}
}
}
</script>

在上面的代码中,我们使用对象语法来传递多个参数,同时在子组件中使用 $emit 方法触发一个名为 update:user 的事件来更新 user 对象的值。

ref $refs

ref 是 Vue.js 提供的一个特殊属性,用于在模板或渲染函数中给子组件、DOM 元素或其他 Vue 实例注册引用信息。可以使用 $refs 实例属性来访问注册的引用信息。

使用 ref 属性将一个 DOM 元素或组件注册为一个引用,利用ref 和 $refs 可以用于 获取 dom 元素 或 组件实例

查找范围: document → 当前组件内(更精确稳定)

语法

1.给要获取的盒子添加ref属性

1
<div ref="chartRef">我是渲染图表的容器</div>

2.获取时通过 $refs获取 this.$refs.chartRef 获取

1
2
3
mounted () {
  console.log(this.$refs.chartRef)
}

$refs 只在组件渲染完成后才填充,并且它是非响应式的,这意味着不能将 $refs 作为响应式数据来使用。另外,如果在模板中使用了 v-ifv-for 等指令,$refs 中只会包含已经渲染完成的元素或组件,因此需要注意使用时机。

另外,$refs 属性只在当前组件以及其子组件中生效。如果需要在父组件中访问子组件的引用信息,可以在父组件中使用 ref 属性将子组件注册为一个引用,并在子组件的 mounted 钩子函数中将该引用赋值给一个实例变量,从而在父组件中访问子组件的引用

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
31
32
33
34
35
36
37
38
39
<!-- 父组件 -->
<template>
<div>
<my-child ref="childRef" />
<button @click="focusChild">Focus Child Input</button>
</div>
</template>

<script>
import MyChild from './MyChild.vue'

export default {
components: {
MyChild
},
methods: {
focusChild() {
this.$refs.childRef.focusInput()
}
}
}
</script>

<!-- 子组件:MyChild.vue -->
<template>
<div>
<input ref="inputRef" type="text">
</div>
</template>

<script>
export default {
methods: {
focusInput() {
this.$refs.inputRef.focus()
}
}
}
</script>

我们在父组件中使用 ref 属性将 <my-child> 组件注册为一个引用,并在 focusChild 方法中使用 $refs.childRef 来获取该引用并调用其 focusInput 方法,从而将焦点聚焦到子组件的输入框中。在子组件中,我们同样使用 ref 属性将 <input> 元素注册为一个引用,并在 focusInput 方法中使用 $refs.inputRef 来获取该引用并调用其 focus 方法,从而将焦点聚焦到输入框中

异步更新 & $nextTick

需求

编辑标题, 编辑框自动聚焦

  1. 点击编辑,显示编辑框
  2. 让编辑框,立刻获取焦点
68239449534
代码实现
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
31
<template>
<div class="app">
<div v-if="isShowEdit">
<input type="text" v-model="editValue" ref="inp" />
<button>确认</button>
</div>
<div v-else>
<span>{{ title }}</span>
<button @click="editFn">编辑</button>
</div>
</div>
</template>

<script>
export default {
data() {
return {
title: '大标题',
isShowEdit: false,
editValue: '',
}
},
methods: {
editFn() {
// 显示输入框
this.isShowEdit = true
// 获取焦点
this.$refs.inp.focus()
} },
}
</script>
问题

“显示之后”,立刻获取焦点是不能成功的!

原因:Vue 是异步更新DOM (提升性能)

解决方案

$nextTick:等 DOM更新后,才会触发执行此方法里的函数体

语法: this.$nextTick(函数体)

1
2
3
this.$nextTick(() => {
  this.$refs.inp.focus()
})

注意:$nextTick 内的函数体 一定是箭头函数,这样才能让函数内部的this指向Vue实例

Vue 特性、进阶

可复用性

自定义指令

除了核心功能默认内置的指令 (v-modelv-show),Vue 也允许注册自定义指令。注意,在 Vue2.0 中,代码复用和抽象的主要形式是组件。

自定义指令可以让你在DOM元素上添加一些额外的行为,例如:控制元素的可见性、自动聚焦、自动滚动等等。Vue提供了一个directive方法,用于注册全局或局部的自定义指令。

概念

自己定义的指令,可以封装一些DOM操作,扩展额外的功能

语法
  • 全局注册

    1
    2
    3
    4
    5
    6
    7
    //在main.js中
    Vue.directive('指令名', {
      "inserted" (el) {
        // 可以对 el 标签,扩展额外功能
    el.focus()
      }
    })
  • 局部注册

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //在Vue组件的配置项中
    directives: {
      "指令名": {
        inserted () {
          // 可以对 el 标签,扩展额外功能
    el.focus()
        }
      }
    }
  • 使用指令

    注意:在使用指令的时候,一定要先注册再使用,否则会报错
    使用指令语法: v-指令名。如:

    注册指令时不用v-前缀,但使用时一定要加v-前缀

指令中的配置项介绍

inserted() :被绑定元素 插入父节点时调用的 钩子函数

el: 使用指令的 那个 dom 元素

钩子函数

一个指令定义对象可以提供如下几个钩子函数 (均为可选):

  • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。

  • inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。

  • update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。

  • componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。

  • unbind:只调用一次,指令与元素解绑时调用。

接下来我们来看一下钩子函数的参数 (即 elbindingvnodeoldVnode)。

钩子函数参数

指令钩子函数会被传入以下参数:

  • el:指令所绑定的元素,可以用来直接操作 DOM。

  • ```
    binding

    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
    31
    32
    33
    34
    35
    36
    37
    38
    39

    :一个对象,包含以下 property:

    - `name`:指令名,不包括 `v-` 前缀。
    - `value`:指令的绑定值,例如:`v-my-directive="1 + 1"` 中,绑定值为 `2`。
    - `oldValue`:指令绑定的前一个值,仅在 `update` 和 `componentUpdated` 钩子中可用。无论值是否改变都可用。
    - `expression`:字符串形式的指令表达式。例如 `v-my-directive="1 + 1"` 中,表达式为 `"1 + 1"`。
    - `arg`:传给指令的参数,可选。例如 `v-my-directive:foo` 中,参数为 `"foo"`。
    - `modifiers`:一个包含修饰符的对象。例如:`v-my-directive.foo.bar` 中,修饰符对象为 `{ foo: true, bar: true }`。

    - `vnode`:Vue 编译生成的虚拟节点。移步 [VNode API](https://v2.cn.vuejs.org/v2/api/#VNode-接口) 来了解更多详情。

    - `oldVnode`:上一个虚拟节点,仅在 `update` 和 `componentUpdated` 钩子中可用。

    除了 `el` 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 [`dataset`](https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLElement/dataset) 来进行。

    这是一个使用了这些 property 的自定义钩子样例:

    ```js
    <div id="hook-arguments-example" v-demo:foo.a.b="message"></div>
    Vue.directive('demo', {
    bind: function (el, binding, vnode) {
    var s = JSON.stringify
    el.innerHTML =
    'name: ' + s(binding.name) + '<br>' +
    'value: ' + s(binding.value) + '<br>' +
    'expression: ' + s(binding.expression) + '<br>' +
    'argument: ' + s(binding.arg) + '<br>' +
    'modifiers: ' + s(binding.modifiers) + '<br>' +
    'vnode keys: ' + Object.keys(vnode).join(', ')
    }
    })

    new Vue({
    el: '#hook-arguments-example',
    data: {
    message: 'hello!'
    }
    })
动态指令参数

指令的参数可以是动态的。例如,在 v-mydirective:[argument]="value" 中,argument 参数可以根据组件实例数据进行更新!这使得自定义指令可以在应用中被灵活使用。

例如你想要创建一个自定义指令,用来通过固定布局将元素固定在页面上。我们可以像这样创建一个通过指令值来更新竖直位置像素值的自定义指令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div id="baseexample">
<p>Scroll down the page</p>
<p v-pin="200">Stick me 200px from the top of the page</p>
</div>
Vue.directive('pin', {
bind: function (el, binding, vnode) {
el.style.position = 'fixed'
el.style.top = binding.value + 'px'
}
})

new Vue({
el: '#baseexample'
})

这会把该元素固定在距离页面顶部 200 像素的位置。但如果场景是我们需要把元素固定在左侧而不是顶部又该怎么办呢?这时使用动态参数就可以非常方便地根据每个组件实例来进行更新。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<div id="dynamicexample">
<h3>Scroll down inside this section ↓</h3>
<p v-pin:[direction]="200">I am pinned onto the page at 200px to the left.</p>
</div>
Vue.directive('pin', {
bind: function (el, binding, vnode) {
el.style.position = 'fixed'
var s = (binding.arg == 'left' ? 'left' : 'top')
el.style[s] = binding.value + 'px'
}
})

new Vue({
el: '#dynamicexample',
data: function () {
return {
direction: 'left'
}
}
})
对象字面量

如果指令需要多个值,可以传入一个 JavaScript 对象字面量。记住,指令函数能够接受所有合法的 JavaScript 表达式。

1
2
3
4
5
<div v-demo="{ color: 'white', text: 'hello!' }"></div>
Vue.directive('demo', function (el, binding) {
console.log(binding.value.color) // => "white"
console.log(binding.value.text) // => "hello!"
})

自定义指令-指令的值

需求

实现一个 color 指令 - 传入不同的颜色, 给标签设置文字颜色

语法

1.在绑定指令时,可以通过“等号”的形式为指令 绑定 具体的参数值

1
<div v-color="color">我是内容</div>

2.通过 binding.value 可以拿到指令值,指令值修改会 触发 update 函数

1
2
3
4
5
6
7
8
9
10
directives: {
  color: {
    inserted (el, binding) {
      el.style.color = binding.value
    },
    update (el, binding) {
      el.style.color = binding.value
    }
  }
}
代码示例

App.vue

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
<template>
<div>
<!--显示红色-->
<h2 v-color="color1">指令的值1测试</h2>
<!--显示蓝色-->
<h2 v-color="color2">指令的值2测试</h2>
<button>
改变第一个h1的颜色
</button>
</div>
</template>

<script>
export default {
data () {
return {
color1: 'red',
color2: 'blue'
}
}
}
</script>

<style>

</style>

自定义指令 - v-loading 指令的封装

场景

实际开发过程中,发送请求需要时间,在请求的数据未回来时,页面会处于空白状态 => 用户体验不好

需求

封装一个 v-loading 指令,实现加载中的效果

分析

1.本质 loading效果就是一个蒙层,盖在了盒子上

2.数据请求中,开启loading状态,添加蒙层

3.数据请求完毕,关闭loading状态,移除蒙层

实现

1.准备一个 loading类,通过伪元素定位,设置宽高,实现蒙层

2.开启关闭 loading状态(添加移除蒙层),本质只需要添加移除类即可

3.结合自定义指令的语法进行封装复用

1
2
3
4
5
6
7
8
9
.loading:before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background: #fff url("./loading.gif") no-repeat center;
}
准备代码
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
<template>
<div class="main">
<div class="box">
<ul>
<li v-for="item in list" :key="item.id" class="news">
<div class="left">
<div class="title">{{ item.title }}</div>
<div class="info">
<span>{{ item.source }}</span>
<span>{{ item.time }}</span>
</div>
</div>
<div class="right">
<img :src="item.img" alt="">
</div>
</li>
</ul>
</div>
</div>
</template>

<script>
// 安装axios => yarn add axios || npm i axios
import axios from 'axios'

// 接口地址:http://hmajax.itheima.net/api/news
// 请求方式:get
export default {
data () {
return {
list: [],
isLoading: false,
isLoading2: false
}
},
async created () {
// 1. 发送请求获取数据
const res = await axios.get('http://hmajax.itheima.net/api/news')

setTimeout(() => {
// 2. 更新到 list 中,用于页面渲染 v-for
this.list = res.data.data
}, 2000)
}
}
</script>

<style>
.loading:before {
content: '';
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: #fff url('./loading.gif') no-repeat center;
}

.box2 {
width: 400px;
height: 400px;
border: 2px solid #000;
position: relative;
}



.box {
width: 800px;
min-height: 500px;
border: 3px solid orange;
border-radius: 5px;
position: relative;
}
.news {
display: flex;
height: 120px;
width: 600px;
margin: 0 auto;
padding: 20px 0;
cursor: pointer;
}
.news .left {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
padding-right: 10px;
}
.news .left .title {
font-size: 20px;
}
.news .left .info {
color: #999999;
}
.news .left .info span {
margin-right: 20px;
}
.news .right {
width: 160px;
height: 120px;
}
.news .right img {
width: 100%;
height: 100%;
object-fit: cover;
}
</style>
完整

main.js 进行 指令的全局注册

1
2
3
4
5
6
7
8
9
10
Vue.directive('loading', {
inserted(el, binding) {
console.log('inserted 生效')
binding.value ? el.classList.add('loading') : el.classList.remove('loading')
},
update(el, binding) {
console.log('update 生效')
binding.value ? el.classList.remove('loading') : el.classList.add('loading')
},
})

在 组件内 .box 中添加 v-loading 指令,提供 binding

1
<div class="box" v-loading="isLoading">
1
2
3
4
5
6
data() {
return {
list: [],
isLoading: true,
}
},

插槽 slot

概述

在 Vue.js 中,槽的概念用于创建灵活且可重用的组件。插槽允许您在组件模板中定义占位符,可以从父组件插入内容。插槽背后的主要思想是为父组件提供一种将内容注入子组件模板的方法。这允许在不同上下文中使用子组件时实现更大的自定义和灵活性

Vue.js 中有两种类型的槽:命名槽和默认槽。

    1. 命名槽:当您希望在子组件的模板中具有多个插入点时,可以使用命名槽。通过使用带有 name 属性的 <slot> 元素,您可以定义命名槽。例如:
    1
    2
    3
    4
    5
    6
    7
    8
    <child-component>
    <template v-slot:header>
    <!-- Content for the header slot -->
    </template>
    <template v-slot:footer>
    <!-- Content for the footer slot -->
    </template>
    </child-component>

    在子组件的模板中,您可以使用具有相应名称的 <slot> 元素来渲染从父组件传递的内容:

    1
    2
    3
    4
    5
    <div>
    <slot name="header"></slot>
    <!-- Child component's content -->
    <slot name="footer"></slot>
    </div>
  1. 默认插槽:当您有应插入组件但与任何命名插槽不匹配的内容时,将使用默认插槽。它的定义没有名称,使用 v-slot 指令或简写 # 符号。例如:

    1
    2
    3
    4
    5
    <child-component>
    <template v-slot>
    <!-- Content for the default slot -->
    </template>
    </child-component>

    在子组件的模板中,您可以仅使用 <slot> 呈现默认槽内容:

    1
    2
    3
    4
    <div>
    <slot></slot>
    <!-- Child component's content -->
    </div>

在 2.6.0 中,我们为具名插槽和作用域插槽引入了一个新的统一的语法 (即 v-slot 指令)。它取代了 slotslot-scope 这两个目前已被废弃但未被移除且仍在文档中 的 attribute。新语法的由来可查阅这份 RFC

quickstart

作用

让组件内部的一些 结构 支持 自定义

68241021524
需求

将需要多次显示的对话框,封装成一个组件

问题

组件的内容部分,不希望写死,希望能使用的时候自定义。怎么办

插槽的基本语法
  1. 组件内需要定制的结构部分,改用****占位
  2. 使用组件时, ****标签内部, 传入结构替换slot
  3. 给插槽传入内容时,可以传入纯文本、html标签、组件

68241032979

模板

MyDialog.vue

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<template>
<div class="dialog">
<div class="dialog-header">
<h3>友情提示</h3>
<span class="close">✖️</span>
</div>

<div class="dialog-content">
您确定要进行删除操作吗?
</div>
<div class="dialog-footer">
<button>取消</button>
<button>确认</button>
</div>
</div>
</template>

<script>
export default {
data () {
return {

}
}
}
</script>

<style scoped>
* {
margin: 0;
padding: 0;
}
.dialog {
width: 470px;
height: 230px;
padding: 0 25px;
background-color: #ffffff;
margin: 40px auto;
border-radius: 5px;
}
.dialog-header {
height: 70px;
line-height: 70px;
font-size: 20px;
border-bottom: 1px solid #ccc;
position: relative;
}
.dialog-header .close {
position: absolute;
right: 0px;
top: 0px;
cursor: pointer;
}
.dialog-content {
height: 80px;
font-size: 18px;
padding: 15px 0;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
}
.dialog-footer button {
width: 65px;
height: 35px;
background-color: #ffffff;
border: 1px solid #e1e3e9;
cursor: pointer;
outline: none;
margin-left: 10px;
border-radius: 3px;
}
.dialog-footer button:last-child {
background-color: #007acc;
color: #fff;
}
</style>

App.vue

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
<template>
<div>
<MyDialog>
</MyDialog>
</div>
</template>

<script>
import MyDialog from './components/MyDialog.vue'
export default {
data () {
return {

}
},
components: {
MyDialog
}
}
</script>

<style>
body {
background-color: #b3b3b3;
}
</style>
示例
1
2
3
<div class="App">
<my-dialog>this is a test </my-dialog>
</div>
1
2
3
<div class="dialog-content">
<slot></slot>
</div>

插槽内容

Vue 实现了一套内容分发的 API,这套 API 的设计灵感源自 Web Components 规范草案 ,将 <slot> 元素作为承载分发内容的出口。

它允许你像这样合成组件:

1
2
3
<navigation-link url="/profile">
Your Profile
</navigation-link>

然后你在 <navigation-link> 的模板中可能会写为:

1
2
3
4
5
6
<a
v-bind:href="url"
class="nav-link"
>
<slot></slot>
</a>

当组件渲染的时候,<slot></slot> 将会被替换为“Your Profile”。插槽内可以包含任何模板代码,包括 HTML:

1
2
3
4
5
<navigation-link url="/profile">
<!-- 添加一个 Font Awesome 图标 -->
<span class="fa fa-user"></span>
Your Profile
</navigation-link>

甚至其它的组件:

1
2
3
4
5
<navigation-link url="/profile">
<!-- 添加一个图标的组件 -->
<font-awesome-icon name="user"></font-awesome-icon>
Your Profile
</navigation-link>

如果 <navigation-link>template没有包含一个 <slot> 元素,则该组件起始标签和结束标签之间的任何内容都会被抛弃。

编译作用域

当你想在一个插槽中使用数据时,例如:

1
2
3
<navigation-link url="/profile">
Logged in as {{ user.name }}
</navigation-link>

该插槽跟模板的其它地方一样可以访问相同的实例 property (也就是相同的“作用域”),而不能访问 <navigation-link> 的作用域。例如 url 是访问不到的:

关于上面的这个url属性,它指的是父组件中的url属性。在这个例子中,<navigation-link>组件包含了一个具名插槽,但是这个插槽中并没有定义url属性。因此,在这个具名插槽中使用{{ url }}表达式将会返回undefined

1
2
3
4
5
6
7
8
<navigation-link url="/profile">
Clicking here will send you to: {{ url }}
<!--
这里的 `url` 会是 undefined,因为其 (指该插槽的) 内容是
_传递给_ <navigation-link> 的而不是
在 <navigation-link> 组件*内部*定义的。
-->
</navigation-link>

这段说明的意思是,如果你想在一个插槽中使用数据,那么这个插槽可以访问与模板中其他地方相同的实例属性或变量(即相同的“作用域”),但是不能访问包含插槽的组件的作用域。例如,在下面的代码中,插槽中的{{ user.name }}可以访问组件实例中的user属性,但不能访问组件中的url属性:

1
2
3
<navigation-link url="/profile">
Logged in as {{ user.name }}
</navigation-link>

在这个例子中,url属性是在<navigation-link>组件中定义的,而不是在插槽中定义的。因此,插槽中的{{ url }}表达式将会返回undefined

这个说明还提到了一个重要的原则,即“父模板中的所有内容都编译在父作用域中,而子模板中的所有内容都编译在子作用域中”。这意味着在Vue组件中,父组件中的数据和方法可以传递给子组件,而子组件中的数据和方法不会影响父组件。

作为一条规则,请记住:

“父模板中的所有内容都编译在父作用域中,而子模板中的所有内容都编译在子作用域中”。这意味着在Vue组件中,父组件中的数据和方法可以传递给子组件,而子组件中的数据和方法不会影响父组件。

后备内容

通过插槽完成了内容的定制,传什么显示什么, 但是如果不传,则是空白

68241149461

能否给插槽设置 默认显示内容 呢?

插槽的后备内容

封装组件时,可以为预留的 <slot> 插槽提供后备内容(默认内容)。

语法

标签内,放置内容, 作为默认显示内容

68241233912
效果
  • 外部使用组件时,不传东西,则slot会显示后备内容

    68241243265

  • 外部使用组件时,传东西了,则slot整体会被换掉

    68241245902

示例

有时为一个插槽设置具体的后备 (也就是默认的) 内容是很有用的,它只会在没有提供内容的时候被渲染。例如在一个 <submit-button> 组件中:

1
2
3
<button type="submit">
<slot></slot>
</button>

我们可能希望这个 <button> 内绝大多数情况下都渲染文本“Submit”。为了将“Submit”作为后备内容,我们可以将它放在 <slot> 标签内:

1
2
3
<button type="submit">
<slot>Submit</slot>
</button>

现在当我在一个父级组件中使用 <submit-button> 并且不提供任何插槽内容时:

1
<submit-button></submit-button>

后备内容“Submit”将会被渲染:

1
2
3
<button type="submit">
Submit
</button>

但是如果我们提供内容:

1
2
3
<submit-button>
Save
</submit-button>

则这个提供的内容将会被渲染从而取代后备内容:

1
2
3
<button type="submit">
Save
</button>

具名插槽

一个组件内有多处结构,需要外部传入标签,进行定制

68241313487

上面的弹框中有三处不同,但是默认插槽只能定制一个位置,这时候怎么办呢?

概述

有时我们需要多个插槽。例如对于一个带有如下模板的 <base-layout> 组件:

1
2
3
4
5
6
7
8
9
10
11
<div class="container">
<header>
<!-- 我们希望把页头放这里 -->
</header>
<main>
<!-- 我们希望把主要内容放这里 -->
</main>
<footer>
<!-- 我们希望把页脚放这里 -->
</footer>
</div>

对于这样的情况,<slot> 元素有一个特殊的 attribute:name。这个 attribute 可以用来定义额外的插槽:

1
2
3
4
5
6
7
8
9
10
11
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>

一个不带 name<slot> 出口会带有隐含的名字“default”。

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

1
2
3
4
5
6
7
8
9
10
11
12
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>

<p>A paragraph for the main content.</p>
<p>And another one.</p>

<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>

现在 <template> 元素中的所有内容都将会被传入相应的插槽。任何没有被包裹在带有 v-slot<template> 中的内容都会被视为默认插槽的内容。

然而,如果你希望更明确一些,仍然可以在一个 <template> 中包裹默认插槽的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>

<template v-slot:default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>

<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>

任何一种写法都会渲染出:

1
2
3
4
5
6
7
8
9
10
11
12
<div class="container">
<header>
<h1>Here might be a page title</h1>
</header>
<main>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</main>
<footer>
<p>Here's some contact info</p>
</footer>
</div>

注意 v-slot 只能添加在 <template> (只有一种例外情况 ),这一点和已经废弃的 slot attribute 不同。

具名插槽语法
  • 多个slot使用name属性区分名字

    68241339172
  • template配合v-slot:名字来分发对应标签

    68241341192
v-slot的简写

v-slot写起来太长,vue给我们提供一个简单写法 v-slot —> #

示例

子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div class="base-layout">
<header>
<slot name="header"></slot>
</header>

<main>
<slot name="main"></slot>
</main>

<footer>
<slot name="footer"></slot>
</footer>
</div>

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div class="App">
<base-layout>
<template v-slot:header>
<h1>this is header</h1>
</template>
<template v-slot:main>
<h1>this is main</h1>
</template>
<template v-slot:footer>
<h1>this is footer</h1>
</template>
</base-layout>
</div>
</template>

作用域插槽

概述
插槽分类
  • 默认插槽

  • 具名插槽

    插槽只有两种,作用域插槽不属于插槽的一种分类

作用

定义slot 插槽的同时, 是可以传值的。给 插槽 上可以 绑定数据,将来 使用组件时可以用

场景

封装表格组件

68241434213

使用步骤
  1. 给 slot 标签, 以 添加属性的方式传值

    1
    <slot :id="item.id" msg="测试文本"></slot>
  2. 所有添加的属性, 都会被收集到一个对象中

    1
    { id: 3, msg: '测试文本' }
  3. 在template中, 通过 #插槽名= "obj" 接收,默认插槽名为 default

    1
    2
    3
    4
    5
    <MyTable :list="list">
      <template #default="obj">
        <button @click="del(obj.id)">删除</button>
      </template>
    </MyTable>
代码模板

MyTable.vue

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
<template>
<table class="my-table">
<thead>
<tr>
<th>序号</th>
<th>姓名</th>
<th>年纪</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>赵小云</td>
<td>19</td>
<td>
<button>
查看
</button>
</td>
</tr>
<tr>
<td>1</td>
<td>张小花</td>
<td>19</td>
<td>
<button>
查看
</button>
</td>
</tr>
<tr>
<td>1</td>
<td>孙大明</td>
<td>19</td>
<td>
<button>
查看
</button>
</td>
</tr>
</tbody>
</table>
</template>

<script>
export default {
props: {
data: Array
}
}
</script>

<style scoped>
.my-table {
width: 450px;
text-align: center;
border: 1px solid #ccc;
font-size: 24px;
margin: 30px auto;
}
.my-table thead {
background-color: #1f74ff;
color: #fff;
}
.my-table thead th {
font-weight: normal;
}
.my-table thead tr {
line-height: 40px;
}
.my-table th,
.my-table td {
border-bottom: 1px solid #ccc;
border-right: 1px solid #ccc;
}
.my-table td:last-child {
border-right: none;
}
.my-table tr:last-child td {
border-bottom: none;
}
.my-table button {
width: 65px;
height: 35px;
font-size: 18px;
border: 1px solid #ccc;
outline: none;
border-radius: 3px;
cursor: pointer;
background-color: #ffffff;
margin-left: 5px;
}
</style>

App.vue

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
<template>
<div>
<MyTable :data="list"></MyTable>
<MyTable :data="list2"></MyTable>
</div>
</template>

<script>
import MyTable from './components/MyTable.vue'
export default {
data () {
return {
list: [
{ id: 1, name: '张小花', age: 18 },
{ id: 2, name: '孙大明', age: 19 },
{ id: 3, name: '刘德忠', age: 17 },
],
list2: [
{ id: 1, name: '赵小云', age: 18 },
{ id: 2, name: '刘蓓蓓', age: 19 },
{ id: 3, name: '姜肖泰', age: 17 },
]
}
},
components: {
MyTable
}
}
</script>
示例

子组件

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
<template>
<table class="my-table">
<thead>
<tr>
<th>序号</th>
<th>姓名</th>
<th>年纪</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in data" :key="item.id">
<td>{{ index + 1 }}</td>
<td>{{ item.name }}</td>
<td>
{{ item.age }}
</td>
<slot name="func" :row="item"></slot>
</tr>
</tbody>
</table>
</template>

<script>
export default {
props: {
data: Array,
},
}
</script>

App.vue

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
31
32
<template>
<div class="App">
<my-table :data="list">
<template #func="obj">
<button @click="del(obj.row.id)">删除</button>
</template>
</my-table>
</div>
</template>

<script>
import MyTable from './components/MyTable.vue'
export default {
data() {
return {
list: [
{ id: 1, name: '张小花', age: 18 },
{ id: 2, name: '孙大明', age: 19 },
{ id: 3, name: '刘德忠', age: 17 },
],
}
},
components: {
MyTable,
},
methods: {
del(id) {
this.list = this.list.filter((item) => item.id !== id)
},
},
}
</script>
作用域插槽

有时让插槽内容能够访问子组件中才有的数据是很有用的。例如,设想一个带有如下模板的 <current-user> 组件:

1
2
3
<span>
<slot>{{ user.lastName }}</slot>
</span>

我们可能想换掉备用内容,用名而非姓来显示。如下:

1
2
3
<current-user>
{{ user.firstName }}
</current-user>

然而上述代码不会正常工作,因为只有 <current-user> 组件可以访问到 user,而我们提供的内容是在父级渲染的。

为了让 user 在父级的插槽内容中可用,我们可以将 user 作为 <slot> 元素的一个 attribute 绑定上去:

1
2
3
4
5
<span>
<slot v-bind:user="user">
{{ user.lastName }}
</slot>
</span>

绑定在 <slot> 元素上的 attribute 被称为插槽 prop。现在在父级作用域中,我们可以使用带值的 v-slot 来定义我们提供的插槽 prop 的名字:

1
2
3
4
5
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
</current-user>

在这个例子中,我们选择将包含所有插槽 prop 的对象命名为 slotProps,但你也可以使用任意你喜欢的名字。

独占默认插槽的缩写语法

在上述情况下,当被提供的内容只有默认插槽时,组件的标签才可以被当作插槽的模板来使用。这样我们就可以把 v-slot 直接用在组件上:

1
2
3
<current-user v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</current-user>

这种写法还可以更简单。就像假定未指明的内容对应默认插槽一样,不带参数的 v-slot 被假定对应默认插槽:

1
2
3
<current-user v-slot="slotProps">
{{ slotProps.user.firstName }}
</current-user>

注意默认插槽的缩写语法不能和具名插槽混用,因为它会导致作用域不明确:

1
2
3
4
5
6
7
<!-- 无效,会导致警告 -->
<current-user v-slot="slotProps">
{{ slotProps.user.firstName }}
<template v-slot:other="otherSlotProps">
slotProps is NOT available here
</template>
</current-user>

只要出现多个插槽,请始终为所有的插槽使用完整的基于 <template> 的语法:

1
2
3
4
5
6
7
8
9
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>

<template v-slot:other="otherSlotProps">
...
</template>
</current-user>
解构插槽 Prop

作用域插槽的内部工作原理是将你的插槽内容包裹在一个拥有单个参数的函数里:

1
2
3
function (slotProps) {
// 插槽内容
}

这意味着 v-slot 的值实际上可以是任何能够作为函数定义中的参数的 JavaScript 表达式。所以在支持的环境下 (单文件组件 现代浏览器 ),你也可以使用 ES2015 解构 来传入具体的插槽 prop,如下:

1
2
3
<current-user v-slot="{ user }">
{{ user.firstName }}
</current-user>

这样可以使模板更简洁,尤其是在该插槽提供了多个 prop 的时候。它同样开启了 prop 重命名等其它可能,例如将 user 重命名为 person

1
2
3
<current-user v-slot="{ user: person }">
{{ person.firstName }}
</current-user>

你甚至可以定义后备内容,用于插槽 prop 是 undefined 的情形:

1
2
3
<current-user v-slot="{ user = { firstName: 'Guest' } }">
{{ user.firstName }}
</current-user>

路由 router

quickstart

Vue中的路由:路径和组件映射关系

68244304037
作用

修改地址栏路径时,切换显示匹配的组件

说明

Vue 官方的一个路由插件,是一个第三方包

官网

https://v3.router.vuejs.org/zh/

VueRouter的使用(5+2)

固定5个固定的步骤(不用死背,熟能生巧)

  1. 下载 VueRouter 模块到当前工程,版本3.6.5

    1
    yarn add vue-router@3.6.5
  2. main.js中引入VueRouter

    1
    import VueRouter from 'vue-router'
  3. 安装注册

    1
    Vue.use(VueRouter)
  4. 创建路由对象

    1
    const router = new VueRouter()
  5. 注入,将路由对象注入到new Vue实例中,建立关联

    1
    2
    3
    4
    5
    new Vue({
      render: h => h(App),
      router:router
    }).$mount('#app')

当我们配置完以上5步之后 就可以看到浏览器地址栏中的路由 变成了 /#/的形式。表示项目的路由已经被Vue-Router管理了

68247920745

代码示例

main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 路由的使用步骤 5 + 2
// 5个基础步骤
// 1. 下载 v3.6.5
// yarn add vue-router@3.6.5
// 2. 引入
// 3. 安装注册 Vue.use(Vue插件)
// 4. 创建路由对象
// 5. 注入到new Vue中,建立关联


import VueRouter from 'vue-router'
Vue.use(VueRouter) // VueRouter插件初始化

const router = new VueRouter()

new Vue({
render: h => h(App),
router
}).$mount('#app')
两个核心步骤
  1. 创建需要的组件 (views目录),配置路由规则

    68247963966

  2. 配置导航,配置路由出口(路径匹配的组件显示的位置)

    App.vue

    1
    2
    3
    4
    5
    6
    7
    8
    <div class="footer_wrap">
      <a href="#/find">发现音乐</a>
      <a href="#/my">我的音乐</a>
      <a href="#/friend">朋友</a>
    </div>
    <div class="top">
      <router-view></router-view>
    </div>

安装

Vue 2 使用 Vuerouter 3 Vuex 3

Vue 3 使用 Vuerouter 4 Vuex 4

npm
1
npm install vue-router

入门

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script src="https://unpkg.com/vue@2/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router@3/dist/vue-router.js"></script>

<div id="app">
<h1>Hello App!</h1>
<p>
<!-- 使用 router-link 组件来导航. -->
<!-- 通过传入 `to` 属性指定链接. -->
<!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
<router-link to="/foo">Go to Foo</router-link>
<router-link to="/bar">Go to Bar</router-link>
</p>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
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
31
// 0. 如果使用模块化机制编程,导入Vue和VueRouter,要调用 Vue.use(VueRouter)

// 1. 定义 (路由) 组件。
// 可以从其他文件 import 进来
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }

// 2. 定义路由
// 每个路由应该映射一个组件。 其中"component" 可以是
// 通过 Vue.extend() 创建的组件构造器,
// 或者,只是一个组件配置对象。
// 我们晚点再讨论嵌套路由。
const routes = [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar }
]

// 3. 创建 router 实例,然后传 `routes` 配置
// 你还可以传别的配置参数, 不过先这么简单着吧。
const router = new VueRouter({
routes // (缩写) 相当于 routes: routes
})

// 4. 创建和挂载根实例。
// 记得要通过 router 配置参数注入路由,
// 从而让整个应用都有路由功能
const app = new Vue({
router
}).$mount('#app')

// 现在,应用已经启动了!

通过注入路由器,我们可以在任何组件内通过 this.$router 访问路由器,也可以通过 this.$route 访问当前路由:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Home.vue
export default {
computed: {
username() {
// 我们很快就会看到 `params` 是什么
return this.$route.params.username
}
},
methods: {
goBack() {
window.history.length > 1 ? this.$router.go(-1) : this.$router.push('/')
}
}
}

该文档通篇都常使用 router 实例。留意一下 this.$routerrouter 使用起来完全一样。我们使用 this.$router 的原因是我们并不想在每个独立需要封装路由的组件中都导入路由。

要注意,当 <router-link> 对应的路由匹配成功,将自动设置 class 属性值 .router-link-active

动态路由匹配

我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件。例如,我们有一个 User 组件,对于所有 ID 各不相同的用户,都要使用这个组件来渲染。那么,我们可以在 vue-router 的路由路径中使用“动态路径参数”(dynamic segment) 来达到这个效果:

1
2
3
4
5
6
7
8
const router = new VueRouter({
routes: [
{
path: '/user/:id',
component: User
}
]
})

现在呢,像 /user/foo/user/bar 都将映射到相同的路由。

在这个例子中,我们使用了冒号来定义了一个动态路由参数,这个参数的名称是id。这样,当我们访问/user/123这个路径时,这个路径就会匹配到/user/:id这个路由配置中,同时将参数id的值设置为123。因此,我们可以在User组件中通过this.$route.params.id来获取到参数id的值

1
2
3
const User = {
template: '<div>User {{ $route.params.id }}</div>'
}

你可以在一个路由中设置多段“路径参数”,对应的值都会设置到 $route.params 中。例如:

模式 匹配路径 $route.params
/user/:username /user/evan { username: 'evan' }
/user/:username/post/:post_id /user/evan/post/123 { username: 'evan', post_id: '123' }

除了 $route.params 外,$route 对象还提供了其它有用的信息,例如,$route.query (如果 URL 中有查询参数)、$route.hash 等等。

$route.query是Vue Router中用来获取查询参数的一个属性。查询参数是指URL中问号后面的参数,例如/user?id=123&name=John中的id=123&name=John就是查询参数。我们可以使用$route.query来获取这些查询参数的值。

在使用$route.query之前,我们需要先在路由配置中定义查询参数。例如,下面是一个定义了查询参数的路由配置:

1
2
3
4
5
6
7
8
9
const router = new VueRouter({
routes: [
{
path: '/user',
component: User,
props: route => ({ id: route.query.id, name: route.query.name })
}
]
})

在这个路由配置中,我们定义了两个查询参数,分别是idname。这些查询参数可以通过$route.query来获取。在这个例子中,我们使用了props属性来将查询参数作为组件的props传递进去。

使用$route.query获取查询参数的值非常简单,只需要在组件中访问$route.query即可。例如,在上面的路由配置中,我们可以在User组件中通过this.$route.query.idthis.$route.query.name来获取查询参数的值。

需要注意的是,查询参数的值都是字符串类型。如果我们需要将其转换为其他类型,例如数字或布尔值,需要手动进行类型转换。

提醒一下,当使用路由参数时,例如从 /user/foo 导航到 /user/bar原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用

复用组件时,想对路由参数的变化作出响应的话,你可以简单地 watch (监测变化) $route 对象:

1
2
3
4
5
6
7
8
const User = {
template: '...',
watch: {
$route(to, from) {
// 对路由变化作出响应...
}
}
}

或者使用 2.2 中引入的 beforeRouteUpdate 导航守卫

1
2
3
4
5
6
7
const User = {
template: '...',
beforeRouteUpdate(to, from, next) {
// react to route changes...
// don't forget to call next()
}
}
捕获所有路由或 404 Not found 路由

常规参数只会匹配被 / 分隔的 URL 片段中的字符。如果想匹配任意路径,我们可以使用通配符 (*):

1
2
3
4
5
6
7
8
{
// 会匹配所有路径
path: '*'
}
{
// 会匹配以 `/user-` 开头的任意路径
path: '/user-*'
}

当使用通配符 路由时,请确保路由的顺序是正确的,也就是说含有通配符的路由应该放在最后。路由 { path: '*' } 通常用于客户端 404 错误。如果你使用了History 模式,请确保正确配置你的服务器

当使用一个通配符时,$route.params 内会自动添加一个名为 pathMatch 参数。它包含了 URL 通过通配符被匹配的部分:

1
2
3
4
5
6
// 给出一个路由 { path: '/user-*' }
this.$router.push('/user-admin')
this.$route.params.pathMatch // 'admin'
// 给出一个路由 { path: '*' }
this.$router.push('/non-existing')
this.$route.params.pathMatch // '/non-existing'
高级匹配模式

vue-router 使用 path-to-regexp (opens new window) 作为路径匹配引擎,所以支持很多高级的匹配模式,例如:可选的动态路径参数、匹配零个或多个、一个或多个,甚至是自定义正则匹配。查看它的文档 (opens new window) 学习高阶的路径匹配,还有这个例子 (opens new window) 展示 vue-router 怎么使用这类匹配。

匹配优先级

有时候,同一个路径可以匹配多个路由,此时,匹配的优先级就按照路由的定义顺序:路由定义得越早,优先级就越高。

声明式导航

quickstart

实现导航高亮效果

68249204474

如果使用a标签进行跳转,需要给当前跳转的导航加样式,同时要移除上一个a标签的样式

解决方案

vue-router 提供了一个全局组件 router-link (取代 a 标签)

  • 能跳转,配置 to 属性指定路径(必须) 。本质还是 a 标签 ,to 无需 #
  • 能高亮,默认就会提供高亮类名,可以直接设置高亮样式

语法: <router-link to="path的值">发现音乐</router-link>

1
2
3
4
5
6
7
8
9
10
11
<div>
<div class="footer_wrap">
<router-link to="/find">发现音乐</router-link>
<router-link to="/my">我的音乐</router-link>
<router-link to="/friend">朋友</router-link>
</div>
<div class="top">
<!-- 路由出口 → 匹配的组件所展示的位置 -->
<router-view></router-view>
</div>
</div>
通过router-link自带的两个样式进行高亮

使用router-link跳转后,我们发现。当前点击的链接默认加了两个class的值 router-link-exact-activerouter-link-active

我们可以给任意一个class属性添加高亮样式即可实现功能

概述

声明式导航是Vue Router中用于通过组件的名称或路径来进行页面跳转的一种方式。通过Vue Router提供的<router-link>组件来实现的。这个组件会被渲染成一个<a>标签,它的to属性指定了导航目标的路径,点击这个链接会触发路由器的导航操作。

在声明式导航中,我们可以通过使用<router-link>组件或$router.push()方法来实现页面跳转。

可以直接使用路由器的命名路由。例如:

1
<router-link to="/home">Home</router-link>

这个链接会导航到路径为/home的页面。另外,如果你定义了名为home的路由,那么你可以使用它的名称来代替路径:

1
<router-link :to="{ name: 'home' }">Home</router-link>

这个链接会导航到名为home的路由所匹配的页面。

两个类名

当我们使用<router-link></router-link>跳转时,自动给当前导航加了两个类名

68249312105

模糊匹配(用的多)

to=”/my” 可以匹配 /my /my/a /my/b ….

只要是以/my开头的路径 都可以和 to=”/my”匹配到

精确匹配

to=”/my” 仅可以匹配 /my

查询参数传参

在跳转路由时,进行传参

两种方式

  • 查询参数传参
  • 动态路由传参
查询参数传参
  • 如何传参?

    <router-link to="/path?参数名=值"></router-link>

  • 如何接受参数

    固定用法:$router.query.参数名

示例

App.vue

1
2
3
4
5
6
7
8
9
10
<template>
<div id="app">
<div class="link">
<router-link to="/home">首页</router-link>
<router-link to="/search">搜索页</router-link>
</div>

<router-view></router-view>
</div>
</template>

Home.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<div class="home">
<div class="logo-box"></div>
<div class="search-box">
<input type="text" />
<button>搜索一下</button>
</div>
<div class="hot-link">
热门搜索:
<router-link to="/search?key=黑马程序员">黑马程序员</router-link>
<router-link to="/search?key=前端培训">前端培训</router-link>
<router-link to="/search?key=如何成为前端大牛">
如何成为前端大牛
</router-link>
</div>
</div>
</template>

Search.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<div class="search">
<p>搜索关键字: {{ $route.query.key }}</p>
<p>搜索结果:</p>
<ul>
<li>.............</li>
<li>.............</li>
<li>.............</li>
<li>.............</li>
</ul>
</div>
</template>

<script>
export default {
name: 'MyFriend',
created() {
// 在created中,获取路由参数
// this.$route.query.参数名 获取
console.log(this.$route.query.key)
},
}
</script>

main.js

1
2
3
4
5
6
7
8
9
10
11
import Vue from 'vue'
import App from './App.vue'
import router from './router'

Vue.config.productionTip = false

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

router/index.js

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
import Vue from 'vue'
import VueRouter from 'vue-router'

import Home from '@/views/Home'
import Search from '@/views/Search'

Vue.use(VueRouter)

const routes = [
{
path: '/search',
component: Search,
},
{
path: '/home',
component: Home,
},
]

const router = new VueRouter({
routes,
})

export default router

动态路由传参

动态路由是指路由路径中包含可变部分的路由,例如:/user/:id。这种路由可以通过$route.params属性来获取路由参数。例如,在路由配置中定义:

1
2
3
4
5
{
path: '/user/:id',
name: 'user',
component: User
}

在组件中可以通过$route.params属性来获取路由参数,例如:

1
2
3
4
5
6
// User.vue
export default {
created() {
console.log(this.$route.params.id)
}
}

这个组件会在创建时打印出路由参数id的值。

在使用<router-link>组件时,可以使用对象形式的to属性来动态传递路由参数。例如:

1
<router-link :to="{ name: 'user', params: { id: 123 }}">User</router-link>

这个链接会导航到名为user的路由,并且传递了路由参数id的值为123。注意,在使用命名路由时,需要在路由配置中指定name属性。

  • 配置动态路由

    动态路由后面的参数可以随便起名,但要有语义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const router = new VueRouter({
      routes: [
    ...,
        {
    path: '/search/:words',
    component: Search
    }
      ]
    })
  • 配置导航链接

    to=”/path/参数值”

  • 对应页面组件接受参数

    $route.params.参数名

    params后面的参数名要和动态路由配置的参数保持一致

动态路由参数的可选符

search/:words 表示,必须要传参数。如果不传参数,也希望匹配,可以加个可选符”?”

1
2
3
4
5
6
const router = new VueRouter({
  routes: [
...
    { path: '/search/:words?', component: Search }
  ]
})
查询参数传参 VS 动态路由传参
  1. 查询参数传参 (比较适合传多个参数)

    1. 跳转:to=”/path?参数名=值&参数名2=值”
    2. 获取:$route.query.参数名
  2. 动态路由传参 (优雅简洁,传单个参数比较方便)

    1. 配置动态路由:path: “/path/:参数名”
    2. 跳转:to=”/path/参数值”
    3. 获取:$route.params.参数名

    注意:动态路由也可以传多个参数,但一般只传一个

vue 路由重定向

Vue Router提供了重定向功能,可以将一个路径重定向到另一个路径,或者重定向到一个命名路由。在路由配置中,我们可以使用redirect属性来实现重定向。例如:

1
2
3
4
5
6
7
8
{
path: '/home',
redirect: '/'
},
{
path: '/user/:id',
redirect: { name: 'user' }
}

这个配置将/home路径重定向到根路径(/),将/user/:id路径重定向到名为user的路由。另外,重定向也可以是一个函数,可以根据需要返回重定向的路径或者重定向对象。例如:

1
2
3
4
5
6
7
{
path: '/user/:id',
redirect: to => {
// 根据路由参数进行重定向
return { name: 'user', params: { id: to.params.id } }
}
}

这个配置将/user/:id路径重定向到名为user的路由,并且传递了路由参数id的值。需要注意的是,重定向会在路由匹配之前进行,所以重定向的路径不需要在路由配置中定义。

Vue 路由 - 404

作用

当路径找不到匹配时,给个提示页面

位置

404的路由,虽然配置在任何一个位置都可以,但一般都配置在其他路由规则的最后面

语法

path: “*” (任意路径) – 前面不匹配就命中最后这个

1
2
3
4
5
6
7
8
import NotFind from '@/views/NotFind'

const router = new VueRouter({
  routes: [
    ...
    { path: '*', component: NotFind } //最后一个
  ]
})
代码示例

NotFound.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div>
<h1>404 Not Found</h1>
</div>
</template>

<script>
export default {

}
</script>

<style>

</style>

router/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
...
import NotFound from '@/views/NotFound'
...

// 创建了一个路由对象
const router = new VueRouter({
routes: [
...
{ path: '*', component: NotFound }
]
})

export default router

Vue 路由 - 模式设置

路由的路径看起来不自然, 有#,能否切成真正路径形式?

语法
1
2
3
4
const router = new VueRouter({
mode:'histroy', //默认是hash
routes:[]
})

编程式导航

编程时导航是指通过JavaScript代码来实现路由导航的方式。在Vue组件中,可以使用$router对象来访问当前路由器实例,从而实现编程时导航。$router对象提供了一系列方法,例如pushreplacego等,可以用来导航到不同的路由。

Vue提供了丰富的API用于编程式导航,您可以通过JS代码来实现路由的跳转、参数传递等操作。以下是一些常用的Vue Router编程式导航API:

  • $router.push(location: RawLocation, onComplete?: Function, onAbort?: Function): 导航到一个新路由,这个新路由可以是一个字符串路径或者一个描述地址的对象。可以传递一个可选的onComplete回调函数和一个可选的onAbort回调函数,用于在导航完成或者中止后执行一些操作。例如:
1
this.$router.push('/home')
  • $router.replace(location: RawLocation, onComplete?: Function, onAbort?: Function): 导航到一个新路由,与push方法的区别在于,使用replace方法导航时不会留下历史记录,也就是说导航后不能通过浏览器的后退按钮返回到之前的页面。例如:
1
this.$router.replace('/home')
  • $router.go(n: number): 前进或后退到历史记录中的某个位置。例如:
1
2
this.$router.go(-1) // 后退一页
this.$router.go(1) // 前进一页
1
2
3
this.$router.push('/home', () => {
console.log('导航完成')
})

或者:

1
2
3
4
5
this.$router.push('/home').then(() => {
console.log('导航完成')
}).catch(() => {
console.log('导航中止')
})

另外,对于动态路由,也可以通过params属性来传递参数,例如:

1
this.$router.push({ name: 'user', params: { id: 123 }})

这个方法会导航到名为user的路由,并且传递了路由参数id的值为123。需要注意的是,在使用命名路由时,需要在路由配置中指定name属性。

注意:在 Vue 实例中,你可以通过 $router 访问路由实例。因此你可以调用 this.$router.push

想要导航到不同的 URL,可以使用 router.push 方法。这个方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,会回到之前的 URL。

当你点击 <router-link> 时,内部会调用这个方法,所以点击 <router-link :to="..."> 相当于调用 router.push(...)

声明式 编程式
<router-link :to="..."> router.push(...)

该方法的参数可以是一个字符串路径,或者一个描述地址的对象。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 字符串路径
router.push('/users/eduardo')

// 带有路径的对象
router.push({ path: '/users/eduardo' })

// 命名的路由,并加上参数,让路由建立 url
router.push({ name: 'user', params: { username: 'eduardo' } })

// 带查询参数,结果是 /register?plan=private
router.push({ path: '/register', query: { plan: 'private' } })

// 带 hash,结果是 /about#team
router.push({ path: '/about', hash: '#team' })

注意:如果提供了 pathparams 会被忽略,上述例子中的 query 并不属于这种情况。取而代之的是下面例子的做法,你需要提供路由的 name 或手写完整的带有参数的 path

1
2
3
4
5
6
7
8
9
const username = 'eduardo'
// 我们可以手动建立 url,但我们必须自己处理编码
router.push(`/user/${username}`) // -> /user/eduardo
// 同样
router.push({ path: `/user/${username}` }) // -> /user/eduardo
// 如果可能的话,使用 `name` 和 `params` 从自动 URL 编码中获益
router.push({ name: 'user', params: { username } }) // -> /user/eduardo
// `params` 不能与 `path` 一起使用
router.push({ path: '/user', params: { username } }) // -> /user

当指定 params 时,可提供 stringnumber 参数(或者对于可重复的参数 可提供一个数组)。任何其他类型(如 undefinedfalse 等)都将被自动字符串化。对于可选参数 ,你可以提供一个空字符串("")来跳过它。

由于属性 torouter.push 接受的对象种类相同,所以两者的规则完全相同。

router.push 和所有其他导航方法都会返回一个 Promise,让我们可以等到导航完成后才知道是成功还是失败。

path 路径跳转传参
quickstart

点击搜索按钮,跳转需要把文本框中输入的内容传到下一个页面如何实现?

68250272058

两种传参方式

1.查询参数

2.动态路由传参

传参

两种跳转方式,对于两种传参方式都支持:

① path 路径跳转传参

② name 命名路由跳转传参

path路径跳转传参(query传参)

1
2
3
4
5
6
7
8
9
10
//简单写法
this.$router.push('/路径?参数名1=参数值1&参数2=参数值2')
//完整写法
this.$router.push({
  path: '/路径',
  query: {
    参数名1: '参数值1',
    参数名2: '参数值2'
  }
})

接受参数的方式依然是:$route.query.参数名

path路径跳转传参(动态路由传参)

1
2
3
4
5
6
//简单写法
this.$router.push('/路径/参数值')
//完整写法
this.$router.push({
  path: '/路径/参数值'
})

接受参数的方式依然是:$route.params.参数值

注意:path不能配合params使用

在Vue Router中,pathparams是不能同时使用的。如果使用path属性来指定路径,那么路由参数只能通过查询字符串的方式传递。例如:

1
this.$router.push({ path: '/user?id=123' })

这个方法会导航到路径为/user的页面,并且传递了一个名为id、值为123的查询参数。在目标页面中可以通过$route.query属性来获取这个参数的值,例如:

1
2
3
4
5
6
// User.vue
export default {
created() {
console.log(this.$route.query.id)
}
}

这个组件会在创建时打印出查询参数id的值。

如果想要使用路由参数来传递参数,需要在路由配置中指定路径中的参数,例如:

1
2
3
4
5
{
path: '/user/:id',
name: 'user',
component: User
}

这个路由会匹配路径为/user/123的页面,并且可以通过$route.params.id属性来获取路由参数的值。在使用$router.push()方法时,需要使用params属性来传递路由参数。例如:

1
this.$router.push({ name: 'user', params: { id: 123 } })

这个方法会导航到名为user的路由,并且传递了路由参数id的值为123。在目标页面中可以通过$route.params属性来获取这个参数的值,例如:

1
2
3
4
5
6
// User.vue
export default {
created() {
console.log(this.$route.params.id)
}
}

这个组件会在创建时打印出路由参数id的值。

概览

编程式导航可以通过$router.push()方法来实现,其中的location参数可以是一个字符串路径或者一个描述地址的对象。

如果要在路径中传递参数,可以使用字符串路径的方式,例如:

1
this.$router.push('/user/123')

这个方法会导航到路径为/user/123的页面,其中123是路由参数的值。在目标页面中可以通过$route.params属性来获取这个参数的值,例如:

1
2
3
4
5
6
// User.vue
export default {
created() {
console.log(this.$route.params.id)
}
}

这个组件会在创建时打印出路由参数id的值。

如果要使用对象形式的location参数,可以将路径和参数分开设置,例如:

1
this.$router.push({ path: '/user', params: { id: 123 } })

这个方法会导航到路径为/user的页面,并且传递了路由参数id的值为123。需要注意的是,在使用这种方式时,需要在路由配置中指定路径中的参数,例如:

1
2
3
4
5
{
path: '/user/:id',
name: 'user',
component: User
}

这个路由会匹配路径为/user/123的页面,并且可以通过$route.params.id属性来获取路由参数的值。

name 命名路由传参
name 命名路由跳转传参 (query传参)
1
2
3
4
5
6
7
this.$router.push({
  name: '路由名字',
  query: {
    参数名1: '参数值1',
    参数名2: '参数值2'
  }
})
name 命名路由跳转传参 (动态路由传参)
1
2
3
4
5
6
this.$router.push({
  name: '路由名字',
  params: {
    参数名: '参数值',
  }
})
总结

编程式导航,如何跳转传参?

1.path路径跳转

  • query传参

    1
    2
    3
    4
    5
    6
    7
    8
    this.$router.push('/路径?参数名1=参数值1&参数2=参数值2')
    this.$router.push({
      path: '/路径',
      query: {
        参数名1: '参数值1',
        参数名2: '参数值2'
      }
    })
  • 动态路由传参

    1
    2
    3
    4
    this.$router.push('/路径/参数值')
    this.$router.push({
      path: '/路径/参数值'
    })

2.name命名路由跳转

  • query传参

    1
    2
    3
    4
    5
    6
    7
    this.$router.push({
      name: '路由名字',
      query: {
        参数名1: '参数值1',
        参数名2: '参数值2'
      }
    })
  • 动态路由传参 (需要配动态路由)

    1
    2
    3
    4
    5
    6
    this.$router.push({
      name: '路由名字',
      params: {
        参数名: '参数值',
      }
    })

Vuex

看此 笔记 ——> https://blog.cccs7.icu/2023/07/16/Vuex/

  • Title: Vue
  • Author: cccs7
  • Created at: 2023-07-11 15:40:22
  • Updated at: 2023-07-17 23:28:56
  • Link: https://blog.cccs7.icu/2023/07/11/Vue/
  • License: This work is licensed under CC BY-NC-SA 4.0.
 Comments
On this page
Vue