后端小白程序员的Vue学习笔记

文章目录

  • 写在前面
  • Vue
  • 一、什么是 Vue
  • 二、相关技术栈
    • 前端
    • 后端
    • 关于前后端分离
  • 三、入门使用
    • 3.1、Hello,Vue
    • 3.2、Mustache 语法
    • 3.3、插值操作
    • 3.4、属性绑定 v-bind
    • 3.5、条件插值
      • 3.5.1、v-if、v-else、v-else-if
      • 3.5.2、v-show
      • 3.5.3、使用 key 来管理复用元素
    • 3.6、列表渲染 v-for
      • 3.6.1、遍历数组
      • 3.6.2、遍历对象
      • 3.6.3、v-for 添加 key
      • 3.6.4、数组更新检测
    • 3.7、绑定事件 v-on
      • 3.7.1、修饰符
    • 3.8、MVVM 模型
    • 3.9、表单绑定 v-model
      • 3.9.1、基本使用
      • 3.9.2、值绑定
      • 3.9.3、修饰符
  • 三、(补充)Vue 的生命周期
  • 四、组件化
    • 4.1、全局组件与局部组件
    • 4.2、父子组件
    • 4.3、Template 分离写法
    • 4.4、组件复用的问题
    • 4.5、父子组件通信之 props
    • 4.6、父子组建通信之$emit
    • 4.7、父子组件访问
  • 五、(补充)计算属性
    • 5.1、基本使用
    • 5.2、复杂使用
    • 5.3、计算属性 VS. Methods
    • 5.4、计算属性 VS. 侦听属性
  • 六、插槽 slot
    • 6.1、基本使用
    • 6.2、编译作用域
    • 6.3、具名插槽
    • 6.4、作用域插槽
  • >> 请转到学习模块开发与Webpack >>
  • 九、Vue-CLI
    • 9.1、CLI? what? why?
    • 9.2、Vue-CLI 使用
    • 9.3、模板编译与渲染函数(提高)
  • >> 请转到学习Vue-Router >>
  • 十二、动态组件
    • 12.1、入门案例
    • 12.2、keep-alive
  • 十三、混入(Mixin)
    • 13.0、基础介绍
    • 13.1、选项合并
    • 13.2、全局mixin
    • 13.3、局限性
    • 13.2、全局mixin
    • 13.3、局限性

写在前面

本博文仅作为个人学习过程的记录,可能存在诸多错误,希望各位看官不吝赐教,支持错误所在,帮助小白成长!

Vue

一、什么是 Vue

  • 开发者:尤雨溪(中国)
  • 一套用于构建用户界面的渐进式框架,发布于 2014 年 2 月,与其他大型框架不同的是,Vue 被设计为可以自顶向下逐层应用。
  • Vue 核心库只关注视图层(HTML+CSS)。
  • 便于与第三方库(网络通信:axios,页面跳转:vue-router , 状态管理:vuex)或者既有项目整合。

二、相关技术栈

前端

  • HTML(容易)

  • CSS(难点、重点)

    企业中开发,多用 CSS 预处理器,用编程的方式来自动生成输出 CSS

  • JS(重点)

    JS 框架:

    • jQuery

    • Angular(Java 程序员开发)

      将 MVC 搬到了前端,增加了模块化开发的理念,采用 TypeScript(微软)开发

    • React(Facebook 出品)

      提出了虚拟 Dom(Visual Dom)的概念

      需要学 JSX 语言

    • Vue

      渐进式:不要求完全使用全部功能,可以只在项目中只嵌入一部分。

      综合了 Angular 和 React

      特色:属性计算

      强调模块化

      后端小白程序员的Vue学习笔记_第1张图片

    • Axios(前端通信框架)

  • UI 框架

    1. ElementUI(饿了么)
    2. AmazeUI
    3. Bootstrap(Twitter)
    4. Ant-Design(阿里巴巴)

后端

  • NodeJS

    由于过于笨重,作者声称已经放弃了 NodeJS,开始开发新的架构Deno

  • NodeJS 及项目管理工具

    1. Express: NodeJS 框架

    2. NPM:项目综合管理工具,类似于 Java 开发中的 Maven

关于前后端分离

模式也好,技术也罢,没有好坏优劣之分,只有合不合适;

前后端分离的开发思想主要是基于SoC(关注度分离原则),让前后端职责更清晰,分工合作更高效。

三、入门使用

3.1、Hello,Vue

DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Hello Vuetitle>
  head>
  <body>
    <div id="app">{
    {message}}div>
    <script src="../js/vue.js">script>
    <script>
      let app = new Vue({
        el: '#app', data: {
        message: 'Hello, Vue!' } })
    script>
  body>
html>

声明式编程范式!显示与数据分离!

响应式:页面显示会随着数据的改变而改变!

3.2、Mustache 语法

Mustache语法:代码中我们使用{ {message}},进行元素插值。双大括号就是 Mustache 语法的标志!

大括号内支持使用运算符对数据进行处理后显示!

3.3、插值操作

v-once

只会进行一次渲染,后面数据的更新不会触发页面的刷新,但是对象的数据还是会变化!

DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>V Oncetitle>
  head>
  <body>
    <div id="app"><span v-once>{
    {message}}span>div>
    <script src="../js/vue.js">script>
    <script>
      const app = new Vue({
       
        el: '#app',
        data: {
        message: 'this is a readonly message' },
      })
    script>
  body>
html>

代码效果:

后端小白程序员的Vue学习笔记_第2张图片

v-html

将数据按照 html 代码做解析,然后渲染。

与之相反的是:v-pre,显示原生的文本内容,不做任何解析!

DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>V Htmltitle>
  head>
  <body>
    <div id="app">
      <h1>v-html:h1>
      <span v-html="message">span>
      <h1>v-preh1>
      <span v-pre>{
    {message}}span>
      <h1>Mustacheh1>
      <span>{
    {message}}span>
    div>
    <script src="../js/vue.js">script>
    <script>
      const app = new Vue({
       
        el: '#app',
        data: {
        message: '百度一下' },
      })
    script>
  body>
html>

代码效果:

后端小白程序员的Vue学习笔记_第3张图片

v-text

其作用与 mustache 相似,接收一个字符串变量,然后渲染,还是利用上面那个例子,效果如图

后端小白程序员的Vue学习笔记_第4张图片

v-cloak

当 Mustache 语法未被正确解析时,用户可能看到类似{ {message}}的文本元素。

使用 v-cloak,可以判断元素是否渲染成功:{

当数据渲染成功前:v-cloak 作为标签属性存在,

数据渲染成功后:v-cloak 从标签属性清除

}

因此我们可以借助这个特殊属性,使用 css 来装饰对应标签选择不让用户看到原生内容

DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />

    <title>V-Cloaktitle>

    <style>
      [v-cloak] {
       
        display: none;
      }
    style>
  head>

  <body>
    <div id="app">
      <h1>Message:h1>
      <span v-cloak>{
    {message}}span>
    div>
    <script src="../js/vue.js">script>
    <script>
      // 设置延时,2s后创建Vue
      setTimeout(function () {
       
        const app = new Vue({
       
          el: '#app',
          data: {
       
            message: 'Hello World!',
          },
        })
      }, 2000)
    script>
  body>
html>

3.4、属性绑定 v-bind

像控制元素内容一样,动态控制属性值,使用v-bind:xxx="??"将标签内的 xxx 属性绑定到 data 中的??数据上!

<div id="app">
  <a v-for="movie in movies" v-bind:href="movie.src">{
    {movie.name}}<br />a>
div>
<script src="../js/vue.js">script>
<script>
  const app = new Vue({
       
    el: '#app',
    data: {
       
      movies: [
        {
        name: '钢铁侠', src: 'xxxx' },
        {
        name: '美国队长', src: 'xxxx' },
        {
        name: '雷神', src: 'xxxx' },
      ],
    },
  })
script>

可以看到我们的 a 标签的 href 是与 movie 的 src 值绑定的!

v-bind 使用的频率极高,于是官方为其提供了简写版::xxx=??。就可以完成 xxx 属性与 data 中??的值的绑定!

同时官方文档提到了在 2.6 版本后,还提供了动态参数(v-bind、v-on 是支持接收参数的!其绑定的内容就是参数)

也就是说以后绑定的属性或者行为也不用“写死”了,可以通过 data 中的参数进行指定:

<div id="app">
  <a v-bind:[link.attr]="link.attrValue" v-text="link.linkName">a>
div>
<script src="../js/vue.js">script>
<script>
  const app = new Vue({
       
    el: '#app',
    data: {
       
      link: {
       
        linkName: '百度一下',
        attr: 'href',
        attrValue: 'https://www.baidu.com',
      },
    },
  })
script>

可以看到,我们直接通过数据就为 a 标签动态绑定了 href 属性!

再来看看 v-bind 的高阶使用:结合对象动态修改 class:

<style>
  .red {
    color: red;
  }
  .yello {
    color: yellow;
  }style
>
<body>
  <div id="app">
    <h1 :class="{
      'red': true, 'yello': false}">Hello, Worldh1>
  div>
  <script src="../js/vue.js">script>
  <script>
    const app = new Vue({
        el: '#app' })
  script>
body>

通过一个:class = {classname: boolean, classname2: boolean},会将 boolean 为 true 的 class 加入 class 列表。这种写法我们称其为对象语法!


<div id="app">
  <h1 :class="{
      'red': isRed, 'yello': isYello}">Hello, Worldh1>
div>
<script src="../js/vue.js">script>
<script>
  const app = new Vue({
        el: '#app', data: {
        isRed: true, isYello: false } })
script>

这里进行简单优化后,class 的列表的增删与否通过具体的数据isRedisYello进行控制


<div id="app"><h1 :class="getClasses()">Hello, Worldh1>div>
<script src="../js/vue.js">script>
<script>
  const app = new Vue({
       
    el: '#app',
    data: {
       
      isRed: true,
      isYello: false,
    },
    methods: {
       
      getClasses: function () {
       
        this.isRed = false
        this.isYello = true
        return {
        red: this.isRed, yello: this.isYello }
      },
    },
  })
script>

继续优化后,我们的 class 属性值通过一个方法返回一个 object,然后判断是否加入 class。我们还可以通过 v-on 来通过界面交互改变 isRed、isYello 的数据值,以达到改变界面显示的效果!

这种使用会经常应用在实际开发中,请务必熟悉掌握!

v-bind 动态绑定 style

其使用方式和上面的绑定 class 大同小异,我们通过一个 kv 集合对象来动态为标签添加样式!这种操作在组件化开发中很常见!

<div id="app">
  <span :style="{fontSize: '50px', backgroundColor: 'blue'}">{
    {message}}span>
div>
<script src="../js/vue.js">script>
<script>
  const app = new Vue({
        el: '#app', data: {
        message: 'Hello World!' } })
script>

这样写就感觉很鸡肋,然而其真正的用法应该是利用数据控制样式表的变化,或者使用 method 获取样式表。

<div id="app">
  <span :style="{fontSize: spanFontSize + 'px'}">{
    {message}}span>
  <span :style="getBgColor()">{
    {message}}span>
div>
<script src="../js/vue.js">script>
<script>
  const app = new Vue({
       
    el: '#app',
    data: {
        message: 'Hello World!', spanFontSize: 20, spanBgColor: 'blue' },
    methods: {
       
      getBgColor: function () {
       
        return {
        backgroundColor: this.spanBgColor }
      },
    },
  })
script>

有几个注意点

  1. 传统样式表中带-的样式属性(例如font-size等)在进行属性绑定时,属性名作为 key,要使用小驼峰命名法(lower-Camel-Case)。例如fontSize、backgroundColor
  2. k-v 在表示时一定要分清是变量还是字符串!key 作为属性名可以不使用字符串表示,但是 value 如果不加以区分,会导致 vue 解析失败。(例如::style={fontSize: 50px},很明显我们希望 50px 作为属性值被解析,但是 vue 会认为 50px 就是一个变量名,所以需要改写成::style={fontSize: '50px'}

3.5、条件插值

3.5.1、v-if、v-else、v-else-if


  
  

A

B

C

3.5.2、v-show

v-show 使用效果与 v-if 相同,但是 v-show 是使用 css 的 display 属性来控制元素的显示:(v-show 在 template 中不能使用!)

<div id="app">
  
  <h1 v-if="display">{
    {message}}h1>
  
  <h1 v-show="display">{
    {message}}h1>
div>
<script src="../js/vue.js">script>
<script>
  const app = new Vue({
       
    el: '#app',
    data: {
        message: 'hello world', display: false },
  })
script>

后端小白程序员的Vue学习笔记_第5张图片

3.5.3、使用 key 来管理复用元素

Vue 在进行页面渲染时,为了保证效率会最大程度上复用页面元素

我们修改了数据值导致页面重新渲染时,首先会在内存中创建虚拟 DOM,虚拟 DOM 使用diff算法会将旧页面和新页面都有的元素进行复用,然后将渲染完成的虚拟 DOM 渲染回浏览器上。

我们用一个案例来演示一下:

<div id="app">
  <form v-if='loginType === "username"'>
    <label>Usernamelabel>
    <input type="text" placeholder="Please input your username" />
  form>
  <form v-else>
    <label>Emaillabel>
    <input type="text" placeholder="Please input your email" />
  form>
  <button @click="toggleLoginType">Toggle Login Typebutton>
div>
<script src="../js/vue.js">script>
<script>
  const app = new Vue({
       
    el: '#app',
    data: {
        loginType: 'username' },
    methods: {
       
      toggleLoginType() {
       
        if (this.loginType === 'username') {
       
          this.loginType = 'email'
        } else {
       
          this.loginType = 'username'
        }
      },
    },
  })
script>

后端小白程序员的Vue学习笔记_第6张图片

即使我们使用按钮进行登录类型的切换,我们输入框中的内容依旧没有消失。这就很好地说明了我们的输入框(即 input 元素)被复用了,那么 label 元素也就必然被复用了。

可是如果我们想要切换了登录类型后,就清除原来输入的内容,简单说就是不复用这个 input 元素。我们就需要为这个元素加上特定的标识,在 Vue 中 元素的 key 属性是用于区分元素的关键。所以我们只需要给 input 元素绑定一个独一无二的 key 属性,告诉 Vue“它们是两个独立的东西,请不要复用!”

<form v-if='loginType === "username"'>
  <label>Usernamelabel>
  <input
    type="text"
    placeholder="Please input your username"
    :key="'usernameInput'"
  />
form>
<form v-else>
  <label>Emaillabel>
  <input
    type="text"
    placeholder="Please input your email"
    :key="'email-input'"
  />
form>
<button @click="toggleLoginType">Toggle Login Typebutton>

修改后,去试试效果!

后端小白程序员的Vue学习笔记_第7张图片

3.6、列表渲染 v-for

3.6.1、遍历数组

遍历时,第二个可选参数 index 表示元素在数组中的下标!


  
  • { {letter.element}}-->{ {index}}

后端小白程序员的Vue学习笔记_第8张图片

v-for 也会监听列表的变化,如果列表发生了变化会触发页面的更新!

v-for 还支持for-of,更接近原生 JavaScript 的遍历。

3.6.2、遍历对象

除了遍历数组,还支持使用 v-for 遍历数组,遍历时有三个参数:{value, name, index},依次为属性值、属性名、属性下标!后两个参数为可选参数!

遍历的顺序与对象属性声明的顺序相同!

<div v-for="(value, name, index) in object">
  {
    { index }}. {
    { name }}: {
    { value }}
div>
<script src="../js/vue.js">script>
<script>
  new Vue({
       
    el: '#v-for-object',
    data: {
       
      object: {
       
        title: 'How to do lists in Vue',
        author: 'Jane Doe',
        publishedAt: '2016-04-10',
      },
    },
  })
script>

后端小白程序员的Vue学习笔记_第9张图片

3.6.3、v-for 添加 key

这是 Vue 推荐我们在使用 v-for 的时候所做的操作。

当我们不为遍历元素绑定 key 的时候(默认),当列表插入值后,导致列表的数据顺序变化后,不会移动 DOM 元素来匹配列表的数据项。即列表的数据与 DOM 中的元素是没有绑定的!它会从发生了变化的位置挨个更新元素的值。

但是当我们使用:key为元素绑定一个 key 以后,并且确保 key 与数据是可以做到一一对应的(index 无效),列表数据就可以与元素进行绑定。可以便于 Vue 追踪这个元素。当列表变化后,Vue 就可以通过元素进行复用和元素重排序来完成更高效的渲染工作。

注意一下,key 尽量不要使用 index,因为 index 无法保证数据变化时与数据保持对应关系!

3.6.4、数组更新检测

并不是所有的数组更新都会触发页面重渲染(例如直接通过下标修改数组)。(响应式更新)

支持更新检测的数组方法有:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

以上的方法属于数组变更方法,即是在原数组的数据上进行变动。当然也有非变更方法:

例如:filter()concat()slice()。这些方法调用后会返回一个新的数组,当我们将新数组赋值给原数据时,**页面会在保证元素重用最大化下进行重新渲染!**即相同的 DOM 元素会被保留重用!而不是丢弃原有所有 DOM 元素,将新的数组进行渲染到整个列表。

3.7、绑定事件 v-on

<body>
  <div id="p1">
    
    <button v-on:click="showMsg">Click Mebutton>
  div>
  <script src="../vue-js/vue.js">script>
  <script type="text/javascript">
    var vm = new Vue({
       
      el: '#p1',
      data: {
        message: 'hello Vue' },
      methods: {
       
        showMsg: function () {
       
          alert(this.message)
        },
      },
    })
  script>
body>

v-on: click=“xxx” 可缩写为 @click="xxx"

我们的事件监听回调函数是可以接收参数的!

如果我们的回调函数是有参数要求的,当你的绑定事件时,省略了函数调用的小括号,那么将默认将触发的事件作为第一个参数传入:

<div id="app">
  
  <button @click="showMessage">点我button>
div>
<script src="../js/vue.js">script>
<script>
  const app = new Vue({
       
    el: '#app',
    methods: {
       
      showMessage(message) {
       
        console.log(message)
      },
    },
  })
script>

于是控制台的输出效果就是:

image-20210515160155554

3.7.1、修饰符

一个事件我们也可以分为很多状态,修饰符就用于指出一个指令以特殊的方式进行绑定。通过在事件后加上.xxx来为事件加上修饰符。

@click.stop:停止事件冒泡

<div id="app" @click="clickDiv">
  text-content <button @click="clickBtn">点我button>
div>
<script src="../js/vue.js">script>
<script>
  const app = new Vue({
       
    el: '#app',
    methods: {
       
      clickDiv() {
       
        console.log('div was clicked!')
      },
      clickBtn() {
       
        console.log('btn was clicked!')
      },
    },
  })
script>

这样的代码,通常情况下当我们点击按钮的时候,会触发 clickBtn 回调函数执行,然后事件会向上冒泡,然后引起 clickDiv 回调函数执行!

后端小白程序员的Vue学习笔记_第10张图片

可是我们希望我们的按钮点击事件不要向上冒泡,就仅仅只触发 clickBtn 回调函数。那就对绑定click事件做一个特殊的修饰:绑定click.stop事件,就会有效阻止事件冒泡:


<button @click="clickBtn">点我button>

<button @click.stop="clickBtn">点我button>
后端小白程序员的Vue学习笔记_第11张图片

@click.prevent 阻止事件的默认操作。

对于一个表单来说,submit 按钮按下后会触发默认动作,将数据提交到对应的 web 服务器

<form action="https://www.baidu.com/" method="post">
  <label for="username">
    <input type="text" id="username" placeholder="Please Input Username..." />
  label>
  <input type="submit" value="拦截,我要手动提交" @click.prevent="onSubmit" />
  <input type="submit" value="自动提交" />
form>

而我们这里的.prevent标签就可以阻止这个默认动作,然后我们就可以选择将数据进行手动的提交。

{keyCode|keyAlias}监听键盘的某一个键的具体事件(指定一个键可以使用前对应的键码或者键别名)。

tips: 一般键盘的事件有:键被按下keydown、键弹起keyUp、键按住keypress

所以说@keyup.enter(enter 是回车键的 Alias),就是监听 enter 键被按下的事件。

<input
  type="text"
  placeholder="Please Input Anything"
  @keyup.enter="inputFinished()"
/>
<script src="../js/vue.js">script>
<script>
  const app = new Vue({
       
    el: '#app',
    methods: {
       
      inputFinished() {
       
        console.log('Input finished!')
      },
    },
  })
script>

当我们完成输入后,按下回车并松开时,就会执行这个回调函数。除此以外,鼠标键系统修饰键的事件也可以被监听!


<input v-on:keyup.alt.67="clear" />

<input v-on:keyup.alt.c="clear" />

对于一些特殊的键,可以通过 Vue 进行全局配置 keyCode 的别名:

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

鼠标按键:

后端小白程序员的Vue学习笔记_第12张图片

除了这些以外,还有很多修饰符,在未来的学习应用中我们会慢慢遇到!以下是官方给出的案例:


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


<div v-on:click.self="doThat">...div>

3.8、MVVM 模型

  • Model-View-ViewMode,一种软件架构设计模式。
  • 事件驱动编程方式
  • 源自于 MVC 模式

将前端的视图层(View)[Html,CSS,Template],与 ViewModel[JavaScript]实现双向绑定,ViewModel 又可以通过 Ajax 和 Json 与服务端建立联系,从而从后端拿到数据,并动态修改前端视图,而不再需要频繁去修改前端的 View 的模板。

视图状态和行为都封装在 ViewModel 里,这样使得 ViewModel 可以完整的去描述 View 层。由于实现了双向绑定,又得益于 JS 的即时编译运行的动态特性,View 的内容会由 ViewModel 实时地展现,而不必再使用原生的 JS 去操作 Dom 元素去更新 View。

MVVM 的核心就是:DOM 监听与数据绑定

它是连接 view 和 model 的桥梁。它有两个方向:

一是将【模型】转化成【视图】,即将后端传递的数据转化成所看到的页面。实现的方式是:数据绑定。

二是将【视图】转化成【模型】,即将所看到的页面转化成后端的数据。实现的方式是:DOM 事件监听。

这两个方向都实现的,我们称之为数据的双向绑定。

后端小白程序员的Vue学习笔记_第13张图片

3.9、表单绑定 v-model

前面的例子只是实现了 View 绑定 ViewModel 实时更新,而在有一些用户可以操作的地方可以实现,Model 随 View 变化实时更新,例如文本框输入,下拉框,单选框,多选框等…

使用 v-model 于 ViewModel 对象实现双向绑定。

3.9.1、基本使用

<body>
  <div id="div1">
    <input type="text" id="text" value="1111" v-model="message" />
    输入的是:{
    {message}}
    <p>
      性别 <input type="radio" value="" v-model="sex" /><input type="radio" value="" checked v-model="sex" />女
      选择的是:{
    {sex}}
    p>
    <p>
      省份
      <select v-model="province">
        <option value="">option>
        <option value="湖北" selected>湖北option>
        <option value="湖南">湖南option>
        <option value="上海">上海option>
        <option value="广东">广东option>
        <option value="江苏">江苏option>
      select>
      {
    {province}}
    p>
  div>
  <script src="../vue-js/vue.js">script>
  <script type="text/javascript">
    var vm = new Vue({
       
      el: '#div1',
      data: {
        message: '输入名字', sex: '男', province: '' },
    })
  script>
body>

注意点:

当使用了 v-model 双向绑定时,表单的一些默认值都会失效,例如 text 的 value,radio 的 checked,select 中 option 的 selected 都会被忽略 ,而是从绑定的 Vue 实例中的 data 进行取值渲染。

使用 v-model 绑定表单后,input 的输入元素会抛出事件:

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

使用类似@input可以绑定一个输入事件,然后调用具体的回调方法。

还有一个注意点:当使用通过输入法组合文字的语言中(例如:日文、韩文、中文),在通过输入法组合文字的过程中是不会触发 v-model 刷新的!如果要处理这个过程,请使用input事件!

后端小白程序员的Vue学习笔记_第14张图片

在使用下拉框进行绑定时,若 data 中输出的初始值没有匹配到任何一个可选值,就会被渲染为“未选中”的状态:

后端小白程序员的Vue学习笔记_第15张图片

因此推荐在候选项目中加上一个空值的禁用选项:

<div id="app">
  <form>
    <label for="ans"
      >select a letter
      <select id="ans" v-model="answer">
        
        <option value="" disabled>请选择option>
        <option>Aoption>
        <option>Boption>
        <option>Coption>
        <option>Doption>
      select>
    label>
  form>
div>
<script src="../js/vue.js">script>
<script>
  const app = new Vue({
        el: '#app', data: {
        answer: '' } })
script>

后端小白程序员的Vue学习笔记_第16张图片

当 select 支持多选的时候,绑定的数据应该是一个数组!!

并且结合我们之前的v-forv-bind还可以实现动态显示候选项

3.9.2、值绑定

上面的单选框、多选框、下拉框的选项值都是我们“写死”的。而我们大多情况下,我们是希望选项值是动态绑定我们数据中以及准备好的数据,或者是更多样的写法(绑定的 value 不一定是字符串!)。

如果要使选项值动态绑定 Vue 实例中的数据,使用 v-bind、v-for 就可以实现了:

<div id="app">
  <h2>您的性别是: {
    {sexSelect.sex}}h2>
  <h2>您的爱好有: {
    {hobbies}}h2>
  <h2>您的职业是: {
    {identity}}h2>
  <div style="border: black solid 2px; display: inline-block">
    <form style="margin: 10px">
      请选择您的性别:
      <label>
        <input type="radio" :value="sexOptions[0]" v-model="sexSelect" /><input type="radio" :value="sexOptions[1]" v-model="sexSelect" />label>
      <br />
      请勾选您的爱好:
      <label v-for="item of optionHobbies" :for="item">
        <input
          type="checkbox"
          :value="item"
          :id="item"
          v-model="hobbies"
        />{
    {item}}
      label>
      <br />
      请选择您的职业:
      <label for="identity">
        <select id="identity" v-model="identity">
          <option value="" disabled>请选择option>
          <option v-for="option of optionIdentities" :value="option">
            {
    {option}}
          option>
        select>
      label>
    form>
  div>
div>
<script src="../js/vue.js">script>
<script>
  const app = new Vue({
       
    el: '#app',
    data: {
       
      sexSelect: '',
      sexOptions: [{
        sex: '男' }, {
        sex: '女' }],
      hobbies: [],
      optionHobbies: ['音乐', '电影', '运动', '编程', '游戏', '绘画', '阅读'],
      identity: '',
      optionIdentities: ['学生', '教育从业者', '职员', '自由职业者'],
    },
  })
script>

代码中有很多看似花哨无用的写法(例如性别的 value 绑定使用了对象),但是其实在实际开发中某些情况下大有用处,这里只是演示一些特殊用法!

3.9.3、修饰符

好的,我们又见面了。之前我们说过 v:on 在绑定事件时,可以通过特殊的方式控制事件。我们表单的输入也可以通过修饰符来控制数据绑定的过程。

.lazy

类似于“懒加载”,默认情况下 text 的输入会触发 input 事件(排除输入法组合文字的情况),页面会实时进行数据渲染。但是 v-model 加上.lazy修饰符后,只有当触发了 change 事件后才会同步并渲染数据。

<h2>您的输入的内容是: {
    {inputContent}}h2>
<div>
  <form>
    请输入:
    <label for="textInput">
      <input type="text" id="textInput" v-model.lazy="inputContent" />
    label>
  form>
div>

后端小白程序员的Vue学习笔记_第17张图片

当输入窗口失焦、或者我们按下回车。就会触发 change 事件,然后才会进行数据同步并渲染。略微减轻了浏览器的压力。

.number

在表单输入中,我们通常使用 input 元素并将 type 设置为 number,在输入时就会限制只能输入数字。但是存在一个问题就是,表单在解析数据的时候会将用户的输入转为字符创,所以我们通过 v-model 取到的数据虽然值是数字但是类型是字符串!

后端小白程序员的Vue学习笔记_第18张图片

使用.number修饰符就可以轻松解决这个问题。

<div>
  <form>
    请输入一个数字
    <label> <input type="number" v-model.number="number" /> label>
  form>
div>
<h2>{
    {number}}==>{
    {typeof number}}h2>

后端小白程序员的Vue学习笔记_第19张图片

会自动将你的输入转化为 number 类型。在开发中这非常有用!

.trim

不用多说了,会将输入内容的左右两端多余空格去除

代码略。

image-20210516112609436

所有的修饰符都是可以串联使用的!!!


三、(补充)Vue 的生命周期

后端小白程序员的Vue学习笔记_第20张图片

上图就是 Vue 官网给出我们的 Vue 的完整生命周期,红色框则是我们可以定义钩子函数的位置

例如beforeMount就会 Vue 实例被挂载前,触发执行:

挂载 mount:将 Vue 实例装载到对应的 DOM 元素的动作,我们称其为挂载。挂载时会替换所有 vue 使用 el 属性控制 dom 标签内的数据。挂载完成后,页面会一致监听数据变化,当数据变化页面也会实时刷新。

DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Vue Mounttitle>
  head>
  <body>
    <div id="app"><span>{
    {year}}span>div>
    <script src="../js/vue.js">script>
    <script>
      const app = new Vue({
       
        el: '#app',
        data: {
        year: 2020 },
        beforeMount: function () {
       
          this.year++
          console.log(`Now is ${ this.year}`)
        },
      })
    script>
  body>
html>

我们这里的 beforeMount,在生命周期图中处于挂载之前,所以 year 数据会先自增,然后被挂载显示到页面上!


四、组件化

一张图看懂组件化

后端小白程序员的Vue学习笔记_第21张图片

组件化开发的步骤:

  1. 创建组件构造器:Vue.extend() (Vue2.0 后很少见)
  2. 注册组件:Vue.component()
  3. 使用组件:在 Vue 实例作用范围内使用已注册的组件。

直接使用 Vue.component() 注册组件

<div id="app"><list-component>list-component>div>
<script src="../js/vue.js">script>
<script>
  // 自定义的组件
  Vue.component('list-component', {
       
    template: `
  • { {item}}
`
, data() { return { balls: ['篮球', '网球', '乒乓球'] } }, }) const app = new Vue({ el: '#app', })
script>

演示效果:

后端小白程序员的Vue学习笔记_第22张图片


需要注意的几点:官方给组件的定义是可复用的 Vue 实例

所以我们的注册工作主要是将一个自定义 Vue 对象变为可复用的。

Vue.component()需要两个参数:

  1. 组件名,即我们以后希望通过什么标签来使用这个组件(代码风格指南中,强烈要求组件名使用多个单词,并使用连词符连接!或者也可以使用大驼峰命名法,但是在 DOM 中(非字符串模板)使用时只有前者是有效的!
  2. 组件的参数。其中的内容与我们创建的 Vue 实例对象类似,data、computed、methods 等(除 el,为根实例独有),但是他们都是服务于当前组件的 template 的,并不是通过 el 挂载到某个 DOM 元素上进行使用!所以要求有所不同,后面再说。可以使用一个 JavaScript 对象来定义组件元素
var ComponentA = {
     
  /* ... */
}
var ComponentB = {
     
  components: {
     
    'component-a': ComponentA,
  },
  //...
}

组件的使用注意事项:

**一定是在 Vue 实例的作用范围内使用,组件才是生效的!**例图中第二个位置我们使用组件,并没有生效,就因为其位置超出 Vue 实例的作用范围即

...

后端小白程序员的Vue学习笔记_第23张图片

4.1、全局组件与局部组件

上面我们提到了组件的有效范围,那就不得不提一下全局组件和局部组件。

首先,无论是全局组件还是局部组件都只能在 Vue 实例的作用范围内生效!!

后端小白程序员的Vue学习笔记_第24张图片

这里就引出了我们的全局组件。注意到我们开始定义组件的时候,是直接在 js 文件的根下创建的。所以说这个组件就是一个全局组件,在所有 new 的 Vue 实例控制范围内下均可使用!并且可以被其他组件直接使用!

后端小白程序员的Vue学习笔记_第25张图片

但是有时候,我们希望组件只能在我们特定的 Vue 实例控制范围内使用!(例如,只想 list-component 只能在 id 为 app 的 div 内使用,其他位置均无法使用。即这个组件只受限在一个 Vue 实例内使用)那就要使用我们的局部组件了。

注册局部组件:

const app = new Vue({
     
  el: '#app',
  components: {
     
    'app-component': {
     
      template: `
      

我是app独有的组件!

`
, }, }, })

我们直 接在 Vue 实例内部的components属性下注册模板,那么这个模板的就只能在当前的 Vue 实例的作用范围内使用!

后端小白程序员的Vue学习笔记_第26张图片

**同属于一个实例对象的组件是不能相互引用的!**如果要使用,需要结合我们下面要学习的父子组件!

4.2、父子组件

前面我们在注册局部组件时,是在 Vue 实例中 components 属性中进行注册的。那么组件内部应该也可以再注册组件。

那么两个组件的关系就变为了父子关系:

<div id="app">
  <parent-component>parent-component>
  <hr />
  
  <child-component>child-component>
div>
<script src="../js/vue.js">script>
<script>
  Vue.component('parent-component', {
       
    template: `

SuperHeroes

`
, components: { 'child-component': { template: `
  • 钢铁侠
  • 雷神
  • 美国队长
`
, }, }, }) const app = new Vue({ el: '#app' })
script>

演示效果:

后端小白程序员的Vue学习笔记_第27张图片

==注意点:==子组件不可单独使用,当其在父组件进行注册使用时,就已经将 template 与父组件的 template 编译为一个整体了。

4.3、Template 分离写法

有没有感觉我们在写注册组件的 template 时特别费力?!下面我们来使用集中 template 分离的写法。

在写之前,需要注意一个 template 的编写规则:==template 只能有一个根元素!==即一个 template 代码的所有元素必须被包含在一个根元素内:


<div>
  <h1>h1>
  <form>...form>
  
div>


<div>
  <h1>h1>
div>
<div>
  <p>....p>
div>

这样错误的写法,会导致页面在渲染的时候丢失内容!

后端小白程序员的Vue学习笔记_第28张图片

解决方法:就是在所有元素的最外层套上一个根标签。

下面我们正式开始学习 template 分离写法:

<script type="text/x-handlebars-template" id="template01">
  <div> <h2>分离Template01</h2> <p>爽爽爽!!</p> </div>
script>
<script>
  const app = new Vue({
       
    el: '#app',
    components: {
        template01: {
        template: '#template01' } },
  })
script>

在注册组件的时候,template 属性直接使用 id 选择器,选中对应的模板代码即可,但是会报红。或者可以直接写 id, 并且不用引号包裹(请看下面的代码示例)

直接使用

<template id="template02">
  <div>
    <h2>分离Template02h2>
    <p>冲冲冲!!p>
  div>template
>
<script>
  const app = new Vue({
       
    el: '#app',
    components: {
       
      mytemplate02: {
       
        // 直接通过id引用
        template: template02,
      },
    },
  })
script>

引用方法同样有两种:

  1. id 选择器
  2. 直接用 id 作为 template 属性值

4.4、组件复用的问题

为什么 data 必须是函数?!(直击灵魂)

如果我们 data 不使用函数,在运行的时候,控制台会显示 Vue 的警告信息:

image-20210516163018012

官网也给出了为什么 Vue 需要这条规则的原因:[为什么组件的 data 必须是函数?](组件基础 — Vue.js (vuejs.org))

我们抽取组件的初心本就是提高代码的复用率,让组件化的代码更易于管理。那么我们就应该保证组件在使用时的独立性,一个组件的变化尽量不去影响其他外部组件的变化。所以在复用组件的时候,我们更希望的是每个组件维护一份数据的独立拷贝!

所以我们使用函数来定义数据,那么组件每次复用时拿到的 data 都是一份份独立的拷贝。每个组件对数据的操作不会影响到其他组件!

<div id="app">
  <counter>counter>
  <hr />
  <counter>counter>
  <hr />
  <counter>counter>
div>
<script src="../js/vue.js">script>
<script>
  Vue.component('counter', {
       
    template: `

current number:{ {number}}

`
, data() { return { number: 0 } }, methods: { increment() { this.number++ }, }, }) const app = new Vue({ el: '#app' })
script>

示例效果:

后端小白程序员的Vue学习笔记_第29张图片

4.5、父子组件通信之 props

现在我们了解到了在组件中数据 data 是以函数的方式出现。但是 data 只能是在我们组件内部使用,一般我们的组件是需要从外面拿数据进来显示的,那么就涉及到父子组件的通信问题。

说一个特别常见的场景:我们现在有一个大的组件,其作用是显示一个文章列表。在这组件内部,我们要通过复用一个小的组件来显示每篇文章的标题、与简述内容。

我们不可能针对每一篇文章去写一个组件,然后将数据在 data 中写死。我们更希望通过组件复用来完成这个任务。所以最好的实现就是我们的文章数据都存放在外部这个大组件中,然后通过特殊方式依次传递给内部的小组件进行显示。

那么就要说一说组件中一个重要的属性了props,在这个属性中可以存放一些组件 template 中需要使用的属性名。当外部为这个属性传值以后,template 中使用了属性的位置都会被替换为传入的属性值!在使用属性的时候可以像使用 data 中的数据一样利用 mustache 语法。例如:

后端小白程序员的Vue学习笔记_第30张图片

我们可以先将属性名就想象为真实数据直接在 template 中使用,当父组件向这两个属性传值的时候,template 中使用属性名的位置就会直接替换为对应的真实值!

下面我们演示父组件如何对子组进行传值:

<div id="app">
  <blog-component
    v-for="blog in blogs"
    :key="blog.id"
    :title="blog.title"
    :content="blog.content"
  >
  blog-component>
div>
<script src="../js/vue.js">script>
<template id="blog-template">
  <div style="margin: 10px; border: black solid 2px; display: inline-block">
    <h3>{
    {title}}h3>
    <hr />
    <p>{
    {content}}p>
  div>template
>
<script>
  const app = new Vue({
       
    el: '#app',
    data: {
       
      blogs: [
        {
       
          id: 1,
          title: '菜鸟的成长之路',
          content: '讲述一个菜鸟在互联网艰辛耕作的历程...',
        },
        {
       
          id: 2,
          title: '大牛带你学Vue',
          content: '大厂leader手把手教你学Vue,从入门到精通...',
        },
        {
       
          id: 3,
          title: '数据库调优的伤心泪',
          content: '资深数据库开发大佬让你从数据库小白进阶调优能手...',
        },
      ],
    },
    components: {
       
      'blog-component': {
       
        template: '#blog-template',
        props: ['title', 'content'],
      },
    },
  })
script>

数据在我们的父组件中,我们在调用子组件时,通过v:bind:xxx="yy.zz"将属性值绑定到了组件的属性上。实现了父组件向子组件传递数据,子组件动态绑定外部数据。

实现效果:

image-20210516212108213

刚才也说了在进行传值的时候,一个属性是可接收任何值的,那我们把上面的例子修改一下:

<div id="app">
  <blog-component v-for="blog in blogs" :key="blog.id" :blog="blog">
  blog-component>
div>
<script src="../js/vue.js">script>
<template id="blog-template">
  <div style="margin: 10px; border: black solid 2px; display: inline-block">
    <h3>{
    {blog.title}}h3>
    <hr />
    <p>{
    {blog.content}}p>
  div>
template>
<script>
  const app = new Vue({
       
    el: '#app',
    data: {
       
      blogs: [
        // 略,同上
      ],
    },
    components: {
       
      'blog-component': {
       
        template: '#blog-template',
        props: ['blog'],
      },
    },
  })
script>

可以看到我们直接给 blog 属性传了一个对象,在 template 内部,解析这个对象的具体内容。

上面我们在声明props使用的字符串数组,对于组件来说这些属性就只是一个单纯的数组,具体拿到什么数据我们压根都不知道,这导致别人来用我们的组件的时候,不知道如何传值。所以官方更推荐我们对属性进行具体的描述,例如加上属性值类型:

后端小白程序员的Vue学习笔记_第31张图片

更加高阶的操作还包括,我们对属性加上一些验证选项:[Props 验证](Prop — Vue.js (vuejs.org))

后端小白程序员的Vue学习笔记_第32张图片

这里只截取了小部分,更详细的用法请参考官方文档。

单向数据流

简单来说就是,当父子组件的 props 存在依赖关系时,父组件的 props 会影响子组件的 props 变化。但是反过来则不行,是为了防止内部数据的变化影响了父级组件的转态,导致分析难度增加。

并且在父组件更新后,所有子组件的 props 都会刷新,所以不推荐在内部组件修改 props!

首先我们来验证一下这个说法:(肯定是对的,但是眼见为实)

<div id="app">
  <h2 style="display: inline-block">父组件中的值为:{
    {pageIndex}}h2>
  <button @click="pageIndex--" :disabled="pageIndex === 1">-1button>
  <button @click="pageIndex++">+1button>
  <hr />
  <child-component :page="pageIndex">child-component>
div>
<script src="../js/vue.js">script>
<template id="overview-template">
  <div>
    当前在
    <button @click="page--" :disabled="page === 1">上一页button><span>{
    {page}}span><button @click="page++">下一页button>
  div>
template>
<script>
  Vue.component('child-component', {
       
    template: '#overview-template',
    props: {
       
      page: Number,
    },
  })
  const app = new Vue({
       
    el: '#app',
    data: {
       
      pageIndex: 1,
    },
  })
script>

这里 Vue 实例可以视为父组件,而我们注册的组件视为子组件。

下图中,我们尝试通过按钮来修改父组件的数据,并且成功引起了子组件中 props 的变化:

后端小白程序员的Vue学习笔记_第33张图片

可是当我们试图通过子组件修改 props 从而影响父级组件时:

后端小白程序员的Vue学习笔记_第34张图片

发现父组件并没有受到影响,并且控制台中出现了 Vue 给出的警告信息:

image-20210516235233695

原文:Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop’s value. Prop being mutated: “page”

告诉我们**避免直接修改一个 props 的值,因为它会在父组件重新渲染的时候被覆盖掉!**下图就演示一下这个覆盖过程:

后端小白程序员的Vue学习笔记_第35张图片

并且 Vue 给出了建议:

而是使用一个基于 prop‘s value 的 data 或者计算属性。

官方的文档上也给出了这个解决方案的实现:

使用 data:

后端小白程序员的Vue学习笔记_第36张图片

使用计算属性:

后端小白程序员的Vue学习笔记_第37张图片

那么我们基于这个解决方案来改一改我们的代码:

<template id="overview-template">
  <div>
    当前在
    <button @click="pageNum--" :disabled="page === 1">上一页button><span>{
    {pageNum}}span><button @click="pageNum++">下一页button>
  div>template
>
<script>
  Vue.component('child-component', {
       
    template: '#overview-template',
    props: {
       
      page: Number,
    },
    // 使用基于prop的data
    data() {
       
      return {
       
        pageNum: this.page,
      }
    },
  })
  const app = new Vue({
       
    el: '#app',
    data: {
       
      pageIndex: 1,
    },
  })
script>

使用了 data 作为中间值隔断与父组件中数据的联系,这样父子组件中的对数据的修改阻隔了。

所以如果你希望父组件的修改能影响子组件,就请限制子组件内对 prop 进行修改!

有一个容易忽略的问题是:当 prop 传递的数据是对象或者数组的时候,由于 JavaScript 中传递对象和数组是传递的引用,那么子组件中对其的修改,将会影响到父组件中数据。但是不会引起 Vue 警告?!

后端小白程序员的Vue学习笔记_第38张图片

非 Props 的 Attribute

之前我们都通过 v-bind 向组件 props 进行传值,大多都是用来进行数据传递。但是有些属性与数据相关性不大,或者说我们不需要对它做过多的逻辑处理,我们可以直接以将属性键值对写在组件标签上,会自动添加到组件的根元素上

我们希望对每个组件都使用不同的背景颜色。通过 props 传值过程太复杂,我们完全可以这样做:

<div id="app">
  <blog-component style="background: orange">blog-component>
  <blog-component style="background: pink">blog-component>
  <blog-component style="background: skyblue">blog-component>
div>
<script src="../js/vue.js">script>
<template id="blog-template">
  <div style="display: inline-block; margin: 10px">
    <h3>我是子组件h3>
  div>template
>
<script>
  const app = new Vue({
       
    el: '#app',
    components: {
        'blog-component': {
        template: '#blog-template' } },
  })
script>
后端小白程序员的Vue学习笔记_第39张图片

属性(Attribute)的替换与合并

通过上面的例子,你应该能看出来:我们写在组件标签上的style attribute 和组件根元素上的 style 进行了合并。除此以外class attribute 也会会进行属性合并。

但是有些特殊的属性,则不会进行合并,而是将根元素中的属性直接替换!例如type!(组件标签上的 type=’text‘ 会替换根元素的 type=’date’)


4.6、父子组建通信之$emit

上面我们学习了父组件通过 props 向子组件传递信息,但是由于单向数据流的控制,禁止我们通过对子组件的修改来影响父组件。可是我们又难免由于一些场景,需要子组件向父组件传递信息。

例如导航栏和展示框,往往同时隶属于同一个父组件,当我们点击导航栏的时候,我们就需要利用外部的父组件来间接控制我们的展示框。那么中间就涉及导航栏向父组件传递数据的需求!(如图)

后端小白程序员的Vue学习笔记_第40张图片

为了解决这需求,我们可以通过子组件向父组件发送事件,并携带参数来完成子组件与父组件的通信。当子组件触发了某一个监听事件后,在事件的回调函数中可以使用this.$emit来发送一个自定义的事件,父组件只需要在外部使用 v-on 监听并处理这个事件就可以啦!

我们先用一个小 demo 来演示一下:通过子组件内的按钮,来修改父组件的中 data 值。呈代码上来!

<div id="app">
  <h2>当前父组件中number: {
    {number}}h2>
  <hr />
  <child-component @number-incr="add" @number-desc="sub">child-component>
div>
<script src="../js/vue.js">script>
<template id="button-template">
  <div>
    <button @click="decrement">-1button>
    <button @click="increment">+1button>
  div>template
>
<script>
  const childComponent = {
       
    template: '#button-template',
    methods: {
       
      increment() {
       
        this.$emit('number-incr')
      },
      decrement() {
       
        this.$emit('number-desc')
      },
    },
  }
  const app = new Vue({
       
    el: '#app',
    data: {
       
      number: 0,
    },
    methods: {
       
      add() {
       
        this.number++
      },
      sub() {
       
        this.number--
      },
    },
    components: {
       
      'child-component': childComponent,
    },
  })
script>

效果演示:

后端小白程序员的Vue学习笔记_第41张图片

发出携带参数的事件

但是这个 demo 并不能完全实现我们的需求,我们需要通过事件向父组件传值。官方当然也想到了这个问题,下面我们来一更具体的来演示一下:

<div id="app">
  <nav-component
    style="text-align: center"
    @to-page="currentPage = $event"
  >nav-component>
  <hr />
  <div style="border: black solid 2px" v-if="!(currentPage === '')">
    <h2 style="text-align: center">您当前的位置: {
    {currentPage}}h2>
  div>
div>
<script src="../js/vue.js">script>
<template id="button-template">
  <div>
    <button @click="$emit('to-page', '主页')">主页button>
    <button @click="$emit('to-page', '热门')">热门button>
    <button @click="$emit('to-page', '排行')">排行button>
    <button @click="$emit('to-page', '我的')">我的button>
  div>template
>
<script>
  const navComponent = {
       
    template: '#button-template',
  }
  const app = new Vue({
       
    el: '#app',
    data: {
        currentPage: '' },
    components: {
        'nav-component': navComponent },
  })
script>

这里注意下,代码中使用了一些简化写法:

  1. 子组件中直接使用$emit(xx, yy)抛出带有数据的时间。其中 xx 为事件名,yy 为携带的参数。
  2. 在父组件中对子组件自定义事件进行监听的时候,我们使用$event直接取到了数据。使用$event直接取数据,个人感觉只适用于单个事件参数(因为我并不知道如何使用$event 取出其他参数…)

在事件参数 大于 1 或者需要复杂处理时,你可以将监听事件的回调操作使用函数完成:

<div id="app">
  <nav-component
    style="text-align: center"
    @to-page="jumpToPage"
  >nav-component>
  <hr />
  <div style="border: black solid 2px" v-if="!(currentPage === '')">
    <h2 style="text-align: center">您当前的位置: {
    {currentPage}}h2>
  div>
div>
<script src="../js/vue.js">script>
<template id="button-template">
  <div>
    <button @click="$emit('to-page', '主页', 1)">主页button>
    <button @click="$emit('to-page', '热门', 2)">热门button>
    <button @click="$emit('to-page', '排行', 3)">排行button>
    <button @click="$emit('to-page', '我的', 4)">我的button>
  div>template
>
<script>
  const navComponent = {
       
    template: '#button-template',
  }
  const app = new Vue({
       
    el: '#app',

    data: {
       
      currentPage: '',
    },
    methods: {
       
      // 函数接收处理多个事件参数
      jumpToPage(pageName, pageIndex) {
       
        this.currentPage = pageIndex + '. ' + pageName
      },
    },
    components: {
        'nav-component': navComponent },
  })
script>

演示效果:

后端小白程序员的Vue学习笔记_第42张图片

在自定义组件上使用v-model

先利用官方的例子来演示一下,

默认情况下,例如我们对 input text 使用 v-model:


<input type="text" v-model="inputContent" />


<input
  type="text"
  :value="inputContent"
  @input="inputContent = $event.target.value"
/>

但是当你自定义了一个输入组件时,我要如何让其中的输入值绑定到父组件的 data 上呢?直接使用 v-model?!(显然走不通,你都不能访问到父组件的 data,只能父组件向 props 传值。)而父组件又不能直接访问到组件内部的 input 元素的 value 属性。

我们不妨转变一个思路:在子组件内部我们能监控到 input 元素的 value 变化,然后我们可以再通过自定义事件将变化后的 value 作为事件参数携带出来!!

<div id="app">
  <input-component v-model="inputContent">input-component>
  <h3>{
    {inputContent}}h3>
div>
<script src="../js/vue.js">script>
<template id="input-template">
  <div>
    <input
      type="text"
      :value="inputValue"
      @input="$emit('input', $event.target.value)"
      style="border-radius: 5px"
    />div
>template>
<script>
  const inputComponent = {
       
    template: '#input-template',
    props: {
        inputValue: String },
  }
  const app = new Vue({
       
    el: '#app',
    data: {
        inputContent: '' },
    components: {
        'input-component': inputComponent },
  })
script>
  1. 首先,我们在组件内部将 input 元素的 value 绑定到一个 prop 上。
  2. 然后,在组件内部的 input 元素触发 input 事件时,同时组件向外抛出携带 input 元素的新值一个自定义事件。(==注意这个自定义事件名一定要是input,因为 v-model 的默认通过 input 事件来进行监听数据改变的!!==否则步骤 3 你得改为组合使用v-bind@xxx="yy = $event",xxx 为组件内抛出的自定义事件名,yyy 为 data 中你绑定的数据名,$event 则是事件携带的值,即最新的输入值)
  3. 在组件上使用v-model绑定 data 中的数据。

效果演示:

后端小白程序员的Vue学习笔记_第43张图片

自定义 checkbox 等需要使用v-model请参考官方文档:[自定义组件使用 v-model](自定义事件 — Vue.js (vuejs.org))


关于事件命名的问题:官方是更推荐我们使用短横线命名法

props 在命名时,采用驼峰命名,由于在 HTML 中是不区分大小写的,在进行绑定的时候会自动转化为“连字符命名”。

后端小白程序员的Vue学习笔记_第44张图片

但是在自定义事件中,是没有这个效果的,需要保证你发出的事件名和外部监听的事件名一模一样!否则不会生效:

后端小白程序员的Vue学习笔记_第45张图片

以上是关于子组件事件监听的基础使用,后续会遇到更高级的应用。


4.7、父子组件访问

除了父子组件的通信之外,Vue 还提供了父子组件相互访问的机制。分别通过$children$refs$parent$root

我们将页面抽为组件化是希望各个组件独立性、复用性更强。所以说我们通过使用这些方式访问父/子组件的机会很少。但是我们还是来看一些他们的使用!

$children:访问子组件

<div id="app">
  <child-component>child-component> <child-component>child-component>
  <child-component>child-component> <button @click="getChildren">按钮button>
div>
<script src="../js/vue.js">script>
<template id="child-template">
  <div><h2>我是子组件~h2>div>template
>
<script>
  const childComponent = {
        template: `#child-template` }
  Vue.component('child-component', childComponent)
  const app = new Vue({
       
    el: '#app',
    methods: {
       
      getChildren() {
       
        console.log('所有子组件:', this.$children)
        console.log('第一个子组件:', this.$children[0])
      },
    },
  })
script>

后端小白程序员的Vue学习笔记_第46张图片

通过这种方式(下标)依次访问子组件特别呆板。来看看另一种:

$refs,有一个特殊要求:需要组件上添加ref属性,并且属性值是唯一的!

<div id="app">
  <child-component ref="child1">child-component>
  <child-component ref="child2">child-component>
  <child-component ref="child3">child-component>
  <button @click="getChildren">按钮button>
div>
<script src="../js/vue.js">script>
<template id="child-template">
  <div><h2>我是子组件~h2>div>template
>
<script>
  const childComponent = {
       
    template: `#child-template`,
  }
  Vue.component('child-component', childComponent)
  const app = new Vue({
       
    el: '#app',
    methods: {
       
      getChildren() {
       
        console.log('所有子组件:', this.$refs)
        console.log('第一个子组件:', this.$refs.child1)
      },
    },
  })
script>

在使用$refs 访问时,我们得到一个 object,里面有若干属性,每一条属性代表一个子组件,属性名对应子组件上的ref值,属性值则为一个 VueComponent 对象即子组件本身。

这样我们访问某一个组件的时候,就不用呆呆地用下标了。而是直接使用组件的 ref 就能找到了!

image-20210517185725257

$parent访问父组件

<div id="app"><child-component>child-component>div>
<script src="../js/vue.js">script>
<template id="child-template">
  <div>
    <h2>我是子组件~h2>
    <button @click="getParent">按钮button>
  div>template
>
<script>
  const childComponent = {
       
    template: `#child-template`,
    methods: {
       
      getParent() {
       
        console.log(this.$parent)
      },
    },
  }
  Vue.component('child-component', childComponent)
  const app = new Vue({
        el: '#app' })
script>

演示效果:

后端小白程序员的Vue学习笔记_第47张图片

因为我们的组件是在 Vue 实例下的挂载块下面使用的,所以我们使用$parent就取到了其父级组件也即根实例!为了与下面的$root区分,建议再嵌套一层进行测试。。这里就不做过多演示了

$root访问根组件

我就不过多演示了,效果与前面的一样(只是巧合~)。

我们通过以上方式获取到父/子组件后是可以访问其他内容的,例如 data 等,但是并不推荐这样做。因为这样的代码出现在组件化中会增加组件之间的耦合,有违我们进行组件化的初心。


五、(补充)计算属性

5.1、基本使用

当我们经常需要用到的一些属性,是需要在已有属性上做一些修改得到的属性,或者说是不经常改变的属性,我们可以使用属性,如果在视图中的模板中放入过多的逻辑,会让代码难以维护,可以尝试利用计算属性,来创建一个与已有属性相关的属性。

例如当我们需要频繁使用到 message 的倒序串时

不使用计算属性:

<div id="app">
  message
  <h3>{
    {message}}h3>
  Reverse message
  <h3>{
    {message.split('').reverse().join('')}}h3>
div>
<script src="../vue-js/vue.js">script>
<script type="text/javascript">
  var vm = new Vue({
       
    el: '#app',
    data() {
       
      return {
        message: 'Hello' }
    },
  })
script>

我们没调用一次就需要在模板中添加相应的逻辑,大量的使用会提高代码维护的难度。

使用计算属性:

<body>
  <div id="app">
    message
    <h3>{
    {message}}h3>
    
    reverse message
    <h3>{
    {reverseMsg}}h3>
  div>
  <script src="../vue-js/vue.js">script>
  <script type="text/javascript">
    var vm = new Vue({
       
      el: '#app',
      data() {
       
        return {
       
          message: 'Hello',
        }
      },
      // 使用计算属性
      computed: {
       
        reverseMsg: function () {
       
          return this.message.split('').reverse().join('')
        },
      },
    })
  script>
body>

使用了 Vue 实例对象的computed属性,并声明了 reverseMsg 属性,并为其增加了一个函数用做 reverseMsg 属性的 getter 函数,且这个属性依赖于 message,一旦 message 发生变化,相应的 reverseMsg 也会发生变化。

5.2、复杂使用

也许你会认为当计算属性的复用价值不大的时候,直接在 Mustache 表达式中直接写数据处理逻辑比较简单。但是当你需要结合 data 中的数据做统计处理,然后将结果渲染到页面上的时候,计算属性的高效性就不言而喻!

<div id="app"><h2>当前购物车总价格:{
    {total.price}}h2>div>
<script src="../js/vue.js">script>
<script>
  const app = new Vue({
       
    el: '#app',
    data: {
       
      goods: [
        {
        name: 'iPhone 12', price: 6199 },
        {
        name: '码出高效', price: 59 },
        {
        name: 'FL 980', price: 699 },
      ],
    },
    computed: {
       
      total: function () {
       
        return this.goods.reduce((previousValue, currentValue, currentIndex) => {
       
            return {
       
              name: 'total',
              price: previousValue.price + currentValue.price,
            }
          })
      },
    },
  })
script>

5.3、计算属性 VS. Methods

说到这,你可能会疑问为什么不使用methods属性,视图直接调用方法就可以,当然最终效果是一样的。

那就要来说说计算属性的另一个好处:计算属性是基于它们的响应式依赖进行缓存的

<div id="app">
  message
  <h3>{
    {message}}h3>
  
  reverse message
  <h3>{
    {reverseMsg2()}}h3>
div>
<script src="../vue-js/vue.js">script>
<script type="text/javascript">
  var vm = new Vue({
       
    el: '#app',
    data() {
       
      return {
        message: 'Hello' }
    },
    methods: {
       
      reverseMsg2: function () {
       
        return this.message.split('').reverse().join('')
      },
    },
  })
script>

但是两者之间有一个最大的不同就是,计算属性是基于它们的响应式依赖进行缓存的,而是同方法则是每次刷新页面都需要调用方法重新计算。而所谓响应式依赖进行缓存,意思就是当计算属性所依赖的属性不发生变化时,可以直接从缓存中直接取出值,而不需要执行函数,唯有当依赖属性变化后,才会执行函数重新求值。

而使用 method,在每次触发页面重新渲染时总是需要重新调用方法计算!

什么时候会触发页面重新渲染呢?任何数据变化都会触发页面的重新渲染。不妨来看一个例子:

<div id="app">
  <h2>{
    {message}}h2>
  <h2>计算属性结果:{
    {reversedMessage}}h2>
  <h2>执行方法的结果:{
    {getReversedMessage()}}h2>
  <h2>无关数据:{
    {name}}h2>
div>
<script src="../js/vue.js">script>
<script>
  const app = new Vue({
       
    el: '#app',
    data: {
       
      message: 'Hello World!',
      name: 'sakura',
    },
    computed: {
       
      reversedMessage: function () {
       
        console.log('执行了一次属性计算!')
        return this.message.split('').reverse().join('')
      },
    },
    methods: {
       
      getReversedMessage: function () {
       
        console.log('执行了一次反转方法!')
        return this.message.split('').reverse().join('')
      },
    },
  })
script>

因为计算属性是响应式依赖的,也就是说我们修改了 message,控制台就会同时输出“执行了一次属性计算!”和“执行了一次反转方法!”。

但是如果我们修改了无关数据 name,就会触发页面的重新渲染:

  • 由于计算属性所依赖的数据没有变化,所以是不会引起属性计算的方法调用的,而是直接从缓存中拿上一次的数据。
  • 但是 methods 就没这么“聪明”了,它会被重新调用。

image-20210515111144966

可以试想一下,如果我们计算属性的过程是一个十分耗时的操作时,当我们使用 methods 来实现,就算无关数据的变化也会重复执行这个耗时的操作,造成大量的资源浪费!

当然使用方法也可以控制数据不存在缓存,在控制不应该出现缓存的数据时,请使用 methods!

5.4、计算属性 VS. 侦听属性

Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性。会通过侦听数据的变动,来执行对应的回调函数!但是通常情况下,更推荐使用计算属性来代替侦听属性!

同样来看一个案例:

使用侦听属性:

<div id="app"><h2>{
    {fullName}}h2>div>
<script src="../js/vue.js">script>
<script>
  const app = new Vue({
       
    el: '#app',
    data: {
       
      firstName: 'Tony',
      lastName: 'Stark',
      fullName: 'Tony Stark',
    },
    watch: {
       
      // 当lastName变化后,会触发此回调函数
      lastName: function (newLastName) {
       
        console.log('lastName was changed!')
        this.fullName = this.firstName + ' ' + newLastName
      }, // 当firstName变化后,会触发此回调函数
      firstName: function (newFirstName) {
       
        console.log('firstName was changed!')
        this.fullName = newFirstName + ' ' + this.lastName()
      },
    },
  })
script>

使用 watch 来监听两个属性的变化,通过不同的回调函数来调整数据。显得十分繁琐!

使用计算属性:

<div id="app2"><h2>{
    {fullName}}h2>div>
<script src="../js/vue.js">script>
<script>
  const app2 = new Vue({
       
    el: '#app2',
    data: {
        firstName: 'Tony', lastName: 'Stark' },
    computed: {
       
      fullName: function () {
       
        return this.firstName + ' ' + this.lastName
      },
    },
  })
script>

在保证功能一致的情况下,大大缩减了代码量并简化了逻辑。



六、插槽 slot

有没有发现我们在使用组件时,有一个很大问题?!我们在使用我们注册的组件的时候,就仅仅是一对对标签。标签内部什么都没有。组件在复用时拉出来除了数据外都“长得”一模一样!

有没有想过通过在组件标签内加上一些自定义的元素,让即使同一个组件也可以有很多模样!?Vue 的**插槽(Slot)**机制,就帮助我们实现了这个需求。

什么是插槽?!

从名字来看其实以及能猜到它的用处了。比如我们的电脑就有很多各种插槽(USB、HDMI、耳机孔),这些插槽设计就是为了用户可能会连接各种设备到 PC 上来进行定制化。那 Vue 的插槽的设计也是相同的思想:

在组件中预留位置(插槽),供给用户进行个性化使用。

6.1、基本使用

<div id="app">
  <slot-cpn> <button>我是一个按钮button> slot-cpn>
  <slot-cpn> <input type="text" placeholder="我是一个输入框" /> slot-cpn>
  <slot-cpn> <a href="#">我是一个超链接a> slot-cpn> 
  <slot-cpn>slot-cpn>
div>
<script src="../js/vue.js">script>
<template id="slot-template">
  <div style="border: black solid 2px; margin: 10px; display: inline-block">
    <div style="margin: 5px">
      <h3>我是一个子组件h3>
      <slot>默认内容slot>
    div>
  div>
template>
<script>
  Vue.component('slot-cpn', {
           
      template: `#slot-template`  
  })  
  const app = new Vue({
           el: '#app'  })
script>

在组件模板中使用为组件创建插槽。在使用组件时,组件标签内包裹的元素会被替换到插槽的位置!(slot 标签在编译后代码中不可见!)

使用效果:

image-20210517201904053

插槽内除了写这些基础的 html 标签,还可以使用其他组件!?!!

<slot-cpn>
    <login-cpn>login-cpn>
slot-cpn>

后端小白程序员的Vue学习笔记_第48张图片

当组件内没有预留插槽的话,组件标签内所有内容都会被抛弃!
但是即使只有一个插槽,也会保留所有组件标签内的所有元素!!

当 slot 设置了后备内容(即默认内容),若外部不传递内容,将会对默认内容进行渲染。这是一个好选择,可以减少因为缺少内容导致错误显示的尴尬!!


6.2、编译作用域

当你想在插槽中使用组件内的数据时,例如:

<user-cpn url="xxxx"> 当前登录的用户是:{
    {user.name}}user-cpn>

你需要了解,插槽可以与组件内其他位置一视同仁,他们可以访问的范围相同。(即作用域是相同的)但是无法访问user-cpn的作用域,例如其中的url属性是无法访问到的,因为插槽的内容是传给 user-cpn 组件的,而非在其内部定义的!

<div id="app">
  <slot-cpn> <h4>当前登录的用户是{
    {user.name}}h4> slot-cpn>
div>
<script src="../js/vue.js">script>
<template id="slot-template">
  <div style="border: black solid 2px; margin: 10px; display: inline-block">
    <h3>我是一个子组件h3>
    <slot>slot>div
>template>
<script>
  Vue.component('slot-cpn', {
       
    template: `#slot-template`,
    data() {
       
      return {
       
        user: {
       
          uid: 123,
          name: 'admin',
        },
      }
    },
  })
  const app = new Vue({
       
    el: '#app',
    data: {
        user: {
        uid: 170312, name: 'sakura' } },
  })
script>

演示效果表明,slot 是可以访问到组件内部数据值的!

后端小白程序员的Vue学习笔记_第49张图片

官方给出了一句规则:(初次读起来有些生涩,后续对其进行详细的说明)

父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。

在理解这句话之前,我们通过上面这个例子来学习一下作用域

上述例子的结果,就说明了一个问题:h4元素即使是写在标签内,但是在获取数据进行渲染的时候,依然是使用 Vue 实例的数据。而非组件内的数据!

因为在进行编译渲染数据时,所谓的组件标签只是一个普普通通的标签。所以通通归由 Vue 实例进行管理,即 Vue 实例挂载的

就是 Vue 实例的作用域!这就是父级模板在父级作用域内编译!

而我们的组件呢?它也有自己的作用域,而它的数据仅在