Vue03-样式操作+列表数据渲染+数据监视

Vue03-样式操作+列表数据渲染+数据监视

1.绑定class和style

<html lang="en">
<head>
    <meta charset="UTF-8">
    <script src="../js/vue.js"></script>
    <style>
        .basic {
            width: 300px;
            height: 100px;
            border: 1px solid;
        }

        .borderRedColor {
            border: 1px solid red;
        }

        .borderGreenColor {
            border: 1px solid green;
        }

        .bigFont {
            font-size: 50px;
        }
    </style>
</head>
<body>

<div id="app">
    <!--
        1 绑定class样式。写法::class='xxx',xxx可以是字符串、数组和对象。
            字符串使用于类名不确定,需要动态获取的场景;
            数组适用于要绑定的多个样式,个数不确定,类名也不确定的场景;
            对象适用于要绑定的多个样式个数确定,类名也取确定的场景。

        2 绑定style样式。写法::style='xxx',其中xxx可以是对象;
            也可以是数组,如果是数组,数组中应该包含的是样式对象。
    -->

    <!-- 绑定class样式,:class字符串写法。适用于样式的类型不确定,需要动态指定。 -->
    <div class="basic" :class="borderColor" @click="changeBorderColor">test</div> <br>

    <!-- 绑定class样式,:class数组写法。适用于绑定的样式个数不确定,名字也不确定时。 -->
    <div class="basic" :class="borderColorArr" @click="changeBorderColorAndFontSize">test</div>

    <!-- 绑定class样式,:class对象写法。适用于绑定的样式个数确定,名字也确定时。 -->
    <!-- 使用对象写法时,如果对象中属性的值为boolean类型,如果希望应用某个属性,就将给属性的值设为true。 -->
    <div class="basic" :class="classObject" @click="change">test</div>

    <!-- 绑定style样式对象写法。 -->
    <!-- 当使用style对象写法时需要注意,样式font-size需要写为fontSize,
    其他的包含两个单词的样式也需要进行同样的操作。 -->
    <div class="basic" :style="styleObject">test</div>

    <!-- 绑定style样式数组写法,数组中写样式的对象即可。style样式数组写法不经常使用。 -->
    <div class="basic" :style="styleArray">test</div>
</div>

<script>
    const vm = new Vue({
        el: '#app',
        data: {
            borderColor: 'borderGreenColor',
            borderColorArr: ['borderRedColor', 'bigFont'],
            classObject: {
                borderGreenColor: false,
                bigFont: false
            },
            styleObject: {
                fontSize: '50px'
            },
            styleArray: [
                {
                    backgroundColor: 'red'
                },
                this.styleObject
            ]
        },
        methods: {
            changeBorderColor() {
                if (this.borderColor === 'borderGreenColor') {
                    this.borderColor = 'borderRedColor';
                } else {
                    this.borderColor = 'borderGreenColor';
                }
            },
            changeBorderColorAndFontSize() {
                if (this.borderColorArr.indexOf('borderRedColor') !== -1) {
                    this.borderColorArr.shift();
                } else {
                    this.borderColorArr.unshift('borderRedColor');
                }
            },
            change() {
                this.classObject.borderGreenColor = !this.classObject.borderGreenColor;
                this.classObject.bigFont = !this.classObject.bigFont;
            }
        }
    });
</script>
</body>
</html>

2.条件渲染v-if和v-show

<body>
<div id="app">

    <!--
    1 v-if。
        写法:v-if="xxx" v-else-if="xxx" v-else="xxx"。
        v-if适用于切换频率较低的场景,同时v-if会将不展示的元素从DOM中移除。
        v-if可以和v-else-if、v-else一起使用时,但是要求元素的结构不能被打断。

    2 v-show。
        写法。v-show="xxx"。
        v-show适用于元素是否显示切换频率较高的场景。
        v-show不会将不展示的元素从DOM中移除,仅仅是样式隐藏。

     3 v-if可能会导致元素无法获取到,因为在不展示时会将元素从DOM中移除。
        而使用v-show在元素不展示时,依然可以获取到。

     4 当某个条件下需要同时展示多个元素,可以使用template和v-if。
    -->

    <!-- v-show。
            1 v-show可以控制元素是否显示。
            2 v-show底层是通过设置 style='display: none',来使得元素不显示的。
                所以对于不显示的元素依然会存在于DOM中。
            3 v-show='xxx',xxx可以是一个boolean值true或者false;xxx也可以是一个表达式,这个表达式返回一个布尔值。
    -->
    <p v-show="false">v-show 1</p>
    <p v-show="1 === 1">v-show 2</p>

    <!-- v-if -->
    <!-- v-if可以用来控制元素是否显示。如果v-if的值为false,则会在DOM中将该元素删除。 -->
    <!-- 对于v-if,如果'm === 0'成立了,依然会走'm === 1'的判断逻辑。 -->
    <p v-if="m === 0">0 - {{m}}</p>
    <p v-if="m === 1">1 - {{m}}</p>
    <button @click="m++">m+1</button>

    <!-- v-if...v-else-if...v-else -->
    <!--
        1 v-if...v-else-if...v-else和JavaScript中的if...else的作用相同。
        2 如果v-if="n === 0"成立,则不会走其他的v-else-if的逻辑判断。
        3 如果要使用v-if...v-else-if...v-else,这几个逻辑判断的元素要连续才能生效。
     -->
    <p v-if="n === 0">0 - {{n}}</p>
    <!-- 如果v-if...v-else-if...v-else使用的元素没有连续,则会导致vue产生错误。 -->
    <!--<p>不连续</p>-->
    <p v-else-if="n === 1">1 - {{n}}</p>
    <p v-else-if="n === 2">2 - {{n}}</p>
    <p v-else>其他 - {{n}}</p>
    <button @click="n++">n+1</button>

    <!-- 1 v-if和template配置使用,当在某个条件时需要同时展示多个元素,
               可以将这多个元素都放在template中,让其同时展示或者同时隐藏。
         2 template的使用不影响元素的结构,在vue实例化后会将template去掉。
         3 template只能和v-if配合使用,不能和v-show配合使用。
    -->
    <template v-if="1 === 1">
        <h3>hello</h3>
        <h3>lisi</h3>
        <h3>wangwu</h3>
    </template>
</div>

<script>
    const vm = new Vue({
        el: '#app',
        data: {
            m: 0,
            n: 0
        }
    });
</script>
</body>

3.列表渲染

<body>
<div id="app">

    <!--
        v-for用于遍历列表数据。语法v-for="(item,index) in xxx" :key="yyy"。:key,key需要唯一。
        v-for用于遍历数组、对象、字符串和指定次数。
    -->
    <!-- 1 v-for遍历数组。 -->
    <ul>
        <li v-for="(person,index) in persons" :key="index">
            {{person.name}} -- {{person.age}} -- {{index}}
        </li>
    </ul>

    <ul>
        <li v-for="(person,index) of persons" :key="index">
            {{person.name}} -- {{person.age}} -- {{index}}
        </li>
    </ul>

    <!-- 2 v-for遍历对象。 -->
    <ul>
        <li v-for="(value,key) in student" :key="key">
            {{key}} --- {{value}}
        </li>
    </ul>

    <!-- 3 v-for遍历字符串。 -->
    <ul>
        <li v-for="(char,index) of name" :key="index">
            {{char}} --- {{index}}
        </li>
    </ul>

    <!-- 4 v-for遍历指定的次数。 -->
    <ul>
        <li v-for="(number,index) of 5" :key="index">
            {{number}} --- {{index}}
        </li>
    </ul>
</div>

<script>
    const vm = new Vue({
        el: '#app',
        data: {
            persons: [
                {id: '001', name: 'tom', age: 10},
                {id: '002', name: 'alice', age: 11},
                {id: '003', name: 'lisi', age: 12},
            ],
            student: {
                id: 'A001',
                name: 'zhangsan'
            },
            name: 'name',
        }
    })
</script>
</body>

4.v-for变量时key的作用

<body>
<div id="app">

    <!--
        react和vue中key的作用。
        1 虚拟DOM中key的作用。
            key是虚拟DOM的标识,当数据发生变化时,Vue会根据[新数据]生成[新的虚拟DOM],
            随后Vue进行[新虚拟DOM]和[旧虚拟DOM]的差异比较,比较规则如下:

        2 虚拟DOM替换时的比较规则。
            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是没有问题的。
    -->

    <!-- 当指定key不合适时存在的问题。
        1 指定key为遍历数据的索引。
        2 每个li中添加input输入框。
        3 输入框中添加内容。
        4 点击按钮给数组头部添加数据。
        5 之后会发现当前填充的数据发生了错乱。
     -->
    <ul>
        <li v-for="(person,index) in persons" :key="index">
            {{person.name}}
            <input type="text">
        </li>
    </ul>
    <button @click="add">添加</button>

    <!-- 列表中key的正确使用,使用数据的唯一id作为key,即使进行逆序添加、逆序删除也不会有问题。 -->
    <ul>
        <li v-for="(person,index) in persons" :key="person.id">
            {{person.name}}
            <input type="text">
        </li>
    </ul>
    <button @click="add">添加</button>
</div>
<script>
    const vm = new Vue({
        el: '#app',
        data: {
            persons: [
                {id: '001', name: 'tom', age: 10},
                {id: '002', name: 'alice', age: 11},
                {id: '003', name: 'lisi', age: 12},
            ],
        },
        methods: {
            add() {
                let person = {id: '004', name: 'wangwu', age: 20};
                this.persons.unshift(person);
            }
        }
    });
</script>
</body>

5.列表过滤

<body>

<div id="app01">
    <input type="text" v-model="keyword">
    <ul>
        <li v-for="person in usePersons">
            {{person.name}}
        </li>
    </ul>
</div>

<div id="app02">
    <input type="text" v-model="keyword">
    <ul>
        <li v-for="person in usePersons">
            {{person.name}}
        </li>
    </ul>
</div>
<script>
    // 列表过滤watch实现。
    const vm01 = new Vue({
        el: '#app01',
        data: {
            keyword: '',
            persons: [
                {id: '001', name: '马冬梅', sex: '女'},
                {id: '002', name: '周冬雨', sex: '女'},
                {id: '003', name: '周杰伦', sex: '男'},
                {id: '004', name: '温兆伦', sex: '男'}
            ],
            usePersons: []
        },
        watch: {
            keyword: {
                // 初始化时也执行handler。
                immediate: true,
                handler(newValue) {
                    // 数组过滤,将name包含newValue的数据过滤出来。
                    this.usePersons = this.persons.filter((person) => {
                        return person.name.indexOf(newValue) !== -1
                    })
                }
            }
        }
    });

    // 通过计算属性实现列表过滤。
    const vm02 = new Vue({
        el: '#app02',
        data: {
            keyword: '',
            persons: [
                {id: '001', name: '马冬梅', sex: '女'},
                {id: '002', name: '周冬雨', sex: '女'},
                {id: '003', name: '周杰伦', sex: '男'},
                {id: '004', name: '温兆伦', sex: '男'}
            ]
        },
        computed: {
            // 计算属性,当get中所依赖的属性(keyword)发生变化时触发计算。
            usePersons() {
                return this.persons.filter((person) => {
                    return person.name.indexOf(this.keyword) !== -1
                })
            }
        }
    })
</script>
</body>

6.列表排序

<body>

<div id="app">
    <input type="text" v-model="keyword">
    <button @click="sortType = 2">升序</button>
    <button @click="sortType = 1">降序</button>
    <button @click="sortType = 0">原来的顺序</button>
    <ul>
        <li v-for="person in usePersons">
            {{person.name}} --- {{person.age}}
        </li>
    </ul>
</div>
<script>
    const vm = new Vue({
        el: '#app',
        data: {
            sortType: 0,
            keyword: '',
            persons: [
                {id: '001', name: '马冬梅', sex: '女', age: 30},
                {id: '002', name: '周冬雨', sex: '女', age: 31},
                {id: '003', name: '周杰伦', sex: '男', age: 22},
                {id: '004', name: '温兆伦', sex: '男', age: 23}
            ]
        },
        computed: {
            // 计算属性,当get中所依赖的属性(keyword)发生变化时触发计算。
            usePersons() {
                // 根据关键字过滤,数组过滤不会改变原来的数字,而是返回一个新数组。
                const arr = this.persons.filter((person) => {
                    return person.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>
</body>

7.更新列表数据存在的问题

<body>
<div id="app01">
    <button @click="update">更新马冬梅数据</button>
    <ul>
        <li v-for="person in persons">
            {{person.name}} --- {{person.age}} --- {{person.sex}}
        </li>
    </ul>
</div>

<div id="app02">
    <button @click="update">更新马冬梅数据</button>
    <ul>
        <li v-for="person in persons">
            {{person.name}} --- {{person.age}} --- {{person.sex}}
        </li>
    </ul>
</div>
<script>
    const vm01 = new Vue({
        el: '#app01',
        data: {
            persons: [
                {id: '001', name: '马冬梅', sex: '女'},
                {id: '002', name: '周冬雨', sex: '女'},
                {id: '003', name: '周杰伦', sex: '男'},
                {id: '004', name: '温兆伦', sex: '男'}
            ],
        },
        methods: {
            update() {
                // 数据正常更新
                this.persons[0].name = '马老师';
                this.persons[0].sex = '男';
            }
        }
    });

    const vm02 = new Vue({
        el: '#app02',
        data: {
            persons: [
                {id: '001', name: '马冬梅', sex: '女'},
                {id: '002', name: '周冬雨', sex: '女'},
                {id: '003', name: '周杰伦', sex: '男'},
                {id: '004', name: '温兆伦', sex: '男'}
            ],
        },
        methods: {
            update() {
                // 使用这样方式更新,就会出现数据更新的问题。
                // 在浏览器窗口中输出vm02.persons[0],发现数据正常更新,
                // 但是Vue中数据是没有更新的,导致界面的数据依然没有更新。

                // 原因是直接进行数组的修改无法被Vue检测到。
                // 如果对数组的修改需要被Vue检测到,可以使用数组中的相关方法进行修改。
                this.persons[0] = {id: '001', name: '马老师', sex: '男'};
            }
        }
    });
</script>
</body>

8.模拟vue对象数据监视

let data = {
    name: 'tom',
    age: 20
};

// 1 创建监视器对象,用于监视data数据的变化,相当与vue中对Vue实例中_data封装一样。
const obs = new Observser(data);
console.log(obs);

// 2 将监视对象赋值给_data。
const vm = {};
vm._data = data = obs;

// vue中对象的监视
function Observser(val) {
    // 获取对象中所有的属性名。
    const keys = Object.keys(val);

    // 遍历数据中所有的属性,然后通过Object.defineProperty(),将这些属性添加到this中,
    // 这里的this为Observser对象的实例,并且在Observser对象中为这些属性提供get和set,
    // set就可以用于属性变化时的监控。
    keys.forEach(k => {
        Object.defineProperty(this, k, {
            // get返回真实数据的值。
            get() {
                return val[k];
            },
            // set去修改真实数据的值。
            set(newValue) {
                console.log('当数据发生变化修改真实DOM中的值。');
                val[k] = newValue;
            }
        });
    })
}

9.Vue.set()

<body>
<div id="app">
    <button @click="add01">给学生添加性别属性</button>
    <br>
    <p>姓名  {{student.name}}</p>
    <p>性别  {{student.sex}}</p>

    <button @click="add02">给学生添加性别属性</button>
    <br>
    <p>姓名  {{student.name}}</p>
    <p>性别  {{student.sex}}</p>
    <p>年龄  {{student.age}}</p>
    <p>地址  {{student.address}}</p>
    <p>手机号  {{student.phone}}</p>

    <button @click="add03">添加属性</button>
</div>

<script>
    const vm = new Vue({
        el: '#app',
        data: {
            student: {
                name: 'tom'
            }
        },
        methods: {
            // 直接给data中的student添加sex属性是不可取的,因为在data中没有给sex添加get和set方法,
            // 所以就会导致添加的属性不能够读取或者访问。
            add01() {
                this.student.sex = '男';
            },

            // 通过Vue.set()添加的属性会在data内生成get和set,即所添加的属性是响应式的。
            // Vue.set(target, key, value)方法的参数。target给哪个对象添加属性;key属性的key;value属性的值。
            add02() {
                Vue.set(this.student, 'sex', '男');

                // 其他几种添加属性的方式。
                // this.student和this._data.student是相同的,所以给this._data.student添加属性和给this.student添加效果是一样的。
                Vue.set(this._data.student, 'age', 10);

                // 调用Vue实例对象的$set()和调用Vue.set()效果是一样的。
                this.$set(this.student, 'address', 'bj');
                this.$set(this._data.student, 'phone', '123456');
            },
            add03(){
                // 不能将添加的属性直接保存到Vue实例中,或者保存到data中,会报错,因为Vue无法探测到新增属性。
                // 可以将添加的属性保存到Vue实例中的对象中(如student),
                // 或者保存到data中的对象中(如data中的student。)。
                Vue.set(this, 'name', 'alice');
                this.$set(this._data, 'age', 10);
            }
        }
    })
</script>
</body>

10.Vue中数组数据更新原理

<body>
<div id="app">
    <ul>
        <li v-for="(student,index) in students" :key="index">
            {{student}}
        </li>
    </ul>
    <button @click="update01">修改数据</button>
    <button @click="update02">修改数据</button>
    <button @click="update03">修改数据</button>
</div>
<script>
    const vm = new Vue({
        el: '#app',
        data: {
            students: ['tom', 'alice']
        },
        methods: {
            // 1 直接修改数组数据存在的问题。
            //  1 数组中的数据,Vue没有提供对应的get和set。
            //  2 由于Vue没有给数组中的数据提供get和set,所以Vue就无法检测到数组数据被修改了,
            //      导致即使数组数据修改了,但是页面依然显示之前的数据。
            update01() {
                this.students[0] = '张三';
            },

            // 2 如果使用数组中的方法修改数据就可以被Vue检测到。
            //      1 Vue中的数组中已经不是JavaScript中原生的数组对象了,而是包装后的数组对象。
            //      包装后的数组对象重写7个对数组有破坏的方法,重写之后的执行逻辑可以分为两步,
            //      以splice为例,会先调用原生数组中的splice方法完成对数据的更新,然后重新渲染DOM。

            //      2 包装后的数组重写的7个方法。push() pop() shift() unshift() splice() sort() reverse()
            update02() {
                console.log(this.students.splice === Array.prototype.splice); // false
                this.students.splice(0, 1, '李四');
            },

            update03() {
                // 除了使用方法修改数组可以被Vue检测到,也可以使用Vue.set()来完成对数组值的修改。
                Vue.set(this.students, 0, '王五');
            }
        }
    })
</script>
</body>

11.Vue数据监视总结

<body>
<div id="app">
    <!--
        Vue监视数据原理。
        1 Vue会监视data中所有层次的数据。

        2 Vue监视对象中的数据。
            Vue通过set实现对象中数据的检查,并且要在new Vue()时传入要检测的数据。
            对象中后添加的数据Vue默认不进行响应式处理。
            如果需要给后添加的数据做响应式处理,需要通过两个API:
                Vue.set(target, propertyName/index, value) 或者
                vm.$set(target, propertyName/index, value)

        3 Vue检测数组中的数据。
            通过包装更新数组的元素的方法实现,本质就是做了两件事情:
                1 调用原生数组方法对数组进行更新。
                2 重新解析模板,进行页面更新。

        4 在Vue中对数组进行修改时,需要使用如下方法:
            1 可以使用的API。push() pop() shift() unshift() splice() sort() reverse()
            2 Vue.set()、vm.$set()

        5 Vue.set()和vm.$set()不能给vm或者vm的根数据直接添加属性。
    -->

    <h1>学生信息</h1>

    <button @click="student.age++">年龄+1</button>
    <button @click="addSex">添加性别属性,默认值 男</button>
    <button @click="student.sex = '女'">修改性别</button>
    <button @click="addFriend">在列表头部添加朋友</button>
    <button @click="updateFirstFriendName">修改第一个朋友的名字为 张三</button>
    <button @click="addHobby">添加一个爱好</button>
    <button @click="updateFirstHobby">修改第一个爱好为 开车</button>

    <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>
    const vm = new Vue({
        el: '#app',
        data: {
            student: {
                name: 'tom',
                age: 10,
                hobby: ['学习', '游戏'],
                friends: [
                    {name: '李四', age: 21},
                    {name: '王五', age: 22},
                ]
            }
        },
        methods: {
            addSex() {
                // Vue.set(this.student, 'sex', '男');
                this.$set(this.student, 'sex', '男')
            },
            addFriend() {
                this.student.friends.unshift({name: 'alice', age: 26});
            },
            updateFirstFriendName() {
                this.student.friends[0].name = '张三';
            },
            addHobby() {
                this.student.hobby.push('篮球');
            },
            updateFirstHobby() {
                this.student.hobby.splice(0, 1, '开车');
            }
        }
    })
</script>
</body>
posted @ 2022-11-25 11:00  行稳致远方  阅读(203)  评论(0)    收藏  举报