XM的小窝


 

【填坑中】Vue学习笔记

简介

Vue是一个动态构建用户界面的渐进式 Javascript 框架

特点

  1. 遵循mvvm模式
  2. 编码简洁,体积小,运行效率高,适合移动/pc 端开发
  3. 它本身只关注UI,也可以引入其他第三方库开发项目

浅尝一下

引入Vue.js

<script src="https://cn.vuejs.org/js/vue.js"></script>

HTML容器:

<!-- 准备一个容器 -->
<div id="root">
    <!-- 插值 -->
    <p>Hello {{name}}</p>
    <p>事{{name2}}捏</p>
</div>

Vue脚本:

//创建Vue实例
const x = new Vue({
    //配置对象
    el: '#root', //el 用于指定当前Vue实例为哪个容器服务,css选择器格式
    data: { //data 中用于指定存储的数据,数据供el所指定的容器去使用
        name: 'kanokano',
        name2: '嘉然'
    }
});

Vue的工作条件

  • 想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象
  • root容器里的代码依然符合html的规范标准,只是混入了vue的一些语法
  • root容器里的代码被称为Vue模板
  • 一个容器对应一个Vue实例,反之亦然,Vue实例配合组件一起使用
  • {{}}中需要写的是js表达式,且变量可以自动读取到data中所有的属性
  • 一旦data中的数据发生改变,模板(页面)中的目标也会自动更新

模板语法

插值语法

插值语法直接在html标签体内使用Vue插值模板,填入data中的属性即可

<div id="root">
    <!-- 插值 -->
    <p>Hello {{name}}</p>
</div>
//创建Vue实例
const x = new Vue({
    //配置对象
    el: '#root', 
    data: { 
        name: 'kanokano',
    }
});

指令语法

指令语法以 v- 开头,用于解析标签(包括标签属性,标签体内容,绑定事件等)

举例 (v-bind)

使用v-bind:来模板化href属性

<div id="root">
    <a v-bind:href="url">kanokano的博客</a>
</div>
 const x = new Vue({
     //配置对象
     el: '#root', 
     data: {
         url: 'https://kanokano.cn'
     }
 });

有时,v-bind: 也可以简写为 :

<div id="root">
    <a :href="url">kanokano的博客</a>
</div>

总结:标签体内用插值语法,标签属性用指令语法

数据绑定

由于v-bind是单向的数据绑定,只能通过对象传递到DOM

有了单向数据绑定这个概念,那就一定会有双向数据绑定

双向数据绑定使用:v-model:

<span>单向数据绑定:</span><input type="text" :value="name2"><br>
<span>双向数据绑定:</span><input type="text" v-model:value="name2">

注意,以下代码是错误的

<h1 v-model:data="name"></h1>

这是因为 v-model 只支持表单类等输入型元素内

v-model:value 可以简写为 v-model 因为 v-model 默认收集的就是value的值

<span>双向数据绑定:</span><input type="text" v-model="name2">

el与data的两种写法

el的两种写法:

对象属性

const v = new Vue({
    el: '#root',
    data: {
        text: "你好呀"
    }
});

使用实例对象的原型对象上的$mount方法挂载元素

const v = new Vue({
    data: {
        text: "你好呀"
    }
});
v.$mount('#root')

data的两种写法

对象式:

data: {
    text: "你好呀"
}

函数式:

 data: function(){
     //此处的this式Vue的实例对象
     console.log(this)
     return {
         name:'你好呀'
     }
 }

注意,函数写法不可以是箭头函数,因为会扰乱this的指向(原本this指向一个Vue对象:鹿乃_打扰了:

//函数式可适用于vue组件

MVVM模型

  • M:模型(Model): 对应 data中的数据
  • V:视图(View): 模板
  • VM:视图模型(ViewModel): vue实例对象

image-20220802003004189

<body>
    <!-- View -->
    <div id="root">
        <p>{{kano}}</p>
    </div>
    <script>
        new Vue({ //ViewModel
            el: '#root',
            data: { //Model
                kano: "kano"
            }
        });
    </script>
</body>

数据代理

Object.defineproperty 方法

此方法可以设置一个对象的属性,该方法有三个参数,区别于直接赋值,该方法的作用更多,更高级

简单用法:

let singer = {
    name: 'kano'
}
Object.defineProperty(singer, 'age', {
    value: 8
})
console.log(singer);

这样就能给一个对象添加属性了,但是你会发现,这个属性居然不能修改,也不能删除,更不能被遍历出来!

其实是因为 Object.defineProperty 方法默认创建出来的属性,他的 writable, enumerable, configurable的值都为false.

console.log(Object.getOwnPropertyDescriptors(singer)); 
//该方法返回指定对象所有 自身属性 的描述对象

想要解决无法修改,配置,遍历属性的问题,其实很简单,只需要在第三个参数中指定属性的特征即可:

Object.defineProperty(singer, 'age', {
    value: 8,
    enumerable: true, //控制属性是否可被枚举
    writable: true, //控制属性是否可被修改
    configurable: true //控制属性是否可被删除
});
singer.age=11;
console.log(singer);

以上代码配置了可修改,可配置,可被枚举的属性特征,所以是可以遍历且值可以被修改删除

getter与setter

如标题所言,JavaScript已经开始逐渐像java/c#类这样的面向对象语言靠拢了(喜)

直接看使用方法:

//数据代理
let msg = 'hello~';
Object.defineProperty(singer, 'msg', {
    get: () => {
        console.log('调用了getter');
        return msg
    },
    set: value => {
        console.log('调用了setter');
        msg = value
    }
});
console.log(singer);

当读取msg属性的时候,get访问器就会自动调用函数获取相应的值,返回的值就是msg属性的值
修改(set访问器)也是一样,有java/C#类语言的基础会更好理解

这样,通过get访问器就可以实现代理msg变量了,实现了数据的双向/单向同步

总结:Object.defineProperty 是一个比较高级的给对象添加属性的方法,不仅增加了对象属性的安全性,也可以让我们更灵活使用对象中的属性,更重要的是 这个方法可以设置访问代理,Vue的数据双向绑定就是依照这个方法构建的

数据代理实例

我们可以通过一个简单的例子来实现数据代理:

let obj = {
    a: 10
};
let objj = {
    b: 20
};

Object.defineProperty(objj, 'a', {
    get: () => obj.a,
    set: (value) => {
        obj.a = value
    }
})
console.log(objj.a);
console.log(obj.a);
obj.a = 111;
console.log(objj.a);
console.log(obj.a);

Vue中的数据代理

  1. Vue中的数据代理:
    • V通过vm对象来代理data对象中属性的操作读和写
  2. Vue中数据代理的好处:
    • 更加方便操作data中的数据
  3. 基本原理:
    • 通过Object。defineProperty() 把data对象中所有的属性添加到vm上,为每一个添加到vm上的属性,都指定一个getter/setter 在里面进行读写data中对应的属性

原理图:

image-20220803221650284

示例代码:

let data = {
    name: 'kano',
    age: 10
}
var vm = new Vue({
    el: '#root',
    data
});
console.log(data === vm._data); //true
vm.name = "kanokano"
console.log(data.name, vm._data.name); //一致

事件处理

事件的基本使用

  1. 使用v-on:xxx 或 @xxx 绑定事件,其中xxx是事件名
  2. 事件的回调需要配置在methods对象中,最终会在vm上
  3. methods中配置的函数不能使用箭头函数,会造成this指向错误
  4. methods中配置的函数,都是被Vue所管理的函数,this的指向是 vm 或 组件的实例对象
  5. @click 中的字符串可以使用函数写法进行传参,@click="fun"@click=”fun($event)“是一样的
  6. 函数传参的时候如有多个参数,会造成event无法使用,可以在实参列表中,添加$event 进行占位,就可以使用了

代码演示

<div id="root">
    <h1>{{hello}}</h1>
    <button v-on:click="showInfo">点我</button>
    <!-- 简写 -->
    <!-- 函数没有参数时候可以省略括号 -->
    <!-- 想用event可以使用$event关键词占位 -->
    <button @click="showInfo1(123,$event)">点我点我</button>
</div>
const vm = new Vue({
    el: '#root',
    data: {
        hello: '你好!'
    },
    methods: { //methods内的方法不做数据代理
        showInfo(e) {
            alert('okk')
            console.log(this); //这里的this是vm
            console.log(e.target.innerText);
        },
        showInfo1(num, e) {
            alert(num)
            console.log(this); //这里的this是vm
            console.log(e.target.innerText);
        }
    }
});

事件修饰符

Vue中的事件修饰符:

  1. prevent:阻止默认事件(常用)
  2. stop:阻止事件冒泡(常用)
  3. once:事件只触发一次(常用)
  4. capture:使用事件捕获模式
  5. self:只有event.target 是当前操作的元素才会触发事件
  6. passive:事件的默认行为是立即执行,无需等待事件回调即可执行完毕

注意,事件修饰符是可以连用的 比如 @click.stop.prevent 先阻止冒泡。再阻止默认行为

<div id="root">
    <h1>{{hello}}</h1>
    <!-- .prevent就可以阻止默认行为 -->
    <!-- .prevent就是事件修饰符 -->
    <a href="//baidu.com" @click.prevent="showInfo">点我</a>

    <!-- 阻止事件冒泡(常用) -->
    <div @click="showInfo" style="background-color:skyblue">
        <button @click.stop="showInfo">点点点</button>
    </div>

    <!-- 一次性事件 -->
    <button @click.once="showInfo">我是一次性的</button>

    <!-- 事件捕获模式 -->
    <!-- 点击紫色盒子,先输出1再输出2 -->
    <div @click.capture="show(1)" style="background-color:skyblue;padding: 10px;">1
        <div @click.capture="show(2)" style="background-color:blueviolet;">2</div>
    </div>

    <!-- 只有event.target 是当前操作的元素才会触发事件,点击紫色盒子的时候会触发事件,此时e.target是点击的那个button,然后会触发事件冒泡 -->
    <!-- 事件冒泡到上层绑定事件的元素时,由于加了.self修饰,发现了触发事件的event不是自己,就不会触发事件操作 -->
    <div @click.self="show(1)" style="background-color:skyblue;padding: 10px;">1
        <button @click="show(2)" style="background-color:blueviolet;">2</button>
    </div>

    <!-- 事件的默认行为是立即执行,无需等待事件回调即可执行完毕 -->
    <!-- wheel是鼠标滚轮事件 -->
    <ul @wheel.passive="scrollbig">
        <li>1111</li>
        <li>2222</li>
        <li>3333</li>
        <li>4444</li>
    </ul>

</div>
const vm = new Vue({
    el: '#root',
    data: {
        hello: '你好!'
    },
    methods: { //methods内的方法不做数据代理
        showInfo(e) {
            alert('okk')
            console.log(this); //这里的this是vm
            console.log(e.target.innerText);
        },
        show(s) {
            console.log(s);
        },
        scrollbig() {
            for (var i = 0; i < 999; i++) {
                console.log('dddd');
            }
        }
    }
});

键盘事件

  1. 常用按键别名:
    • 回车 enter删除 delete (删除和退格都行
    • 退出 esc空格 space换行 tab (特殊,必须配合keydown)
    • 上 up
    • 下 down
    • 左 left
    • 右 right
  2. Vue未提供别名的按键,可以使用按键原始值的key值绑定,但要注意要转为 keybab-case 短横线小写命名
  3. 系统修饰键(特殊) ctrl alt shift 等
    1. 配合keyup使用:按下修饰键的同时,再按下其他键,随后释放,事件才能触发
    2. 配合keydown使用,正常触发事件
  4. 可以使用keyCode指定具体的按键(不建议)
  5. Vue.config.keyCodes.自定义按键名=键码,可以自定义按键别名
  6. 可以利用修饰符连写的特性,进行组合键的侦听
    <div id="root">
    <h2>{{name}}</h2>
    <input type="text" placeholder="按下回车键提示输入" @keyup.enter="into">
    <!-- 修饰符连写 -->
    <input type="text" placeholder="按下ctrl+y提示输入" @keyup.ctrl.y="into">
    </div>
    <script>
    Vue.config.keyCodes = 41;
    new Vue({
        el: '#root',
        data: {
            name: '键盘事件演示'
        },
        methods: {
            into(e) {
                console.log(e.target.value);
            }
        }
    });
    </script>

计算属性

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

代码演示

<div id="root">
    <!-- v-model 双向数据绑定 -->
    <p> 姓:<input type="text" v-model="firstname"> </p>
    <p> 名:<input type="text" v-model="lastname"></p>
    <!-- 上面的input内容只要发生变化,vue的模板都会重新解析一遍html,从而带动下面的函数的重载 -->
    <p> 姓名:<span>{{fullname}}</span></p>
</div>
const vm = new Vue({
    el: '#root',
    data: {
        firstname: '张',
        lastname: '三'
    },
    //计算属性,computed内的就是计算属性,用来处理复杂的属性计算,里面的属性值也是用数据代理
    //计算属性是有缓存的,get调用了一次,如果值没有改变,再次调用属性的时候就不会调用get函数了
    //初次读取fullname时候,get会被调用,所依赖的数据发生变化时,get也会被调用
    //computed 属性中的get不能被手动调用,Vue会在使用到属性的时候自动调用
    computed: {
        fullname: {
            get() {
                //此处的this是vm
                return this.firstname + '-' + this.lastname;
            },
            //当fullname被修改的时候自动调用set
            set(value) {
                let arr = value.split('-');
                //改的是这两个变量,而不是fullname
                this.firstname = arr[0];
                this.lastname = arr[1]
            }
        }
    }
});

此外,fullname还有一种简写形式:

//fullname的简写形式
fullname() {
    return this.firstname + '-' + this.lastname;
}

可以把属性写成一个函数,函数名就是属性名(es6)函数体默认就有getter的作用

注意!! 不要把fullname理解为一个函数了,它本身还是一个属性,函数只是负责计算返回结果,在模板内只需要写属性名即可!!!

监视属性watch

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

代码演示

<div id="root">
    <h1>今天天气很{{Info}}</h1>
    <button v-on:click="changeWeather">切换天气</button>
    <!-- 如果函数功能很简单,可以直接在click里面写语句 -->
    <button v-on:click="isHot = !isHot;">切换天气</button>
    <!-- 但是这里面不能写alert等函数,因为Vue中的原型对象不包含window对象 -->
    <!-- 解决方法:可以在vm实例中添加window属性,指向window对象即可 -->
    <!-- <button v-on:click="isHot = !isHot;">切换天气</button> -->
</div>
const vm = new Vue({
    el: '#root',
    data: {
        isHot: true
    },
    methods: {
        changeWeather() {
            this.isHot = !this.isHot;
        }
    },
    computed: {
        Info() {
            return this.isHot ? '炎热' : '凉爽';
        }
    },
    //监视属性
    // watch: {
    //     isHot: {
    //         //立即执行,初始化时候,让handler调用一次
    //         immediate: true,
    //         //handler当ishot发生改变时调用
    //         handler() {
    //             console.log('isHot被修改了');
    //         }
    //     }
    // },
});

//监视属性的另一种写法$watch('属性名',配置)
//属性名如果找不到的话也不会报错
vm.$watch('isHot', {
    handler() {
        console.log('isHot被修改了');
    }
})

这里有一个小问题注意:当模板里不使用info变量的时候,点击切换天气,vue开发者工具里的变量显示可能不会更新,但vm内属性实际是更新了的

深度监视

看完了监视属性的简单使用,这时候我会提出一个需求,那就是,如果data属性内有一个对象,我们该怎么监视该对象里面的特定属性呢?

监视多级结构中某属性的变化

答案是使用 ’对象名.属性名‘:{监视配置}

看代码:

data: {
    isHot: true,
        numbers: {
            a: 1,
                b: 1
        }
}

还原成原始写法,就可以使用 点. 来进行单个数据的监视了,也就是深度监视

'numbers.a': {
    handler() {
        console.log('a被修改了');
    }
},

这样就可以监视多级结构中的某个属性的变化了

深度监视的简易写法

如果监视属性里面的语句很简单,就可以使用简易写法:

//正常写法
isHot: {
    // immediate: true,
    // deep: true,
    handler(newValue, oldValue) {
        console.log('isHot被修改了');
    }
}
//简写
isHot() {
    console.log('isHot被修改了');
}

外部写法也是如此:

//正常外部写法
vm.$watch('isHot', {
    immediate: true,
    deep: true,
    handler(newValue, oldValue) {
        console.log('isHot被修改了');
    }
})

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

watch 对比 computed

computed和watch的区别:

  1. computed能完成的功能,watch都可以完成
  2. watch能完成的功能,computed不一定能完成,例如,watch可以执行异步操作

重要的两个小原则:

  1. 所被vue管理的函数,最好写成普通函数,这样this的指向才会是vm
  2. 所有不会被vue所管理的函数,(定时器,ajax回调)。最好写成箭头函数,这样this指向才是vm

比如下面的姓名案例,用watch实现:

<div id="root">
    <p> 姓:<input type="text" v-model="firstname"> </p>
    <p> 名:<input type="text" v-model="lastname"></p>
    <p> 姓名:<span>{{fullName}}</span></p>
</div>
new Vue({
    el: '#root',
    data: {
        firstname: '张',
        lastname: '三',
        fullName: '张-三'
    },
    watch: {
        firstname(newValue) {
            //可以写定时器
            setTimeout(() => {
                this.fullName = newValue + '-' + this.lastname;
            }, 1000);

        },
        lastname(newValue) {
            this.fullName = this.firstname + '-' + newValue;
        }
    },
});

以上代码,会发现我在firstname的处理函数内写了一个定时器,达到了数据更改之后一秒后才执行更改的目的

绑定样式

绑定class样式

在vue中,绑定class的方式是使用 v-bind来动态处理class样式的

一共有三种方式:

  1. 字符串写法,适用于:样式类名不确定 ,需要动态指定
  2. 数组写法,适用于:样式类名不确定 ,个数也不确定,名字也不确定,需要动态指定
  3. 对象写法,适用于:样式类名确定 ,个数也确定,名字也确定,需要动态决定用不用

代码:

<div id="root">
    <!-- 绑定class样式 字符串写法,适用于:样式类名不确定 ,需要动态指定 -->
    <div class="basic normal" :class="mood" @click="changeMood">{{a}}1</div>
    <hr>
    <!-- 绑定class样式 数组写法,适用于:样式类名不确定 ,个数也不确定,名字也不确定,需要动态指定 -->
    <div class="basic" :class="array">{{a}}2</div>
    <button @click="md">点我删除类</button>
    <hr>
    <!-- 绑定class样式 对象写法,适用于:样式类名确定 ,个数也确定,名字也确定,需要动态决定用不用 -->
    <div class="basic" :class="classObj">{{a}}2</div>
</div>
new Vue({
    el: '#root',
    data: {
        a: 'hello',
        mood: 'normal',
        //存放类的数组
        array: ['normal', 'happy', 'sad'],
        classObj: {
            //类的对象写法,false为不启用
            happy: true,
            sad: true
        }
    },
    methods: {
        changeMood() {
            const arr = ['happy', 'sad', 'normal'];
            const index = Math.floor(Math.random() * 3);
            this.mood = arr[index]
        },
        md() {
            this.array.shift();
        }
    },
});

绑定style样式

绑定style样式的方法和绑定class样式大同小异

使用 :style="styleObj" 进行绑定

对象名可以使用驼峰命名的css属性 fontSize: '44px'

也可以是原始css属性,但需要加上引号 'font-size': '44px'

代码演示

<!-- 绑定style样式 也是对象写法 -->
<div class="basic" :style="styleObj">{{a}}2</div>

<!-- 绑定style样式 数组对象写法 -->
<div class="basic" :style="[styleObj1,styleObj]">{{a}}2</div>
data: {
    //样式的对象写法
    styleObj: {
        //vue中的css属性写法
        // fontSize: '44px'
        //也可以写原生css写法
        'font-size': '44px'
    },
    styleObj1: {
        //vue中的css属性写法
        // fontSize: '44px'
        //也可以写原生css写法
        color: 'red'
    }
}

条件渲染

1.v-if

写法:

  1. v-if="表达式"
  2. v-else-if="表达式"
  3. v-else="表达式"

适用于:切换频率较低的场景

特点:不展示DOM元素直接被移除

注意:v-if可以用:v-else-if ,v-else一起使用,但中间不能有元素打断

2.v-show

写法:v-show=”表达式“

适用于:切换频率较高的场景

特点:不展示dom元素,未被移除,仅仅是使用样式隐藏掉

注意:使用v-if时候,元素可能无法被获取到,但是使用v-show是一定可以获取到的

v-if可以配合template模板使用

代码演示

<div id="root">
    <!-- 条件渲染 相当于css的display -->
    <h2 v-show="true">{{a}}</h2>
    <h2 v-show="1===1">{{a}}</h2>
    <h2 v-show="aa">{{a}}</h2>

    <hr>

    <!-- 使用v-if做条件渲染 影响结构-->
    <h2 v-if="aa" @click="n++">{{n}}</h2>
    <h2 v-if="true">{{a}}</h2>
    <h2 v-if="1===1">{{a}}</h2>

    <!-- v-else-if -->
    <!-- if和elseif 中间不能有元素打断 -->
    <div v-if="n===1">1111</div>
    <div v-else-if="n===2">2222</div>
    <div v-else-if="n===3">3333</div>
    <!-- v-else 不用写参数,是上面条件都不符合的情况 -->
    <div v-else>好</div>

    <!-- 使用template模板配合if渲染多个元素 -->
    <!-- 使用template模板只能配合if,不能配合show -->
    <template v-if="n===4">
        <h2 v-show="true">template{{a}}</h2>
        <h2 v-show="1===1">template{{a}}</h2>
        <h2 v-show="aa">template{{a}}</h2>
    </template>
</div>
new Vue({
    el: '#root',
    data: {
        a: 'hello',
        n: 1,
        aa: true
    }
});

v-for 指令

v-for指令可以用来循环遍历对象、数组、字符串、数字 到相应个数的标签中

作用、语法:

  1. 用于展示列表数据
  2. 语法:v-for="(item,index) in xxx" :key="yyy"
  3. 可遍历: 数组、对象、字符串、数字

注意:只要你使用了遍历语法,你就必须要使用key这个关键字,给每一个li设定唯一的标识

格式::key="xxx“

代码演示:

<div id="root">
    <h2>人员列表</h2>
    <ul>
        <!-- 使用v-for进行遍历输出 -->
        <!-- 只要你使用了遍历语法,你就必须要使用key这个关键字,给每一个li设定唯一的标识 -->
        <li v-for="p in persons" :key="p.id">{{p.name}}</li>
    </ul>
    <ul>
        <!-- key的另一种写法 -->
        <!-- a就相当于item,b就相当于index -->
        <li v-for="(a,b) in persons" :key="b">A:{{a}} B:{{b}} </li>

    </ul>
    <ul>
        <!-- 也可以使用of 遍历对象(in也可以) 这样a就是value b就是key了 -->
        <li v-for="(a,b) of car" :key="b">key: {{b}} ---- value :{{a}}</li>
    </ul>
    <ul>
        <!-- 甚至还能遍历字符串 -->
        <li v-for="(a,b) in str" :key="b">key: {{b}} ---- value :{{a}}</li>
    </ul>
    <ul>
        <!-- 甚至还能遍历输出数字 -->
        <li v-for="(a,b) in 10" :key="b">key: {{b}} ---- value :{{a}}</li>
    </ul>
</div>
new Vue({
    el: '#root',
    data: {
        persons: [{
            id: '001',
            name: '张三',
            age: '18'
        }, {
            id: '002',
            name: '李四',
            age: '19'
        }, {
            id: '003',
            name: '王五',
            age: '20'
        }],
        car: {
            name: '马自达',
            price: '114514'
        },
        str: 'kanokano123'
    }
});

v-for中key的作用与坑

key在v-for中扮演者极为重要的角色,key是vue作为判断数据的唯一性的重要标识

所以,key的唯一性成了一个非常重要的前提保证

这里就要提一下Vue在生成数据的时候的步骤了:

  1. 首先vue会在渲染页面前,在计算机内存中计算并生成一个虚拟DOM,然后才会渲染成真实DOM文档到页面上
  2. 如果需要在相同的地方进行重新渲染,Vue就会依次对比新数据和之前的虚拟DOM数据是否一致,如果发现旧数据有一致的地方,则会复用旧的虚拟DOM缓存渲染的真实DOM,以提高渲染效率,如果不一致,则会按照新dom来重新渲染

既然是依次对比,所以,这里就会牵扯到顺序问题,vue是按照key进行顺序遍历对比的,所以,key一定要是一个唯一的值!!!

要点归纳:
  1. 虚拟DOM中key的作用:
    • key是虚拟dom对象的标识,当状态中的数据发生变化时,Vue会根据新数据生成“新虚拟dom”
      随后Vue进行“新虚拟dom” 与 ”旧虚拟dom“ 的差异比较,规则如下:
  2. 对比规则:
    • 旧虚拟DOM中找到了与新的虚拟DOM相同的key
      • 若虚拟DOM中的内容没有改变,则会直接复用之前的真实dom
      • 若虚拟DOM中的内容改变了,则会生成新的真实DOM,随后替换掉页面中之前的真实DOM
    • 旧虚拟DOM中未找到与新虚拟DOM相同的key
      • 创建真实的DOM,随后渲染到页面
  3. 用index作为key可能会引发的问题:
    • 若对数据进行:逆序添加,逆序删除等破坏顺序的操作:
      • 产生没有必要的真实DOM更改 => 界面没有问题,但是渲染效率降低
    • 如果结构中还包括输入类的dom
      • 会产生错误的DOM更新 => 界面有问题,顺序错乱
  4. 开发中如何选择key?
    1. 最好使用每条数据的唯一标识作为key,比如id,手机号,身份证号,学号等唯一值
    2. 如果不存在对数据的逆序添加、逆序删除等破坏顺序的操作,仅用于渲染列表用于显示,使用index作为key是没有问题的
错误示例
<div id="root">
    <h2>人员列表</h2>
    <ul>
        <button @click="add">点我添加老六</button>
        <!-- 使用v-for进行遍历输出 -->
        <li v-for="(i,index) in persons" :key="index">
            {{i.name}}-{{i.id}}
            <input type="text">
        </li>
    </ul>
</div>
data: {
    persons: [{
        id: '001',
        name: '张三',
        age: '18'
    }, {
        id: '002',
        name: '李四',
        age: '19'
    }, {
        id: '003',
        name: '王五',
        age: '20'
    }]
},
methods: {
    add() {
        let p = {
            id: '004',
            name: '老六',
            age: 21
        };
        //往数组第一个写入数据
        this.persons.unshift(p);
    }
}

点击之前: image-20220807225408598

点击之后 : image-20220807225444623

你会发现,老六旁边的输入框本应该是空的,结果变成了张三,导致下面的输入框全都错了一位

这是因为:Vue在对比新老虚拟DOM的时候,发现张三在index 0的位置,无法渲染,但是旁边的输入框在虚拟DOM中都是空的,Vue则会复用之前的实际DOM,然后继续往后依次比对,发现每一个的姓名和index都对不上,所以只能使用新虚拟DOM重新渲染名字了,但是,输入框在新老虚拟DOM中,都是空的,Vue就会误以为可以直接复用,就会把前三个复用掉,最后才会渲染新的input元素到最后

这里的主要问题就是,index不是唯一的,修改DOM树的时候容易造成查找不正确,从而进行错误的操作

解决方法

:key="index" 替换为 :key="i.id"即可

或者在这个项目例子里,没有必要非要把老六放在第一位,可以使用push方法插入数据到数组尾部,这样就不会打乱index的标号顺序了

使用列表过滤实现简单搜索与排序

基本原理就是,使用计算属性于filter,sort函数

切记,计算属性执行的条件是:页面刚加载时和依赖数据发生变化时

<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>
        <!-- key的另一种写法 -->
        <!-- a就相当于item,b就相当于index -->
        <li v-for="(a,b) in findPersons" :key="a.id">{{a.name}}-{{a.age}}-{{a.sex}}</li>
    </ul>
</div>
new Vue({
    el: '#root',
    data: {
        Keyword: '',
        sortType: 0, //0原序 1 降序 2升序
        persons: [{
            id: '000',
            name: '张三',
            age: '11',
            sex: '男'
        }, {
            id: '001',
            name: '张麻子',
            age: '28',
            sex: '男'
        }, {
            id: '002',
            name: '李四',
            age: '19',
            sex: '男'
        }, {
            id: '003',
            name: '王五',
            age: '50',
            sex: '男'
        }, {
            id: '004',
            name: '马冬梅',
            age: '12',
            sex: '女'
        }],
        // findPersons: []
    },
    //使用计算属性实现
    computed: {
        //这玩意开始的时候执行一次
        //所依赖的keyword变化时候又会执行一次
        //里面用到了sortType 所以sortType改变的时候也会触发这个函数
        findPersons() {
            const arr = this.persons.filter((p) => {
                //true就是符合,返回一个新数组
                //indexof对于空字符串的返回是0(特别重要)
                return p.name.indexOf(this.Keyword) !== -1
            });
            //别急着返回,判断一下是否要排序
            if (this.sortType) {
                arr.sort((a, b) => {
                    //由于a和b在这里都是对象,不能直接比,我们要获取里面的age
                    return this.sortType == 1 ? b.age - a.age : a.age - b.age
                })
            }
            return arr;
        }
    }
    //#region
    //使用监视属性实现
    // watch: {
    //     Keyword: {
    //         //立即执行一次,否则页面没数据
    //         immediate: true,
    //         handler(val) {
    //             this.findPersons = this.persons.filter((p) => {
    //                 //true就是符合,返回一个新数组
    //                 //indexof对于空字符串的返回是0(特别重要)
    //                 return p.name.indexOf(val) !== -1
    //             })
    //         }
    //     }
    // },
    //#endregion
});

Vue数据侦测

实现类似vue的简单数据检测

代码演示

//需要传入的data对象
let data = {
    name: 'kano',
    adress: 'Janpan'
};
//创建一个监视的实例对象,用于监视data中属性的变化
const obs = new Observer(data);
console.log(obs);
//创建一个vm
let vm = {};
vm._data = obs;
vm.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("我被修改了,我要去处理页面响应");
                obj[k] = val;
            }
        })
    })
}

不完善的地方:无法找出数组中的对象并赋予getter setter , 多层对象也一样

使用Vue.set方法新增响应式属性

首先得注意的是:

这个set方法只能给vue data里面某个对象增加属性!!!
也就是要操作的对象不能是Vue实例,或者Vue实例的根数据对象(data、_data)

语法

Vue.set(vue实例, '属性', 值或对象)

例子

const vm = new Vue({
    el: '#root',
    data: {
        name: 'kanokano',
        adress: '湖南',
        student: {
            name: 'tony',
            // sex: '男',
        }
    },
});

//vm.$set(vm.student, 'sex', '男')
//或者
Vue.set(this.student, 'sex', '男')

数组更新检测

Vue将被监听的数组的变更方法进行了包装,所以也可以触发网页元素更新,方法包括:

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

注意:使用下标索引直接修改数组数据是不会触发视图更新的!!!

错误示范

vm.hobby[0]='ddd';

上面的操作是不会生效的(

对于非修改性质的方法,比如filter() ,过滤完成后可以直接替换赋值掉原对象

vm.hobby = vm.hobby.filter((x)=>{
    return x != '抽烟'
})

代码演示

const vm = new Vue({
    el: '#root',
    data: {
            name: 'Xukun Cai',
            hobby: ['唱', '跳', 'rap', '篮球'],
    }
});
//添加元素
vm.hobby.push('你干嘛~ 哎哟');
vm.hobby.shift();

此外,除了可以用上面提到的几个方法操作数组之外,其实也可以使用Vue.set方法进行变更数据:

Vue.set(vm.hobby,0,'唱');
//或者
vm.$set(vm.hobby,0,'唱');

总结

Vue监视数据的原理:

  1. Vue会监视data中所有层次的数据

  2. 如何检测对象中的数据

    • 通过setter实现监视,且要造new Vue 时就要传入要监测的数据
      • 对象中后追击啊的属性,Vue默认不做响应式处理
      • 如需给后添加的属性做响应式,请使用如下API
      • vue.set(target,propertyName/index,value)
      • vm.$set(target,propertyName/index,value)
  3. 如何监测数组中的数据?

    • 通过包装数组对应更新元素的方法来实现,本质就是
      • 调用原生对应的方法对数组进行更新
      • 重新解析没模板,进而更新页面
  4. vue修改数组中某一个元素一定要使用支持的方法

    1. API: push , pop , shift , unshift , splice , sort , reverse
    2. Vue.set 或者 vm.$set

    特别注意的是:Vue.set 和 vm.$set 不能给vm或者vm的根数据对象添加属性!

    对于非修改性质的方法,比如filter() ,过滤完成后可以直接替换赋值掉原对象

    后续内容学习中.... :鹿乃_惊:

点赞

发表评论

电子邮件地址不会被公开。必填项已用 * 标注