前端 - Vue基础


Vue文档

目录
  • 简介
  • 环境安装
  • Hello案例
    • Vue实例和容器是一一对应的
    • Vue模板
    • el和data的两种写法
  • 模板语法
  • 数据绑定
  • MVVM模型
  • 数据代理
    • 回顾Object.defineproperty()
    • 数据代理定义
    • Vue中的数据代理
  • 事件处理
    • 事件修饰符
    • 按键事件
  • 计算属性
    • 姓名案例
    • 应用计算属性
    • 计算属性简写
  • 监视属性
    • 深度监视
    • 监视的简写
    • 计算属性和监视属性的对比
  • 函数写法
  • 绑定样式
    • 绑定class样式
    • 绑定style样式
  • 条件渲染
  • 列表渲染
    • key的原理
    • 列表过滤与排序
  • Vue检测数据变化
    • 原理
    • Vue.set()方法
    • Vue检测数组
  • 收集表单数据
  • 内置指令补充
    • v-text
    • v-html
    • v-cloak
    • v-once
    • v-pre
    • 自定义指令
  • Vue实例生命周期
    • 挂载流程
    • 更新流程
    • 销毁流程

简介

Vue是一套用于构建用户界面渐进式JS框架:

  • 构建用户界面:将数据通过界面显示给用户
  • 渐进式:对于简单的应用,只需要一个小巧的核心库;对于复杂应用,可以引入各式各样的Vue插件

特点:

  1. 组件化模型,提高代码复用率、让代码更好维护
  2. 声明式编码,无需直接操作DOM,提高开发效率
  3. 使用虚拟DOMDiff算法,尽量复用DOM节点

环境安装

  1. 直接用

    Vue实例和容器是一一对应的

    Vue实例需要和容器一一对应,当出现以下两种情况时:

    • 多对一:例如两个Vue实例指定同一个容器,则第二个Vue实例的任何操作都是无效的
    • 一对多:Vue实例的el通过class选择器等匹配时,即使CSS选择器能匹配到多个标签,Vue实例只会和找到的第一个容器绑定

    在真实开发中,只会有一个Vue实例,并且配合组件使用。

    Vue模板

    在上面的案例中,root容器中的代码依然符合HTML规范,只不过混入了一些Vue语法。root容器中的代码被称为Vue模板。在{{}}中,需要写JS表达式,且内部可以读到data中的所有属性,例如,作出如下修改:

    Hello {{name.toUpperCase() + age * 5}}!

    Age: {{Math.PI}}

    JS表达式是一种特殊的JS代码,一个表达式会产生一个值,可以放在任何一个需要值的地方,例如:

    1. a
    2. a + b
    3. func(a)
    4. x === y ? 'a': 'b'

    el和data的两种写法

    • el写法1:new Vue实例的时候配置el
    • el写法2:先创建Vue实例,然后通过指定el的值
    const v = new Vue({
        // el: '#root',  // el写法1
        data: {  // data写法1
            name: 'test',
            age: 30
        }
    });
    
    v.$mount('#root');  // el写法2
    

    下面的两种写法都可以,但使用组件时,必须采取函数式,否则报错

    • data写法1:如上面所示的对象式
    • data写法2:函数式,需要返回一个对象
    new Vue({
        el: '#root',
        // data: function () { // data写法2
        //     return {
        //         name: 'test',
        //         age: 30
        //     }
        // }
        // data写法2, 简写
        data(){
            return {
                name: 'test',
                age: 30
            }
        }
    });
    

    注意:

    • 由Vue管理的函数,不要写箭头函数,否则this将不再是Vue实例

    模板语法

    上面的Hello {{name}}就属于Vue模板语法中的插值语法,除了插值语法,还有指令语法

    1. 插值语法:用于解析标签体内容,写作{{xxx}}xxx是JS表达式,且能够直接读到data中的所有内容
    2. 指令语法:用于解析标签 (标签属性、标签体内容、绑定事件等) ,用法有v-bind:href="xxx",同样的,xxx是JS表达式

    一个使用v-bind:的例子,且能够简写为:

    
    
    

    数据绑定

    1. v-bind单向绑定:数据只能从data流向页面
    2. v-model双向绑定:数据不仅能从data流向页面,还能从页面流向data
      • 由于双向绑定一般应用在表单类元素上,例如,
        双向数据绑定:

MVVM模型

在Vue官方文档中,出现了下面这句话:

虽然没有完全遵循 MVVM 模型,但是 Vue 的设计也受到了它的启发。因此在文档中经常会使用 vm(ViewModel 的缩写) 这个变量名表示 Vue 实例。

  1. M模型 (Model) :对应data中的数据
  2. V视图 (View) :模板
  3. VM视图模型 (View Model) :Vue实例对象

打印一下VM,即Vue实例对象:

const vm = new Vue({
    el: '#root',
    data(){
        return {
            name: 'test'
        }
    }
});
console.log(vm);

从输出可以发现,name包含在Vue实例对象上,事实上,Vue模板等能读到Vue实例对象的所有属性和方法,包括隐式原型对象中的属性和方法,例如可以下面这样写:

Hello, {{name}}

{{$el}}

总结:

  1. data中的所有属性,最后都出现在了Vue实例上
  2. Vue实例的所有属性以及Vue原型的所有属性,Vue模板都可以直接使用

数据代理

回顾Object.defineproperty()

Object.defineProperty(obj, prop, descriptor)

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

备注:应当直接在 Object 构造器对象上调用此方法,而不是在任意一个 Object 类型的实例上调用。

  • 使用该方法添加的属性,默认不可枚举:
let person = {
    name: 'Lee',
    gender: 'male'
};
Object.defineProperty(person, 'age', {
    value: 30
});

console.log(person, Object.keys(person));
// {name: 'Lee', gender: 'male', age: 30}
// ['name', 'gender']
  • 一些默认值:
Object.defineProperty(person, 'age', {
    value: 30,
    // enumerable: true,  // 控制属性是否能枚举, 默认false
    // writable: true,  // 控制属性是否能被修改, 默认false
    // configurable: true  // 控制属性是否能被删除, 默认false
});

person.age = 1;
console.log(person);  // {name: 'Lee', gender: 'male', age: 30}

console.log(delete person.gender, delete person.age);  // true false
console.log(person);  // {name: 'Lee', age: 30}
  • 通过gettersetter,使得变量myAgeperson.age建立关联
let myAge = 25;
Object.defineProperty(person, 'age', {
    /* 使用Object.defineProperty() 定义对象属性的时候,
     *  如果设置了 set 或 get, 就不能设置 writable 和 value 中的任何一个 
     */
    // value: 30,
    // writable: true,

    // 当age属性被读取时调用get(), 返回值就是age的值
    // 这样做使得myAge被修改, person.age也被修改
    get() {
        console.log('age被读取');
        return myAge;
    },
    // 当age属性被修改时调用set(), value是被修改的值
    // 这样做使得person.age被修改, myAge也被修改
    set(value) {
        console.log('age被修改');
        myAge = value;
    }
});

person.age = 10;  // age被修改
console.log(person.age);  // age被读取 10
console.log(myAge);  // 10

myAge = 50;
console.log(person.age);  // age被读取 50

数据代理定义

通过一个对象代理另一个对象中属性的操作。上面的例子中,将变量myAge包装成一个对象,则就是一个最简单的数据代理。

Vue中的数据代理

通过vm对象来代理data对象中属性的操作,能够更好的操作data中的数据:

let myData = {
    name: 'Lee',
    age: 30
};
const vm = new Vue({
    el: '#root',
    data() {
        return myData;
    }
});
console.log(vm._data === myData);  // true
console.log(vm.name, vm.age);  // Lee 30
  1. 通过Object.defineProperty()来把data对象中的数据都添加到vm上,例如上面的vm.namevm.age
  2. vm.namevm.age中,都指定getter和setter,建立数据代理

于是,可以发现:

  • vm._data就是实例化Vue对象时传入的myData
  • 实例化时,vm根据vm._data对象,通过Object.defineProperty()来把vm._data对象中的每个数据添加到自身,于是对象有了vm.namevm.age属性
  • 最后,通过getter和setter建立数据代理,这样就不需要使用vm._data.name访问数据,而是直接使用vm.name

事件处理

一个简单的例子:

传参数:

  • 理论上,将方法写进data,程序也能正常运行,但是这样会让Vue实例对象将该方法也进行一次代理,而方法本身是不需要数据代理的,所以需要写入methods

总结:

  1. 使用v-on:xxx或者@xxx绑定事件,其中xxx是事件名
  2. methods配置的方法,不要写成箭头函数,否则this不再是vm或者组件的实例对象
  3. @click="show($event)"可以简写成@click="show"

事件修饰符

Vue提供的数据修饰符:

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

例如阻止的默认跳转行为:

百度

这样写等价于:

const vm = new Vue({
    el: '#root',
    methods: {
        show(event) {
            event.preventDefault();
        }
    },
});

按键事件

  1. Vue中常用的按键别名:

    • enter
    • delete (捕获删除和退格)
    • esc, space
    • tab,较为特殊,需要配合keydown,如果配合keyup,则松开Tab时,焦点已经移走,无法触发事件
    • up, down, left, right
  2. 系统修饰键ctrl, alt, shift, meta

    • 配合keyup使用:按下修饰键的同时,再按下其他键,则其他键释放后,才触发
    • 配合keydown使用:正常触发
  3. 对于Vue没有提供的按键,按照按键原始的key值绑定

  4. 可以使用keyCode绑定具体按键,但是不推荐

  5. Vue.config.keyCodes.自定义 = 键码,可以自定义按键别名

上面的写法等价于:

对于使用key值绑定的,需要改成小写,如果遇到多个单词,中间加下划线,例如CapsLockPageUp需要变为:



计算属性

姓名案例

首先,尝试使用插值语法和methods实现相同的功能,最后再使用计算属性,插值语法实现


{{surname.slice(0, 2)}}-{{givenName}}

使用插值语法可以发现,当出现一些需求时,例如上面的字符串切片{{surname.slice(0, 2)}},会导致其中的代码不易阅读,这违背了简单的计算属性的代码风格。

使用methods实现,只需要作出简单修改,首先添加方法:

methods: {
    fullName() {
    return this.surname + '-' + this.givenName;
    }
}

修改为:

{{fullName()}}

这种方法效率一般,因为data中的任何一个数据发生变化,Vue模板都会重新解析,从而引起fullName()方法的多次调用。

应用计算属性

最后,使用计算属性实现,首先为vm添加属性:

computed: {
    fullName: {  // 需要是一个对象
        // 当需要更新fullName时, get就会被调用, 且返回值作为fullName的值
        // 调用时机: 1. 第一次读fullName 2. 所依赖的数据发生变化
        get() {
        	return this.surname.slice(0, 3) + '-' + this.givenName;
        }
        // 当fullName被修改时调用set
        set(value) {
            const arr = value.split('-');
            this.surname = arr[0];
            this.givenName = arr[1];
        }
    }
}

修改为:

{{fullName}}

计算属性:

  1. 定义:计算属性需要通过已有的属性计算而来
  2. 原理:借助Object.defineproperty()方法提供的getter和setter
  3. 优势:内部缓存机制,效率更高,换句话来说,如果使用methods方法,则每次读取fullName都会调用一次函数,而计算属性fullName只会在值改变时运行get(),其余时间直接读取缓存
  4. 计算属性最终会出现在vm,可以直接读取
  5. 如果计算属性被修改,则必须设置set()函数去响应修改,否则报错

计算属性简写

当不考虑修改计算属性时,即不需要写set()方法时,可以将上面的计算属性简写为:

computed: {
    fullName() {
        return this.surname.slice(0, 3) + ' - ' + this.givenName;
    }
}

此时函数体内容即为上面get()方法的内容。

监视属性

watch用来监视属性:

watch: {
    fullName: {
        immediate: true,  // 初始化时是否调用handler, 默认为false
        handler(newValue, oldValue) {  // 当fullName发生改变时调用
        	console.log(newValue, oldValue);
        }
    }
}

上面的写法等价于:

vm.$watch('fullName', {
    immediate: true,
    handler(newValue, oldValue) {
        console.log(newValue, oldValue);
    }
});

需要注意:

  1. 当被监视的属性变化时,回调函数handler自动调用
  2. 监视的属性必须存在,才能被监视

深度监视

a的值{{nums.a}}
b的值{{nums.b}}
const vm = new Vue({
    el: '#root',
    data() {
        return {
            nums: {
                a: 1,
                b: 1
            }
        }
    },
    watch: {
        'nums.a': {  // 监视多级结构的某个属性
            handler() {
                console.log('Change a.');
            }
        },
        nums: {  // 监视多级结构中所有属性的变化
            deep: true,  // 是否开启深度监视
            handler() {
                console.log('Change nums.');
            }
        }
    }
});

当我们监视一个对象时,即使对象的属性值改变了,引用也没有变化,所以需要开启deep深度监视。

  1. Vue中的watch默认不监视对象内部值的改变(一层)
  2. watch中配置deep: true则可以检测对象内部值的改变(多层)

监视的简写

当不需要配置immediatedeep,只需要handler时,可以简写:

vm.$watch('fullName', function (newValue, oldValue) {
    console.log(newValue, oldValue);
});

或者:

watch: {
    'nums.a'() {
        console.log('Change a.');
    }
}

计算属性和监视属性的对比

计算属性和监视属性对比

函数写法

  1. 所有被Vue管理的函数,最好写成普通函数,这样this才指向vm或者组件实例对象
  2. 所有不被Vue管理的函数(定时器回调函数、ajax回调函数、Promise的回调函数等),最好写成箭头函数,这样this才指向vm或者组件实例对象

绑定样式

绑定class样式

方式1:字符串写法,通过v-bind动态指定样式

  • 适用于:class属性不确定,需要动态指定
const vm = new Vue({
    el: '#root',
    data() {
        return {
            myStyle: 'test1'
        }
    },
    methods: {
        change() {  // 随机切换样式
            styles = ['test1', 'test2', 'test3'];
            let index = Math.floor(Math.random() * styles.length);
            this.myStyle = styles[index];
        }
    },
});

方式2:数组写法

  • 适用于:要绑定的class个数不确定,名字也不确定
const vm = new Vue({
    el: '#root',
    data() {
        return {
            styles: ['test1', 'test2', 'test3']
        }
    }
});

通过方式2,

拥有了styles数组中的所有class,可以通过push()shift()等数组增删操作为
增加或删除class属性。

方式3:对象写法

  • 适用于:要绑定的class个数确定,名字确定,需要动态增删class属性
// 与方式2类似, 唯一的不同是styles是一个对象
data() {
    return {
        styles: {
            test1: true,
            test2: true,
            test3: false
        }
    }
}

于是可以通过修改对象的属性值来给

增加或删除class属性。

绑定style样式

绑定style样式,同样的可以使用对象写法或者数组写法

Hello
Hello
const vm = new Vue({
    el: '#root',
    data() {
        return {
            style1: {
                fontSize: '40px',  // 对应font-size
                color: 'red'
            }, 
            style2: {
                backgroundColor: 'skyblue'  // 对应backgroundColor
            }
        }
    }
});

条件渲染

  1. v-if
    • 特点:不展示的节点,DOM直接移除,所以适用于切换频率较低的场景
    • 注意:可以搭配v-else-ifv-else,要求结构连续,不能被打断
  2. v-show
    • 特点:不展示的节点等价于添加style="display: none;",所以适用于切换频率较高的场景

使用v-show的时候:

n的值是{{n}}
1111111
2222222
3333333

n的值为0时,网页文档实际上为:

n的值是0
1111111
2222222
3333333

而使用v-if的时候:


1111111
2222222
3333333
xxxxxxx

n的值为0时,网页文档实际上为:

n的值是0
xxxxxxx

如果想一次性控制多个标签,需要将他们都放入一个

容器,这样做的缺点是破坏文档原有的结构,于是可以使用