vue 03基本操作(基本指令,过滤器,组件,插件,组件传值。。)

第一章 Vue.js 概要介绍

在这里插入图片描述

1.1 Vue.js 介绍

  • Vue 是什么

    1. 主流的渐进式 JavaScript 框架
  • 什么是渐近式

  1. 可以和传统的网站开发架构融合在一起,例如可以简单的把它当作一个类似 JQuery 库来使用。
  2. 也可以使用Vue全家桶框架来开发大型的单页面应用程序 。
  • 使用它的原因
  1. vue.js 体积小,编码简洁优雅,运行效率高,用户体验好. 无Dom操作,它能提高网站应用程序的开发效率
  • 什么场景下使用它
  1. 一般是需要开发单页面应用程序 (Single Page Application, 简称:SPA) 的时候去用
  2. 单页面应用程序,如:网易云音乐 https://music.163.com/
  3. 因为 Vue 是 渐进式 的,Vue 其实可以融入到不同的项目中,即插即用

1.2 学习资源

英文官网:https://vuejs.org/
中文官网(中文文档很友好):https://cn.vuejs.org/
官方教程:https://cn.vuejs.org/v2/guide/
GitHub:https://github.com/yyx990803
API文档:https://cn.vuejs.org/v2/api/

不建议买书 ,官方文档很详细,多查官方文档,因为很多书基本上都是直接抄官方文档的

1.3 发展历史

  • 作者:尤雨溪(微博:尤小右),一位华裔前 Google 工程师,江苏无锡人。
    个人博客:http://www.evanyou.me/
    新浪微博:http://weibo.com/arttechdesign
    知乎:https://www.zhihu.com/people/evanyou/activities
  1. 2013年12月8号在 GitHub 上发布了 0.6 版
  2. 2015年10月份正式发布了 1.0 版本,开始真正的火起来
  3. 2016年10月份正式发布了 2.0 版
  4. 2019.4.8号发布了 Vue 2.5.10 版本 https://github.com/vuejs/vue/releases
  5. 1.x 版本老项目可能还在用,新项目绝对都是选择 2.x

1.4 对比其他前端 JS 框架

  1. Angular
    2009 年诞生的,起源于个人开发,后来被 Google 收购了。
    核心技术: 模板 和 数据绑定 技术,体验越来越差,走下坡路了。
  2. React
    2013年5月开源的,起源于 Facebook 的内部项目,对市场上所有 JS 框架都不满意,于是自已写了一套。
    核心技术: 组件化 和 虚拟DOM 技术。
  3. Vue.js
    吸收了上面两个框架的技术优点。
    使用情况:
    BAT 级别的企业:React 最多 > Angular > Vue.js
    中小型公司:Vue.js 更多一些,有中文文档学习成本低。
    Vue 不支持 IE8 及以下版本,因为 Vue 使用了 IE8 无法模拟 ECMAScript 5 特性。
    推荐使用最新谷歌浏览器。

第二章 Vue 核心技术

2.1 Vue 入门开发

2.1.1 创建工程

  1. 在本地创建文件夹 D:\StudentProject\WebStudy
    在这里插入图片描述

  2. 打开 VS Code ,点击 File > Open Folder , 找到 D:\StudentProject\WebStudy 打开

  3. 单击 WEBSTUDY 右侧的新建目录图标,创建目录: vue-01-core
    在这里插入图片描述

2.1.2 创建 HTML 和 安装 vue 模块

  1. 在 vue-01-core 目录下新建一个页面 01-helloworld.html
  2. 在 vue-01-core 目录下的命令行窗口,安装2.6.10版本的 vue 模块
 npm install vue@2.6.10

2.1.3 编写HTML页面

编写步骤:

1. 采用 <script> 标签引入 vue.js 库 
2.  定义一个 <div> 
3.  new Vue() 实例化Vue应用程序
el 选项: 元素element的缩写,指定被 Vue 管理的 Dom 节点入口(值为选择器 ),必须是一个普通的
HTML 标签节点,一般是 div。
data 选项:指定初始化数据,在 Vue 所管理的 Dom 节点下,可通过模板语法来进行使用
4. 标签体显示数据: {{xxxxx}} 
5. 表单元素双向数据绑定: v-model 
6.  注意: el 的值不能为 html 或 body

源码实现:

<body> 
<div id="app"> 
	<p>Hello, {{ msg }}</p> 
	<input type="text" v-model="msg"> 
</div> 
<script src="./node_modules/vue/dist/vue.js"></script> 
<script type="text/javascript"> 
var vm = new Vue({ 
el: '#app', // el选项的值不能指定html或 body
data: {
	msg: 'Vue.js'
	 } }) 
</script> 
</body>

2.2 分析 MVVM 模型

常见面试题:什么是 MVVM 模型?

  • MVVM 是 Model-View-ViewModel 的缩写,它是一种软件架构风格
    Model:模型, 数据对象(data选项当中 的)
    View:视图,模板页面(用于渲染数据)
    ViewModel:视图模型,其实本质上就是 Vue 实例
  • 它的哲学思想是:
    通过数据驱动视图
  • 把需要改变视图的数据初始化到 Vue中,然后再通过修改 Vue 中的数据,
    从而实现对视图的更新。
  • 声明式编程
    按照 Vue 的特定语法进行声明开发,就可以实现对应功能,不需要我们直接操作Dom元素, **命令式编程:**Jquery它就是,需要手动去操作Dom才能实现对应功能。
    在这里插入图片描述

2.3 Vue Devtools 插件安装

Vue Devtools 插件让我们在一个更友好的界面中审查和调试 Vue 项目。

  • 谷歌浏览器访问: chrome://extensions ,然后右上角打开 开发者模式 , 打开的效果如下,
    在这里插入图片描述

  • 将 直接拖到上面页面空白处,会自动安装
    在这里插入图片描述

  • 效果如下则安装成功
    在这里插入图片描述

  • 当你访问Vue开发的页面时,按 F12 可 Vue 标签页
    在这里插入图片描述

2.4 模板数据绑定渲染

可生成动态的HTML页面,页面中使用嵌入 Vue.js 语法可动态生成

  1. {{xxxx}} 双大括号文本绑定
  2. v-xxxx 以 v- 开头用于标签属性绑定,称为指令

在 vue-01-core 目录下新建一个页面: 02-模板数据绑定渲染.html

2.4.1 双大括号语法 {{}}

  • 格式: {{表达式}}
  • 作用:
    使用在标签体中,用于获取数据
    可以使用 JavaScript 表达式
  • 案例源码:
<body>
  <div id="app">
    <h3>1、双大括号输出文本内容</h3>
    <!--文本内容-->
    <p>普通文本:{{ message }}</p>
    <!--使用JS表达式-->
    <p>JS表达式:{{ number + 1 }}</p>
  </div>
  <script src="./node_modules/vue/dist/vue.js"></script>
  <script type="text/javascript">
  var vm = new Vue({
    el: "#app",
    data: {
      message: "haha",
      number: 1
    }
  });
  </script>
</body>

2.4.2 一次性插值 v-once

  • 通过使用 v-once 指令,你也能执行一次性地插值,当数据改变时,插值处的内容不会更新。
<h3>2、一次性插值 v-once </h3>
<span v-once> 这个将不会改变: {{ message }}</span>

2.4.3 输出HTML指令 v-html

  • 格式: v-html=‘xxxx’
  • 作用:
    如果是HTML格式数据,双大括号会将数据解释为普通文本,为了输出真正的 HTML,你需要使用 v- html 指令。
    Vue 为了防止 XSS 攻击,在此指令上做了安全处理,当发现输出内容有 script 标签时,则不渲染
    - XSS 攻击主要利用 JS 脚本注入到网页中,读取 Cookie 值(Cookie一般存储了登录身份信息),读取到了发送到黑客服务器,从而黑客可以使用你的帐户做非法操作。
    - XSS 攻击还可以在你进入到支付时,跳转到钓鱼网站。
    案例源码:
<body>
  <div id="app">
    <h3>3、v-html 指令输出真正的 HTML 内容</h3>
    <p>双大括号:{{ contentHtml }}</p>
    <!-- 指令的值不需要使用双大括号获取,直接写获取的属性名 -->
    <!-- <p>v-html指令:<span v-html="{{contentHtml}}"></span></p>-->
    <p>
      v-html指令:
      <span v-html="contentHtml"></span>
    </p>
  </div>
  <script src="./node_modules/vue/dist/vue.js"></script>
  <script type="text/javascript">
  var vm = new Vue({
    el: "#app",
    data: {
      message: "haha",
      number: 1, //contentHtml: '<span style="color:red">红色字体内容</span>'
      contentHtml:
        '<span style="color:red">红色字体内容><script>alert("hello vue")<\/script></span>'
    }
  });
  </script>
</body>
  • 效果图
    在这里插入图片描述

2.4.4 元素绑定指令 v-bind

  • 完整格式: v-bind:元素的属性名=‘xxxx’
  • 缩写格式: :元素的属性名=‘xxxx’
  • 作用:将数据动态绑定到指定的元素上
    案例源码:
<body>
  <div id="app">
    <h3>4、v-bind 属性绑定指令</h3>
    <!-- 红色字体是正常的 -->
    <img v-bind:src="imgUrl" alt="VueLogo" />
    <!-- 缩写 -->
    <img :src="imgUrl" alt="VueLogo" />
  </div>
  <script src="./node_modules/vue/dist/vue.js"></script>
  <script type="text/javascript">
  var vm = new Vue({
    el: "#app",
    data: {
      message: "haha",
      number: 1,
      contentHtml: '<span style="color:red">红色字体内容</span>',
      imgUrl: "https://cn.vuejs.org/images/logo.png"
    }
  });
  </script>
</body>
  • 效果图
    在这里插入图片描述

2.4.5 事件绑定指令 v-on

  • 完整格式: v-on:事件名称=“事件处理函数名”
  • 缩写格式: @事件名称=“事件处理函数名” 注意: @ 后面没有冒号
  • 作用:用于监听 DOM 事件
    案例源码: 每点击1次,数据就加1
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <h3>5、v-on 事件绑定指令</h3> 
        <input type="text" v-model="num"> 
        <button v-on:click="add">点击+1</button>
    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script type="text/javascript">
        var vm = new Vue({
            el: '#app',
            data: {
                message: 'haha',
                number: 1,
                contentHtml: '<span style="color:red">红色字体内容</span>',
                imgUrl: 'https://cn.vuejs.org/images/logo.png',
                num: 2
            },
            methods: { //指定事件处理方法, 在模板页面中通过 v-on:事件名 来调用 

                add: function () { //key为方法名
                    console.log('add被调用') // this 表示当前 vm 实例 
                    this.num++ //每点击1次num加1 
                }
            }
        })
    </script>
</body>

</html>
  • 效果图
    在这里插入图片描述

2.4.6 完整源码

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Vue-模板数据绑定渲染</title>
</head>

<body>
    <div id="app">
        <h3>1、双大括号输出文本内容</h3>
        <!--文本内容-->
        <p>普通文本:{{ message }}</p>
        <!--使用JS表达式-->
        <p>JS表达式:{{ number + 1 }}</p>
        <h3>2、一次性插值 v-once </h3> <span v-once> 这个将不会改变: {{ message }}</span>
        <h3>3、v-html 指令输出真正的 HTML 内容</h3>
        <p>双大括号:{{ contentHtml }}</p> <!-- 指令的值不需要使用双大括号获取 -->
        <!-- <p>v-html指令:<span v-html="{{contentHtml}}"></span></p> -->
        <p>v-html指令:<span v-html="contentHtml"></span></p>
        <h3>4、v-bind 属性绑定指令</h3> <!-- 直接写属性名是获取不到--> <img src="imgUrl" alt="VueLogo"> <!-- 红色字体是正常的 -->
        <img v-bind:src="imgUrl" alt="VueLogo"> <!-- 缩写 --> <img :src="imgUrl" alt="VueLogo">
        <h3>5、v-on 事件绑定指令</h3> <input type="text" v-model="num"> <button v-on:click="add">点击+1</button>
    </div> <br>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script type="text/javascript">
        var vm = new Vue({
            el: '#app',
            data: {
                message: 'haha',
                number: 1,
                contentHtml: '<span style="color:red">红色字体内容</span>',
                imgUrl: 'https://cn.vuejs.org/images/logo.png',
                num: 2
            },
            methods: { //指定事件处理方法, 在模板页面中通过 v-on:事件名 来调用 
                add: function () { //key为方法名
                    console.log('add被调用')
                    vm.num++ //每点击1次num加1 
                }
            }
        })
    </script>
</body>

</html>

2.5 计算属性和监听器

在 vue-01-core 目录下新建一个页面 03-计算属性和监听器.html

2.5.1 计算属性 computed

  • computed 选项定义计算属性

  • 计算属性 类似于 methods 选项中定义的函数

    1. 计算属性 会进行缓存,只在相关响应式依赖发生改变时它们才会重新求值。
    2. 函数 每次都会执行函数体进行计算。
  • 需求:输入数学与英语分数,采用 methods 与 computed 分别计算出总得分

案例源码:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Vue-模板数据绑定渲染</title>
</head>

<body>
    <div id="app"> 
        数学:<input type="text" v-model="mathScore"> 
        英语:<input type="text" v-model="englishScore">

        总分(方法-单向):<input type="text" v-model="sumScore()"> 
        总分(计算属性-单向):<input type="text" v-model="sumScore1"> 

        <!-- 总分(方法-单向):<input type="text" v-bind:value="sumScore()"> 上面的v-model 就是vue的一个语法糖 -->
    
    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script type="text/javascript">
        var vm = new Vue({
            el: '#app',
            data: {
                mathScore: 80,
                englishScore: 90,
            },
            methods: { //不要少了s 
                sumScore : function () { //在控制台输入 vm.sumScore() 每次都会被调用
                    console.info('sumScore被调用') // `this` 指向当前 vm 实例, 减 0 是为了字符串转为数字运算 
                    return (this.mathScore - 0) + (this.englishScore - 0)
                }
            },
            computed: { //计算属性 
                sumScore1: function () { //在控制台输入vm.sumScore1 不会被重新调用,说明计算属性有缓存
                    console.info('sumScore1被调用')
                    return (this.mathScore - 0) + (this.englishScore - 0)
                }
            }
        })
    </script>
</body>

</html>

computed 选项内的计算属性默认是 getter 函数,所以上面只支持单向绑定,当修改数学和英语的数据才会
更新总分,而修改总分不会更新数据和英语

2.5.2 计算属性(双向绑定)

  • 计算属性默认只有 getter ,不过在需要时你也可以提供一个 setter

案例源码:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Vue-模板数据绑定渲染</title>
</head>

<body>
    <div id="app">
        数学:<input type="text" v-model="mathScore"><br>
        英语:<input type="text" v-model="englishScore"><br>
        总分(方法-单向):<input type="text" v-model="sumScore()"><br>
        总分(计算属性-单向):<input type="text" v-model="sumScore1"><br>
        总分(计算属性-双向):<input type="text" v-model="sumScore2"><br>
    </div>
    <script src="./node_modules/vue/dist/vue.js">
    </script>
    <script type="text/javascript">
        var vm = new Vue({
            el: '#app',
            data: {
                mathScore: 80,
                englishScore: 90,
            },
            methods: { //不要少了s 
                sumScore: function () { 
                    //在控制台输入 vm.sumScore() 每次都会被调用
                    console.info('sumScore被调用') 
                    // `this` 指向当前 vm 实例, 减 0 是为了字符串转为数字运算 
                    return (this.mathScore - 0) + (this.englishScore - 0)
                }
            },
            computed: { //计算属性 默认 getter 只支持单向绑定 
                sumScore1: function () { 
                    //在控制台输入vm.sumScore1 不会被重新调用,说明计算属性有缓存
                    console.info('sumScore1被调用') 
                    return (this.mathScore - 0) + (this.englishScore - 0)
                }, //指定 getter/setter 双向绑定 
                sumScore2: {
                    get: function () {
                        console.info('sumScore2被调用') 
                        return (this.mathScore - 0) + (this.englishScore - 0)
                    },
                    set: function (newValue) { //value为更新后的值 
                        // 被调用则更新了sumScore2,然后将数学和英语更新为平均分 
                        var avgScore = newValue / 2 
                        this.mathScore = avgScore 
                        this.englishScore = avgScore
                    }
                }
            }
        })
    </script>
</body>

</html>

2.5.3 监听器 watch

  • 当属性数据发生变化时,对应属性的回调函数会自动调用, 在函数内部进行计算
  • 通过 watch 选项 或者 vm 实例的 $watch() 来监听指定的属性
  • 需求:
  1. 通过 watch 选项 监听数学分数, 当数学更新后回调函数中重新计算总分sumScore3
  2. 通过 vm.$watch() 选项 监听英语分数, 当英语更新后回调函数中重新计算总分sumScore3

源码实现:
注意: 在data 选择中添加一个 sumScore3 属性

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Vue-模板数据绑定渲染</title>
</head>

<body>
    <div id="app"> 数学:<input type="text" v-model="mathScore"><br> 英语:<input type="text" v-model="englishScore"><br>
        总分(方法-单向):<input type="text" v-model="sumScore()"><br> 总分(计算属性-单向):<input type="text" v-model="sumScore1"><br>
        总分(计算属性-双向):<input type="text" v-model="sumScore2"><br> 总分(监听器):<input type="text" v-model="sumScore3"><br>
    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script type="text/javascript">
        var vm = new Vue({
            el: '#app',
            data: {
                mathScore: 80,
                englishScore: 90,
                sumScore3: 170
            },
            methods: { //不要少了s 
                sumScore: function () { //在控制台输入 vm.sumScore() 每次都会被调用
                    console.log('sumScore被调用') // `this` 指向当前 vm 实例, 减 0 是为了字符串转为数字运算 
                    return (this.mathScore - 0) + (this.englishScore - 0)
                }
            }, // 计算属性
            computed: { // 默认 getter 只支持单向绑定 
                sumScore1: function () { //在控制台输入 vm.sumScore1 不会被重新调用 ,说明计算属性有缓存
                    console.log('sumScore1被调用')
                    return (this.mathScore - 0) + (this.englishScore - 0)
                },
                //指定 getter/setter 双向绑定 
                sumScore2: {
                    get: function () {
                        console.log('sumScore2被调用')
                        return (this.mathScore - 0) + (this.englishScore - 0)
                    },
                    set: function (
                        newValue) { //value为更新后的值 // 被调用则更新了sumScore2,然后将数学和英语更新为平均分 
                        var avgScore = newValue / 2
                        this.mathScore = avgScorethis.englishScore = avgScore
                    }
                }
            }, //监听器方式1:watch选项 
            watch: { //当数学修改后,更新总分sumScore3
                mathScore: function (newValue, oldValue) {
                    //newValue 就是新输入的数学得分 
                    this.sumScore3 = (newValue - 0) + (this.englishScore - 0)
                }
            }
        })
        //监听器方式2:通过vm对象调用 //第1个参数为监听的属性名,第2个回调函数 
        vm.$watch('englishScore', function (newValue) { //newValue 就是新输入的英语得分 
            this.sumScore3 = (newValue - 0) + (this.mathScore - 0)
        })
    </script>
</body>

</html>

2.6 Class 与 Style 绑定 v-bind

通过 class 列表和 style 指定样式是数据绑定的一个常见需求。它们都是元素的属性,都用 v-bind 处理,其中表达
式结果的类型可以是:字符串、对象或数组。

2.6.1 语法格式

  • v-bind:class=‘表达式’ 或 :class=‘表达式’
  1. class 的表达式可以为:
  • 字符串 :class=“activeClass”
  • 对象 :class="{active: isActive, error: hasError}"
  • 数组 :class="[‘active’, ‘error’]" 注意要加上单引号,不然是获取data中的值
    v-bind:style=‘表达式’ 或 :style=‘表达式’`
  1. style 的表达式一般为对象
    :style="{color: activeColor, fontSize: fontSize + ‘px’}"
    注意:对象中的value值 activeColor 和 fontSize 是data中的属性

2.6.2 案例源码

在 vue-01-core 目录下新建一个页面 04-Class与Style绑定.html

  • 效果图在这里插入图片描述
    源码实现
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Vue-模板数据绑定渲染</title>
</head>

<body>
    <!-- 第2步:定义样式 -->
    <style>
        .active {
            color: green;
        }

        .delete {
            background: red;
        }

        .error {
            font-size: 30px;
        }
    </style>
    <div id="app">
        <h2>Class绑定,v-bind:class 或 :class</h2>
        <!--activeClass会从data中获取值为active,则对应样式为绿色-->
        <p v-bind:class="activeClass">字符串达式</p> <!-- isDelete为 true,渲染delete样式;当 hasError为false,不取 error 样式;-->
        <p :class="{delete: isDelete, error: hasError}">对象表达式</p>
        <!--- 渲染 'active', 'error' 样式,注意要加上单引号,不然是获取data中的值 -->
        <p :class="['active', 'error']">数组表达式</p>
        <h2>Style绑定, v-bind:style 或 :class</h2>
        <p :style="{color: activeColor, fontSize: fontSize + 'px'}">Style绑定</p>
    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script type="text/javascript">
        new Vue({
            el: '#app',
            data: {
                activeClass: 'active',
                isDelete: true,
                hasError: false, //演示 Style 绑定 
                activeColor: 'red',
                fontSize: 20
            }
        })
    </script>
</body>

</html>

2.7 条件渲染 v-if

2.7.1 条件指令

  • v-if 是否渲染当前元素
  • v-else
  • v-else-if
  • v-show 与 v-if 类似,只是元素始终会被渲染并保留在 DOM 中,只是简单切换元素的 CSS 属性 display 来显示或隐藏

2.7.2 案例源码
在 vue-01-core 目录下新建一个页面 05-条件渲染.html
效果图:
在这里插入图片描述
源码实现

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue-模板数据绑定渲染</title>
</head>
<body>
    <style>
        .box {
            width: 200px;
            height: 200px;
            background: red;
        }
    </style>
    <div id="app">
        <h2>v-if 条件渲染</h2> <input v-model="seen" type="checkbox">勾选后显示红色小块
        <!-- v-if 为 true则显示渲染当前元素, -->
        <div v-if="seen" class="box"></div>
        <p v-else="seen">红块已隐藏</p>
        <h2>v-show 条件渲染</h2> 
        <!-- v-show 的元素始终会被渲染并保留在 DOM 中,只是简单切换元素的 CSS 属性 display 显示隐藏,而不是重新加载div-->
        <div v-show="seen" class="box"></div>
    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script type="text/javascript">
        var vm = new Vue({
            el: '#app',
            data: {
                seen: false
            }
        })
    </script>
</body>

</html>

2.7.3 v-if 与 v-show 比较

  1. 什么时候元素被渲染
    v-if 如果在初始条件为假,则什么也不做,每当条件为真时,都会重新渲染条件元素
    v-show 不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换
  2. 使用场景选择
    v-if 有更高的切换开销,
    v-show 有更高的初始渲染开销。
    因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行后条件很少改变,则使用 v-if 较好。

2.8 列表渲染 v-for

2.8.1 列表渲染指令

  1. v-for 迭代数组
    语法: v-for="(alias, index) in array"
    说明: alias : 数组元素迭代的别名; index : 数组索引值从0开始(可选) 。
    举例
<div v-for="item in items" :key="item.id"></div> 
<div v-for="(item, index) in items" :key="item.id"></div>

items 是源数据数组, item 是数组元素迭代的别名。 注意:使用 key 特殊属性, 它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素

  1. v-for 迭代对象的属性
    • 语法: v-for="(value, key, index) in Object"
    • 说明: value : 每个对象的属性值; key : 属性名(可选); index : 索引值(可选) 。
      举例:
 <div v-for="value in object" ></div> 
 <div v-for="(value, key) in object"></div> 
 <div v-for="(value, key, index) in object"></div>

注意: 在遍历对象时,是按 Object.keys() 的结果遍历,但不能保证它的结果在不同的 JavaScript 引擎下
是顺序一致的。
3. 可用 of 替代 in 作为分隔符

2.8.2 案例源码

在 vue-01-core 目录下新建一个页面 06-列表渲染.html
效果图
在这里插入图片描述
源码实现

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Vue-模板数据绑定渲染</title>
</head>

<body>
    <div id="app">
        <h2>1. 迭代数组</h2>
        <ul>
            <!-- e 为当前对象别名,index 数组下标0开始-->
            <li v-for="(e, index) in emps" :key="index"> 编号:{{index+1}},姓名:{{e.name}},工资:{{e.salary}} </li>
        </ul> <br>
        <h2>2. 迭代对象</h2>
        <ul>
            <!-- value是属性值,key是属性名,index索引值-->
            <li v-for="(value, key, index) in emps[0]"> 第{{index+1}}个属性为:{{key}} = {{value}} </li>
        </ul>
    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script type="text/javascript">
        var vm = new Vue({
            el: '#app',
            data: {
                emps: [
                    //数组 
                    {
                        name: '马云',
                        salary: '20000'
                    }, {
                        name: '马化腾',
                        salary: '18000'
                    }, {
                        name: '刘强东',
                        salary: '13000'
                    }
                ]
            }
        })
    </script>
</body>
</html>

2.9 事件处理 v-on

在 vue-01-core 目录下新建一个页面 07-事件处理.html

2.9.1 事件处理方法

  • 完整格式: v-on:事件名=“函数名” 或 v-on:事件名=“函数名(参数……)”
  • 缩写格式: @事件名=“函数名” 或 @事件名=“函数名(参数……)” 注意: @ 后面没有冒号
  • event :函数中的默认形参,代表原生 DOM 事件
    当调用的函数,有多个参数传入时,需要使用原生DOM事件,则通过 $event 作为实参传入
  • 作用:用于监听 DOM 事件
    案例源码:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue-模板数据绑定渲染</title>
</head>
<body>
    <div id="app">
        <h2>1. 事件处理方法</h2> 
        <button @click="say">Say {{msg}}</button> 
        <button @click="warn('hello', $event)">Warn</button>
    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script type="text/javascript">
        var vm = new Vue({
            el: '#app',
            data: {
                msg: 'Hello, Vue.js'
            },
            methods: {
                say: function (event) { 
                    // `this` 在方法里指向当前 Vue 实例 
                    alert(this.msg) 
                    // `event` 是原生 DOM 事件 
                    alert(event.target.innerHTML)
                }, 
                //多个参数如果需要使用原生事件,将 $event 作为实参传入 
                warn: function (msg, event) {
                    alert(msg + "," + event.target.tagName)
                }
            }
        })
    </script>
</body>
</html>

2.9.2 事件修饰符

  • stop 阻止单击事件继续传播 event.stopPropagation()
  • prevent 阻止事件默认行为 event.preventDefault()
  • once 点击事件将只会触发一次
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue-模板数据绑定渲染</title>
</head>
<body>
    <div id="app">
        <h2>1. 事件处理方法</h2>
        <button @click="say">Say {{msg}}</button>
        <button @click="warn('hello', $event)">Warn</button> <br>
        <h2>2. 事件修饰符</h2>
        <!--单击事件继续传播-->
        <div @click="todo">
            <!--点击后会调用doThis再调用todo-->
            <button @click="doThis">单击事件会继续传播</button>
        </div> <br /> 
        
        <!-- 阻止单击事件继续传播,-->
        <div @click="todo">
            <!--点击后只调用doThis-->
            <button @click.stop="doThis">阻止单击事件会继续传播</button>
        </div> 
        <!-- 阻止事件默认行为 -->
        <a href="http://www.mengxuegu.com" @click.prevent="doStop">梦学谷官网</a>
        <!-- 点击事件将只会触发一次 -->
        <button @click.once="doOnly">点击事件将只会触发一次: {{num}}</button>
    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script type="text/javascript">
        var vm = new Vue({
            el: '#app',
            data: {
                msg: 'Hello, Vue.js',
                num: 1
            },
            methods: {
                say: function (
                    event) { // `this` 在方法里指向当前 Vue 实例 alert(this.msg) // `event` 是原生 DOM 事件 
                    alert(event.target.innerHTML)
                }, //多个参数如果需要使用原生事件,将 $event 作为实参传入 
                warn: function (msg, event) {
                    alert(msg + "," + event.target.tagName)
                },
                todo: function () {
                    alert("todo....");
                },
                doThis: function () {
                    alert("doThis....");
                },
                doStop: function () {
                    alert("href默认跳转被阻止....")
                },
                doOnly: function () {
                    this.num++
                }
            }
        })
    </script>
</body>
</html>

2.9.3 按键修饰符

  • 格式: v-on:keyup.按键名 或 @keyup.按键名
  • 常用按键名:
  1. enter
  2. tab
  3. delete (捕获“删除”和“退格”键)
  4. esc
  5. space
  6. up
  7. down
  8. left
  9. right
    源码实现:
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Vue-模板数据绑定渲染</title>
</head>

<body>
    <div id="app">
        <h2>1. 事件处理方法</h2>
        <button @click="say">Say {{msg}}</button>
        <button @click="warn('hello', $event)">Warn</button> <br>
        <h2>2. 事件修饰符</h2>
        <!--单击事件继续传播-->
        <div @click="todo">
            <!--点击后会调用doThis再调用todo-->
            <button @click="doThis">单击事件会继续传播</button>
        </div> <br />

        <!-- 阻止单击事件继续传播,-->
        <div @click="todo">
            <!--点击后只调用doThis-->
            <button @click.stop="doThis">阻止单击事件会继续传播</button>
        </div>
        <!-- 阻止事件默认行为 -->
        <a href="http://www.mengxuegu.com" @click.prevent="doStop">梦学谷官网</a>
        <!-- 点击事件将只会触发一次 -->
        <button @click.once="doOnly">点击事件将只会触发一次: {{num}}</button>
        <h2>3. 按键修饰符</h2>
        <input @keyup.enter="keyEnter">
        <!--进入输入框按回车时调用keyEnter-->
        <input @keyup.space="keySpace">
        <!--进入输入框按回车时调用keySpace-->

    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script type="text/javascript">
        var vm = new Vue({
            el: '#app',
            data: {
                msg: 'Hello, Vue.js',
                num: 1
            },
            methods: {
                say: function (
                    event) {
                    // `this` 在方法里指向当前 Vue 实例 
                    alert(this.msg)
                    // `event` 是原生 DOM 事件 
                    alert(event.target.innerHTML)
                }, 
                //多个参数如果需要使用原生事件,将 $event 作为实参传入
                warn: function (msg, event) {
                    alert(msg + "," + event.target.tagName)
                },
                todo: function () {
                    alert("todo....");
                },
                doThis: function () {
                    alert("doThis....");
                },
                doStop: function () {
                    alert("href默认跳转被阻止....")
                },
                doOnly: function () {
                    this.num++
                },
                keyEnter: function () {
                    alert("已按:回车键")
                },
                keySpace: function () {
                    alert("已按:空格键")
                }
            }
        })
    </script>
</body>

</html>

2.10 表单数据双向绑定v-model

单向绑定:数据变,视图变;视图变(浏览器控制台上更新html),数据不变;上面的都是单向绑定
双向绑定:数据变,视图变;视图变(在输入框更新),数据变;

2.10.1 基础用法

v-model 指令用于表单数据双向绑定,针对以下类型

  • text 文本
  • textarea 多行文本
  • radio 单选按钮
  • checkbox 复选框
  • select 下拉框

2.10.2 案例源码

在 vue-01-core 目录下新建一个页面 08-表单数据双向绑定.html
模板页面

<body>
    <div id="demo">
        <form action="#"> 
            姓名(文本):<input type="text"> 
            <br><br> 
            性别(单选按钮): 
            <input name="sex" type="radio" value="1" /><input name="sex" type="radio" value="0" /><br><br> 
            技能(多选框): 
            
            <input type="checkbox" name="skills"  value="java">Java开发 
            <input type="checkbox" name="skills" value="vue">Vue.js开发 
            <input type="checkbox" name="skills" value="python">Python开发 
            
            <br><br> 
            城市(下拉框): 
            
            <select name="citys">
                <option value="bj">北京</option>
            </select> 
            
            <br><br> 
            说明(多行文本):
            <textarea cols="30" rows="5"></textarea> 
            
            <br><br> 
            <button type="submit">提交</button> 
        </form>
    </div>
</body>

Vue.js源码实现

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue-模板数据绑定渲染</title>
</head>
<body>
    <div id="demo">
        <!-- @submit.prevent 阻止事件默认提交行为 -->
        <form action="#" @submit.prevent="submitForm"> 
            姓名(文本):<input type="text" v-model="name"> 
            <br><br> 
            性别(单选按钮):
            <input name="sex" type="radio" value="1" v-model="sex" /><input name="sex" type="radio" value="0"  v-model="sex" /><br><br> 
            技能(多选框): 
            <input type="checkbox" name="skills" value="java" v-model="skills">Java开发 
            <input type="checkbox" name="skills" value="vue" v-model="skills">Vue.js开发
            <input type="checkbox" name="skills" value="python" v-model="skills">Python开发 
            
            <br><br>
            城市(下拉框): 
            <select name="citys" v-model="city">
                <option v-for="c in citys" :value="c.code"> {{c.name}} </option>
            </select> 
            
            <br><br> 
            说明(多行文本):
            <textarea cols="30" rows="5" v-model="desc"></textarea> 
            <br><br> 

            <button type="submit">提交</button> 
        </form>
    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script type="text/javascript">
        var vm = new Vue({
            el: '#demo',
            data: {
                name: '',
                sex: '0', //默认选中:女 skills: ['vue'], //默认勾选:vue.js开发
                citys: [
                    //初始化下拉框 
                    {
                        code: 'bj',
                        name: '北京'
                    }, {
                        code: 'sh',
                        name: '上海'
                    }, {
                        code: 'sz',
                        name: '深圳'
                    }
                ],
                city: 'sh', //默认选中:上海,
                desc: ''
            },
            methods: {
                submitForm: function () {
                    //发送ajax请求 
                    alert(this.name + "," + this.sex + "," + this.skills + "," + this.city + "," + this
                        .desc)
                }
            }
        })
    </script>
</body>
</html>

第三章 Vue 过渡&动画和自定义指令

3.1 过渡&动画效果

3.1.1 什么是过渡&动画

元素在显示和隐藏时,实现过滤或者动画的效果。常用的过渡和动画都是使用 CSS 来实现的

  • 在 CSS 中操作 trasition (过滤 )或 animation (动画)达到不同效果
  • 为目标元素添加一个父元素 , 让父元素通过自动应用 class 类名来达到效果
  • 过渡与动画时,会为对应元素动态添加的相关 class 类名:
  • xxx-enter :定义显示前的效果。
  • xxx-enter-active :定义显示过程的效果。
  • xxx-enter-to : 定义显示后的效果。
  • xxx-leave : 定义隐藏前的效果。
  • xxx-leave-active :定义隐藏过程的效果。
  • xxx-leave-to :定义隐藏后的效果。
    在这里插入图片描述

3.1.2 过滤效果案例

  • 为目标元素添加父元素
  • 定义 class 过渡样式
    指定过渡样式: transition
    指定隐藏时的样式: opacity(持续的时间)/其它
  • 功能实现
    点击按钮后, 显示隐藏文本
    效果1:显示和隐藏有渐变效果
    效果2:显示和隐藏的有平移效果,并持续时长不同
    在这里插入图片描述
  • 在 vue-02-过渡&动画和指令 目录下创建 01-过渡效果.html
  • 进入 vue-02-过渡&动画和指令 目录 ,执行 npm install vue@2.6.10 命令安装 vue模块
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>过渡效果</title>
    <style>
        /*显示或隐藏的过渡效果*/
        .mxg-enter-active,
        .mxg-leave-active {
            transition: opacity 1s;
            /*过渡,渐变效果 1秒*/
        }

        /*显示前或隐藏后的效果*/
        .mxg-enter,
        .mxg-leave-to {
            opacity: 0
                /*都是隐藏效果*/
        }

        /* 可以设置不同的进入和离开动画 */
        /*显示过渡效果*/
        .meng-enter-active {
            transition: all 1s;
            /*all 所有效果,持续1秒 */
        }

        /*隐藏过渡效果*/
        .meng-leave-active {
            transition: all 5s;
        }

        /*显示前或隐藏后的效果*/
        .meng-enter,
        .meng-leave-to {
            opacity: 0;
            /*都是隐藏效果*/
            transform: translateX(10px);
            /*水平方向 X 坐标移动10px*/
        }
    </style>
</head>
<body>
    <div id="app1"> 
        <button @click="show = !show">渐变过渡</button>
        <!--在目标元素上添加此元素,结合name值来指定样式-->
        <transition name="mxg">
            <p v-if="show">mengxuegu</p>
        </transition>
    </div> <br>
    <!--可以设置不同的进入和离开动画 -->
    <div id="app2"> 
        <button @click="show = !show">渐变平滑过渡</button>
        <!--在目标元素上添加此元素,结合name值来指定样式-->
        <transition name="meng">
            <p v-if="show">mengxuegu</p>
        </transition>
    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script type="text/javascript">
        var vm1 = new Vue({
            el: '#app1',
            data: {
                show: true
            }
        }) 
        var vm2 = new Vue({
            el: '#app2',
            data: {
                show: true
            }
        })
    </script>
</body>
</html>

3.1.3 动画效果案例

  • CSS 动画用法同 CSS 过渡,只不过采用 animation 为指定动画效果
  • 功能实现:
  1. 点击按钮后, 文本内容有放大缩小效果
  2. 在 vue-02-过渡&动画和指令 目录下创建 02-动画效果.html
    注意:官网上面源码有问题,要在 <p> 元素上增加样式 style="display: inline-block"
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>动画效果</title>
    <style>
        /*显示过程中的动画效果*/
        .bounce-enter-active {
            animation: bounce-in 1s;
            /*bounce-in引用了下面@keyframes中定义的持续3秒*/
        }

        /*隐藏过程中的动画效果*/
        .bounce-leave-active {
            animation: bounce-in 3s reverse;
            /*reverse 相反的顺序*/
        }

        @keyframes bounce-in {
            0% {
                /*持续时长的百分比,如持续1s,0%表示当0秒,50%表示当0.5秒,100%表示当1秒*/
                transform: scale(0);
                /*缩小为0*/
            }

            50% {
                transform: scale(1.5);
                /*放大1.5倍*/
            }

            100% {
                transform: scale(1);
                /*原始大小*/
            }
        }
    </style>
</head>
<body>
    <div id="example-2"> 
        <button @click="show = !show">放大缩小动画</button><br>
        <transition name="bounce">
            <p v-if="show" style="display: inline-block"> 陪你学习,伴你成长 </p>
        </transition>
    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script type="text/javascript">
        new Vue({
            el: '#example-2',
            data: {
                show: true
            }
        })
    </script>
</body>
</html>

3.2 Vue 内置指令总结

参考:https://cn.vuejs.org/v2/api/#指令

  • v-html 内容按普通 HTML 插入,可防止 XSS 攻击
  • v-show 根据表达式的真假值,切换元素的 display CSS 属性来显示隐藏元素
  • v-if 根据表达式的真假值,来渲染元素
  • v-else 前面必须有 v-if 或 v-else-if
  • v-else-if 前面必须有 v-if 或 v-else-if
  • v-for 遍历的数组或对象
  • v-on 绑定事件监听器
  • v-bind 用于绑定元素属性
  • v-model 在表单控件或者组件上创建双向绑定
  • v-once 一次性插值,当后面数据更新后视图数据不会更新
  • v-pre 可以用来显示原始插入值标签 {{}} 。并跳过这个元素和它的子元素的编译过程。加快编译。
    **例如:**网页中的一篇文章,文章内容不需要被 Vue 管理渲染,则可以在此元素上添加 v-pre 忽略文章编译提
    高性能。
    在 vue-02-过渡&动画和指令 目录下创建页面: 03-Vue内置指令.html
<span v-pre>{{ this will not be compiled }}</span> 
浏览页面显示内容:并没有识别{{}} 
{{ this will not be compiled }}
  • v-text
    • 等价于 {{}} 用于显示内容,但区别在于:
    • {{}} 会造成闪烁问题, v-text 不会闪烁
      如果还想用 {{}} 又不想有闪烁问题,则使用 v-cloak 来处理
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>指令</title>
    <style>
        /*将带有 v-clock 属性的标签隐藏*/
        [v-cloak] {
            display: none;
        }
    </style>
</head>

<body>
    <!-- 在被 Vue 管理的模板入口节点上作用 v-cloak 指令-->
    <div id="app" v-cloak>
        <!-- 用QQ浏览器刷新页面时, {{}} 会有明显闪烁现象, 
            原因: 是浏览器从上往下依次解析, 会先把 {{ message }} 当作标签体直接先渲染, 
            然后 Vue 再进行解析 {{ message }} 变成了 message 的值: 'hello mengxuegu' -->
        <h3>{{ message }}</h3>
        <h3>{{ message }}</h3>
        <h3 v-text="message"></h3>
    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script type="text/javascript">
        new Vue({
            el: '#app',
            data: {
                message: 'hello mengxuegu'
            }
        })
    </script>
</body>

</html>
  • v-cloak
    如果想用 {{}} 又不想有闪烁问题,则使用 v-cloak 来处理,步骤如下:
    1. 在被 Vue 管理的模板入口节点上作用 v-cloak 指令
  1. 添加一个属性选择器 [v-cloak] 的CSS 隐藏样式: [v-cloak] {display: none;}
    原理:默认一开始被 Vue 管理的模板是隐藏着的,当 Vue 解析处理完 DOM 模板之后,会自动把这个样式去除,然后就显示出来。

3.3 自定义指令

3.3.1 自定义指令的作用

除了内置指令外,Vue 也允许注册自定义指令。有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候使用自定义指令更为方便。
自定义指令文档: https://cn.vuejs.org/v2/guide/custom-directive.html

3.3.2 注册与使用自定义指令方式

  1. 注册全局指令:
// 指令名不要带 v- 
Vue.directive('指令名', { 
// el 代表使用了此指令的那个 DOM 元素 
// binding 可获取使用了此指令的绑定值 等 
inserted: function (el, binding) { 
// 逻辑代码 
	}
})
  1. 注册局部指令
directives : { 
'指令名' : { // 指令名不要带 v- 
inserted (el, binding) { 
// 逻辑代码 
		} 
	} 
}

注意:注册时,指令名不要带 v-

  • 使用指令:
    引用指令时,指令名前面加上 v-
    直接在元素上在使用即可 : v-指令名=‘表达式’

3.3.3 案例演示

需求:

  • 实现输出文本内容全部自动转为大写,字体为红色 ( 功能类型于 v-text , 但显示内容为大写)
  • 当页面加载时,该元素将获得焦点 (注意: autofocus 在移动版 Safari 上不工作)
    在这里插入图片描述
  • 实现:在 vue-02-过渡&动画和指令 目录下创建页面: 04-自定义指令.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>自定义指令</title>
</head>
<body>
    <div id="app">
        <p v-upper-text="message"></p> 
        自动获取焦点:<input type="text" v-focus>
    </div>
    <script src="../js/vue.js" type="text/javascript"></script>
    <script type="text/javascript">
        // 1. 注册一个全局 v-upper-text 指令,注意指令名不要带 v- 
        Vue.directive('upper-text', {
            // 因为是样式,所以不需要元素插入到DOM中,就好像link引入CSS文件时并不关心元素是否加载
            bind: function (el) {
                el.style.color = 'red'
            },
            // el 代表使用了此指令的那个 DOM 元素
            // binding 可获取使用了引指令的绑定值 等 
            inserted: function (el, binding) {
                // 将在 v-upper-text 指令中获取到的值,变成大写输出到标签体中 
                el.innerHTML = binding.value.toUpperCase()
            }
        })
        new Vue({
            el: '#app',
            data: {
                message: 'mengxuegu,陪你学习伴你梦想'
            }, 
            //2. 注册一个局部指令 v-focus
            directives: {
                'focus': {
                    //和js行为有关的操作,最好在inserted中执行,和样式相关的操作都可在bind中执行 
                    inserted: function (el) { // 聚焦元素
                        el.focus()
                    }
                }
            }
        })
    </script>
</body>
</html>

第四章 经典实战项目-TodoMVC

4.1 项目介绍与演示

  • TodoMVC
    是一个非常经典的案例,功能非常丰富,并且针对多种不同技术分别都开发了此项目,比如React、AngularJS、JQuery等等。
  • TodoMVC 案例官网:http://todomvc.com/
  • 在官网首页右下角, 有 案例的模板下载 和 开发规范(需求文档),如下图:
    在这里插入图片描述

4.2 需求说明

4.2.1 数据列表渲染

  • 当任务列表(items )没有数据时, #main 和 #footer 标识的标签应该被隐藏 任务涉及字段 : id 、任务名称(name)、是否完成( completed true 为已完成)
    在这里插入图片描述

4.2.2 添加任务

  1. 在最上面的文本框中添加新的任务。
  2. 不允许添加非空数据。
  3. 按 Enter 键添加任务列表中,并清空文本框。
  4. 当加载页面后文本框自动获得焦点,在 input 上使用 autofocus 属性可获得。
    在这里插入图片描述

4.2.3 显示所有未完成任务数

  1. 左下角要显示未完成的任务数量。确保数字是由 标签包装的。
  2. 还要将 item 单词多元化( 1 没有 s , 其他数字均有 s ): 0 items , 1 item , 2 items
    示例: 2 items left
    在这里插入图片描述

4.2.4 切换所有任务状态

  1. 点击复选框 V 后,将所有任务状态标记为复选框相同的状态。
    在这里插入图片描述
  2. 当 选中/取消 了单个任务时,复选框 V 也应同步更新。
    在这里插入图片描述

4.2.5 移除任务项

  1. 悬停在某个任务项上显示 X 移除按钮,可点击移除当前任务项。
    在这里插入图片描述

4.2.6 清除所有已完成任务

  1. 单击右下角 Clear completed 按钮时,移除所有已完成任务。
  2. 单击 Clear completed 按钮后,确保复选框清除了选中状态。
  3. 当列表中没有已完成的任务时,应该隐藏 Clear completed 按钮。
    在这里插入图片描述

4.2.7 编辑任务项

  1. 双击<label> (某个任务项)进入编辑状态(在 <li> 上通过 .editing 进行切换状态)。
  2. 进入编辑状态后输入框显示原内容,并获取编辑焦点。
  3. 输入状态按 Esc 取消编辑, editing 样式应该被移除。
  4. 按 Enter 键 或 失去焦点时 保存改变数据,移除 editing 样式;
    在这里插入图片描述

4.2.8 路由状态切换(过滤不同状态数据)

根据点击的不同状态( All / Active / Completed ),进行过滤出对应的任务,并进行样式的切换。
在这里插入图片描述

4.2.9 数据持久化

将所有任务项数据持久化到 localStorage 中,它主要是用于本地存储数据。

4.3 下载与导入模板

在 Github 下载 TodoMVC 模板在:https://github.com/tastejs/todomvc-app-template

4.3.1 TodoMVC下载方式

  1. 使用 git 克隆项目
git clone https://github.com/tastejs/todomvc-app-template.git
  • 直接下载 zip 压缩包
    在这里插入图片描述

4.3.2 nmp 安装依赖

因为下载的模板没有样式 ,所以要通过 nmp 安装相关依赖,依赖配置在 package.json 文件中

  • 通过 cmd 进入到 TodoMVC项目所在目录,执行命令: npm install
    在这里插入图片描述
    注意:要使用 npm 命令,需要安装 node.js 环境才可用。
  • 依赖安装完成后,生成的node_modules 目录效果如下:
    在这里插入图片描述

4.3.3 导入到 VS Code

  • 将 todomvc-app-template 目录重命名为: vue-03-todomvc
  • 拷贝 vue-03-todomvc 拷贝到 D:\StudentProject\WebStudy 目录下
  • 在 VS Code 左侧资源管理窗口刷新一下就可以看到 vue-03-todomvc ,直接访问 index.html,效果如下:
    在这里插入图片描述

4.4 初始化项目

4.4.1 下载Vue依赖

  • 下载 vue.js 后,会在 node_modules 目录下出现 vue
npm install vue@2.6.10

4.4.2 引入 vue.js

  • 引入 vue.js 到 index.html 中

注意:vue.js 的引入要在使用 Vue 操作的前面,我们在 app.js 中编写Vue代码,所以要在 app.js 前面引入

<!-- 在 app.js 前面引入--> 
<script src="./node_modules/vue/dist/vue.js" type="text/javascript"></script> 
<script src="js/app.js"></script>

4.4.3 找到被 Vue 管理的元素

  • 找到需要被 Vue 管理的元素
    ,在第13行取一个id属性值为 todoapp
<section class="todoapp" id="todoapp">
  • 在 app.js 中添加如下 Vue 入口
(function (Vue) { //表示依赖了全局的 Vue 
var app = new Vue({ 
el: '#todoapp' 
	}) 
})(Vue);

4.5 数据列表渲染实战

4.5.1 功能分析

  • 有数据

    列表中的记录有3种状态且

  • 样式不一样:
    未完成(没有样式)、已完成( .completed )、编辑中( .editing )
    任务字段 : id (主键) 、 content (内容)、 completed (状态 ; true 已完 成, false 未完成 )
  • 无数据

    main 和 .footer 标识的标签应该被隐藏 ( v-show )
    在这里插入图片描述

4.5.2 有数据列表功能实现

  1. 在 app.js 声明一个存储任务数据的数组 items,并初始化一些数据。
(function (Vue) { //表示依赖了全局的 Vue, 其实不加也可以,只是更加明确点 //初始化任务
const items = [ 
	{  
	id: 1,
	content: 'vue.js',
	completed: false //是否完成
	 },
	{ id: 2,
	content: 'java',
	completed: true 
	},
	{ id: 3,
	content: 'pyhton',
	completed: false 
	} 
]
var app = new Vue({
 el: '#todoapp',
data: { 
items // ES6中对象属性简写,等价于items: items
		} 
	})
})(Vue);
  1. 修改 list.html 列表从第25行开始<li class="completed">
    如果修改 app.js 或 html 后,刷新浏览器发现没有变,关闭当前浏览器窗口,重新打开访问。
	<ul class="todo-list">
		<!-- 三种状态:未完成(没有样式)、已完成(.completed )、编辑中( .editing ) -->
		<!--修改:1、v-for迭代; 2、:class={key为class样式名,value为获取的数据true或false}-->
		<li v-for="(item, index) in items" :key="item.id" :class="{completed: item.completed}">
			<div class="view">
                <!-- 修改:1、v-model 绑定状态值是否选中 --> 
                <input class="toggle" type="checkbox" v-model="item.completed">
                <!-- 修改:1、{{ content }} 显示内容 --> 
                <label>{{ item.content }}</label>
                <!--修改: 1、:value 绑定id删除 --> 
                <button class="destroy" :value="item.id"></button> 
            </div>
			<input class="edit" value="Create a TodoMVC template">
		</li>
	</ul>
  • 效果图
    在这里插入图片描述

4.5.3 无数据隐藏功能实现

只要判断 items 数组 length 等于0,则表示没有数据,结合 v-show 即可

  • 修改 index.html 中 <section class="main"><footer class="footer">
方式1(todomvc官网方法):在两个标签上处理,要添加2次麻烦 
<!-- items.length 值为 0 就是 false --> 
<section class="main" v-show="items.length">
....
</section> 
<footer class="footer" v-show="items.length">
....
</footer> 
方式2:使用 div 元素将下面包住,但是页面渲染后多出 div 元素 
<div v-show="items.length"> 
<section class="main">
....
</section> <footer class="footer">
...
</footer> 
</div> 
方式3:可使用 Vue 提供的 template 元素,页面渲染后不会有 template 元素, 但是不能使用 v-show, 因为template渲染后就消失了, 而v-show是 display:none控制显示隐藏的。 需要使用 v-if 才可以 
<template v-if="items.length"> 
<section class="main">
....
</section> <footer class="footer">
....
</footer> </template>
  • 在 app.js 中注释 items 数组中的元素后,重新访问即可演示效果
    在这里插入图片描述

4.5.4 完整源码

<!doctype html>
<html lang="en">

<head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<title>Template • TodoMVC</title>
	<link rel="stylesheet" href="node_modules/todomvc-common/base.css">
	<link rel="stylesheet" href="node_modules/todomvc-app-css/index.css">
	<!-- CSS overrides - remove if you don't need it -->
	<link rel="stylesheet" href="css/app.css">
</head>

<body>
	<section class="todoapp" id="todoapp">
		<header class="header">
			<h1>todos</h1>
            <!--添加任务-->
            <input class="new-todo" placeholder="What needs to be done?" autofocus>
        </header> 
        <!-- This section should be hidden by default and shown when there are todos -->
        <!--修改: items.length 值为 0 就是 false --> 
        <template v-if="items.length">
			<section class="main"> 
                <input id="toggle-all" class="toggle-all" type="checkbox"> 
                <label for="toggle-all">Mark all as complete</label>
				<ul class="todo-list">
					<!-- These are here just to show the structure of the list items -->
					<!-- List items should get the class `editing` when editing and `completed` when marked as
    completed -->
					<!-- 三种状态:未完成(没有样式)、已完成(.completed )、编辑中( .editing ) -->
					<!--修改:1、v-for迭代; 2、:class={key为class样式名,value为获取的数据true或false}-->
					<li v-for="(item, index) in items" :class="{completed: item.completed}">
						<div class="view">
                            <!-- 修改:1、v-model 绑定状态值是否选中 --> 
                            <input class="toggle" type="checkbox" v-model="item.completed"> 
                            <!-- 修改:1、{{ content }} 显示内容 -->
							<label>{{ item.content }}</label>
                            <!--修改: 1、:value 绑定id删除 --> 
                            <button class="destroy" :value="item.id"></button> 
                        </div> 
                        <input class="edit" value="Create a TodoMVC template">
					</li>
				</ul>
            </section> 
            <!-- This footer should hidden by default and shown when there are todos -->
			<!--修改: items.length 值为 0 就是 false -->
			<footer class="footer">
                <!-- This should be `0 items left` by default -->
                <span class="todo-count"><strong>0</strong> item left</span> 
                <!-- Remove this if you don't implement routing -->
				<ul class="filters">
					<li><a class="selected" href="#/">All</a> </li>
					<li><a href="#/active">Active</a> </li>
					<li><a href="#/completed">Completed</a> </li>
                </ul> 
                <!-- Hidden if no completed items are left ↓ --> 
                <button class="clear-completed">Clear completed</button>
			</footer>
		</template>
	</section>
	<footer class="info">
		<p>Double-click to edit a todo</p> <!-- Remove the below line ↓ -->
		<p>Template by <a href="http://sindresorhus.com">Sindre Sorhus</a></p>
		<!-- Change this out with your name and url ↓ -->
		<p>Created by <a href="http://todomvc.com">you</a></p>
		<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
	</footer> <!-- Scripts here. Don't remove ↓ -->
	<script src="node_modules/todomvc-common/base.js"></script> <!-- 在 app.js 前面-->
	<script src="node_modules/vue/dist/vue.js" type="text/javascript"></script>
	<script src="js/app.js"></script>
</body>

</html>

4.6 添加任务实战

4.6.1 功能分析

  1. 在最上面的文本框中添加新的任务。
  2. 不允许添加非空数据。
    分析:在添加到列表前,使用 .trim() 去除空格,如果去除后是空的就不添加。
  3. 按 Enter 键添加任务列表中,并清空文本框。
    分析:在页面添加 Enter 按键监听事件,最后向文本框赋空值 ‘’
    在这里插入图片描述

4.6.2 添加任务功能实现

  1. 在 hello.html 中的 文本输入框,添加 Enter 按键监听事件@keyup.enter=“addItem”
<header class="header">
 <h1>todos</h1> 
 <!--添加任务, keyup.enter 回车键监听--> 
 <input @keyup.enter="addItem"
	class="new-todo" placeholder="What needs to be done?" autofocus> </header>
  1. 在 app.js 中添加 addItem 函数,步骤如下:
    1、获取文本框输入的数据
    2、判断数据如果为空,则什么都不做
    3、如果不为空,则添加到数组中
    生成id值
    添加到数组中 (默认状态为 未完成)
    4、清空文本框内容
var app = new Vue({
			el: '#todoapp',
			data: {
				items // 对象属性简写,等价于items: items
			},
			methods: {
				addItem(event) { //对象属性函数简写,等价于addItem: function () {
					console.log('addItem', event.target.value) //1. 获取文本框输入的数据
					const content = event.target.value
						.trim() //2. 判断数据如果为空,则什么都不做 
					if (!content.length) {
						return
					}
					//3.如果不为空,则添加到数组中 // 生成id值
					const id = this.items.length + 1
					this.items.push({
						id, //等价于 id:id
						content,
						completed: false
					})
					//4. 清空文本框内容 
					event.target.value = ''
				}
			}
		})

4.6.3 完整源码

app.js

(function (Vue) { //表示依赖了全局的 Vue, 其实不加也可以,只是更加明确点 //初始化任务
    const items = [
    //     {
	// 	id: 1,
	// 	content: 'vue.js',
	// 	completed: false //是否完成
	// }, {
	// 	id: 2,
	// 	content: 'java',
	// 	completed: true
	// }, {
	// 	id: 3,
	// 	content: 'pyhton',
	// 	completed: false
    //     }
    ]
	var app = new Vue({
		el: '#todoapp',
		data: {
			items // 对象属性简写,等价于items: items
		},
		methods: {
			addItem(event) { //对象属性函数简写,等价于addItem: function () {
				console.log('addItem', event.target.value)
				//1. 获取文本框输入的数据
				const content = event.target.value.trim()
				//2. 判断数据如果为空,则什么都不做
				if (!content.length) {
					return
				}
				//3.如果不为空,则添加到数组中 
				// 生成id值
				const id = this.items.length + 1
				// 添加到数组中 
				this.items.push({
					id, //等价于 id:id
					content,
					completed: false
				})
				//4. 清空文本框内容 
				event.target.value = ''
			}
		}
	})
})(Vue);

4.7 显示所有未完成任务数实战

4.7.1 功能分析

  1. 左下角要显示未完成的任务数量。数字是由 标签包装的。
    分析:
    当 items 数组中的元素有改变,则重新计算未完成任务数量,可通过 计算属性 来获取 未完成 的任务数量
    通过数组函数 filter 过滤未完成任务,然后进行汇总
  2. 还要将 item 单词多元化( 1 没有 s , 其他数字均有 s ): 0 items , 1 item , 2 items
    分析:
    当任务数据为 1 不显示 s ,否则显示
    在这里插入图片描述

4.7.2 功能实现

  1. 在 list.html 页面添加剩余任务数计算属性 {{ remaining }}
<span class="todo-count"><strong>{{ remaining }}</strong> item left</span>
  1. 在 app.js 的Vue实例的 methods 上一行添加一个 computed 选项,其中定义一个计算属性 remaining
    注意 computed 选项的大括号最后不要少了逗号 ,
(function (Vue) { //表示依赖了全局的 Vue, 其实不加也可以,只是更加明确点 
	//初始化任务
	const items = []
	var app = new Vue({
		el: '#todoapp',
		data: {
			items // 对象属性简写,等价于items: items
		},
		// 定义计算属性选项
		computed: { // 过滤出所有未完成的任务项
			remaining() {
				/*return this.items.filter(function (item) { return !item.completed }).length*/
				//ES6 箭头函数 
				return this.items.filter(item => !item.completed).length
			}
		},
		// **注意** 后面不要少了逗号 ,
		methods: {
			addItem(event) { //对象属性函数简写,等价于addItem: function () {
				console.log('addItem', event.target.value)
				//1. 获取文本框输入的数据
				const content = event.target.value.trim()
				//2. 判断数据如果为空,则什么都不做 
				if (!content.length) {
					return
				}
				//3.如果不为空,则添加到数组中 
				// 生成id值
				const id = this.items.length + 1
				// 添加到数组中 
				this.items.push({
					id, //等价于 id:id
					content,
					completed: false
				})
				//4. 清空文本框内容
				event.target.value = ''
			}
		}
	})
})(Vue);

  1. 将 list.html 显示的 item 单词多元化( 1 没有 s , 其他数字均有 s )
    添加 {{ remaining === 1 ? ‘’ : ‘s’ }}
<span class="todo-count"> 
<strong>{{ remaining }}</strong> item{{ remaining === 1 ? '' : 's' }} left 
</span>
  1. 效果图
    在这里插入图片描述

4.8 切换所有任务状态实战

4.8.1 功能分析

  1. 点击复选框 V 后,将所有任务状态标记为复选框相同的状态。
    分析:
    复选框状态发生变化后,就迭代出每一个任务项, 再将复选框状态值赋给每个任务项即可 。
    为复选框绑定一个 计算属性 ,通过这个计算属性的 set 方法监听复选框更新后就更新任务项状态。
  2. 当 选中/取消 某个任务后,复选框 V 也应同步更新状态。
    分析:
    当所有未完成任务数( remaining )为 0 时,表示所有任务都完成了,就可以选中复选框。
    在复选框绑定的 计算属性 的 get 方法中判断所有 remaining 是否为 0 ,从而绑定了 remaining ,
    当 remaining 发生变化后, 会自动更新复选框状态(为 0 复选框会自动选中,反之不选中)。
    综合上述:计算属性默认情况只有 getter 方法,而当前要用到 setter 方法,所以采用 计算属性 双向绑定。

在这里插入图片描述
在这里插入图片描述

4.8.2 切换所有任务状态功能实现

  1. 在 index.html 中的 id=“toggle-all” 复选框,添加计算属性 toggleAll 双向绑定
<section class="main"> 
<!-- 添加计算属性 toggleAll 双向绑定--> 
<input v-model="toggleAll" id="toggle-all" class="toggle-all" type="checkbox">
  1. 在 app.js 中的步骤如下:
    在 computed 选项中,添加计算属性 toggleAll 进行双向绑定 ( getter/setter )
	// 定义计算属性选项
		computed: {
			// 过滤出所有未完成的任务项
			remaining() {
				/*
				return this.items.filter(function (item) {
					 return !item.completed }).length 
			   */
				//ES6 箭头函数 
				return this.items.filter(item => !item.completed).length
			},
			// 定义计算属性选项
			//复选框计算属性(双向绑定) 
			toggleAll: {

				get() { //等价于 get : functon () {...}
					console.log(this.remaining)
					//2. 当 this.remaining 发生变化后,会触发该方法运行 
					// 当所有未完成任务数为 0 , 表示全部完成, 则返回 true 让复选框选中 //反之就 false 不选中 
					return this.remaining === 0
				},
				set(newStatus) {
					// console.log(newStatus) 
					//1. 当点击 checkbox 复选框后状态变化后,就会触发该方法运行, 
					// 迭代出数组每个元素,把当前状态值赋给每个元素的 completed 
					// 下面箭头函数等价于:this.items.forEach(function (item) {
					this.items.forEach((item) => {
						item.completed = newStatus
					})
				}

			}

		}, // **注意** 后面不要少了逗号 ,

4.8.3 完整源码

  1. index.html
<!doctype html>
<html lang="en">

<head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<title>Template • TodoMVC</title>
	<link rel="stylesheet" href="node_modules/todomvc-common/base.css">
	<link rel="stylesheet" href="node_modules/todomvc-app-css/index.css">
	<!-- CSS overrides - remove if you don't need it -->
	<link rel="stylesheet" href="css/app.css">
</head>

<body>
	<section class="todoapp" id="todoapp">
		<header class="header">
			<h1>todos</h1>
			<!--添加任务-->
			<!--添加任务, keyup.enter 回车键监听-->
			<input @keyup.enter="addItem" class="new-todo" placeholder="What needs to be done?" autofocus>
		</header> 
		<!-- This section should be hidden by default and shown when there are todos -->
		<!--修改: items.length 值为 0 就是 false --> 
		<template v-if="items.length">
			<section class="main"> 
				<!-- 添加计算属性 toggleAll 双向绑定-->
				<input v-model="toggleAll" id="toggle-all" class="toggle-all" type="checkbox"> 
				<label for="toggle-all">Mark all as complete</label>
				<ul class="todo-list">
					<!-- These are here just to show the structure of the list items -->
					<!-- List items should get the class `editing` when editing and `completed` when marked as
	completed -->
					<!-- 三种状态:未完成(没有样式)、已完成(.completed )、编辑中( .editing ) -->
					<!--修改:1、v-for迭代; 2、:class={key为class样式名,value为获取的数据true或false}-->
					<li v-for="(item, index) in items" :class="{completed: item.completed}">
						<div class="view">
							<!-- 修改:1、v-model 绑定状态值是否选中 --> 
							<input class="toggle" type="checkbox" v-model="item.completed"> 
							<!-- 修改:1、{{ content }} 显示内容 -->
							<label>{{ item.content }}</label>
							<!--修改: 1、:value 绑定id删除 --> 
							<button class="destroy" :value="item.id"></button> 
						</div> 
							<input class="edit" value="Create a TodoMVC template">
					</li>
				</ul>
			</section> 
			<!-- This footer should hidden by default and shown when there are todos -->
			<!--修改: items.length 值为 0 就是 false -->
			<footer class="footer">
				<!-- This should be `0 items left` by default --> 
				<span class="todo-count">
					<strong>{{ remaining }}</strong> item{{ remaining === 1 ? '' : 's' }} left
				</span> 
				<!-- Remove this if you don't implement routing -->
				<ul class="filters">
					<li><a class="selected" href="#/">All</a> </li>
					<li><a href="#/active">Active</a> </li>
					<li><a href="#/completed">Completed</a> </li>
				</ul> <!-- Hidden if no completed items are left ↓ --> 
				<button class="clear-completed">Clearcompleted</button>
			</footer>
		</template>
	</section>
	<footer class="info">
		<p>Double-click to edit a todo</p> <!-- Remove the below line ↓ -->
		<p>Template by <a href="http://sindresorhus.com">Sindre Sorhus</a></p>
		<!-- Change this out with your name and url ↓ -->
		<p>Created by <a href="http://todomvc.com">you</a></p>
		<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
	</footer> <!-- Scripts here. Don't remove ↓ -->
	<script src="node_modules/todomvc-common/base.js"></script> <!-- 在 app.js 前面-->
	<script src="node_modules/vue/dist/vue.js" type="text/javascript"></script>
	<script src="js/app.js"></script>
</body>

</html>

  1. app.js
// (function (window) {
// 	'use strict';

// 	// Your starting point. Enjoy the ride!

// })(window);
(function (Vue) { //表示依赖了全局的 Vue, 其实不加也可以,只是更加明确点
	//初始化任务
	const items = []
	var app = new Vue({
		el: '#todoapp',
		data: {
			items // 对象属性简写,等价于items: items
		},
		methods: {
			addItem(event) { //对象属性函数简写,等价于addItem: function () {
				console.log('addItem', event.target.value)
				//1. 获取文本框输入的数据
				const content = event.target.value.trim()
				//2. 判断数据如果为空,则什么都不做 
				if (!content.length) {
					return
				}
				//3.如果不为空,则添加到数组中 // 生成id值
				const id = this.items.length + 1
				this.items.push({
					id, //等价于 id:id
					content,
					completed: false
				})
				//4. 清空文本框内容
				event.target.value = ''
			}
		},
		// 定义计算属性选项
		computed: {
			// 过滤出所有未完成的任务项
			remaining() {
				/*
				return this.items.filter(function (item) {
					 return !item.completed }).length 
			   */
				//ES6 箭头函数 
				return this.items.filter(item => !item.completed).length
			},
			// 定义计算属性选项
			//复选框计算属性(双向绑定) 
			toggleAll: {

				get() { //等价于 get : functon () {...}
					console.log(this.remaining)
					//2. 当 this.remaining 发生变化后,会触发该方法运行 
					// 当所有未完成任务数为 0 , 表示全部完成, 则返回 true 让复选框选中 //反之就 false 不选中 
					return this.remaining === 0
				},
				set(newStatus) {
					// console.log(newStatus) 
					//1. 当点击 checkbox 复选框后状态变化后,就会触发该方法运行, 
					// 迭代出数组每个元素,把当前状态值赋给每个元素的 completed 
					// 下面箭头函数等价于:this.items.forEach(function (item) {
					this.items.forEach((item) => {
						item.completed = newStatus
					})
				}

			}

		}, // **注意** 后面不要少了逗号 ,
	})
})(Vue);

4.9 移除任务项

4.9.1 功能分析

  • 悬停在某个任务项上显示 X 移除按钮,可点击移除当前任务项。
    分析:
    X 移除按钮处添加点击事件
    通过数组函数 splice 移除任务项
    在这里插入图片描述

4.9.2 移除任务项功能实现

  • index.html 添加点击事件: @click="removeItem(index) "
<button class="destroy" :value="item.id" @click="removeItem(index)"></button>
  • app.js 添加函数 removeItem , 通过 this.items.splice(index, 1) 移除
			methods: {

			// 移除任务项
			removeItem(index) {
				// 移除索引为index的一条记录 
				this.items.splice(index, 1)
			},


			addItem(event) { //对象属性函数简写,等价于addItem: function () {
				console.log('addItem', event.target.value)
				//1. 获取文本框输入的数据
				const content = event.target.value.trim()
				//2. 判断数据如果为空,则什么都不做 
				if (!content.length) {
					return
				}
				//3.如果不为空,则添加到数组中 // 生成id值
				const id = this.items.length + 1
				this.items.push({
					id, //等价于 id:id
					content,
					completed: false
				})
				//4. 清空文本框内容
				event.target.value = ''
			}
		},

4.10 清除所有已完成任务

4.10.1 功能分析

  • 单击右下角 Clear completed 按钮时,移除所有已完成任务。
    分析:
  • 页面增加点击事件: @click=“removeCompleted”
  • 在 Vue 中添加 removeCompleted 函数:
    通过数组的 filter 函数过滤出所有未完成的任务项,将过滤出来的未完成数据赋值给 items 数组,
    已完成的任务就被删除。
  • 当列表中没有已完成的任务时,应该隐藏 Clear completed 按钮。
    分析:
  • 在 Clear completed 按钮上使用 v-show ,当总任务数 ( items.length ) > 未完成数 (
    remaining ) ,说明列表中还有已完成数据,则是显示按钮;反之不显示。
  • 实现: v-show=“items.length > remaining”
    在这里插入图片描述

4.10.2 清除所有已完成任务功能实现

  1. index.html 添加点击事件: @click=“removeCompleted”
<button @click="removeCompleted" class="clear-completed">Clear completed</button> 
  1. app.js 添加函数 removeCompleted , 通过数组的 filter 函数过滤出所有未完成的任务项,将过滤出来的未完成数据赋值给 items 数组
methods: {
			
			// 移除所有未完成任务项 
			removeCompleted() {
				// 过滤出所有未完成的任务,重新赋值数组即可 
				this.items = this.items.filter(item => !item.completed)
			},

			// 移除任务项
			removeItem(index) {
				// 移除索引为index的一条记录 
				this.items.splice(index, 1)
			},
			addItem(event) { //对象属性函数简写,等价于addItem: function () {
				console.log('addItem', event.target.value)
				//1. 获取文本框输入的数据
				const content = event.target.value.trim()
				//2. 判断数据如果为空,则什么都不做 
				if (!content.length) {
					return
				}
				//3.如果不为空,则添加到数组中 // 生成id值
				const id = this.items.length + 1
				this.items.push({
					id, //等价于 id:id
					content,
					completed: false
				})
				//4. 清空文本框内容
				event.target.value = ''
			}
		}
  1. 在 index.html 使用 v-show=“items.length > remaining” 进行切换显示/隐藏 Clear completed 按钮
<button class="clear-completed" @click="removeCompleted" v-show="items.length > remaining">Clear completed</button>

4.11 编辑任务项

4.11.1 功能分析

  1. 双击
    • 为 <label> 绑定双击事件 @dblclick=toEdit(item)
      
    • 当 item (任务项) === currentItem (当前点击的任务项,data中新定义的属性) 时,在 <li>上就显
      示 .editing 样式,格式 :class={editing: item === currentItem}
  2. 进入编辑状态后输入框显示原内容,并会自动获取编辑焦点。
    分析:
    • <input> 单向绑定输入框的值即可 :value=“item.content”
    • 通过自定义指令获取编辑焦点
  3. 输入状态按 Esc 取消编辑, editing 样式应该被移除。
    分析<input>绑定 Esc 按键事件 @keyup.esc=cancelEdit ,将 currentItem 值变为 null
  4. 按 Enter 键 或 失去焦点时 保存改变数据,移除 editing 样式;
    分析:
  • 添加事件 @keyup.enter=finishEdit(item, $event) 与 @blur=“finishEdit(item, $event)”
  • 通过 $event 变量获取当前输入框的值,使用 .trim() 去空格后进行判断是否为空,如果为空则清除这条任务,否则修改任务项;.
  • 添加数据保存任务项中
  • 将 currentItem 值变为 null ,移除 editing 样式

4.11.2 进入编辑状态

4.11.2.1 修改 index.html
  1. <label> 绑定双击事件 @dblclick=“toEdit(item)” , 将迭代出来的每个 item 传入当前行的 toEditItem 函数中
  2. <li>上判断是否显示 .editing 样式 :class={editing: item === currentItem}
<!--修改:1、v-for迭代; 2、:class={key为class样式名,value为获取的数据true或false}-->
					<li v-for="(item, index) in items" 
					    :class="{completed: item.completed,editing: item === currentItem}}">
						<div class="view">
							<!-- 修改:1、v-model 绑定状态值是否选中 --> 
							<input class="toggle" type="checkbox" v-model="item.completed"> 
							<!-- 修改:1、{{ content }} 显示内容 -->
							<label @dblclick="toEdit(item)">{{ item.content }}</label>
							<!--修改: 1、:value 绑定id删除 --> 
							<button class="destroy" :value="item.id" @click="removeItem(index)"></button> 
						</div> 
							<input class="edit" value="Create a TodoMVC template">
					</li>
4.11.2.2 修改 app.js
  1. 在 data 选项中添加属性 currentItem
  2. 在 methods 选项中添加函数 toEdit(item) 接收到点击的那个 item 后,将它赋值给 currentItem ,那对应的任务项就会进入编辑状态 this.currentItem = item
var app = new Vue({
		el: '#todoapp',
		data: {
			items, // 对象属性简写,等价于items: items
			currentItem: null //上面不要少了逗号, 接收当前点击的任务项
		},
		methods: {

			// 进入编辑状态,当前点击的任务项item赋值currentItem,用于页面判断显示 .editing 
			toEdit(item) {
				this.currentItem = item
			},
	....

4.11.3 编辑窗口显示原内容

<input> 上显示当前点击的任务内容,单向绑定输入框的值即可: :value=“item.content”

<input class="edit" :value="item.content">

4.11.4 取消编辑

<input> 绑定 Esc 按键事件 @keyup.esc=cancelEdit ,将 currentItem 值变为 null , 就 :class={editing: item ===currentItem} 不成立了,样式就没有了

  • index.html
<input class="edit" :value="item.content" @keyup.esc="cancelEdit" />
  • app.js
	methods: {

			//取消编辑
			cancelEdit() {
				// 移除样式 
				this.currentItem = null
			},

			// 进入编辑状态,当前点击的任务项item赋值currentItem,用于页面判断显示 .editing 
			toEdit(item) {
				this.currentItem = item
			},

			// 移除所有未完成任务项 
			removeCompleted() {
				// 过滤出所有未完成的任务,重新赋值数组即可 
				this.items = this.items.filter(item => !item.completed)
			},

			// 移除任务项
			removeItem(index) {
				// 移除索引为index的一条记录 
				this.items.splice(index, 1)
			},


			addItem(event) { //对象属性函数简写,等价于addItem: function () {
				console.log('addItem', event.target.value)
				//1. 获取文本框输入的数据
				const content = event.target.value.trim()
				//2. 判断数据如果为空,则什么都不做 
				if (!content.length) {
					return
				}
				//3.如果不为空,则添加到数组中 // 生成id值
				const id = this.items.length + 1
				this.items.push({
					id, //等价于 id:id
					content,
					completed: false
				})
				//4. 清空文本框内容
				event.target.value = ''
			}
		}

4.11.5 保存数据

  • 按 Enter 键 或 失去焦点时 保存改变数据,移除 editing 样式;

    分析:

    1. 添加事件 @keyup.enter=finishEdit(item, $event) 与 @blur=“finishEdit(item, $event)”
    2. 通过 $event 变量获取当前输入框的值,使用 .trim() 去空格后进行判断是否为空,如果为空则清除 这条任务,否则修改任务项;
    3. 添加数据保存任务项中
    4. 将 currentItem 值变为 null ,移除 editing 样式

index.html

<input class="edit" :value="item.content" @keyup.esc="cancelEdit" 
@keyup.enter="finishEdit(item, index, $event)" @blur="finishEdit(item, index, $event)" >

app.js

methods: {
			//编辑完成 
			finishEdit(item, index, event) {
				const content = event.target.value.trim();
				// 1. 如果为空, 则进行删除任务项
				if (!event.target.value.trim()) {
					//重用 removeItem 函数进行删除 
					this.removeItem(index)
					return
				}
				// 2. 添加数据到任务项中
				item.content = content
				// 3. 移除 .editing 样式 
				this.currentItem = null
			},

			//取消编辑
			cancelEdit() {
				// 移除样式 
				this.currentItem = null
			},
	............
	

注意:有时候你可能按 Esc 、 Enter 、或者 失去焦点 时,发现没有被触发事件,是因为编辑框没有获取焦点,只有当编辑框有焦点时才可以触发事件。

4.11.6 获取焦点(自定义指令)

  • 刷新页面后, 通过自定义全局指令 v-app-focus ,内容输入框自动获取焦点
  • 当进入编辑状态后,通过自定义局部指令 v-todo-focus ,编辑窗口自动获取焦点
(function (Vue) { //表示依赖了全局的 Vue, 其实不加也可以,只是更加明确点
	//初始化任务
	const items = []

	//自定义全局指令,用于 增加输入框 
	//定义时不要在前面加v-, 引用指令时要加上v- 
	Vue.directive('app-focus', {
		//聚集元素
		inserted(el, binding) {
			el.focus()
		}
	})


	var app = new Vue({
		el: '#todoapp',
		data: {
			items, // 对象属性简写,等价于items: items
			currentItem: null //上面不要少了逗号, 接收当前点击的任务项
		},

		//自定义局部指令,用于编辑输入框
		directives: {
			//定义时不要在前面加v-, 引用指令时要加上v-
			'todo-focus': {
				update(el, binding) { // 每当指令的值更新后,会调用此函数 
					if (binding.value) {
						el.focus()
					}
				}
			}
		},

		methods: {

			//编辑完成 
			finishEdit(item, index, event) {
				const content = event.target.value.trim();
   ..............
  • 在 index.html 中 引入指令
  • 增加输入框引用全局指令 v-app-focus
  • 编辑框中引用局部指令 v-todo-focus=“item === currentItem”
<!--添加任务-->
<!--添加任务, keyup.enter 回车键监听-->
<input @keyup.enter="addItem" class="new-todo" placeholder="What needs to be done?" v-app-focus>
........
<!-- 编辑任务 -->
<input class="edit" :value="item.content" @keyup.esc="cancelEdit" 
@keyup.enter="finishEdit(item, index, $event)" @blur="finishEdit(item, index, $event)" v-todo-focus="item === currentItem" >

4.11.7 完整源码

4.11.6.1 index.html
<!doctype html>
<html lang="en">

<head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<title>Template • TodoMVC</title>
	<link rel="stylesheet" href="node_modules/todomvc-common/base.css">
	<link rel="stylesheet" href="node_modules/todomvc-app-css/index.css">
	<!-- CSS overrides - remove if you don't need it -->
	<link rel="stylesheet" href="css/app.css">
</head>

<body>
	<section class="todoapp" id="todoapp">
		<header class="header">
			<h1>todos</h1>
			<!--添加任务-->
			<!--添加任务, keyup.enter 回车键监听-->
			<input @keyup.enter="addItem" class="new-todo" placeholder="What needs to be done?" v-app-focus>
		</header> 
		<!-- This section should be hidden by default and shown when there are todos -->
		<!--修改: items.length 值为 0 就是 false --> 
		<template v-if="items.length">
			<section class="main"> 
				<!-- 添加计算属性 toggleAll 双向绑定-->
				<input v-model="toggleAll" id="toggle-all" class="toggle-all" type="checkbox"> 
				<label for="toggle-all">Mark all as complete</label>
				<ul class="todo-list">
					<!-- These are here just to show the structure of the list items -->
					<!-- List items should get the class `editing` when editing and `completed` when marked as
	completed -->
					<!-- 三种状态:未完成(没有样式)、已完成(.completed )、编辑中( .editing ) -->
					<!--修改:1、v-for迭代; 2、:class={key为class样式名,value为获取的数据true或false}-->
					<li v-for="(item, index) in items" 
					    :class="{completed: item.completed,editing: item === currentItem}">
						<div class="view">
							<!-- 修改:1、v-model 绑定状态值是否选中 --> 
							<input class="toggle" type="checkbox" v-model="item.completed"> 
							<!-- 修改:1、{{ content }} 显示内容 -->
							<label @dblclick="toEdit(item)">{{ item.content }}</label>
							<!--修改: 1、:value 绑定id删除 --> 
							<button class="destroy" :value="item.id" @click="removeItem(index)"></button> 
						</div> 
						<!-- 编辑任务 -->
							<input class="edit" :value="item.content" @keyup.esc="cancelEdit" 
							@keyup.enter="finishEdit(item, index, $event)" @blur="finishEdit(item, index, $event)" v-todo-focus="item === currentItem" >
					</li>
				</ul>
			</section> 
			<!--修改: items.length 值为 0 就是 false -->
			<footer class="footer">
				<!-- This should be `0 items left` by default --> 
				<span class="todo-count">
					<strong>{{ remaining }}</strong> item{{ remaining === 1 ? '' : 's' }} left
				</span> 
				<!-- Remove this if you don't implement routing -->
				<ul class="filters">
					<li><a class="selected" href="#/">All</a> </li>
					<li><a href="#/active">Active</a> </li>
					<li><a href="#/completed">Completed</a> </li>
				</ul> <!-- Hidden if no completed items are left ↓ --> 
				<button class="clear-completed" @click="removeCompleted" v-show="items.length > remaining">Clear completed</button>
			</footer>
		</template>
	</section>
	<footer class="info">
		<p>Double-click to edit a todo</p> <!-- Remove the below line ↓ -->
		<p>Template by <a href="http://sindresorhus.com">Sindre Sorhus</a></p>
		<!-- Change this out with your name and url ↓ -->
		<p>Created by <a href="http://todomvc.com">you</a></p>
		<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
	</footer> <!-- Scripts here. Don't remove ↓ -->
	<script src="node_modules/todomvc-common/base.js"></script> <!-- 在 app.js 前面-->
	<script src="node_modules/vue/dist/vue.js" type="text/javascript"></script>
	<script src="js/app.js"></script>
</body>

</html>

4.11.6.2 app.js
// (function (window) {
// 	'use strict';

// 	// Your starting point. Enjoy the ride!

// })(window);
(function (Vue) { //表示依赖了全局的 Vue, 其实不加也可以,只是更加明确点
	//初始化任务
	const items = []

	//自定义全局指令,用于 增加输入框 
	//定义时不要在前面加v-, 引用指令时要加上v- 
	Vue.directive('app-focus', {
		//聚集元素
		inserted(el, binding) {
			el.focus()
		}
	})


	var app = new Vue({
		el: '#todoapp',
		data: {
			items, // 对象属性简写,等价于items: items
			currentItem: null //上面不要少了逗号, 接收当前点击的任务项
		},

		//自定义局部指令,用于编辑输入框
		directives: {
			//定义时不要在前面加v-, 引用指令时要加上v-
			'todo-focus': {
				update(el, binding) { // 每当指令的值更新后,会调用此函数 
					if (binding.value) {
						el.focus()
					}
				}
			}
		},

		methods: {

			//编辑完成 
			finishEdit(item, index, event) {
				const content = event.target.value.trim();
				// 1. 如果为空, 则进行删除任务项
				if (!event.target.value.trim()) {
					//重用 removeItem 函数进行删除 
					this.removeItem(index)
					return
				}
				// 2. 添加数据到任务项中
				item.content = content
				// 3. 移除 .editing 样式 
				this.currentItem = null
			},

			//取消编辑
			cancelEdit() {
				// 移除样式 
				this.currentItem = null
			},

			// 进入编辑状态,当前点击的任务项item赋值currentItem,用于页面判断显示 .editing 
			toEdit(item) {
				this.currentItem = item
			},

			// 移除所有未完成任务项 
			removeCompleted() {
				// 过滤出所有未完成的任务,重新赋值数组即可 
				this.items = this.items.filter(item => !item.completed)
			},

			// 移除任务项
			removeItem(index) {
				// 移除索引为index的一条记录 
				this.items.splice(index, 1)
			},


			addItem(event) { //对象属性函数简写,等价于addItem: function () {
				console.log('addItem', event.target.value)
				//1. 获取文本框输入的数据
				const content = event.target.value.trim()
				//2. 判断数据如果为空,则什么都不做 
				if (!content.length) {
					return
				}
				//3.如果不为空,则添加到数组中 // 生成id值
				const id = this.items.length + 1
				this.items.push({
					id, //等价于 id:id
					content,
					completed: false
				})
				//4. 清空文本框内容
				event.target.value = ''
			}
		},
		// 定义计算属性选项
		computed: {
			// 过滤出所有未完成的任务项
			remaining() {
				/*
				return this.items.filter(function (item) {
					 return !item.completed }).length 
			   */
				//ES6 箭头函数 
				return this.items.filter(item => !item.completed).length
			},
			// 定义计算属性选项
			//复选框计算属性(双向绑定) 
			toggleAll: {

				get() { //等价于 get : functon () {...}
					console.log(this.remaining)
					//2. 当 this.remaining 发生变化后,会触发该方法运行 
					// 当所有未完成任务数为 0 , 表示全部完成, 则返回 true 让复选框选中 //反之就 false 不选中 
					return this.remaining === 0
				},
				set(newStatus) {
					// console.log(newStatus) 
					//1. 当点击 checkbox 复选框后状态变化后,就会触发该方法运行, 
					// 迭代出数组每个元素,把当前状态值赋给每个元素的 completed 
					// 下面箭头函数等价于:this.items.forEach(function (item) {
					this.items.forEach((item) => {
						item.completed = newStatus
					})
				}

			}

		}, // **注意** 后面不要少了逗号 ,
	})
})(Vue);

4.12 路由状态切换(过滤不同状态数据)

4.12.1 功能分析

  • 根据点击的不同状态( All / Active / Completed ),进行过滤出对应的任务,并进行样式的切换。
    在这里插入图片描述
  • 分析 :
  1. 在 data 中定义变量 filterStatus , 用于接收变化的状态值
    
  2. 通过 window.onhashchange 获取点击的路由 hash (# 开头的),来获取对应的那个状态值,并将状态值赋值给 filterStatus
    
  3. 定义一个计算属性 filterItems 用于过滤出目标数据, 用于感知 filterStatus 的状态值变化,当变化后,通过 switch-case + filter 过滤出目标数据。
    
  4. 在 html 页面中,将 v-for 中之前的 items 数组替换为 filterItems 迭代出目标数据。
    
  5. 将被点击状态的 </a> 样式切换为 .select ,通过判断状态值实现,如: filterStatus === 'all'
    

4.12.2 功能实现

  1. 在 app.js 中 Vue 实例的 data 中定义变量 filterStatus , 用于接收变化的状态值
    声明一个变量 app 接收 Vue 实例对象,页面要使用到这个 app 变量
var app = new Vue({
		el: '#todoapp',
		data: {
			items, // 对象属性简写,等价于items: items
			currentItem: null, //上面不要少了逗号, 接收当前点击的任务项
			filterStatus: 'all' // 上面不要少了逗号,接收变化的状态值
		},

  1. 在 app.js 通过 window.onhashchange 获取点击的路由 hash (# 开头的),来获取对应的那个状态值
    注意:不是在 Vue 实例中定义,是在它同级结构下添加以下代码:
//当路由 hash 值改变后会自动调用此函数 
	window.onhashchange = function () {
		console.log('hash改变了', window.location.hash)
		// 1.获取点击的路由 hash , 当截取的 hash 不为空返回截取的,为空时返回 'all'
		const hash = window.location.hash.substr(2) || 'all'
		console.log('hash', hash)
		// 2. 状态一旦改变,将 hash 赋值给 filterStatus 
		// 当计算属性 filterItems 感知到 filterStatus 变化后,就会重新过滤 
		// 当 filterItems 重新过滤出目标数据后,则自动同步更新到视图中 
		app.filterStatus = hash
	}
	// 第一次访问页面时,调用一次让状态生效 
	window.onhashchange()
  1. 定义一个计算属性 filterItems 用于过滤出目标数据, 用于感知 filterStatus 的状态值变化,当变化后,通过switch-case + filter 过滤出目标数据。
	// 定义计算属性选项
		computed: {

			// 过滤出不同状态数据 
			filterItems() { //this.filterStatus 作为条件,变化后过滤不同数据 
				switch (this.filterStatus) {
					case "active": // 过滤出未完成的数据 
						return this.items.filter(item => !item.completed)
						break
					case "completed": // 过滤出已完成的数据 
						return this.items.filter(item => item.completed)
						break
					default: // 其他,返回所有数据 
						return this.items
				}
			},
  1. 在index.html页面中,将 v-for 中之前的 items 数组替换为 filterItems 迭代出目标数据。
<!--将之前 items 替换为 filterItems --> <li v-for="(item, index) in filterItems"
:class="{completed: item.completed, editing: item === currentItem}"> 。。。省略 </li>
  1. 在index.html 中, 将被点击状态的 </a>样式切换为 .select ,通过判断状态值实现
<ul class="filters">
<li><a :class="{selected: filterStatus === 'all'}" href="#/" >All</a> </li>
<li><a :class="{selected: filterStatus === 'active'}" href="#/active">Active</a></li>
<li><a :class="{selected: filterStatus === 'completed'}" href="#/completed">Completed</a></li>
</ul> <!-- Hidden if no completed items are left ↓ --> 

4.12.3 完整源码

4.12.2.1 index.html
<!doctype html>
<html lang="en">

<head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<title>Template • TodoMVC</title>
	<link rel="stylesheet" href="node_modules/todomvc-common/base.css">
	<link rel="stylesheet" href="node_modules/todomvc-app-css/index.css">
	<!-- CSS overrides - remove if you don't need it -->
	<link rel="stylesheet" href="css/app.css">
</head>

<body>
	<section class="todoapp" id="todoapp">
		<header class="header">
			<h1>todos</h1>
			<!--添加任务-->
			<!--添加任务, keyup.enter 回车键监听-->
			<input @keyup.enter="addItem" class="new-todo" placeholder="What needs to be done?" v-app-focus>
		</header> 
		<!-- This section should be hidden by default and shown when there are todos -->
		<!--修改: items.length 值为 0 就是 false --> 
		<template v-if="items.length">
			<section class="main"> 
				<!-- 添加计算属性 toggleAll 双向绑定-->
				<input v-model="toggleAll" id="toggle-all" class="toggle-all" type="checkbox"> 
				<label for="toggle-all">Mark all as complete</label>
				<ul class="todo-list">
					<!-- These are here just to show the structure of the list items -->
					<!-- List items should get the class `editing` when editing and `completed` when marked as
	completed -->
					<!-- 三种状态:未完成(没有样式)、已完成(.completed )、编辑中( .editing ) -->
					<!--修改:1、v-for迭代; 2、:class={key为class样式名,value为获取的数据true或false}-->
					<li v-for="(item, index) in  filterItems" 
					    :class="{completed: item.completed,editing: item === currentItem}">
						<div class="view">
							<!-- 修改:1、v-model 绑定状态值是否选中 --> 
							<input class="toggle" type="checkbox" v-model="item.completed"> 
							<!-- 修改:1、{{ content }} 显示内容 -->
							<label @dblclick="toEdit(item)">{{ item.content }}</label>
							<!--修改: 1、:value 绑定id删除 --> 
							<button class="destroy" :value="item.id" @click="removeItem(index)"></button> 
						</div> 
						<!-- 编辑任务 -->
							<input class="edit" :value="item.content" @keyup.esc="cancelEdit" 
							@keyup.enter="finishEdit(item, index, $event)" @blur="finishEdit(item, index, $event)" v-todo-focus="item === currentItem" >
					</li>
				</ul>
			</section> 
			<!--修改: items.length 值为 0 就是 false -->
			<footer class="footer">
				<!-- This should be `0 items left` by default --> 
				<span class="todo-count">
					<strong>{{ remaining }}</strong> item{{ remaining === 1 ? '' : 's' }} left
				</span> 
				<!-- Remove this if you don't implement routing -->
				<ul class="filters">
					<li><a :class="{selected: filterStatus === 'all'}" href="#/" >All</a> </li>
					<li><a :class="{selected: filterStatus === 'active'}" href="#/active">Active</a></li>
					<li><a :class="{selected: filterStatus === 'completed'}" href="#/completed">Completed</a></li>
				</ul> <!-- Hidden if no completed items are left ↓ --> 
				<button class="clear-completed" @click="removeCompleted" v-show="items.length > remaining">Clear completed</button>
			</footer>
		</template>
	</section>
	<footer class="info">
		<p>Double-click to edit a todo</p> <!-- Remove the below line ↓ -->
		<p>Template by <a href="http://sindresorhus.com">Sindre Sorhus</a></p>
		<!-- Change this out with your name and url ↓ -->
		<p>Created by <a href="http://todomvc.com">you</a></p>
		<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
	</footer> <!-- Scripts here. Don't remove ↓ -->
	<script src="node_modules/todomvc-common/base.js"></script> <!-- 在 app.js 前面-->
	<script src="node_modules/vue/dist/vue.js" type="text/javascript"></script>
	<script src="js/app.js"></script>
</body>

</html>

4.12.2.2 app.js
// (function (window) {
// 	'use strict';

// 	// Your starting point. Enjoy the ride!

// })(window);
(function (Vue) { //表示依赖了全局的 Vue, 其实不加也可以,只是更加明确点
	//初始化任务
	const items = []

	//自定义全局指令,用于 增加输入框 
	//定义时不要在前面加v-, 引用指令时要加上v- 
	Vue.directive('app-focus', {
		//聚集元素
		inserted(el, binding) {
			el.focus()
		}
	})


	var app = new Vue({
		el: '#todoapp',
		data: {
			items, // 对象属性简写,等价于items: items
			currentItem: null, //上面不要少了逗号, 接收当前点击的任务项
			filterStatus: 'all' // 上面不要少了逗号,接收变化的状态值
		},

		//自定义局部指令,用于编辑输入框
		directives: {
			//定义时不要在前面加v-, 引用指令时要加上v-
			'todo-focus': {
				update(el, binding) { // 每当指令的值更新后,会调用此函数 
					if (binding.value) {
						el.focus()
					}
				}
			}
		},

		methods: {

			//编辑完成 
			finishEdit(item, index, event) {
				const content = event.target.value.trim();
				// 1. 如果为空, 则进行删除任务项
				if (!event.target.value.trim()) {
					//重用 removeItem 函数进行删除 
					this.removeItem(index)
					return
				}
				// 2. 添加数据到任务项中
				item.content = content
				// 3. 移除 .editing 样式 
				this.currentItem = null
			},

			//取消编辑
			cancelEdit() {
				// 移除样式 
				this.currentItem = null
			},

			// 进入编辑状态,当前点击的任务项item赋值currentItem,用于页面判断显示 .editing 
			toEdit(item) {
				this.currentItem = item
			},

			// 移除所有未完成任务项 
			removeCompleted() {
				// 过滤出所有未完成的任务,重新赋值数组即可 
				this.items = this.items.filter(item => !item.completed)
			},

			// 移除任务项
			removeItem(index) {
				// 移除索引为index的一条记录 
				this.items.splice(index, 1)
			},


			addItem(event) { //对象属性函数简写,等价于addItem: function () {
				console.log('addItem', event.target.value)
				//1. 获取文本框输入的数据
				const content = event.target.value.trim()
				//2. 判断数据如果为空,则什么都不做 
				if (!content.length) {
					return
				}
				//3.如果不为空,则添加到数组中 // 生成id值
				const id = this.items.length + 1
				this.items.push({
					id, //等价于 id:id
					content,
					completed: false
				})
				//4. 清空文本框内容
				event.target.value = ''
			}
		},
		// 定义计算属性选项
		computed: {

			// 过滤出不同状态数据 
			filterItems() { //this.filterStatus 作为条件,变化后过滤不同数据 
				switch (this.filterStatus) {
					case "active": // 过滤出未完成的数据 
						return this.items.filter(item => !item.completed)
						break
					case "completed": // 过滤出已完成的数据 
						return this.items.filter(item => item.completed)
						break
					default: // 其他,返回所有数据 
						return this.items
				}
			},


			// 过滤出所有未完成的任务项
			remaining() {
				/*
				return this.items.filter(function (item) {
					 return !item.completed }).length 
			   */
				//ES6 箭头函数 
				return this.items.filter(item => !item.completed).length
			},
			// 定义计算属性选项
			//复选框计算属性(双向绑定) 
			toggleAll: {

				get() { //等价于 get : functon () {...}
					console.log(this.remaining)
					//2. 当 this.remaining 发生变化后,会触发该方法运行 
					// 当所有未完成任务数为 0 , 表示全部完成, 则返回 true 让复选框选中 //反之就 false 不选中 
					return this.remaining === 0
				},
				set(newStatus) {
					// console.log(newStatus) 
					//1. 当点击 checkbox 复选框后状态变化后,就会触发该方法运行, 
					// 迭代出数组每个元素,把当前状态值赋给每个元素的 completed 
					// 下面箭头函数等价于:this.items.forEach(function (item) {
					this.items.forEach((item) => {
						item.completed = newStatus
					})
				}

			}

		}, // **注意** 后面不要少了逗号 ,
	})

	//当路由 hash 值改变后会自动调用此函数 
	window.onhashchange = function () {
		console.log('hash改变了', window.location.hash)
		// 1.获取点击的路由 hash , 当截取的 hash 不为空返回截取的,为空时返回 'all'
		const hash = window.location.hash.substr(2) || 'all'
		console.log('hash', hash)
		// 2. 状态一旦改变,将 hash 赋值给 filterStatus 
		// 当计算属性 filterItems 感知到 filterStatus 变化后,就会重新过滤 
		// 当 filterItems 重新过滤出目标数据后,则自动同步更新到视图中 
		app.filterStatus = hash
	}
	// 第一次访问页面时,调用一次让状态生效 
	window.onhashchange()
})(Vue);

4.13 数据持久化

4.13.1 功能分析

  • 将所有任务项数据持久化到 localStorage 中,它主要是用于本地存储数据。localStorage中一般浏览器支持的是5M大小,这个在不同的浏览器中 localStorage 会有所不同。
  • 分析 :可以使用 Vue 中的 watch 监听器,监听任务数组 items 一旦有改变,则使用 window.localStorage 将它就重新保存到 localStorage

4.13.2 功能实现

使用 window.localStorage 实例进行保存数据与获取数据

  1. 定义 itemStorage 数据存储对象,里面自定义 fetch 获取本地数据 , save 存数据到本地。
  2. 修改 Vue 实例中 data 选项的 items 属性,通过 itemStorage.fetch() 方法初始化数据
  3. Vue 实例中增加一个 watch 选项,用于监听 items 的变化,一旦变化通过 itemStorage.save() 重新保存数据到本地
    注意:因为items数组内部是对象,当对象的值发生变化后要被监听到,在选项参数中使用deep: true
    参考 :https://cn.vuejs.org/v2/api/#vm-watch
// (function (window) {
// 	'use strict';

// 	// Your starting point. Enjoy the ride!

// })(window);
(function (Vue) { //表示依赖了全局的 Vue, 其实不加也可以,只是更加明确点

	var STORAGE_KEY = 'items-vuejs'; // 本地存储数据对象
	const itemStorage = {
		fetch: function () { // 获取本地数据 
			return JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
		},
		save: function (items) {
			// 保存数据到本地
			localStorage.setItem(STORAGE_KEY, JSON.stringify(items));
		}
	}

	//初始化任务
	const items = []

	//自定义全局指令,用于 增加输入框 
	//定义时不要在前面加v-, 引用指令时要加上v- 
	Vue.directive('app-focus', {
		//聚集元素
		inserted(el, binding) {
			el.focus()
		}
	})


	var app = new Vue({
		el: '#todoapp',
		data: {
			items, // 对象属性简写,等价于items: items
			currentItem: null, //上面不要少了逗号, 接收当前点击的任务项
			filterStatus: 'all' // 上面不要少了逗号,接收变化的状态值
		},


		// 监听器 
		watch: { // 如果 items 发生改变,这个函数就会运行 
			items: {
				deep: true, // 发现对象内部值的变化, 要在选项参数中指定 deep: true。
				handler: function (newItems, oldItems) {
					//本地进行存储 
					itemStorage.save(newItems)
				}
			}
		},


		//自定义局部指令,用于编辑输入框
		directives: {
			//定义时不要在前面加v-, 引用指令时要加上v-
			'todo-focus': {
				update(el, binding) { // 每当指令的值更新后,会调用此函数 
					if (binding.value) {
						el.focus()
					}
				}
			}
		},

		methods: {

			//编辑完成 
			finishEdit(item, index, event) {
				const content = event.target.value.trim();
				// 1. 如果为空, 则进行删除任务项
				if (!event.target.value.trim()) {
					//重用 removeItem 函数进行删除 
					this.removeItem(index)
					return
				}
				// 2. 添加数据到任务项中
				item.content = content
				// 3. 移除 .editing 样式 
				this.currentItem = null
			},

			//取消编辑
			cancelEdit() {
				// 移除样式 
				this.currentItem = null
			},

			// 进入编辑状态,当前点击的任务项item赋值currentItem,用于页面判断显示 .editing 
			toEdit(item) {
				this.currentItem = item
			},

			// 移除所有未完成任务项 
			removeCompleted() {
				// 过滤出所有未完成的任务,重新赋值数组即可 
				this.items = this.items.filter(item => !item.completed)
			},

			// 移除任务项
			removeItem(index) {
				// 移除索引为index的一条记录 
				this.items.splice(index, 1)
			},


			addItem(event) { //对象属性函数简写,等价于addItem: function () {
				console.log('addItem', event.target.value)
				//1. 获取文本框输入的数据
				const content = event.target.value.trim()
				//2. 判断数据如果为空,则什么都不做 
				if (!content.length) {
					return
				}
				//3.如果不为空,则添加到数组中 // 生成id值
				const id = this.items.length + 1
				this.items.push({
					id, //等价于 id:id
					content,
					completed: false
				})
				//4. 清空文本框内容
				event.target.value = ''
			}
		},
		// 定义计算属性选项
		computed: {

			// 过滤出不同状态数据 
			filterItems() { //this.filterStatus 作为条件,变化后过滤不同数据 
				switch (this.filterStatus) {
					case "active": // 过滤出未完成的数据 
						return this.items.filter(item => !item.completed)
						break
					case "completed": // 过滤出已完成的数据 
						return this.items.filter(item => item.completed)
						break
					default: // 其他,返回所有数据 
						return this.items
				}
			},


			// 过滤出所有未完成的任务项
			remaining() {
				/*
				return this.items.filter(function (item) {
					 return !item.completed }).length 
			   */
				//ES6 箭头函数 
				return this.items.filter(item => !item.completed).length
			},
			// 定义计算属性选项
			//复选框计算属性(双向绑定) 
			toggleAll: {

				get() { //等价于 get : functon () {...}
					console.log(this.remaining)
					//2. 当 this.remaining 发生变化后,会触发该方法运行 
					// 当所有未完成任务数为 0 , 表示全部完成, 则返回 true 让复选框选中 //反之就 false 不选中 
					return this.remaining === 0
				},
				set(newStatus) {
					// console.log(newStatus) 
					//1. 当点击 checkbox 复选框后状态变化后,就会触发该方法运行, 
					// 迭代出数组每个元素,把当前状态值赋给每个元素的 completed 
					// 下面箭头函数等价于:this.items.forEach(function (item) {
					this.items.forEach((item) => {
						item.completed = newStatus
					})
				}

			}

		}, // **注意** 后面不要少了逗号 ,
	})

	//当路由 hash 值改变后会自动调用此函数 
	window.onhashchange = function () {
		console.log('hash改变了', window.location.hash)
		// 1.获取点击的路由 hash , 当截取的 hash 不为空返回截取的,为空时返回 'all'
		const hash = window.location.hash.substr(2) || 'all'
		console.log('hash', hash)
		// 2. 状态一旦改变,将 hash 赋值给 filterStatus 
		// 当计算属性 filterItems 感知到 filterStatus 变化后,就会重新过滤 
		// 当 filterItems 重新过滤出目标数据后,则自动同步更新到视图中 
		app.filterStatus = hash
	}
	// 第一次访问页面时,调用一次让状态生效 
	window.onhashchange()
})(Vue);

第五章 Vue 过滤器和插件

5.1 过滤器

5.1.1 什么是过滤器

  • 过滤器对将要显示的文本,先进行特定格式化处理,然后再进行显示
  • 注意:过滤器并没有改变原本的数据, 只是产生新的对应的数据

5.1.2 使用方式

  • 定义过滤器:
    • 全局过滤器:
Vue.filter(过滤器名称, function (value1[,value2,...] ) {
 // 数据处理逻辑 
 })
  • 局部过滤器:在Vue实例中使用 filter 选项 , 当前实例范围内可用
new Vue({ 
filters: { 过滤器名称: function (value1[,value2,...] ) {
 // 数据处理逻辑 
 	}
 } 
 })
  1. 过滤器可以用在两个地方:双花括号 {{}} 和 v-bind 表达式
<!-- 在双花括号中 --> 
<div>{{数据属性名称 | 过滤器名称}}</div> 
<div>{{数据属性名称 | 过滤器名称(参数值)}}</div> 
<!-- 在 `v-bind` 中 --> 
<div v-bind:id="数据属性名称 | 过滤器名称"></div> 
<div v-bind:id="数据属性名称 | 过滤器名称(参数值)"></div>

5.1.3 案例演示

需求:

  1. 实现过滤敏感字符,如当文本中有 tmd、sb 都将进行过滤掉
  2. 过滤器传入多个参数 ,实现求和运算

实现:
3. 新建 vue-04-过滤器和插件 目录, 安装 vue.js 模块
4. 在 vue-04-过滤器和插件 目录下创建 01-过滤器.html

<!doctype html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<title>Template • TodoMVC</title>
	<link rel="stylesheet" href="node_modules/todomvc-common/base.css">
	<link rel="stylesheet" href="node_modules/todomvc-app-css/index.css">
	<!-- CSS overrides - remove if you don't need it -->
	<link rel="stylesheet" href="css/app.css">
</head>
<body>
	<div id="app">
		<h3>过滤器接收多个参数:</h3>
		<p>{{content | contentFilter}}</p> 
		<input type="text" :value="content | sensitive">
		<h3>过滤器接收多个参数:</h3>
		<p>{{vueScore | add(javaScore, pythonScore)}}</p>
	</div>
	<script src="node_modules/vue/dist/vue.js" type="text/javascript"></script>
	<script type="text/javascript">
		/*定义全局过滤器: 过滤敏感数据*/
		/*Vue.filter('contentFilter',function (value) { 
			if (!value) return ''
			 return value.toUpperCase().replace('TMD','***').replace('SB','***') })*/
		new Vue({
			el: '#app',
			data: {
				content: '小伙子,TMD就是个SB',
				vueScore: 80,
				javaScore: 95,
				pythonScore: 90
			}, 
			//局部过滤器 
			filters: { //不要少了 s
			contentFilter(value) { //value是调用时 | 左边的那个属性值 
				if (!value) return ''
				return value.toUpperCase().replace('TMD', '***').replace('SB', '***')
			},
			add(num1, num2, num3) { 
				//num1是调用时 | 左边的那个属性值 
				return num1 + num2 + num3
			}
		}
		})

	</script>
</body>
</html>

5.2 自定义插件

5.2.1 插件的作用

  1. 插件通常会为 Vue 添加全局功能,一般是添加全局方法/全局指令/过滤器等
  2. Vue 插件有一个公开方法 install ,通过 install 方法给 Vue 添加全局功能
  3. 通过全局方法 Vue.use() 使用插件,它需要在你调用 new Vue() 启动应用之前完成.

5.2.2 案例演示

  1. 开发插件, 在 vue-04-过滤器和插件 目录下创建 js 目录,在 js 目录建一个 plugins.js 文件
(function () {
	// 声明 MyPlugin 插件对象
    const MyPlugin = {}
    
	MyPlugin.install = function (Vue, options) {
		// 1. 添加全局方法
		Vue.myGlobalMethod = function () {
			alert('MyPlugin插件: 全局方法生效')
		}

		// 2. 添加全局指令 
		Vue.directive('my-directive', {
			inserted: function (el, binding) {
				el.innerHTML = "MyPlugin插件 my-directive:" + binding.value
			}
		})

		// 3. 添加实例方法 
		Vue.prototype.$myMethod = function (methodOption) {
			alert('Vue 实例方法生效:' + methodOption)
		}
	}

	// 将插件添加到 window 对象中 
    window.MyPlugin = MyPlugin
    
})() // 不要少了括号(),让它立即执行

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>14-自定义插件</title>
</head>
<body>
	<div id="app">
		<!-- 引用指令时不要少了 v- -->
		<span v-my-directive="content"></span>
	</div>
	<script src="./node_modules/vue/dist/vue.js" type="text/javascript"></script>
	<script src="js/plugins.js" type="text/javascript"></script>
	<script type="text/javascript">
		// 1.引入自定义插件 MyPlugin 
		// 如果报错:Uncaught ReferenceError: MyPlugin is not defined 
		// 解决方法: 查看 plugins.js 是否引入,如果引入还是报错,检查 js 语法,特别是最后一行不要少了括号 ()
		Vue.use(MyPlugin)
		// 2. 创建 Vue 实例, 模板中使用引用全局指令 v-my-directive="content" 
		var vm = new Vue({
			el: '#app',
			data: {
				content: 'hello'
			}
		})
		// 3. 调用自定义的全局方法, 所以是 Vue 调用,不是 vm 
		Vue.myGlobalMethod()
		// 4. 调用 Vue 实例方法,所以是 vm 调用,不是 Vue 
		vm.$myMethod('mengxuegu')

	</script>
</body>
</body>
</html>

  1. 访问页面的效果:
  2. alert 弹出: MyPlugin插件: 全局方法生效

在这里插入图片描述

  • alert 弹出: Vue 实例方法生效:
    在这里插入图片描述
  • 页面渲染出: MyPlugin插件 my-directive:hello
    在这里插入图片描述

第六章 Vue 组件化开发

6.1 组件的概念

组件(component) 是 Vue.js 最强大的功能之一。
Vue 中的组件化开发就是把网页的重复代码抽取出来 ,封装成一个个可复用的视图组件,然后将这些视图组件拼
接到一块就构成了一个完整的系统。这种方式非常灵活,可以极大的提高我们开发和维护的效率。
通常一套系统会以一棵嵌套的组件树的形式来组织:
在这里插入图片描述

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

  • 组件就是对局部视图的封装,每个组件包含了:

    1. HTML 结构
    2. CSS 样式
    3. JavaScript 行为
    4. data 数据
    5. methods 行为
  • 提高开发效率,增强可维护性,更好的去解决软件上的高耦合、低内聚、无重用的3大代码问题

  • Vue 中的组件思想借鉴于 React

  • 目前主流的前端框架:Angular、React 、Vue 都是组件化开发思想

6.2 组件的基本使用

为了能在模板中使用,这些组件必须先注册以便 Vue 能够识别。
有两种组件的注册类型:全局注册局部注册

6.2.1 全局注册

6.2.1.1 介绍

一般把网页中特殊的公共部分注册为全局组件:轮播图、分页、通用导航栏

  • 全局注册之后,可以在任何新创建的 Vue 实例 (new Vue) 的模板中使用

  • 简单格式:

Vue.component('组件名',{ 
template: '定义组件模板',
data: function(){ //data 选项在组件中必须是一个函数
 return {} 
 }
 //其他选项:methods
})

说明:

  • 组件名:
    可使用驼峰(camelCase)或者横线分隔(kebab-case)命名方式
    但 DOM 中只能使用横线分隔方式进行引用组件
    官方强烈推荐组件名字母全小写且必须包含一个连字符
  • template:定义组件的视图模板
  • data :在组件中必须是一个函数
6.2.1.2 示例
  1. 创建 vue-05-组件化 目录,安装 vue 模块, 创建 01-全局注册.html
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Document</title>
</head>
<body>
	<div id="app">
		<!-- 通过组件名直接使用, 不能使用驼峰形式 -->
		<component-a></component-a>
	</div>
	<script src="../node_modules/vue/dist/vue.js" type="text/javascript"> </script>
	<script type="text/javascript">
		/*1. 全局组件注册: 它们在注册之后可以用在任何新创建的 Vue 根实例 (new Vue) 的模板中
         参数1:组件名 1.可使用驼峰(camelCase)或者横线分隔(kebab-case)命名方式 
         2.DOM 中只能使用横线分隔方式进行引用组件
*/
		Vue.component('component-a', { // template 选项指定此组件的模板代码 
			template: '<div><h1>头部组件 - {{ name }}</h1></div>',
			// data 必须是函数
			data: function () {
				return {
					name: '全局组件'
				}
			}
		})
		new Vue({
			el: '#app'
		})
	</script>
</body>
</html>

6.2.2 局部注册(子组件)

6.2.2.1 介绍

一般把一些非通用部分注册为局部组件,一般只适用于当前项目的。
格式:

1. JS 对象来定义组件: 
var ComponentA = { data: function(){}, template: '组件模板A'} 
var ComponentA = { data: function(){}, template: '组件模板A'} 

2. 在Vue实例中的 components 选项中引用组件: 
3. new Vue({ 
     el: '#app',
	data: {},
	components: { 
// 组件选项 
'component-a': ComponentA // key:组件名,value: 引用的组件对象 'component-b': ComponentB 
    } 
})
6.2.2.2 示例
<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Document</title>
</head>

<body>
	<div id="app">
		<!-- 通过组件名直接使用, 不能使用驼峰形式 -->
		<component-b></component-b>
	</div>
	<script src="../node_modules/vue/dist/vue.js" type="text/javascript"> </script>
	<script type="text/javascript">
		// 定义局部组件对象 
		var ComponentB = {
			template: '<div> 这是:{{ name }}</div>',
			data: function () {
				return {
					name: '局部组件'
				}
			}
		}

		new Vue({
			el: '#app',
			components: {
				// 局部组件 
				'component-b': ComponentB
			}
		})

	</script>

</body>

</html>

6.2.3 总结

  • 组件是可复用的 Vue 实例,不需要手动实例化
  • 与 new Vue 接收相同的选项,例如 data 、 computed 、 watch 、 methods 等
  • 组件的 data 选项必须是一个函数

6.3 多个组件示例

  • 效果演示
    在这里插入图片描述
  • 可将 组件注册 抽取在一个一个的 js 文件中方便管理
  • 在 vue-05-组件化 目录下创建 component 目录存放组件: Header.js Main.js Footer.js
    在这里插入图片描述
  1. Header.js 文件内容
Vue.component('app-header', {
    // template 选项指定此组件的模板代码 
    template: '<div class="header"><h1>头部组件</h1></div>'
})
  1. Main.js 文件内容
Vue.component('app-main', {
    // template 选项指定此组件的模板代码
	template: '<div class="main"><ul><li>客户管理</li><li>帐单管理</li><li>供应商管理</li></ul><h3></h3> </div>'
})

  1. Footer.js 文件内容
Vue.component('app-footer', {
	// template 选项指定此组件的模板代码 
	template: '<div class="footer"><h1>底部组件</h1></div>'
})
  • 页面引入组件 js 文件后, 进行使用
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Document</title>
</head>
<body>
	<div id="app">
		<!-- 通过组件名直接使用, 不能使用驼峰形式 -->
		<!-- 通过组件名直接使用 -->
		<app-header></app-header>
		<app-main></app-main>
		<app-footer></app-footer>
	</div>
	<script src="../node_modules/vue/dist/vue.js" type="text/javascript"> </script>
	<!-- 要在 vue.js 下面引入组件 -->
	<script src="component/Footer.js" type="text/javascript"></script>
	<script src="component/Header.js" type="text/javascript"></script>
	<script src="component/Main.js" type="text/javascript"></script>
	<script type="text/javascript">
		new Vue({
			el: '#app'
		})
	</script>
</body>
</html>

6.4 Bootstrap 首页组件化

6.4.1 分析首页

  • 分析首页可拆分为多少个组件

html页面位于: 01-配套源码\bootstarp

在这里插入图片描述
共拆分为5个组件
在这里插入图片描述

6.4.2 头部导航组件注册

  1. 在 vue-05-组件化 目录下创建 03-bootstrap 目录
  2. 将网盘中 01-配套源码\bootstrap 目录的所有文件复制到 03-bootstrap 目录下
  3. 在 index.html 的 <body> 标签下添加一个 <div id="app"> Vue管理入口
  4. 在 index.html 引入 vue.js 和 创建 Vue 实例
    在这里插入图片描述
<script src="../node_modules/vue/dist/vue.js"></script> 
<script> 
new Vue({
 el: '#app'
  }) 
</script>
  1. 注册和引用头部导航组件 AppNavbar
    1. 左上角显示 梦学谷 ,通过 data 选项函数指定 message 属性显示
    2. 输入框中失去焦点后 alert(‘失去焦点’) , 通过 methods 选项
    3. 在 头部导航区域采用组件形式
<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
	<meta name="description" content="">
	<meta name="author" content="">
	<title>Dashboard Template for Bootstrap</title>
	<!-- Bootstrap core CSS -->
	<link href="style/bootstrap.min.css" rel="stylesheet">
	<!-- Custom styles for this template -->
	<link href="style/dashboard.css" rel="stylesheet">
</head>

<body>
	<!--头部导航区域-->
	<div id="app">
		<!--头部导航区域-->
		<app-navbar></app-navbar>
	</div>

	<!--核心区域:分左右两边-->
	<div class="container-fluid">
		<div class="row">

			<!--左边菜单栏区域-->
			<div class="col-sm-3 col-md-2 sidebar">
				<ul class="nav nav-sidebar">
					<li class="active"><a href="#">Overview <span class="sr-only">(current)</span></a></li>
					<li><a href="#">Reports</a></li>
					<li><a href="#">Analytics</a></li>
					<li><a href="#">Export</a></li>
				</ul>
				<ul class="nav nav-sidebar">
					<li><a href="">Nav item</a></li>
					<li><a href="">Nav item again</a></li>
					<li><a href="">One more nav</a></li>
					<li><a href="">Another nav item</a></li>
					<li><a href="">More navigation</a></li>
				</ul>
				<ul class="nav nav-sidebar">
					<li><a href="">Nav item again</a></li>
					<li><a href="">One more nav</a></li>
					<li><a href="">Another nav item</a></li>
				</ul>
			</div>

			<!--右边主页面区域: 分上下两个区域-->
			<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">

				<!--右边上半区域-->
				<h1 class="page-header">Dashboard</h1>
				<div class="row placeholders">
					<div class="col-xs-6 col-sm-3 placeholder">
						<img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200"
							height="200" class="img-responsive" alt="Generic placeholder thumbnail">
						<h4>Label</h4>
						<span class="text-muted">Something else</span>
					</div>
					<div class="col-xs-6 col-sm-3 placeholder">
						<img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200"
							height="200" class="img-responsive" alt="Generic placeholder thumbnail">
						<h4>Label</h4>
						<span class="text-muted">Something else</span>
					</div>
					<div class="col-xs-6 col-sm-3 placeholder">
						<img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200"
							height="200" class="img-responsive" alt="Generic placeholder thumbnail">
						<h4>Label</h4>
						<span class="text-muted">Something else</span>
					</div>
					<div class="col-xs-6 col-sm-3 placeholder">
						<img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200"
							height="200" class="img-responsive" alt="Generic placeholder thumbnail">
						<h4>Label</h4>
						<span class="text-muted">Something else</span>
					</div>
				</div>
				<!--右边下半区域-->
				<h2 class="sub-header">Section title</h2>
				<div class="table-responsive">
					<table class="table table-striped">
						<thead>
							<tr>
								<th>#</th>
								<th>Header</th>
								<th>Header</th>
								<th>Header</th>
								<th>Header</th>
							</tr>
						</thead>
						<tbody>
							<tr>
								<td>1,001</td>
								<td>Lorem</td>
								<td>ipsum</td>
								<td>dolor</td>
								<td>sit</td>
							</tr>
							<tr>
								<td>1,002</td>
								<td>amet</td>
								<td>consectetur</td>
								<td>adipiscing</td>
								<td>elit</td>
							</tr>
							<tr>
								<td>1,003</td>
								<td>Integer</td>
								<td>nec</td>
								<td>odio</td>
								<td>Praesent</td>
							</tr>
							<tr>
								<td>1,003</td>
								<td>libero</td>
								<td>Sed</td>
								<td>cursus</td>
								<td>ante</td>
							</tr>
						</tbody>
					</table>
				</div>
			</div>
		</div>
	</div>

	<script src="../../../node_modules/vue/dist/vue.js"></script>
	<script type="text/javascript">
		//组件对象定义
		const AppNavbar = {
			template: `<nav class="navbar navbar-inverse navbar-fixed-top">
      <div class="container-fluid">
        <div class="navbar-header">
          <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </button>
          <a class="navbar-brand" href="#">{{ projectName }}</a>
        </div>
        <div id="navbar" class="navbar-collapse collapse">
          <ul class="nav navbar-nav navbar-right">
            <li><a href="#">Dashboard</a></li>
            <li><a href="#">Settings</a></li>
            <li><a href="#">Profile</a></li>
            <li><a href="#">Help</a></li>
          </ul>
          <form class="navbar-form navbar-right">
            <input type="text" class="form-control" placeholder="Search..." @blur="search">
          </form>
        </div>
      </div>
    </nav>`,

			data: function () {
				return {
					projectName: '梦学谷'
				}
      },
      
      methods: {
        search(){
          alert('失去焦点')
        }
      }
		}

		new Vue({
			el: '#app',
      components:{
        'app-navbar': AppNavbar
      }
		})

	</script>
</body>
</html>

  1. 浏览器访问 index.html
    如果如果控制台报如下错,则 <app-navbar></app-navbar> 标签名与组件名不相同

在这里插入图片描述
7. 抽取定义的 AppNavbar 对象到 AppNavbar.js 中

  • 把 const AppNavbar= {…} 部分剪切进 AppNavbar.js 文件中, AppNavbar对象添加到 window 域
;(function () { 
//粘贴到这里 
window.AppNavbar = { 
//
 } 
})()
  • 将 template 提取出来AppNavbar.js
;
(function () {

	//组件对象定义
	const template = `<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container-fluid">
<div class="navbar-header">
  <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
    <span class="sr-only">Toggle navigation</span>
    <span class="icon-bar"></span>
    <span class="icon-bar"></span>
    <span class="icon-bar"></span>
  </button>
  <a class="navbar-brand" href="#">{{ projectName }}</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
  <ul class="nav navbar-nav navbar-right">
    <li><a href="#">Dashboard</a></li>
    <li><a href="#">Settings</a></li>
    <li><a href="#">Profile</a></li>
    <li><a href="#">Help</a></li>
  </ul>
  <form class="navbar-form navbar-right">
    <input type="text" class="form-control" placeholder="Search..." @blur="search">
  </form>
</div>
</div>
</nav>`
    
	window.AppNavbar = { // 添加 window 域中,html 页面才可以进行获取 
		template, // 等价于template: template,
		data: function () {
			return {
				projectName: '梦学谷'
			}
		},
		methods: {
			search() {
				alert('失去焦点')
			}
		}
	}

})()

  1. 在 index.html 中引入 AppNavbar.js
<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
	<meta name="description" content="">
	<meta name="author" content="">
	<title>Dashboard Template for Bootstrap</title>
	<!-- Bootstrap core CSS -->
	<link href="style/bootstrap.min.css" rel="stylesheet">
	<!-- Custom styles for this template -->
	<link href="style/dashboard.css" rel="stylesheet">
</head>

<body>
	<!--头部导航区域-->
	<div id="app">
		<!--头部导航区域-->
		<app-navbar></app-navbar>
	</div>

	<!--核心区域:分左右两边-->
	<div class="container-fluid">
		<div class="row">

			<!--左边菜单栏区域-->
			<div class="col-sm-3 col-md-2 sidebar">
				<ul class="nav nav-sidebar">
					<li class="active"><a href="#">Overview <span class="sr-only">(current)</span></a></li>
					<li><a href="#">Reports</a></li>
					<li><a href="#">Analytics</a></li>
					<li><a href="#">Export</a></li>
				</ul>
				<ul class="nav nav-sidebar">
					<li><a href="">Nav item</a></li>
					<li><a href="">Nav item again</a></li>
					<li><a href="">One more nav</a></li>
					<li><a href="">Another nav item</a></li>
					<li><a href="">More navigation</a></li>
				</ul>
				<ul class="nav nav-sidebar">
					<li><a href="">Nav item again</a></li>
					<li><a href="">One more nav</a></li>
					<li><a href="">Another nav item</a></li>
				</ul>
			</div>

			<!--右边主页面区域: 分上下两个区域-->
			<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">

				<!--右边上半区域-->
				<h1 class="page-header">Dashboard</h1>
				<div class="row placeholders">
					<div class="col-xs-6 col-sm-3 placeholder">
						<img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200"
							height="200" class="img-responsive" alt="Generic placeholder thumbnail">
						<h4>Label</h4>
						<span class="text-muted">Something else</span>
					</div>
					<div class="col-xs-6 col-sm-3 placeholder">
						<img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200"
							height="200" class="img-responsive" alt="Generic placeholder thumbnail">
						<h4>Label</h4>
						<span class="text-muted">Something else</span>
					</div>
					<div class="col-xs-6 col-sm-3 placeholder">
						<img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200"
							height="200" class="img-responsive" alt="Generic placeholder thumbnail">
						<h4>Label</h4>
						<span class="text-muted">Something else</span>
					</div>
					<div class="col-xs-6 col-sm-3 placeholder">
						<img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200"
							height="200" class="img-responsive" alt="Generic placeholder thumbnail">
						<h4>Label</h4>
						<span class="text-muted">Something else</span>
					</div>
				</div>
				<!--右边下半区域-->
				<h2 class="sub-header">Section title</h2>
				<div class="table-responsive">
					<table class="table table-striped">
						<thead>
							<tr>
								<th>#</th>
								<th>Header</th>
								<th>Header</th>
								<th>Header</th>
								<th>Header</th>
							</tr>
						</thead>
						<tbody>
							<tr>
								<td>1,001</td>
								<td>Lorem</td>
								<td>ipsum</td>
								<td>dolor</td>
								<td>sit</td>
							</tr>
							<tr>
								<td>1,002</td>
								<td>amet</td>
								<td>consectetur</td>
								<td>adipiscing</td>
								<td>elit</td>
							</tr>
							<tr>
								<td>1,003</td>
								<td>Integer</td>
								<td>nec</td>
								<td>odio</td>
								<td>Praesent</td>
							</tr>
							<tr>
								<td>1,003</td>
								<td>libero</td>
								<td>Sed</td>
								<td>cursus</td>
								<td>ante</td>
							</tr>
						</tbody>
					</table>
				</div>
			</div>
		</div>
	</div>

	<script src="../../../node_modules/vue/dist/vue.js"></script>
	<!-- 注意要在 vue.js 下面引入 -->
	<script src="components/AppNavbar.js"></script>
	<script type="text/javascript">
		new Vue({
			el: '#app',
			components: {
				AppNavbar // 等价于 AppNavbar: AppNavbar
			}

		})

	</script>
</body>

</html>
  1. 效果
    在这里插入图片描述
  2. 如果报错解决方式 ,
    在 index.html 中要引入 AppNavbar.js
    AppNavbar.js 要将 AppNavbar 放到 window 域中
    在这里插入图片描述

6.4.3 左侧导航组件注册

  1. AppLeaf.js 文件中定义 AppLeaf 组件对象
;
(function() {

	const template = `<div class="col-sm-3 col-md-2 sidebar">
    <ul class="nav nav-sidebar">
        <li class="active"><a href="#">{{ message }}<span class="sr-only">(current)</span></a></li>
        <li><a href="#">Reports</a></li>
        <li><a href="#">Analytics</a></li>
        <li><a href="#">Export</a></li>
    </ul>
    <ul class="nav nav-sidebar">
        <li><a href="">Nav item</a></li>
        <li><a href="">Nav item again</a></li>
        <li><a href="">One more nav</a></li>
        <li><a href="">Another nav item</a></li>
        <li><a href="">More navigation</a></li>
    </ul>
    <ul class="nav nav-sidebar">
        <li><a href="">Nav item again</a></li>
        <li><a href="">One more nav</a></li>
        <li><a href="">Another nav item</a></li>
    </ul>
</div>`

	window.AppLeaf = {
		template,
		data: function () {
			return {
				message: '学员管理'
			}
		}
	}

})()
  1. 在 index.html 中引入 AppLeaf.js , Vue实例中添加 AppLeaf 组件, 主页面通过 <app-leaf/> 组件渲染
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
	<meta name="description" content="">
	<meta name="author" content="">
	<title>Dashboard Template for Bootstrap</title>
	<!-- Bootstrap core CSS -->
	<link href="style/bootstrap.min.css" rel="stylesheet">
	<!-- Custom styles for this template -->
	<link href="style/dashboard.css" rel="stylesheet">
</head>
<body>
	<!--头部导航区域-->
	<div id="app">
		<!--头部导航区域-->
		<app-navbar></app-navbar>
	  <!--核心区域:分左右两边-->
    <div class="container-fluid">
      <div class="row">
        <!--左边菜单栏区域--> 
        <app-leaf></app-leaf>
        <!--右边主页面区域: 分上下两个区域-->
        <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">

          <!--右边上半区域-->
          <h1 class="page-header">Dashboard</h1>
          <div class="row placeholders">
            <div class="col-xs-6 col-sm-3 placeholder">
              <img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200"
                height="200" class="img-responsive" alt="Generic placeholder thumbnail">
              <h4>Label</h4>
              <span class="text-muted">Something else</span>
            </div>
            <div class="col-xs-6 col-sm-3 placeholder">
              <img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200"
                height="200" class="img-responsive" alt="Generic placeholder thumbnail">
              <h4>Label</h4>
              <span class="text-muted">Something else</span>
            </div>
            <div class="col-xs-6 col-sm-3 placeholder">
              <img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200"
                height="200" class="img-responsive" alt="Generic placeholder thumbnail">
              <h4>Label</h4>
              <span class="text-muted">Something else</span>
            </div>
            <div class="col-xs-6 col-sm-3 placeholder">
              <img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200"
                height="200" class="img-responsive" alt="Generic placeholder thumbnail">
              <h4>Label</h4>
              <span class="text-muted">Something else</span>
            </div>
          </div>
          <!--右边下半区域-->
          <h2 class="sub-header">Section title</h2>
          <div class="table-responsive">
            <table class="table table-striped">
              <thead>
                <tr>
                  <th>#</th>
                  <th>Header</th>
                  <th>Header</th>
                  <th>Header</th>
                  <th>Header</th>
                </tr>
              </thead>
              <tbody>
                <tr>
                  <td>1,001</td>
                  <td>Lorem</td>
                  <td>ipsum</td>
                  <td>dolor</td>
                  <td>sit</td>
                </tr>
                <tr>
                  <td>1,002</td>
                  <td>amet</td>
                  <td>consectetur</td>
                  <td>adipiscing</td>
                  <td>elit</td>
                </tr>
                <tr>
                  <td>1,003</td>
                  <td>Integer</td>
                  <td>nec</td>
                  <td>odio</td>
                  <td>Praesent</td>
                </tr>
                <tr>
                  <td>1,003</td>
                  <td>libero</td>
                  <td>Sed</td>
                  <td>cursus</td>
                  <td>ante</td>
                </tr>
              </tbody>
            </table>
          </div>
        </div>
      </div>
    </div>
</div>
	<script src="../../../node_modules/vue/dist/vue.js"></script>
	<!-- 注意要在 vue.js 下面引入 -->
  <script src="components/AppNavbar.js"></script>
  <script src="components/AppLeaf.js"></script>
	<script type="text/javascript">
		new Vue({
			el: '#app',
			components: {
        AppNavbar, // 等价于 AppNavbar: AppNavbar
        AppLeaf
			}

		})
	</script>
</body>
</html>

  1. 效果
    在这里插入图片描述

6.4.4 右侧主页面组件注册

6.4.4.1 主页面组件化
  1. 在 home\AppHome.js 中定义 AppHome 组件对象
;
(function () {
	const template = `<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
    <!--右边上半区域-->
    <h1 class="page-header">Dashboard</h1>
    <div class="row placeholders">
      <div class="col-xs-6 col-sm-3 placeholder">
        <img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200"
          height="200" class="img-responsive" alt="Generic placeholder thumbnail">
        <h4>Label</h4>
        <span class="text-muted">Something else</span>
      </div>
      <div class="col-xs-6 col-sm-3 placeholder">
        <img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200"
          height="200" class="img-responsive" alt="Generic placeholder thumbnail">
        <h4>Label</h4>
        <span class="text-muted">Something else</span>
      </div>
      <div class="col-xs-6 col-sm-3 placeholder">
        <img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200"
          height="200" class="img-responsive" alt="Generic placeholder thumbnail">
        <h4>Label</h4>
        <span class="text-muted">Something else</span>
      </div>
      <div class="col-xs-6 col-sm-3 placeholder">
        <img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200"
          height="200" class="img-responsive" alt="Generic placeholder thumbnail">
        <h4>Label</h4>
        <span class="text-muted">Something else</span>
      </div>
    </div>
    <!--右边下半区域-->
    <h2 class="sub-header">Section title</h2>
    <div class="table-responsive">
      <table class="table table-striped">
        <thead>
          <tr>
            <th>#</th>
            <th>Header</th>
            <th>Header</th>
            <th>Header</th>
            <th>Header</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>1,001</td>
            <td>Lorem</td>
            <td>ipsum</td>
            <td>dolor</td>
            <td>sit</td>
          </tr>
          <tr>
            <td>1,002</td>
            <td>amet</td>
            <td>consectetur</td>
            <td>adipiscing</td>
            <td>elit</td>
          </tr>
          <tr>
            <td>1,003</td>
            <td>Integer</td>
            <td>nec</td>
            <td>odio</td>
            <td>Praesent</td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>`

	window.AppHome = {
		template
    }
    
})()
  1. 在 index.html 中引入 components/home/AppHome.js , Vue实例添加 AppHome 组件,主页面通过<app-home/> 组件渲染
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
	<meta name="description" content="">
	<meta name="author" content="">
	<title>Dashboard Template for Bootstrap</title>
	<!-- Bootstrap core CSS -->
	<link href="style/bootstrap.min.css" rel="stylesheet">
	<!-- Custom styles for this template -->
	<link href="style/dashboard.css" rel="stylesheet">
</head>
<body>
	<!--头部导航区域-->
	<div id="app">
		<!--头部导航区域-->
		<app-navbar></app-navbar>
	  <!--核心区域:分左右两边-->
    <div class="container-fluid">
      <div class="row">
        <!--左边菜单栏区域--> 
        <app-leaf></app-leaf>
        <!--右边主页面区域: 分上下两个区域-->
        <app-home></app-home>
      </div>
    </div>
</div>
	<script src="../../../node_modules/vue/dist/vue.js"></script>
	<!-- 注意要在 vue.js 下面引入 -->
  <script src="components/AppNavbar.js"></script>
  <script src="components/AppLeaf.js"></script>
  <script src="components/home/AppHome.js"></script>
	<script type="text/javascript">
		new Vue({
			el: '#app',
			components: {
        AppNavbar, // 等价于 AppNavbar: AppNavbar
        AppLeaf,
        AppHome
			}

		})
	</script>
</body>
</html>
6.4.4.2 Dashboard 子组件化
  • 将 AppHome.js 中的 右边上半区域 中的 <div> 部分剪切到 Dashboard 组件中,Dashboar.js 内容如下:
;
(function () {

	const template = `
<!--右边上半区域-->
<div class="row placeholders">
  <div class="col-xs-6 col-sm-3 placeholder">
    <img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200"
      height="200" class="img-responsive" alt="Generic placeholder thumbnail">
    <h4>Label</h4>
    <span class="text-muted">Something else</span>
  </div>
  <div class="col-xs-6 col-sm-3 placeholder">
    <img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200"
      height="200" class="img-responsive" alt="Generic placeholder thumbnail">
    <h4>Label</h4>
    <span class="text-muted">Something else</span>
  </div>
  <div class="col-xs-6 col-sm-3 placeholder">
    <img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200"
      height="200" class="img-responsive" alt="Generic placeholder thumbnail">
    <h4>Label</h4>
    <span class="text-muted">Something else</span>
  </div>
  <div class="col-xs-6 col-sm-3 placeholder">
    <img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200"
      height="200" class="img-responsive" alt="Generic placeholder thumbnail">
    <h4>Label</h4>
    <span class="text-muted">Something else</span>
  </div>
</div>`
    
	window.Dashboard = {
		template
	}

})()

  1. 在 AppHome.js 中引入 Dashboard 组件作为 AppHome 的子组件
    在 AppHome 对象的 components 选项中注册 Dashboard 组件
    在 AppHome 对象的 template 模板中引入组件<dashboard/>
;
(function () {
	const template = `<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
    <!--右边上半区域-->
    <h1 class="page-header">Dashboard</h1>
    <dashboard></dashboard>

    <!--右边下半区域-->
    <h2 class="sub-header">Section title</h2>
    <div class="table-responsive">
      <table class="table table-striped">
        <thead>
          <tr>
            <th>#</th>
            <th>Header</th>
            <th>Header</th>
            <th>Header</th>
            <th>Header</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>1,001</td>
            <td>Lorem</td>
            <td>ipsum</td>
            <td>dolor</td>
            <td>sit</td>
          </tr>
          <tr>
            <td>1,002</td>
            <td>amet</td>
            <td>consectetur</td>
            <td>adipiscing</td>
            <td>elit</td>
          </tr>
          <tr>
            <td>1,003</td>
            <td>Integer</td>
            <td>nec</td>
            <td>odio</td>
            <td>Praesent</td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>`

	window.AppHome = {
        template,
        components: {
            // 子组件
            Dashboard
        }
    }
    
})()
  1. 在 index.html 中引入 Dashboard.js , 注意:Dashboard.js 要在 AppHome.js 前面引入
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
	<meta name="description" content="">
	<meta name="author" content="">
	<title>Dashboard Template for Bootstrap</title>
	<!-- Bootstrap core CSS -->
	<link href="style/bootstrap.min.css" rel="stylesheet">
	<!-- Custom styles for this template -->
	<link href="style/dashboard.css" rel="stylesheet">
</head>
<body>
	<!--头部导航区域-->
	<div id="app">
		<!--头部导航区域-->
		<app-navbar></app-navbar>
	  <!--核心区域:分左右两边-->
    <div class="container-fluid">
      <div class="row">
        <!--左边菜单栏区域--> 
        <app-leaf></app-leaf>
        <!--右边主页面区域: 分上下两个区域-->
        <app-home></app-home>
      </div>
    </div>
</div>
	<script src="../../../node_modules/vue/dist/vue.js"></script>
	<!-- 注意要在 vue.js 下面引入 -->
  <script src="components/AppNavbar.js"></script>
  <script src="components/AppLeaf.js"></script>
  <!-- 注意:Dashboard.js 要在 Home.js 前面引入 --> 
  <script src="components/Home/Dashboard.js"></script>
  <script src="components/home/AppHome.js"></script>
	<script type="text/javascript">
		new Vue({
			el: '#app',
			components: {
        AppNavbar, // 等价于 AppNavbar: AppNavbar
        AppLeaf,
        AppHome
			}

		})
	</script>
</body>
</html>

6.4.4.3 查询列表子组件化
  1. 将 AppHome.js 中的 右边下半区域 中的 <div> 部分剪切到 HomeList.js 组件中, HomeList.js 内容如下:
;
(function () {
	const template = `<div class="table-responsive">
    <table class="table table-striped">
      <thead>
        <tr>
          <th>#</th>
          <th>Header</th>
          <th>Header</th>
          <th>Header</th>
          <th>Header</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>1,001</td>
          <td>Lorem</td>
          <td>ipsum</td>
          <td>dolor</td>
          <td>sit</td>
        </tr>
      </tbody>
    </table>
  </div>`

	window.HomeList = {
		template,
		data() {
			return {
				name: '梦学谷'
			}
		}
	}

})()
  1. 在 AppHome.js 中引入 HomeList 组件作为 Home 的子组件
    在 AppHome 对象的 components 选项中注册 HomeList 组件
    在 AppHome 对象的 template 模板中引入组件 <home-list/> , 注意是横线分隔方式引用组件
;
(function () {
	const template = `<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
    <!--右边上半区域-->
    <h1 class="page-header">Dashboard</h1>
    <dashboard></dashboard>

    <!--右边下半区域-->
    <h2 class="sub-header">Section title</h2>
    <home-list></home-list>
  </div>`

	window.AppHome = {
        template,
        components: {
            // 子组件
            Dashboard,
            HomeList
        }
    }
    
})()

index.html

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
	<meta name="description" content="">
	<meta name="author" content="">
	<title>Dashboard Template for Bootstrap</title>
	<!-- Bootstrap core CSS -->
	<link href="style/bootstrap.min.css" rel="stylesheet">
	<!-- Custom styles for this template -->
	<link href="style/dashboard.css" rel="stylesheet">
</head>
<body>
	<!--头部导航区域-->
	<div id="app">
		<!--头部导航区域-->
		<app-navbar></app-navbar>
	  <!--核心区域:分左右两边-->
    <div class="container-fluid">
      <div class="row">
        <!--左边菜单栏区域--> 
        <app-leaf></app-leaf>
        <!--右边主页面区域: 分上下两个区域-->
        <app-home></app-home>
      </div>
    </div>
</div>
	<script src="../../../node_modules/vue/dist/vue.js"></script>
	<!-- 注意要在 vue.js 下面引入 -->
  <script src="components/AppNavbar.js"></script>
  <script src="components/AppLeaf.js"></script>
  <!-- 注意:Dashboard.js 要在 Home.js 前面引入 --> 
  <script src="components/Home/Dashboard.js"></script>
  <script src="components/Home/HomeList.js"></script>
  <script src="components/home/AppHome.js"></script>
	<script type="text/javascript">
		new Vue({
			el: '#app',
			components: {
        AppNavbar, // 等价于 AppNavbar: AppNavbar
        AppLeaf,
        AppHome
			}

		})
	</script>
</body>
</html>

  • 效果
    在这里插入图片描述
6.4.5 极致组件化

在这里插入图片描述

6.4.5.1 根组件提取 App.js
  • 将 index.html 中<div id="#app"> 标签体中的代码提取出来,变成一个根组件存入 App.js
    中,提取的内容如下:
<app-navbar></app-navbar> 
<!--核心区域:分左右两边--> 
<div class="container-fluid"> 
<div class="row"> 
	<!--左边菜单栏区域--> 
	<app-leaf></app-leaf> 
	<app-home></app-home> 
</div> 
</div>
  • 在 03-bootstrap 目录下创建 App.js 文件
  • <div id="#app"> 标签体中的代码剪切到 App.js 文件中作为模板页面
    注意:template 模板中必须要的根元素,所以要在提取的内容外层加上 <div></div> , 一定要不要少了,不然报以下错误:
    在这里插入图片描述
    App.js
;
(function () {

	// 不要少了最外层的根元素 div
	const template = `<div> 
        <app-navbar></app-navbar> 
        <!--核心区域:分左右两边--> 
        <div class="container-fluid"> 
            <div class="row"> 
                <!--左边菜单栏区域-->
                <app-leaf></app-leaf> 
                <app-home></app-home>
            </div> 
        </div> 
    </div>`
	window.App = {
		template,
		components: {
            AppNavbar, // 等价于 Navbar: Navbar 
            AppLeaf,
            AppHome
		}
	}

})()
6.4.5.2 剪切组件对象
  • 当前 App.js 的 template 中引用了 AppNavbar 、 AppLeaf 和 AppHome 组件,所以我们要将index.html 中的 components 选项中的组件对象剪切到 App 组件对象中。

App.js 代码如下:

;
(function () {

	// 不要少了最外层的根元素 div
	const template = `<div> 
        <app-navbar></app-navbar> 
        <!--核心区域:分左右两边--> 
        <div class="container-fluid"> 
            <div class="row"> 
                <!--左边菜单栏区域-->
                <app-leaf></app-leaf> 
                <app-home></app-home>
            </div> 
        </div> 
    </div>`
	window.App = {
		template,
		components: {
            AppNavbar, // 等价于 Navbar: Navbar 
            AppLeaf,
            AppHome
		}
	}

})()
6.4.5.3 index.html 引入 App.js
  1. 在 index.html 的引入 App.js 文件,并在 Vue 实例中的 components 选项中引入 App 组件,
    然后在 <div id="app"> 下引用 </app>
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
	<meta name="description" content="">
	<meta name="author" content="">
	<title>Dashboard Template for Bootstrap</title>
	<!-- Bootstrap core CSS -->
	<link href="style/bootstrap.min.css" rel="stylesheet">
	<!-- Custom styles for this template -->
	<link href="style/dashboard.css" rel="stylesheet">
</head>
<body>
	<!-- 留一个组件的出口,此处要被子组件替换 --> 
	<div id="app"> 
		<app></app> 
	</div>

	<script src="../../../node_modules/vue/dist/vue.js"></script>
	<!-- 注意要在 vue.js 下面引入 -->
  <script src="components/AppNavbar.js"></script>
  <script src="components/AppLeaf.js"></script>
  <!-- 注意:Dashboard.js 要在 Home.js 前面引入 --> 
  <script src="components/Home/Dashboard.js"></script>
  <script src="components/Home/HomeList.js"></script>
  <script src="components/home/AppHome.js"></script>
  <script src="App.js"></script>
 
	<script type="text/javascript">
		new Vue({
			el: '#app',
			components: {
       			App //等价于 App: App
			}

		})
	</script>
</body>
</html>

  1. <div id="app"> 下通过</app>引用 App 组件不是很好, 因为页面代码中会多出一个 div 。更好的方式是,在 <div id="app"> 下无须使用</app> 引用 App 组件,可以通过 Vue 根实例的 template 选项引用组件 <app></app> 后,然后会把 template 中的渲染结果替换掉#app 标签。
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
	<meta name="description" content="">
	<meta name="author" content="">
	<title>Dashboard Template for Bootstrap</title>
	<!-- Bootstrap core CSS -->
	<link href="style/bootstrap.min.css" rel="stylesheet">
	<!-- Custom styles for this template -->
	<link href="style/dashboard.css" rel="stylesheet">
</head>
<body>
	<!-- 留一个组件的出口,此处要被子组件替换 --> 
	<div id="app"> 
		
	</div>

	<script src="../../../node_modules/vue/dist/vue.js"></script>
	<!-- 注意要在 vue.js 下面引入 -->
  <script src="components/AppNavbar.js"></script>
  <script src="components/AppLeaf.js"></script>
  <!-- 注意:Dashboard.js 要在 Home.js 前面引入 --> 
  <script src="components/Home/Dashboard.js"></script>
  <script src="components/Home/HomeList.js"></script>
  <script src="components/home/AppHome.js"></script>
  <script src="App.js"></script>
 
	<script type="text/javascript">
		new Vue({
			el: '#app',
			components: {
       			App //等价于 App: App
			}

		})
	</script>
</body>
</html>

  1. 当前 index.html 文件中还有 JS 代码,可以将这些 JS 代码提取出来放到 main.js 中
    在 03-bootstrap 目录下创建一个 main.js 文件
    剪切 Vue 实例化代码到 main.js 中
    main.js
new Vue({
	el: '#app', // Vue 根实例中有 template 选项引用了组件后,然后会把 template 中的渲染结果替换掉 #app 标签 
	template: '<app></app>',
	components: {
		App //等价于 App: App
	}
})

index.html 文件中引入 main.js

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
	<meta name="description" content="">
	<meta name="author" content="">
	<title>Dashboard Template for Bootstrap</title>
	<!-- Bootstrap core CSS -->
	<link href="style/bootstrap.min.css" rel="stylesheet">
	<!-- Custom styles for this template -->
	<link href="style/dashboard.css" rel="stylesheet">
</head>
<body>
	<!-- 留一个组件的出口,此处要被子组件替换 --> 
	<div id="app"> 
	</div>

	<script src="../../../node_modules/vue/dist/vue.js"></script>
	<!-- 注意要在 vue.js 下面引入 -->
  <script src="components/AppNavbar.js"></script>
  <script src="components/AppLeaf.js"></script>
  <!-- 注意:Dashboard.js 要在 Home.js 前面引入 --> 
  <script src="components/Home/Dashboard.js"></script>
  <script src="components/Home/HomeList.js"></script>
  <script src="components/home/AppHome.js"></script>
  <script src="App.js"></script>
  <script src="main.js"></script>
</body>
</html>

6.5 组件化注意事项

  • 组件可以理解为特殊的 Vue 实例,不需要手动实例化,管理自己的 template 模板
  • 组件的 template 必须有且只有一个根节点
  • 组件的 data 选项必须是函数,且函数体返回一个对象
  • 组件与组件之间是相互独立的,可以配置自己的一些选项资源 data、methods、computed 等等
  • 思想:组件自己管理自己,不影响别人

6.6 Vue 父子组件间通信

6.6.1 组件间通信方式

1.props 父组件向子组件传递数据
2. $emit自定义事件
3. slot插槽分发内容

6.6.2 组件间通信规则

  1. 不要在子组件中直接修改父组件传递的数据
  2. 数据初始化时,应当看初始化的数据是否用于多个组件中,如果需要被用于多个组件中,则初始化在父组件中;如果只在一个组件中使用,那就初始化在这个要使用的组件中。
  3. 数据初始化在哪个组件, 更新数据的方法(函数)就应该定义在哪个组件。

6.6.3 props 向子组件传递数据

6.6.3.1 声明组件对象中定义 props
  1. 在声明组件对象中使用 props 选项指定
	const MyComponent = {
		template: '<div></div>',
		props: 此处值有以下3种方式,
		components: {}
	}

方式1:指定传递属性名,注意是 数组形式

props: ['id','name''salary', 'isPublished', 'commentIds', 'author', 'getEmp']

方式2:指定传递属性名和数据类型,注意是 对象形式

props: {
		id: Number,
		name: String,
		salary: Number,
		isPublished: Boolean,
		commentIds: Array,
		author: Object,
		getEmp: Function
	}

方式3:指定属性名、数据类型、必要性、默认值

	props: {
		name: {
			type: String,
			required: true,
			default: 'mxg'
		}
	}
6.6.3.2 引用组件时动态赋值

在引用组件时,通过 v-bind 动态赋值

<my-component v-bind:id="2" :name="meng" :salary="9999" :is-published="true" :comment-ids="[1, 2]" :author="{name: 'alan'}" :get-emp="getEmp" > </my-component>
6.6.3.3 传递数据注意
  1. props 只用于父组件向子组件传递数据
  2. 所有标签属性都会成为组件对象的属性, 模板页面可以直接引用
  3. 问题:
    a. 如果需要向非子后代传递数据,必须多层逐层传递
    b. 兄弟组件间也不能直接 props 通信, 必须借助父组件才可以

6.6.4 props 案例-列表渲染

需求:实现 DashBoard 和 HomeList 组件中渲染数据功能
6.6.4.1 复制 bootstrp 案例
  1. 复制 vue-05-组件化\03-bootstrap 项目为 vue-06-组件间通信
  2. NPM 安装 vue 模块
  3. 更改 vue-06-组件间通信\index.html 中 vue.js 的路径
<script src="node_modules/vue/dist/vue.js"></script>
  1. 测试访问 index.html 正常,再进行下面操作
6.6.4.2 AppHome.js
;
(function () {
	const template = `<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
    <!--右边上半区域-->
    <h1 class="page-header">Dashboard</h1>
    <dashboard :hobbies="hobbies"></dashboard>

    <!--右边下半区域-->
    <h2 class="sub-header">Section title</h2>
    <home-list :emp-list="empList"></home-list>
  </div>`

	window.AppHome = {
		template,
		data() {
			return {
				hobbies: ['看书', '台球', '睡觉', '撸代码'],
				empList: [{
					id: 1,
					name: '放生1',
					salary: 80001
				}, {
					id: 2,
					name: '放生2',
					salary: 80002
				}, {
					id: 3,
					name: '放生3',
					salary: 80003
				}, {
					id: 4,
					name: '放生4',
					salary: 80004
				}]
			}
		},
		components: {
			// 子组件
			Dashboard,
			HomeList
		}
	}

})()

6.6.4.3 Dashboard.js
;
(function () {

	const template = `
<!--右边上半区域-->
<div class="row placeholders">
  <div v-for="(hobby,index) in hobbies" :key="index" class="col-xs-6 col-sm-3 placeholder">
    <img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200"
      height="200" class="img-responsive" alt="Generic placeholder thumbnail">
    <h4>{{hobby}}</h4>
    <span class="text-muted">Something else</span>
  </div>
</div>`

	window.Dashboard = {
		template,
		// 声明当前子组件接收父组件的属性
		props: ['hobbies']
	}

})()

6.6.4.4 HomeList.js

将 HomeList.js 中列表<tr></tr> 抽取到一个新的 Item 组件中

;
(function () {
	const template = `<div class="table-responsive">
    <table class="table table-striped">
      <thead>
        <tr>
          <th>ID</th> 
          <th>姓名</th> 
          <th>工资</th>
        </tr>
      </thead>
      <tbody>
      <Item v-for="(emp, index) in empList" :key="emp.id" :emp="emp"/>
      </tbody>
    </table>
  </div>`

	window.HomeList = {
		template,
		//声明组件接收父组件传递的属性
		props: {
			empList: Array
		},
		data() {
			return {
				name: '梦学谷'
			}
		},
		components: {
			Item
		}
	}



})()

6.6.4.5 Item.js

新增的 Item.js 位于 vue-06-组件化通信\components\home 目录下

;
(function () {
	const template = ` <tr><td>{{emp.id}}</td>
    <td>{{emp.name}}</td>
    <td>{{emp.salary}}</td>
    </tr> `
	window.Item = {
		props: {
			emp: { // 指定属性名/数据类型/是否必须 
				type: Object,
				required: true
			}
		},
		template
	}
})()

6.6.4.6 index.js 引入 item.js

注意: 引入时的顺序和位置

<body>
	<!-- 留一个组件的出口,此处要被子组件替换 --> 
	<div id="app"> 
	</div>

	<script src="../../node_modules/vue/dist/vue.js"></script>
	<!-- 注意要在 vue.js 下面引入 -->
  <script src="components/AppNavbar.js"></script>
  <script src="components/AppLeaf.js"></script>
  <!-- 注意:Dashboard.js 要在 Home.js 前面引入 --> 
  <script src="components/home/Item.js"></script>
  <script src="components/Home/Dashboard.js"></script>
  <script src="components/Home/HomeList.js"></script>
  <script src="components/home/AppHome.js"></script>
  <script src="App.js"></script>
  <script src="main.js"></script>
</body>

6.6.5 props 案例-删除员工功能实现

6.6.5.1 AppHome.js
  1. 因为删除 emp 是对 empList 做更新操作,而 empList 是初始化在当前这个组件里,所以删除的函数要定义在 AppHome.js 这个组件中
  2. 向子组件传递 deleteEmp 函数 :deleteEmp=“deleteEmp”
;
(function () {
	const template = `<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
    <!--右边上半区域-->
    <h1 class="page-header">Dashboard</h1>
    <dashboard :hobbies="hobbies"></dashboard>

    <!--右边下半区域-->
    <h2 class="sub-header">Section title</h2>
    <home-list :emp-list="empList"  :deleteEmp="deleteEmp"></home-list>
  </div>`

	window.AppHome = {
		template,
		data() {
			return {
				hobbies: ['看书', '台球', '睡觉', '撸代码'],
				empList: [{
					id: 1,
					name: '放生1',
					salary: 80001
				}, {
					id: 2,
					name: '放生2',
					salary: 80002
				}, {
					id: 3,
					name: '放生3',
					salary: 80003
				}, {
					id: 4,
					name: '放生4',
					salary: 80004
				}]
			}
		},

		methods: {
			// 删除指定下标的数据 
			// 因为删除 emp 会对 empList 做更新操作, 
			// 而 empList 是初始化在当前这个组件里,所以删除的函数要定义在这个组件中
			deleteEmp(index) {
				this.empList.splice(index, 1)
			}
		},


		components: {
			// 子组件
			Dashboard,
			HomeList
		}
	}

})()

6.6.5.2 HomeList.js
  1. 模板中添加 <th>操作</th>
  2. 父组件传递的 deleteEmp 函数在 Item.js 才会使用,所以需要通过 HomeList.js 逐层传递给 Item.js
  3. 将 index 索引值 传递给 Item.js 中的组件
;
(function () {
	const template = `<div class="table-responsive">
    <table class="table table-striped">
      <thead>
        <tr>
          <th>ID</th> 
          <th>姓名</th> 
          <th>工资</th>
          <th>操作</th>
        </tr>
      </thead>
      <tbody>
      <Item v-for="(emp, index) in empList" :key="emp.id" :emp="emp" :deleteEmp="deleteEmp" :index="index"/>
      </tbody>
    </table>
  </div>`

	window.HomeList = {
		template,
		//声明组件接收父组件传递的属性
		props: {
      empList: Array,
      deleteEmp: Function // 逐层传递
		},
		data() {
			return {
				name: '梦学谷'
			}
		},
		components: {
			Item
		}
	}



})()

6.6.5.4 Item.js
  1. props 声明接收父组件传递的 deleteEmp 和 index 属性值
  2. 为 删除 按钮添加点击事件 <a href="#" @click="deleteItem">删除</a>
  3. 定义 deleteItem 处理函数,其中调用 AppHome.js 传递过来 deleteEmp 函数。
;
(function () {
	const template = ` <tr><td>{{emp.id}}</td>
    <td>{{emp.name}}</td>
	<td>{{emp.salary}}</td>
	<td><a href="#" @click="deleteItem">删除</a> </td>
    </tr> `
	window.Item = {
		props: {
			emp: { // 指定属性名/数据类型/是否必须 
				type: Object,
				required: true
			},
			deleteEmp: Function,
			index: Number
		},
		template,
		methods: {
			//删除员工
			deleteItem() {
				if (window.confirm(`确定删除${this.emp.name}的记录吗?`)) {
					// 移除索引为index的一条记录, 
					// 注意:不要少了 this 
					this.deleteEmp(this.index)
				}
			}
		},
	}
})()

6.6.6 自定义事件

作用:通过 自定义事件 来代替 props 传入函数形式

6.6.6.1 绑定自定义事件

在父组件中定义事件监听函数,并引用子组件标签上 v-on 绑定事件监听。

// 通过 v-on 绑定 
// @自定义事件名=事件监听函数 
// 在子组件 dashboard 中触发 delete_hobby 事件来调用 deleteHobby 函数 
<dashboard @delete_hobby="deleteHobby"></dashboard>
6.6.6.2 触发监听事件函数执行

在子组件中触发父组件的监听事件函数调用

// 子组件触发事件函数调用 
// this.$emit(自定义事件名, data) 
this.$emit('delete_emp', index)
6.6.6.3 自定义事件注意
  1. 自定义事件只用于子组件向父组件发送消息(数据)
  2. 隔代组件或兄弟组件间通信此种方式不合适

6.6.7 自定义事件案例-删除仪表盘

6.6.7.1 Dashboard.js

在子组件中触发父组件的监听事件函数执行删除操作

;
(function () {

	const template = `
<!--右边上半区域-->
<div class="row placeholders">
  <div v-for="(hobby,index) in hobbies" :key="index" class="col-xs-6 col-sm-3 placeholder">
    <img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200"
      height="200" class="img-responsive" alt="Generic placeholder thumbnail">
    <h4>{{hobby}}</h4>
    <span class="text-muted">
    <a href="#" @click="deleteHobby">删除</a>
    </span>
  </div>
</div>`

	window.Dashboard = {
		template,
		// 声明当前子组件接收父组件的属性
    props: ['hobbies'],

    methods: {
      // 删除爱好
      deleteHobby(index) {
        // 触发父组件中 delete_hobby 事件进行删除操作
        this.$emit('delete_hobby', index)
      }
    },
  

	}

})()
6.6.7.2 AppHome.js

自定义事件删除函数deleteHobby
在引用 dashboard 组件标签上自定义事件, 绑定事件监听函数@delete_hobby=“deleteHobby”

;
(function () {
	const template = `<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
    <!--右边上半区域-->
    <h1 class="page-header">Dashboard</h1>
    <!--通过自定义事件实现删除功能: @自定义事件名=事件监听函数 在子组件 dashboard 中触发 delete_hobby 事件来调用 deleteHobby 函数 -->
    <dashboard :hobbies="hobbies"  @delete_hobby="deleteHobby"></dashboard>

    <!--右边下半区域-->
    <h2 class="sub-header">Section title</h2>
    <home-list :emp-list="empList"  :deleteEmp="deleteEmp"></home-list>
  </div>`
	window.AppHome = {
		template,
		data() {
			return {
				hobbies: ['看书', '台球', '睡觉', '撸代码'],
				empList: [{
					id: 1,
					name: '放生1',
					salary: 80001
				}, {
					id: 2,
					name: '放生2',
					salary: 80002
				}, {
					id: 3,
					name: '放生3',
					salary: 80003
				}, {
					id: 4,
					name: '放生4',
					salary: 80004
				}]
			}
		},
		methods: {
			// 删除指定下标的数据 
			// 因为删除 emp 会对 empList 做更新操作, 
			// 而 empList 是初始化在当前这个组件里,所以删除的函数要定义在这个组件中
			deleteEmp(index) {
				this.empList.splice(index, 1)
			},
			//删除爱好
			deleteHobby(index) {
				this.hobbies.splice(index, 1)
			}
		},
		components: {
			// 子组件
			Dashboard,
			HomeList
		}
	}

})()

注意: 删除员工有垮组件传递函数,不推荐使用自定义事件来实现删除

6.6.8 slot 插槽

作用: 主要用于父组件向子组件传递 标签+数据 , (而上面prop和自定事件只是传递数据)
场景:一般是某个位置需要经常动态切换显示效果(如饿了么)

6.6.8.1 子组件定义插槽

在子组件中定义插槽, 当父组件向指定插槽传递标签数据后, 插槽处就被渲染,否则插槽处不会被渲染.

<div> 
<!-- name属性值指定唯一插槽名,父组件通过此名指定标签数据--> 
<slot name="aaa">不确定的标签结构 1</slot> 
<div>组件确定的标签结构</div> 
<slot name="bbb">不确定的标签结构 2</slot> 
</div>
6.6.8.2 父组件传递标签数据
<child> 
<!--slot属性值对应子组件中的插槽的name属性值--> 
<div slot="aaa">向 name=aaa 的插槽处插入此标签数据</div> 
<div slot="bbb">向 name=bbb 的插槽处插入此标签数据</div> 
</child>
6.6.8.3 插槽注意事项
  1. 只能用于父组件向子组件传递 标签+数据
  2. 传递的插槽标签中的数据处理都需要定义所在父组件中

6.6.9 slot 插槽案例

需求: 将Dashboard 的标题通过插槽显示

6.6.9.1 AppHome.js

将 Dashboard 的标题标签定义为插槽

;
(function () {
	const template = `<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
    <!--右边上半区域-->
    <h1 class="page-header">Dashboard</h1>
    <!--定义插槽--> 
    <slot name="dashboard"></slot>
    
    <!--通过自定义事件实现删除功能: @自定义事件名=事件监听函数 在子组件 dashboard 中触发 delete_hobby 事件来调用 deleteHobby 函数 -->
    <dashboard :hobbies="hobbies"  @delete_hobby="deleteHobby"></dashboard>

    <!--右边下半区域-->
    <h2 class="sub-header">Section title</h2>
    <home-list :emp-list="empList"  :deleteEmp="deleteEmp"></home-list>
  </div>`

	window.AppHome = {
		template,
		data() {
			return {
				hobbies: ['看书', '台球', '睡觉', '撸代码'],
				empList: [{
					id: 1,
					name: '放生1',
					salary: 80001
				}, {
					id: 2,
					name: '放生2',
					salary: 80002
				}, {
					id: 3,
					name: '放生3',
					salary: 80003
				}, {
					id: 4,
					name: '放生4',
					salary: 80004
				}]
			}
		},

		methods: {
			// 删除指定下标的数据 
			// 因为删除 emp 会对 empList 做更新操作, 
			// 而 empList 是初始化在当前这个组件里,所以删除的函数要定义在这个组件中
			deleteEmp(index) {
				this.empList.splice(index, 1)
			},
			//删除爱好
			deleteHobby(index) {
				this.hobbies.splice(index, 1)
			}
		},


		components: {
			// 子组件
			Dashboard,
			HomeList
		}
	}

})()
6.6.9.2 App.js

向子组件中定义的插槽处传递标签数据。其中标题使用 title 数据绑定

;
(function () {

	// 不要少了最外层的根元素 div
	const template = `<div> 
        <app-navbar></app-navbar> 

        <!--核心区域:分左右两边--> 
        <div class="container-fluid"> 
            <div class="row"> 

                <!--左边菜单栏区域-->
                <app-leaf></app-leaf> 

                <!--右边主页面区域: 分上下两个区域-->
                <app-home>
                <h1 slot="dashboard" class="page-header">{{title}}</h1>
                </app-home>
            </div> 
        </div> 
    </div>`
	window.App = {
		template,
		components: {
            AppNavbar, // 等价于 Navbar: Navbar 
            AppLeaf,
            AppHome
        },
        data() {
            return {
                title: '仪表盘'
            }
        },
	}

})()

6.7 非父子组件间通信 PubSubJS (补充知识)

6.7.1 介绍

Vue.js 可通过 PubSubJS 库来实现非父子组件之间的通信 ,使用 PubSubJS 的消息发布与订阅模式,来进行数据的传递。
理解:订阅信息 ==== 绑定事件监听 ,发布消息 ==== 触发事件。
注意: 但是必须先执行订阅事件 subscribe ,然后才能 publish 发布事件。

6.7.2 订阅消息(绑定事件监听)

先在 created 钩子函数中订阅消息

// event接收的是消息名称,data是接收发布时传递的数据 
PubSub.subscribe('消息名称(相当于事件名)', function(event, data) { 
// 事件回调处理 
})

6.7.3 发布消息(触发事件)

PubSub.publish('消息名称(相当于事件名)', data)

6.7.4 案例

复制 vue-06-组件间通信 为 vue-06-组件间通信2

需求:在右侧删除爱好数据成功后,在左侧导航栏上显示已删除的总数,没有删除则不显示数量。
分析 :左侧导航与右侧爱好仪表盘是非父子组件,采用 PubSubJS 消息订阅发布实现功能
在这里插入图片描述

6.7.4.2 安装 pubsub-js
npm install pubsub-js
6.7.4.2 index.html 引入 pubsub-js 库
<script src="../../../node_modules/vue/dist/vue.js"></script> 
<script src="./node_modules/pubsub-js/src/pubsub.js"></script>
6.7.4.3 AppLeaf.js 订阅消息

初始化数量 ,在 created 钩子中订阅消息,监听当删除爱好后,进行统计已删除总数

;
(function () {

	const template = `<div class="col-sm-3 col-md-2 sidebar">
    <ul class="nav nav-sidebar"> 
        <li class="active"> 
        <a href="#">Overview 
        <!--是否显示和显示总删除数--> 
        <span v-show="delNum">( {{ delNum }} )</span>
        </a> </li> <li><a href="#">Reports</a>
        </li> <li><a href="#">Analytics</a></li> 
        <li><a href="#">Export</a></li> 
    </ul>
</div>`

	window.AppLeaf = {
		template,
		data: function () {
			return {
				message: '学员管理',
				delNum: 0 //初始化数量
			}
		},
		created() {
			//一开始初始化实例时,就进行订阅消息
			PubSub.subscribe('changeNum', (event, num) => {
				// 箭头函数 
				// 删除成功 
				this.delNum = this.delNum + num
			})
		},
	}

})()
6.7.4.4 AppHome.js 发布消息
methods: { 
	。。。 
	。。。 
// 删除爱好
deleteHobby (index) { 
	this.hobbies.splice(index, 1) // 删除成功,发布消息,导航统计数据
	 PubSub.publish('changeNum', 1)
   } 
 },

6.7.5 优点

不管是父子之间还是非父子之间通信 PubSubJS 都可以实现

6.8 单文件组件

6.8.1 当前存在的问题

上面定义组件时存在的问题:

  • 全局定义 (Global definitions) 强制要求每个 component 中的命名不得重复
  • 字符串模板 (String templates) 缺乏语法高亮,在 HTML 有多行的时候,需要用到丑陋的 \
  • 不支持 CSS (No CSS support) 意味着当 HTML 和 JavaScript 组件化时,CSS 明显被遗漏
  • 没有构建步骤 (No build step) 限制只能使用 HTML 和 ES5 JavaScript, 而不能使用预处理器,如 Pug (formerly Jade) 和 Babel

文件扩展名为 .vue 的 single-file components(单文件组件) 为以上所有问题提供了解决方法,并且还可以使用
webpack 或 Browserify 等构建工具。

6.8.2 单文件组件模板格式

<template> 
 // 组件的模块 
 </template> 

 <script> 
  // 组件的JS 
 </script> 
 
 <style> 
// 组件的样式 
 </style>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue组件Vue.js中非常重要的一个概念,它涉及到父子组件之间的数据递和通信。下面是一些常见的Vue组件面试题及其答案: 1. 请介绍Vue组件之间的父子组件方式。 答:Vue组件之间的父子组件方式有props和$emit。通过props可以将数据从父组件递给子组件,子组件通过props接收数据。而$emit则是在子组件中触发一个自定义事件,并通过事件参数将数据递给父组件。 2. 请介绍Vue组件之间的兄弟组件方式。 答:Vue组件之间的兄弟组件方式可以通过共享状态管理工具(如Vuex)或者事件总线来实现。使用共享状态管理工具可以在全局定义一个状态,兄弟组件通过读取和修改该状态来进行数据递。而事件总线则是在Vue实例上定义一个事件中心,兄弟组件通过$emit和$on来进行事件的发布和订阅,从而实现数据递。 3. 请介绍Vue组件之间的跨级组件方式。 答:Vue组件之间的跨级组件方式可以通过provide和inject来实现。父级组件通过provide提供数据,然后子孙级组件通过inject来注入数据。这样就可以实现跨级组件之间的数据递。 4. 请介绍Vue组件之间的非父子组件方式。 答:Vue组件之间的非父子组件方式可以通过事件总线、Vuex或者localStorage等方式来实现。事件总线和Vuex的方式在前面已经介绍过了,而localStorage则是通过将数据存储在浏览器的本地存储中,然后在其他组件中读取该数据来实现非父子组件之间的数据递。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值