列表渲染

  • 基础demo
<body>
    <div id="container">
        <ul>       <!--vue建议我们绑定每一项的唯一标识,真实dom不会被渲染,虚拟dom才有这个属性-->
                   <!--也可以写成 :key="index",如何取舍?看需求-->
                   <!--person in persons 也可以写成: person of persons-->
            <li v-for="person in persons" :key="person.id"> <!--遍历每一项,获取值-->
                {{person.name}} -- {{person.age}}
            </li>
        </ul>
    </div>
    
    <script type="text/javascript">
        
        var vm = new Vue({
            el:'#container',
            data:{
                persons:[ // 一组测试数据
                    {id:'001',name:'JimGreen',age:20},
                    {id:'002',name:'KateGreen',age:18},
                    {id:'003',name:'LiLei',age:21},
                ]
            }
        })
    </script>
</body>
  • 遍历List的时候,可以获取'项',也可以获取'索引'
<div id="container">
    <ul>
        <li v-for="(item,index) in persons" :key="item.id">
            {{item.name}} -- {{item.age}} -- {{index}}
        </li>
    </ul>
</div>
  • 遍历对象,可以获取'key'和'value'
......
<ul>
    <li v-for="(value,key) in car" :key="key">
        {{key}} -- {{value}}
    </li>
</ul>
......
data:{
    ......
    car:{
        name:'Benz',
        price:'80万',
        color:'白色'
    }
}
  • 遍历数字,获取循环的次数
<ul>
    <li v-for="(index,num) in 5">
        {{index}} -- {{num}}
    </li>
</ul>
  • 小结

    • 遍历list/对象,使用的比较多

    • 遍历字符串/数字,使用的比较少

    • :key最好加上

':key'的渲染原理解析

  • 引入场景,添加一个按钮,在列表最头部插入一行数据,测试效果,好像一切正常
<div id="container">
    <ul>                    <!--once 只执行一次-->
        <button type="button" @click.once="add">添加一个韩梅梅</button>
        <li v-for="(item,index) in persons" :key="index"> <!--以index作为key-->
            {{item.name}} -- {{item.age}} -- {{index}}
        </li>
    </ul>
</div>
......
<script type="text/javascript">
            
    var vm = new Vue({
        el:'#container',
        data:{
            persons:[
                {id:'001',name:'JimGreen',age:20},
                {id:'002',name:'KateGreen',age:18},
                {id:'003',name:'LiLei',age:21},
            ],
        },
        methods:{
            add(){
                const newData = {id:'004',name:'Hanmeimei',age:19};
                this.persons.unshift(newData) // list头部插入一行数据
            }
        }
    })
</script>
  • 引入问题,当li再插入一个input的时候,问题就来了,input内容错位了...
<div id="container">
    <ul>
        <button type="button" @click.once="add">添加一个韩梅梅</button>
        <li v-for="(item,index) in persons">
            {{item.name}} -- {{item.age}} -- {{index}}
            <input type="text" name="" id="" value="" /> <!--新增,把上面的内容复制进入-->
        </li>
    </ul>
    
</div>
  • 先说解决办法,把":key=index"修改为":key=item.id"就修复了这个问题

    • :key="index" 会出现问题

    • :key不写,也会出现问题

列表渲染补充

  • 如果不指定循环列表的key,那么默认就是 index

  • key的作用小结

- 虚拟DOM中key的作用

    - key是虚拟DOM对象的标识,当状态中的数据发生变化时,vue会根据新数据生成新的虚拟DOM

    - 随后vue进行"新虚拟DOM"与"旧虚拟DOM"差异比较

- 对比规则

    - "旧虚拟DOM"中找到与"新虚拟DOM"相同的key

        - 若"虚拟DOM"中内容没变,之间使用之前的"真实DOM"

        - 若"虚拟DOM"中内容变了,则生成新的"真实DOM",随后替换掉页面中之前真实DOM

    - "旧虚拟DOM"中没有找到与"新虚拟DOM"相同的key

        - 创建新的真实DOM,随后渲染到页面

  • 使用 index 作为key可能会引发的问题
- 若对数据进行: 逆序添加/逆序删除等破坏顺序的操作

    - 会产生没有必要的真实DOM更新 ==> 界面效果没问题,但是效率低

    - 如果结构中还包含输入类DOM ==> 会产生错误DOM更新,界面会有大问题!!!

- 开发如何选择key

    - 最好使用每条数据的唯一标识作为key,例如ID/手机号码/...

    - 如果不存在对数据的逆序添加,逆序删除等破坏顺序的操作,仅用于列表渲染展示
      此时,使用index作为key是没有问题的!

模糊查询案例

  • 骨架:渲染列表数据+查询框
......
<body>
    <div id="container">
        <ul>
            <input type="text" v-model="keyWord" />
            <li v-for="(item,index) in persons" :key="item.id">
                {{item.name}} -- {{item.age}} -- {{item.sex}}
            </li>
        </ul>
        
    </div>
    
    <script type="text/javascript">
        
        var vm = new Vue({
            el:'#container',
            data:{
                keyWord:'',
                persons:[
                    {id:'001',name:'JimGreen',age:20,sex:'male'},
                    {id:'002',name:'KateGreen',age:18,sex:'female'},
                    {id:'003',name:'LiLei',age:21,sex:'male'},
                    {id:'004',name:'LiMeiMei',age:21,sex:'female'},
                ],
            },
        })
    </script>
</body>
......
  • 解决思路:实时收集用户的输入内容,然后和list的数据对比,成功即显示

  • 第一次处理,使用filter()成功过滤,但是原list数据被修改,回不到过去

......
<script type="text/javascript">
            
    var vm = new Vue({
        el:'#container',
        data:{
            keyWord:'',
            persons:[
                ......
            ],
        },
        watch:{
            keyWord(newVal){
                this.persons = this.persons.filter((item)=>{ // filter()过滤数据以后,会生成新的数组,把它赋值给原数组(所以原数组数据肯定发生变化)
                    return item.name.indexOf(newVal) != -1 // indexOf 返回值只要不是 -1 就表示匹配成功
                }) // 使用filter一定要return,这是规矩...
            }
        }
    })
</script>
  • 第二次处理,使用新的变量来存储过滤后的数据,缺点是初始化页面的时候没有数据(使用过滤以后一切正常)
......
<body>
    <div id="container">
        <ul>
            <input type="text" v-model="keyWord" />
            <li v-for="(item,index) in filterPersons" :key="item.id"> <!--变更数据源-->
                {{item.name}} -- {{item.age}} -- {{item.sex}}
            </li>
        </ul>
        
    </div>
    
    <script type="text/javascript">
        
        var vm = new Vue({
            el:'#container',
            data:{
                keyWord:'',
                persons:[
                    {id:'001',name:'JimGreen',age:20,sex:'male'},
                    {id:'002',name:'KateGreen',age:18,sex:'female'},
                    {id:'003',name:'LiLei',age:21,sex:'male'},
                    {id:'004',name:'LiMeiMei',age:21,sex:'female'},
                ],
                filterPersons:[], // 新定义的数据源
            },
            watch:{
                keyWord(newVal){ // 当传入"空字符串"的时候,filter()会返回原数组
                    this.filterPersons = this.persons.filter((item)=>{
                        return item.name.indexOf(newVal) != -1
                    })
                }
            }
        })
    </script>
</body>
......
  • 上述示例bug修复:初始化页面的时候,执行一次空字符串即可(变更keyWord极简写法,添加一个参数搞定)
......
<script type="text/javascript">
    var vm = new Vue({
        el:'#container',
        data:{
            ......
            filterPersons:[],
        },
        watch:{
            keyWord:{ // 修改极简写法
                immediate:true, // 页面加载完毕后,先执行一次handler,实现列表数据的渲染
                handler(newVal){
                    this.filterPersons = this.persons.filter((item)=>{
                        return item.name.indexOf(newVal) != -1
                    })
                },
            }
        }
    })
</script>
  • 使用'计算属性'可以快速方便的解决'模糊案例'
<body>
    <div id="container">
        <ul>
            <input type="text" v-model="keyWord" />
            <li v-for="(item,index) in filterPersons" :key="item.id"> <!--遍历属性对象-->
                {{item.name}} -- {{item.age}} -- {{item.sex}}
            </li>
        </ul>
        
    </div>
    
    <script type="text/javascript">
        
        var vm = new Vue({
            el:'#container',
            data:{
                keyWord:'',
                persons:[
                    {id:'001',name:'JimGreen',age:20,sex:'male'},
                    {id:'002',name:'KateGreen',age:18,sex:'female'},
                    {id:'003',name:'LiLei',age:21,sex:'male'},
                    {id:'004',name:'LiMeiMei',age:21,sex:'female'},
                ],
            },
            computed:{
                filterPersons(){ // 和watch一样的逻辑,但不需要新的变量取存储
                    return this.persons.filter((item)=>{
                        return item.name.indexOf(this.keyWord) != -1
                    })
                }
            }
        })
    </script>
</body>

模糊查询案例延伸,对查询后的结果进行排序

  • 需求:对查询后的结果进行排序,年龄升序/降序/原顺序

  • 骨架如下

<body>
<div id="container">
    <ul>
        <input type="text" v-model="keyWord" />
        <!--新增三个按钮,根据变量数字的状态来实现需求-->
        <button type="button" @click="sortType = 1">年龄升序</button>
        <button type="button" @click="sortType = 2">年龄降序</button>
        <button type="button" @click="sortType = 0">原顺序</button>
        <li v-for="(item,index) in filterPersons" :key="item.id">
            {{item.name}} -- {{item.age}} -- {{item.sex}}
        </li>
    </ul>
    
</div>

<script type="text/javascript">
    
    var vm = new Vue({
        el:'#container',
        data:{
            keyWord:'',
            sortType:0, // 排序默认0,即原顺序
            persons:[
                {id:'001',name:'JimGreen',age:20,sex:'male'},
                {id:'002',name:'KateGreen',age:18,sex:'female'},
                {id:'003',name:'LiLei',age:21,sex:'male'},
                {id:'004',name:'LiMeiMei',age:21,sex:'female'},
            ],
        },
        computed:{
            filterPersons(){
                return this.persons.filter((item)=>{
                    return item.name.indexOf(this.keyWord) != -1
                })
            }
        }
       
    })
  • 解决方式:把过滤后的结果先保存起来,不返回,判断一下排序,最后再返回
......
 <script type="text/javascript">
            
    var vm = new Vue({
        el:'#container',
        data:{
            keyWord:'',
            sortType:0,
            ......
        },
        computed:{
            filterPersons(){
                // 把过滤后的结果先保存起来,不返回,处理一下排序
                var arr = this.persons.filter((item)=>{
                    return item.name.indexOf(this.keyWord) != -1
                })
                // 若为升序/降序才进行排序处理
                if(this.sortType){
                    arr.sort((item1,item2)=>{ // sort()要求传入一个function,两个项的参数比较
                        return this.sortType ==1 ? item1.age-item2.age : item2.age-item1.age
                    })
                }
                return arr
            }
        }
    })
</script>
  • 完整代码如下
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <style type="text/css">
            
        </style>
        <script src="js/v2.6.10/vue.js"></script>
    </head>
    <body>
        <div id="container">
            <ul>
                <input type="text" v-model="keyWord" />
                <button type="button" @click="sortType = 1">年龄升序</button>
                <button type="button" @click="sortType = 2">年龄降序</button>
                <button type="button" @click="sortType = 0">原顺序</button>
                <li v-for="(item,index) in filterPersons" :key="item.id">
                    {{item.name}} -- {{item.age}} -- {{item.sex}}
                </li>
            </ul>
            
        </div>
        
        <script type="text/javascript">
            
            var vm = new Vue({
                el:'#container',
                data:{
                    keyWord:'',
                    sortType:0,
                    persons:[
                        {id:'001',name:'JimGreen',age:20,sex:'male'},
                        {id:'002',name:'KateGreen',age:18,sex:'female'},
                        {id:'003',name:'LiLei',age:21,sex:'male'},
                        {id:'004',name:'LiMeiMei',age:19,sex:'female'},
                    ],
                },
                computed:{
                    filterPersons(){
                        // 把过滤后的结果先保存起来,不返回,处理一下排序
                        var arr = this.persons.filter((item)=>{
                            return item.name.indexOf(this.keyWord) != -1
                        })
                        // 若为升序/降序才进行排序处理
                        if(this.sortType){
                            arr.sort((item1,item2)=>{
                                return this.sortType ==1 ? item1.age-item2.age : item2.age-item1.age
                            })
                        }
                        return arr
                    }
                }
            })
        </script>
    </body>
</html>

vue更新数据的原理解析

  • 引入场景:先渲染一组list数据
......
<body>
    <div id="container">
        <ul>
            <li v-for="(item,index) in persons" :key="item.id">
                {{item.name}} -- {{item.age}} -- {{item.sex}} <!--渲染数据-->
                <input type="text" />
            </li>
        </ul>
    </div>
    
    <script type="text/javascript">
        
        var vm = new Vue({
            el:'#container',
            data:{
                persons:[
                    {id:'001',name:'JimGreen',age:20,sex:'male'},
                    {id:'002',name:'KateGreen',age:18,sex:'female'},
                    {id:'003',name:'LiLei',age:21,sex:'male'},
                    {id:'004',name:'LiMeiMei',age:19,sex:'female'},
                ],
            },
        })
    </script>
</body>

  • 插入按钮,更新list第一条信息,没毛病,一点错误也没有
<div id="container">
    <ul>
        <button type="button" @click="updateFirst">更新头部数据</button> <!--增加之处-->
        <li v-for="(item,index) in persons" :key="item.id">
            {{item.name}} -- {{item.age}} -- {{item.sex}}
            <input type="text" />
        </li>
    </ul>
</div>
......
var vm = new Vue({
    ......
    methods:{
        updateFirst(){
            this.persons[0].name = 'Allen'
        },
    }
})
  • 现在,把persons[0]整条数据进行替换,问题就来了,js是刷新了,但是页面不会刷新...
......
methods:{
    updateFirst(){
        // this.persons[0].name = 'Allen'
        // 页面数据不会刷新
        this.persons[0] = {id:'001',name:'Allen',age:28,sex:'male'}
    },
}

Vue.set() 给'响应式对象'添加新属性

  • Vue.set( target, propertyName/index, value )
- {Object | Array} target
- {string | number} propertyName/index
- {any} value

- 返回值:设置的值
- 向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新
- 它必须用于向响应式对象上添加新 property,因为 Vue 无法探测普通的新增 property (比如 this.myObject.newProperty = 'hi')

注意: 对象不能是 Vue 实例,或者 Vue 实例的根数据对象

Vue 解析数组

  • demo演示,先渲染一组arr
<body>
    <div id="container">
        <ul>
            
            <li v-for="(item,index) in hobby" :key="index"> <!--渲染数据-->
                {{item}} 
            </li>
        </ul>
    </div>
    
    <script type="text/javascript">
        
        var vm = new Vue({
            el:'#container',
            data:{
                ......
                hobby:['basketball','football','tennis']
            },
            methods:{
                    ......
                }
        })
    </script>
</body>

......
- VM 控制台输出变量vm,发现vue并没有为数组的每一项生成 getter和setter
    > vm.hobby
    ['basketball', 'football', 'tennis', __ob__: Observer]......

- 造成的结果: 既然不是响应式对象,所以此时修改列表项的值,js部分会更新值,但是view视图不会更新
   
   > vm.hobby[0] = 'horse'
    'horse'
   > vm.hobby
    ['horse', 'football', 'tennis'......]

- 小结

    - 当数组项没有变动(比如顺序)的时候,vue是不会检测到数组项数据的变化的
      此时修改数组项的数据,js虽然会更新,但是view视图不会更新,因为vue没有检测到!

  • 如何让vue检测到数组项的变化?使用以下方法即可
- push: 往arr末尾添加一项
- pop: 删除arr最末尾一项
- shift: 删除arr最开头一项
- unshift: 往arr最开头插入一项
- splice: 往arr某个位置插入一项
- sort: 排序
- reverse: 反向排序

- 测试如下,页面也实现了即时刷新

    > vm.hobby.push('study')
      4
    > vm.hobby.unshift('math')
      5
  • splice 简单上手:具有插入/删除功能
- arr.splice(位置,删除的个数,项)

......
<div id="container">
    <ul>
        <button type="button" @click="updateFirst">更新头部数据</button>
        <li v-for="(item,index) in persons" :key="item.id">
            {{item.name}} -- {{item.age}} -- {{item.sex}}
        </li>
    </ul>
</div>

<script type="text/javascript">
    
    var vm = new Vue({
        el:'#container',
        data:{
            persons:[
                {id:'001',name:'JimGreen',age:20,sex:'male'},
                {id:'002',name:'KateGreen',age:18,sex:'female'},
                {id:'003',name:'LiLei',age:21,sex:'male'},
                {id:'004',name:'LiMeiMei',age:19,sex:'female'},
            ],
            ......
        },
        methods:{
                updateFirst(){
                    // 往数组最开头插入一项 
                    // this.persons.splice(0,0,{id:'001',name:'Allen',age:28,sex:'male'})

                    // 替换索引为0的项
                    this.persons.splice(0,1,{id:'001',name:'Allen',age:28,sex:'male'})

                    // 这种办法可行,但是比较麻烦...
                    // Vue.set(this.persons[0],'id','001');
                    // Vue.set(this.persons[0],'name','Allen');
                    // Vue.set(this.persons[0],'age','22');
                    // Vue.set(this.persons[0],'sex','male');
                    
                },
            }
    })
</script>

vue提供的arr.push和js原型提供的push是不一样的

  • vue 提供的push,在原有的js.push基础上加工了
arr1.push === Array.prototype.push // true

vm.hobby.push === Array.prototype.push // false

官方文档,数组更新检测

  • 变更方法
- Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新

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

  • 替换数组
- 变更方法,顾名思义,会变更调用了这些方法的原始数组
  相比之下,也有非变更方法,例如 filter()、concat() 和 slice()
  它们不会变更原始数组,而总是返回一个新数组
  当使用非变更方法时,可以用新数组替换旧数组

  // 使用新数组替换旧数组
  example1.items = example1.items.filter(function (item) {
      return item.message.match(/Foo/)
    })

- 你可能认为这将导致 Vue 丢弃现有 DOM 并重新渲染整个列表
  幸运的是,事实并非如此。Vue 为了使得 DOM 元素得到最大范围的重用而实现了一些智能的启发式方法
  所以用一个含有相同元素的数组去替换原来的数组是非常高效的操作。

vue监测数据总结

  • 练习之前的示例
......
<body>
    <div id="container">
        <h1>信息</h1>
        
        <!--简单的逻辑,直接写-->
        <button @click="student.age++;">年龄+1岁</button> <br>
        <button @click="student.sex='男' ">添加性别属性,默认值:男</button><br>

        <button @click="addFriend">在列表首位添加一个朋友</button><br>
        <button @click="changeFirstName">修改第一个朋友名字为:张三</button><br>
        <button @click="addHobby">添加一个爱好</button><br>
        <button @click="changeFirstHobby">修改第一个爱好为: 开车</button><br>
        <button @click="filterCar">把爱好为 开车的数据过滤掉</button><br>
        
        <h3>名字: {{student.name}}</h3>
        <h3>年龄: {{student.age}}</h3>
        <h3 v-if="student.sex">性别: {{student.sex}}</h3>
        
        <h3>爱好</h3>
        <ul v-for="(item,index) in student.hobby" :key="index">
            <li>{{item}}</li>
        </ul>
        
        <h3>朋友</h3>
        <ul v-for="(item,index) in student.friend" :key="id">
            <li>ID: {{item.id}}</li>
            <li>名字: {{item.name}}</li>
            <li>年龄: {{item.age}}</li>
        </ul>
        
    </div>
    
    <script type="text/javascript">
        var vm = new Vue({
            'el':'#container',
            data:{
                student:{
                    name:'JimGreen',
                    age:20,
                    sex:'',
                    hobby:['singing','travel','study','basketball'],
                    friend:[
                        {id:'001',name:'LiLei',age:20},
                        {id:'002',name:'HanMeiMei',age:18},
                        {id:'003',name:'Allen',age:19},
                    ]
                }
            },
            methods:{
                addFriend(){
                    this.student.friend.unshift({id:'004',name:'John',age:22},)
                },
                changeFirstName(){
                    this.student.friend[0].name = '张三'
                },
                addHobby(){
                    this.student.hobby.splice(0,0,'football')
                },
                changeFirstHobby(){
                    this.student.hobby.splice(0,1,'car')
                },
                filterCar(){ // 过滤操作,必须对原数组进行整体替换,否则页面不会刷新
                    this.student.hobby = this.student.hobby.filter((item)=>{
                        return item != 'car'
                    })
                }
            }
        })
    </script>
</body>
  • vue监视数据的原理
- vue会监视data中所有层次的数据

- 如何监测对象中的数据

    - 通过 setter 实现监控,且要在new Vue时就传入要监测的数据

        - 在对象中后追加的属性,vue默认不做响应式处理

        - 如果要给后追加的属性做响应式,请使用以下API

            - Vue.set(target,propertyName/index,value)
            - Vue.$set(target,propertyName/index,value)

- 如何监测数组中的数据

    - 通过包裹数组更新元素的方法实现,本质就是做了两件事

        - 调用原生对应的方法对数组进行更新

        - 重新解析模板,进而更新页面

- 在vue中修改数组中的某个元素,一定要用如下方法

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

    - Vue.set()/Vue.$set(): 不能给vm/vm根数据对象添加属性