10.1 基本列表

v-for指令:

  1. 用于展示列表数据
  2. 语法:**<li v-for="(item, index) of items" :key="index">**,这里key可以是index,更好的是遍历对象的唯一标识
    1. key 的特殊attribute主要用在Vue的虚拟DOM算法,在新旧nodes对比时辨识VNodes。如果不使用key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。而使用key时,它会基于key的变化重新排列元素顺序,并且会移除 key 不存在的元素。
    2. 有相同父元素的子元素必须有独特的key。重复的 key 会造成渲染错误。
  3. 可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)

代码演示

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8" />
  5. <title>基本列表</title>
  6. <script type="text/javascript" src="../js/vue.js"></script>
  7. </head>
  8. <body>
  9. <!-- 准备好一个容器-->
  10. <div id="root">
  11. <!-- 遍历数组 -->
  12. <h2>人员列表(遍历数组)</h2>
  13. <ul>
  14. <li v-for="(p,index) of persons" :key="index">
  15. {{p.name}}-{{p.age}}
  16. </li>
  17. </ul>
  18. <!-- 遍历对象 -->
  19. <h2>汽车信息(遍历对象)</h2>
  20. <ul>
  21. <li v-for="(value,k) of car" :key="k">
  22. {{k}}-{{value}}
  23. </li>
  24. </ul>
  25. <!-- 遍历字符串 -->
  26. <h2>测试遍历字符串(用得少)</h2>
  27. <ul>
  28. <li v-for="(char,index) of str" :key="index">
  29. {{char}}-{{index}}
  30. </li>
  31. </ul>
  32. <!-- 遍历指定次数 -->
  33. <h2>测试遍历指定次数(用得少)</h2>
  34. <ul>
  35. <li v-for="(number,index) of 5" :key="index">
  36. {{index}}-{{number}}
  37. </li>
  38. </ul>
  39. </div>
  40. </body>
  41. <script type="text/javascript">
  42. Vue.config.productionTip = false
  43. new Vue({
  44. el:'#root',
  45. data:{
  46. persons:[
  47. {id:'001',name:'张三',age:18},
  48. {id:'002',name:'李四',age:19},
  49. {id:'003',name:'王五',age:20}
  50. ],
  51. car:{
  52. name:'奥迪A8',
  53. price:'70万',
  54. color:'黑色'
  55. },
  56. str:'hello'
  57. }
  58. })
  59. </script>
  60. </html>

演示效果

image.png

10.2 key 的原理

  1. 虚拟DOM中key的作用:
    1. key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】, 随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较。
  2. 对比规则:
    1. 旧虚拟DOM中找到了与新虚拟DOM相同的key:
      1. 若虚拟DOM中内容没变, 直接使用之前的真实DOM!
      2. 若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
    2. 旧虚拟DOM中未找到与新虚拟DOM相同的key
      1. 创建新的真实DOM,随后渲染到到页面。
  3. 用index作为key可能会引发的问题:
    1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
      1. 会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
    2. 如果结构中还包含输入类的DOM:
      1. 会产生错误DOM更新 ==> 界面有问题。
  4. 开发中如何选择key?:

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

      代码演示

      <!DOCTYPE html>
      <html>
      <head>
        <meta charset="UTF-8" />
        <title>key的原理</title>
        <script type="text/javascript" src="../js/vue.js"></script>
      </head>
      <body>
        <!-- 准备好一个容器-->
        <div id="root">
            <!-- 遍历数组 -->
            <h2>人员列表(遍历数组)</h2>
            <button @click.once="add">添加一个老刘</button>
            <ul>
                <li v-for="(p,index) of persons" :key="index">
                    {{p.name}}-{{p.age}}
                    <input type="text">
                </li>
            </ul>
        </div>
      </body>
        <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(){
                        const p = {id:'004',name:'老刘',age:40}
                        this.persons.unshift(p)
                    }
                },
            })
        </script>
      </html>
      

      演示效果

      用 index 作为 key

      现在文本框里输入人名
      image.png
      点击 “添加一个老刘” 后,文本框里的内容发生了错位
      image.png

      用 id 作为 key

      现在文本框里输入人名
      image.png
      点击 “添加一个老刘” 后,文本框里的内容没有发生错位
      image.png

      问题分析

      用 index 作为key
      用户输入的都是真实DOM,点击 “添加一个老刘” 后,生成新的虚拟DOM,Vue 会拿旧的虚拟DOM与新的虚拟DOM用 key 进行虚拟DOM算法对比,虚拟DOM对比出不一致的进行更新,一致的就复用。旧的DOM(张三)中的input和新的DOM(老刘)中input一致,就会复用。
      image.png
      用 id 作为 key
      ip 不一样的 input也会重新生成
      image.png

      10.3 列表过滤

      对数组数据进行过滤

  • watch每次过滤都是返回一个新的数组,改变了原数据的内容,这样会第二次过滤时会导致有些数据搜索不出来,所以要对watch进行特殊处理。
  • computed计算属性要比watch方便简单一些,优先考虑计算属性实现过滤。

    代码演示

    <!DOCTYPE html>
    <html>
      <head>
          <meta charset="UTF-8" />
          <title>列表过滤</title>
          <script type="text/javascript" src="../js/vue.js"></script>
      </head>
      <body>
          <!-- 准备好一个容器-->
          <div id="root">
              <h2>人员列表</h2>
              <input type="text" placeholder="请输入名字" v-model="keyWord">
              <ul>
                  <li v-for="(p,index) of filPerons" :key="index">
                      {{p.name}}-{{p.age}}-{{p.sex}}
                  </li>
              </ul>
          </div>
      </body>
    
          <script type="text/javascript">
              Vue.config.productionTip = false
    
              //用watch实现
              //#region 
              /* new Vue({
                  el:'#root',
                  data:{
                      keyWord:'',
                      persons:[
                          {id:'001',name:'马冬梅',age:19,sex:'女'},
                          {id:'002',name:'周冬雨',age:20,sex:'女'},
                          {id:'003',name:'周杰伦',age:21,sex:'男'},
                          {id:'004',name:'温兆伦',age:22,sex:'男'}
                      ],
                      filPerons:[]
                  },
                  watch:{
                      keyWord:{
                          immediate:true,
                          handler(val){
                              this.filPerons = this.persons.filter((p)=>{
                                  return p.name.indexOf(val) !== -1
                              })
                          }
                      }
                  }
              }) */
              //#endregion
    
              //用computed实现
              new Vue({
                  el:'#root',
                  data:{
                      keyWord:'',
                      persons:[
                          {id:'001',name:'马冬梅',age:19,sex:'女'},
                          {id:'002',name:'周冬雨',age:20,sex:'女'},
                          {id:'003',name:'周杰伦',age:21,sex:'男'},
                          {id:'004',name:'温兆伦',age:22,sex:'男'}
                      ]
                  },
                  computed:{
                      filPerons(){
                          return this.persons.filter((p)=>{
                              return p.name.indexOf(this.keyWord) !== -1
                          })
                      }
                  }
              }) 
          </script>
    </html>
    

    演示效果

    image.png

    10.4 列表排序

    根据条件进行排序
    只要是在computed内的任何属性发生改变,计算属性就会触发重新计算。

    代码演示

    <!DOCTYPE html>
    <html>
      <head>
          <meta charset="UTF-8" />
          <title>列表排序</title>
          <script type="text/javascript" src="../js/vue.js"></script>
      </head>
      <body>
          <!-- 准备好一个容器-->
          <div id="root">
              <h2>人员列表</h2>
              <input type="text" placeholder="请输入名字" v-model="keyWord">
              <button @click="sortType = 2">年龄升序</button>
              <button @click="sortType = 1">年龄降序</button>
              <button @click="sortType = 0">原顺序</button>
              <ul>
                  <li v-for="(p,index) of filPerons" :key="p.id">
                      {{p.name}}-{{p.age}}-{{p.sex}}
                      <input type="text">
                  </li>
              </ul>
          </div>
      </body>
    
          <script type="text/javascript">
              Vue.config.productionTip = false
    
              new Vue({
                  el:'#root',
                  data:{
                      keyWord:'',
                      sortType:0, //0原顺序 1降序 2升序
                      persons:[
                          {id:'001',name:'马冬梅',age:30,sex:'女'},
                          {id:'002',name:'周冬雨',age:31,sex:'女'},
                          {id:'003',name:'周杰伦',age:18,sex:'男'},
                          {id:'004',name:'温兆伦',age:19,sex:'男'}
                      ]
                  },
                  computed:{
                      filPerons(){
                          const arr = this.persons.filter((p)=>{
                              return p.name.indexOf(this.keyWord) !== -1
                          })
                          //判断一下是否需要排序
                          if(this.sortType){
                              arr.sort((p1,p2)=>{
                                  return this.sortType === 1 ? p2.age-p1.age : p1.age-p2.age
                              })
                          }
                          return arr
                      }
                  }
              }) 
    
          </script>
    </html>
    

    演示效果

    image.png

    10.5 数据更新相关问题

    Vue 有个默认侦听数据改变的侦听器,watch是 Vue 对开发人员提供的侦听工具。
    Vue 默认是侦听data里的属性,data里的属性发生任何改变的时候,Vue 就会更新所有用到data属性的地方。
    this.persons[0] = {id:'001',name:'马老师',age:50,sex:'男'}这种方式改变数组,虽然数组内容真的改变了,但 Vue 侦听不到。Vue 对数组和对象的检查是不一样的。

    代码演示

    <!DOCTYPE html>
    <html>
      <head>
          <meta charset="UTF-8" />
          <title>更新时的一个问题</title>
          <script type="text/javascript" src="../js/vue.js"></script>
      </head>
      <body>
          <!-- 准备好一个容器-->
          <div id="root">
              <h2>人员列表</h2>
              <button @click="updateMei">更新马冬梅的信息</button>
              <ul>
                  <li v-for="(p,index) of persons" :key="p.id">
                      {{p.name}}-{{p.age}}-{{p.sex}}
                  </li>
              </ul>
          </div>
      </body>
    
          <script type="text/javascript">
              Vue.config.productionTip = false
    
              const vm = new Vue({
                  el:'#root',
                  data:{
                      persons:[
                          {id:'001',name:'马冬梅',age:30,sex:'女'},
                          {id:'002',name:'周冬雨',age:31,sex:'女'},
                          {id:'003',name:'周杰伦',age:18,sex:'男'},
                          {id:'004',name:'温兆伦',age:19,sex:'男'}
                      ]
                  },
                  methods: {
                      updateMei(){
                          // this.persons[0].name = '马老师' //奏效
                          // this.persons[0].age = 50 //奏效
                          // this.persons[0].sex = '男' //奏效
                          // this.persons[0] = {id:'001',name:'马老师',age:50,sex:'男'} //不奏效
                          this.persons.splice(0,1,{id:'001',name:'马老师',age:50,sex:'男'})
                      }
                  }
              }) 
    
          </script>
    </html>
    

    10.5.1 Vue监测数据改变的原理_对象

    vm._data = datadata赋值给 _data前,Vue 会对data进行加工再赋值给_data,加工就是为了做事件响应。
    _data里的name属性发生改变时set_name就会改变data里的name属性。
    image.png
    image.png

    代码演示

    <!DOCTYPE html>
    <html>
      <head>
          <meta charset="UTF-8" />
          <title>Vue监测数据改变的原理</title>
          <!-- 引入Vue -->
          <script type="text/javascript" src="../js/vue.js"></script>
      </head>
      <body>
          <!-- 准备好一个容器-->
          <div id="root">
              <h2>学校名称:{{name}}</h2>
              <h2>学校地址:{{address}}</h2>
          </div>
      </body>
    
      <script type="text/javascript">
          Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
    
          const vm = new Vue({
              el:'#root',
              data:{
                  name:'尚硅谷',
                  address:'北京',
                  student:{
                      name:'tom',
                      age:{
                          rAge:40,
                          sAge:29,
                      },
                      friends:[
                          {name:'jerry',age:35}
                      ]
                  }
              }
          })
      </script>
    </html>
    

    10.5.2 模拟一个数据检测

    Vue 是用Observer做的数据侦听,下面是根据 Vue 的侦听原理,写的简单的侦听代码

    代码演示

    <!DOCTYPE html>
    <html>
      <head>
          <meta charset="UTF-8" />
          <title>Document</title>
      </head>
      <body>
          <script type="text/javascript" >
    
              let data = {
                  name:'尚硅谷',
                  address:'北京',
              }
    
              //创建一个监视的实例对象,用于监视data中属性的变化
              const obs = new Observer(data)        
              console.log(obs)    
    
              //准备一个vm实例对象
              let vm = {}
              vm._data = data = obs
    
              function Observer(obj){
                  //汇总对象中所有的属性形成一个数组
                  const keys = Object.keys(obj)
                  //遍历
                  keys.forEach((k)=>{
                      Object.defineProperty(this,k,{
                          get(){
                              return obj[k]
                          },
                          set(val){
                              console.log(`${k}被改了,我要去解析模板,生成虚拟DOM.....我要开始忙了`)
                              obj[k] = val
                          }
                      })
                  })
              }
          </script>
      </body>
    </html>
    

    10.5.3 Vue.set 的使用

    在给 Vue 对象上添加属性时不能用 vm.site='北京'添加 Vue 对象上不存在的属性,因为 Vue 侦听不到。
    如果添加的属性需要 Vue 能侦听就使用Vue.set(vm._data.student,'site','北京'),这样页面才能实时展现添加的属性值。也可以写成Vue.set(vm.student,'site','北京')

    代码演示

    <!DOCTYPE html>
    <html>
      <head>
          <meta charset="UTF-8" />
          <title>Vue监测数据改变的原理</title>
          <!-- 引入Vue -->
          <script type="text/javascript" src="../js/vue.js"></script>
      </head>
      <body>
          <!-- 准备好一个容器-->
          <div id="root">
              <h1>学校信息</h1>
              <h2>学校名称:{{school.name}}</h2>
              <h2>学校地址:{{school.address}}</h2>
              <h2>校长是:{{school.leader}}</h2>
              <hr/>
              <h1>学生信息</h1>
              <button @click="addSex">添加一个性别属性,默认值是男</button>
              <h2>姓名:{{student.name}}</h2>
              <h2 v-if="student.sex">性别:{{student.sex}}</h2>
              <h2>年龄:真实{{student.age.rAge}},对外{{student.age.sAge}}</h2>
              <h2>朋友们</h2>
              <ul>
                  <li v-for="(f,index) in student.friends" :key="index">
                      {{f.name}}--{{f.age}}
                  </li>
              </ul>
          </div>
      </body>
    
      <script type="text/javascript">
          Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
    
          const vm = new Vue({
              el:'#root',
              data:{
                  school:{
                      name:'尚硅谷',
                      address:'北京',
                  },
                  student:{
                      name:'tom',
                      age:{
                          rAge:40,
                          sAge:29,
                      },
                      friends:[
                          {name:'jerry',age:35},
                          {name:'tony',age:36}
                      ]
                  }
              },
              methods: {
                  addSex(){
                      // Vue.set(this.student,'sex','男')
                      this.$set(this.student,'sex','男')
                  }
              }
          })
      </script>
    </html>
    

    10.5.4 Vue监测数据改变的原理_数组

    数组在 Vue 实例中是没有getset,这样就导致 Vue 实例里数组数据被改变时,Vue 侦听不到改变,就无法进行更新。
    image.png
    只有调用的数组自身的方法后,Vue 才能侦听到数组的改变
    数组涉及的方法:
    往数组最后新增元素:push
    删除元素:pop
    删除第一个:shift
    往前面加一个:unshift
    在数组指定位置添加、删除、替换:splice
    排序:sort
    反转数组:reverse
    image.png
    在 Vue 对象里的数组,Vue 包装的了数组命令,这样 Vue 才能侦听到数组的改变,对页面进行更新。

    代码演示

    <!DOCTYPE html>
    <html>
      <head>
          <meta charset="UTF-8" />
          <title>Vue监测数据改变的原理_数组</title>
          <!-- 引入Vue -->
          <script type="text/javascript" src="../js/vue.js"></script>
      </head>
      <body>
          <!-- 准备好一个容器-->
          <div id="root">
              <h1>学校信息</h1>
              <h2>学校名称:{{school.name}}</h2>
              <h2>学校地址:{{school.address}}</h2>
              <h2>校长是:{{school.leader}}</h2>
              <hr/>
              <h1>学生信息</h1>
              <button @click="addSex">添加一个性别属性,默认值是男</button>
              <h2>姓名:{{student.name}}</h2>
              <h2 v-if="student.sex">性别:{{student.sex}}</h2>
              <h2>年龄:真实{{student.age.rAge}},对外{{student.age.sAge}}</h2>
              <h2>爱好</h2>
              <ul>
                  <li v-for="(h,index) in student.hobby" :key="index">
                      {{h}}
                  </li>
              </ul>
              <h2>朋友们</h2>
              <ul>
                  <li v-for="(f,index) in student.friends" :key="index">
                      {{f.name}}--{{f.age}}
                  </li>
              </ul>
          </div>
      </body>
    
      <script type="text/javascript">
          Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
    
          const vm = new Vue({
              el:'#root',
              data:{
                  school:{
                      name:'尚硅谷',
                      address:'北京',
                  },
                  student:{
                      name:'tom',
                      age:{
                          rAge:40,
                          sAge:29,
                      },
                      hobby:['抽烟','喝酒','烫头'],
                      friends:[
                          {name:'jerry',age:35},
                          {name:'tony',age:36}
                      ]
                  }
              },
              methods: {
                  addSex(){
                      // Vue.set(this.student,'sex','男')
                      this.$set(this.student,'sex','男')
                  }
              }
          })
      </script>
    </html>
    

    10.5.5 Vue 数据监测总结

    Vue监视数据的原理:

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

    1. 使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
    2. Vue.set()vm.$set()

      代码演示

      <!DOCTYPE html>
      <html>
      <head>
        <meta charset="UTF-8" />
        <title>总结数据监视</title>
        <style>
            button{
                margin-top: 10px;
            }
        </style>
        <!-- 引入Vue -->
        <script type="text/javascript" src="../js/vue.js"></script>
      </head>
      <body>
        <!-- 准备好一个容器-->
        <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>
      </body>
      
      <script type="text/javascript">
        Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
      
        const vm = new Vue({
            el:'#root',
            data:{
                student:{
                    name:'tom',
                    age:18,
                    hobby:['抽烟','喝酒','烫头'],
                    friends:[
                        {name:'jerry',age:35},
                        {name:'tony',age:36}
                    ]
                }
            },
            methods: {
                addSex(){
                    // Vue.set(this.student,'sex','男')
                    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,'开车')
                    // Vue.set(this.student.hobby,0,'开车')
                    this.$set(this.student.hobby,0,'开车')
                },
                removeSmoke(){
          // 不是由Vue回调的函数尽可能的写成箭头函数
                    this.student.hobby = this.student.hobby.filter((h)=>{
                        return h !== '抽烟'
                    })
                }
            }
        })
      </script>
      </html>