Vue 2到Vue3 整合(详细易懂)小白看这一篇就够了

目录

Vue安装(步骤和官网)

Vue是什么:

插值表达式 简介{{ }}

插值表达式是一种Vue的模块语法:

面试题:

Vue核心特性:响应式

​编辑

安装Vue开发者工具:装插件调试Vue应用(使用的是Google Chrome浏览器)

​编辑

二、基础知识(Vue指令)

2.1v-html:

Vue指令 v-show 与 v-if

v-show

v-if

Vue指令 v-else v-else-if

Vue指令v-on

Vue指令 v-on调用传参

Vue指令v-bind

v-bind 对于样式控制的增强 - 操作class

Vue指令 v-for 

v-for 中的 key

Vue指令 v-model

2.2单项数据绑定

双向数据绑定 V-model

事件绑定

 在事件对应的方法中获取到事件对象

2.3指令的修饰符

事件修饰符-阻止冒泡

 事件修饰符-阻止默认行为

一次事件

键盘事件修饰符

2.4vue中的数据代理

2.5计算属性computed

补充:

omputed 计算属性:

缓存特性(提升性能):

methods方法:

2.6监视属性watch

2.7 vue中class样式的动态绑定

2.8 vue中style样式的动态绑定

2.9 vue中列表过滤

2.10 vue中列表排序:

2.11 vue中数据更新的问题

三、生命周期

3.2 Vue的更新流程

3.3 Vue的销毁流程

3.4 回顾生命周期

四、组件开发

4.1 组件的使用

4.2 插槽slot

4.3 $refs和$parent

4.4 父子组件间的通信

4.5 非父子组件的通信

4.6 混入(mixin)

五、路由

5.1 路由原理(hash)

5.2 路由安装和使用(vue2)

5.3 路由跳转

5.4 路由的传参和取值

5.5 嵌套路由

5.6 路由守卫

六、状态管理Vuex

6.1 state状态

.2 mutations

6.3 actions

6.4 getters

6.5 modules

七、初识Vue3和项目搭建

八、Vue3常用Composition API

8.1 setup

8.2 实现数据的响应式-ref函数

8.3 实现数据的响应式-reactive函数

8.4 setup函数的运行时机和两个参数

8.5 setup中的计算属性—computed函数

8.6 setup中的监视属性—watch函数

8.7 了解setup中的watchEffect函数

九、Vue3中的生命周期和数据处理

9.1 vue3中的生命周期

9.2 Vue3中的toRef和toRefs函数

9.3 多层嵌套的组价间的通讯—provide和inject

结束语:


Vue安装(步骤和官网)

https://blog.csdn.net/2301_77638787/article/details/132003867?spm=1001.2014.3001.5502https://blog.csdn.net/2301_77638787/article/details/132003867?spm=1001.2014.3001.5502

Vue是什么:

  • M:Model(模型) 对应data的数据

  • V:View(视图) 模板==>页面

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

插值表达式 简介{{ }}

插值表达式是一种Vue的模块语法:

1.作用:利用表达式进行插值,将数据渲染到页面中

表达式:是可以被求值的代码,JS引擎会将其计算出一个结果

<div id="app">
    {{ msg }}
</div>

data:{
    msg:'Hello 你好'
}

面试题:

Data为啥要写成函数?

Vue 里面data属性之所以不能写成对象的格式,是因为对象是对地址的引用,而不是独立存在的。如果一个.vue 文件有多个子组件共同接收一个变量的话,改变其中一个子组件内此变量的值,会影响其他组件的这个变量的值。如果写成函数的话,那么他们有一个作用域的概念在里面,相互隔阂,不受影响。

2.语法:{{ 表达式 }}

<h3>{{ title }}</h3>
<p>{{ nickname.toUpperCase }}</p>
<p>{{ age >=18 ? '成年' : '未成年' }}</p>
<p>{{ obj.name }}</p>

3.注意点:

(1)使用的数据必须存在(data)

<p>{{ hobby }}</p> //错

2.支持的是表达式,而非语句,比如:if for...

<p>{{ if }}</p> //错

3.不能在标签属性中使用{{ }}插值

<p title="{{ username }}">我是p标签</p> //错

Vue核心特性:响应式

比如:数据的响应式处理→响应式:数据变化,视图自动更新

使用Vue开发→专注于业务核心逻辑即可

如何访问or修改?data中的数据,最终会被添加到实例上

①访问数据:"实例.属性名"

②修改数据:"实例.属性名"="值"

<body>

    <div id="app">
        {{ msg }}
    </div>

</body>
<script src="./JS/vue.js"></script>

<script>
    const app = new Vue({
        el:"#app",
        data:{
            //响应数据 → 数据变化了,视图自动更新
            msg:'你好,少年',
            count:100
        }
    })

    //data中的数据,是会别添加到实例上
    //1.访问数据  实例。属性名
    //2.修改数据   实例。属性名=新值
</script>

响应数据一旦变换了,视图自动更新(是通过浏览器控制台更改数据)

安装Vue开发者工具:装插件调试Vue应用(使用的是Google Chrome浏览器)

网址:极简插件_Chrome扩展插件商店_优质crx应用下载极简插件是一个优质Chrome插件扩展收录下载网站,收录热门好用的Chrome插件扩展,国内最方便的插件下载网站。https://chrome.zzzmh.cn/#/index

(1)通过谷歌应用商场安装

(2)极简插件:下载→开发者模式→拖拽安装→点击详情选择允许访问文件网址

(3)将下载的插件(crx)后缀拖拽到下面的空白处,进行安装

 

 

二、基础知识(Vue指令)

什么是指令? 在vue中提供一些对于页面+数据的更为方便的操作,这些操作就叫做指令。

譬如在HTML页面中这样使用

<div v-xxx=''></div>

2.1v-html:

作用:设置元素的innerHTML

语法:v-html="表达式"→动态设置元素innerHtml

<body>
    <div id="app">
        <div v-html="msg"></div>
    </div>
</body>
<script src="./JS/vue.js"></script>
<script>
    const app =new Vue({
        el:'#app',
        data:{
            msg:`
            <a href="https://www.baidu.com/">
            百度
            </a>
            `
        }
    })
</script>

运行结果:

Vue指令 v-show 与 v-if

v-show

1.作用:控制元素显示隐藏

2.语法:v-show="表达式" 表达式值true显示,false隐藏

3.原理:切换display:none控制显示隐藏

4.场景:频繁切换显示隐藏的场景

v-if

1.作用:控制元素显示隐藏

2.语法:v-if="表达式" 表达式值true显示,false隐藏

3.原理:基于条件判断式,是否创建或移除元素节点

4.场景:要么显示 要么隐藏 不频繁切换的场景

<body>
    
    <!--
        v-show底层原理: 切换 css  的 display:none 来控制显示隐藏
        v-if 底层原理: 根据 判断条件 控制元素的 创建 和 移除(条件渲染)
    -->
    
    <div id="app">
        <div v-show="flag" class="box">我是v-show盒子</div>
        <div v-if="flag" class="box">我是v-id盒子</div>
    </div>
</body>
<script src="./JS/vue.js"></script>

<script>
    const app =new Vue({
        el:"#app",
        data:{
            flag:true  //显示状态  改为false
        }
    })
</script>

Vue指令 v-else v-else-if

1.作用:辅助v-if 进行判断渲染

2.语法:v-else v-else-if="表达式"

3.注意:需要紧挨着v-if一起使用

<body>
    <div id="app">
        <p v-if="gender === 1">性别:男</p>
        <p v-else>性别:女</p>
        <hr>
        <p v-if="score>=90">成绩评定A:奖励电脑一台</p>
        <p v-else-if="score>=70">成绩评定B:奖励周末旅游</p>
        <p v-else-if="score>=60">成绩评定C:奖励零食礼包</p>
        <p v-else>成绩评定D:惩罚一周不能玩手机</p>
    </div>
</body>
<script src="./JS/vue.js"></script>

<script>
    const app = new Vue({
        el:'#app',
        data:{
            gender: 2,
            score:90
        }
    })
</script>

Vue指令v-on

1.作用:注册事件 = 添加监听 + 提供处理逻辑

2.语法:

①v-on:事件名="内联语句"

②v-on:事件名="methods中的函数名"

3.简写:@事件名

4.注意:menthods函数内的this指向Vue实例

<button v-on:click="count++">按钮</button>  <!-- 使用的是v-on:事件名="内联语句" -->
<button @click="count++">按钮</button>

<body>
    <div id="app">
        <button v-on:click="count--">-</button>
        <span>{{ count }}</span>
        <button v-on:click="count++">+</button>
    </div>
</body>
<script src="./JS/vue.js"></script>

<script>
    const app =new Vue({
        el:"#app",
        data:{
            count:100
        }
    })
</script>

运行结果(点击事件):  

<body>
    <div id="app">
        <button v-on:mouseenter="count--">-</button>
        <span>{{ count }}</span>
        <button v-on:mouseenter="count++">+</button>
    </div>
</body>
<script src="./JS/vue.js"></script>

<script>
    const app =new Vue({
        el:"#app",
        data:{
            count:100
        }
    })
</script>

运行结果(放在按钮上自动减加): 

 

使用v-on:事件名="methods中的函数名"

<body>
    <div id="app">
        <button @click="fn">切换隐藏</button>
        <h1 v-show="isShow">哎呦!不错哦</h1>
    </div>
</body>
<script src="./JS/vue.js"></script>

<script>
    const app =new Vue({
        el:'#app',
        data:{
            isShow:true
        },
        methods:{
            fn(){
                //console.log('执行了fn')
                app.isShow=!app.isShow
            }
        }
    })
</script>

运行结果: 

 换成this,任何变量名都可以隐藏

<body>
    <div id="app">
        <button @click="fn">切换隐藏</button>
        <h1 v-show="isShow">哎呦!不错哦</h1>
    </div>
</body>
<script src="./JS/vue.js"></script>

<script>
    const app4 =new Vue({
        el:'#app',
        data:{
            isShow:true
        },
        methods:{
            fn(){
                //console.log('执行了fn')
                this.isShow=!this.isShow
            }
        }
    })
</script>

Vue指令 v-on调用传参

Vue指令v-bind

1.作用:动态的设置html的标签属性→src url title ....

2.语法:v-bind:属性名="表达式"

3.注意:简写形式:属性名="表达式"

  <div id="app">
        <!-- v-bind:src   可以把v-bind去掉->  :src -->
        <img v-bind:src="imgUrl" v-bind:title="msg" alt="">
        <img :src="imgUrl" v-bind:title="msg" alt="">
    </div>
</head>
<body>
    
</body>
<script src="./JS/vue.js"></script>

<script>
    const app =new Vue({
        el:'#app',
        data:{
            imgUrl:'./imgs/24617735c50ecbb974f05e4a4971190.png',
            msg:'hello 五'
        }
    })
</script>

运行结果: 

v-bind 对于样式控制的增强 - 操作class

语法 :class="对象/数组"

①对象 → 键就是类名,值是布尔值。如果值为true,有这个类,否则没有这个类

<div class="box" :class"{类名1:布尔值,类名2: 布尔值}"></div>

适用场景:一个类名,来回切换

②数组 → 数组中所有的类,都会添加到盒子上,本质就是一个 class 列表

<div class="box" :class="[类名1,类名2,类名3]"></div>   :class="['pink','big']"

Vue指令 v-for 

1.作用:基于数据循环,多次渲染整个元素 → (数组)、对象、数字..

 2.遍历数组语法:

v-for="(item.index) in 数组"
v-for 中的 key

语法:key属性="唯一标识"

作用:给列表项添加的唯一标识。便于Vue进行列表项的正确排序复用。

注意点:

1.key的值只能是 字符串 或 数组类型

2.key的值必须具有 唯一性

3.推荐使用id作为key (唯一),不推荐使用index作为key(会变化,不对应)

<li v-for="(item.index) int xxx" :key="唯一值"></li>

Vue指令 v-model

1.作用:给表单 使用,双向数据绑定 → 可以快速 获取 或 设置 表单元素内容

①数据变化 → 视图自动更新

②视图变化 → 数据自动更新

2.语法v-model='变量'

data: {
	username'',
	password:''
},

v-model应用于其他表单元素

常见的表单元素都可以用v-model 绑定关联 → 快速 获取 或 设置 表单元素的值

它会根据 控制类型 自动选取 正确的方法 来更新元素

输入框 input:text →value
文本域 textarea →value
复选框 input:checkbox →checked
单选 input:radio →checked
下拉菜单 select →value

2.2单项数据绑定

vue单向数据流绑定属性值 v-bind: (属性) 简写 :(属性) 例子:

<input v-bind:value="name" v-bind:class="name">      

单向数据绑定:内存改变影响页面改变 内存 》页面

v-bind就是对属性的简单赋值,当内存中值改变,还是会触发重新渲染

双向数据绑定 V-model

1.作用:给表单 使用,双向数据绑定 → 可以快速 获取 或 设置 表单元素内容

①数据变化 → 视图自动更新

②视图变化 → 数据自动更新2.语法v-model='变量'

data: {
	username'',
	password:''
},

vue双向数据流 v-model,只作用于有value属性的元素

例子:

<input v-model="name" v-bind:class="name"> 

内存 《》页面  

双向数据绑定 页面对于input的value改变,能影响内存中name变量 

 内存js改变name的值,会影响页面重新渲染最新值

事件绑定

事件绑定v-on:事件名="表达式||函数名" 简写 @事件名="表达式||函数名"

事件名可以是原生也可以是自定义的。注意函数的定义也要在Vue中,采用methods属性

 在事件对应的方法中获取到事件对象

不加括号

<button @click="fun1" >点击获取事件对象</button>
fun1(event){
	console.log(event.target);
}

加括号,加括号一般是需要有额外参数的情况。

<button @click="fun2($event,'其他参数')" >点击获取事件对象(带括号)</button>
fun2(evnet, arg){
    console.log(event.target,arg);
}

2.3指令的修饰符

事件修饰符-阻止冒泡

冒泡发生的情景:子元素和父元素绑定了相同的事件,然后点击了子元素,父元素也触发了该事件

--使用原生js阻止冒泡  

<div @click="fun3">
    外层div
    <div @click="fun3">里层div</div>
</div>
fun3(event){
    console.log(event.target);
    event.stopPropagation(); // 使用原生js阻止冒泡
},

--使用vue事件的修饰符阻止冒泡

<div @click="fun4">
    外层div
    <div @click.stop="fun4">里层div</div>
</div>

fun4(event){
    console.log(event.target);
}

 事件修饰符-阻止默认行为

有些标签是由默认行为的,比如a标签,有个默认的页面跳转。

--使用原生js阻止默认行为

<a href="http://www.baidu.com" @click="fun5">百度</a>
fun5(event){
    console.log(event.target);
    event.preventDefault(); // 使用原生js阻止默认行为
},

--使用vue的事件修饰符阻止默认行为

<a href="http://www.baidu.com" @click.prevent="fun6">百度</a>
fun6(event){
    console.log(event.target);
},

一次事件

此事件只会执行一次,第二次点击无效

<div @click.once="fun7">一次事件</div>

键盘事件修饰符

键盘事件修饰符,主要筛选输入特定字符才触发。

<!-- 13表示是输入enter,起的keycode值可以查询 -->
<input type="text" @keyup.13="change">
change(event){
    console.log(event.key,event.keyCode);
}

事件的完整示例代码如下:

<!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>vue2</title>
</head>

<body>
    <div id="app">
        <h2>{{message}}</h2>
        <button @click="updateMssage" >点击</button>
        <button @click="fun1" >点击获取事件对象</button>
        <button @click="fun2($event,'其他参数')" >点击获取事件对象(带括号)</button>
        <div @click="fun3">
            外层div
            <div @click="fun3">里层div</div>
        </div>
        <hr>
        <div @click="fun4">
            外层div
            <div @click.stop="fun4">里层div</div>
        </div>
        <hr>
        <a href="http://www.baidu.com" @click="fun5">百度</a>
        <a href="http://www.baidu.com" @click.prevent="fun6">百度</a>
        <div @click.once="fun7">一次事件</div>
        <hr>
        <!-- 13表示是输入enter,起的keycode值可以查询 -->
        <input type="text" @keyup.13="change">
    </div>
</body>
<script src="./js/vue2.js"></script>
<script>
    var app = new Vue({
        el: '#app', // 用于指定当前vue实例为哪个容器使用,值为css选择器字符串
        data() {
            return {
                message:'消息'
            }
        },
        methods:{
            updateMssage(){
                this.message="修改了消息"
            },
            fun1(event){
                console.log(event.target);
            },
            fun2(evnet, arg){
                console.log(event.target,arg);
            },
            fun3(event){
                console.log(event.target);
                event.stopPropagation(); // 使用原生js阻止冒泡
            },
            fun4(event){
                console.log(event.target);
            },
            fun5(event){
                console.log(event.target);
                event.preventDefault(); // 使用原生js阻止默认行为
            },
            fun6(event){
                console.log(event.target);
            },
            fun7(event){
                console.log(event.target);
            },
            change(event){
                console.log(event.key,event.keyCode);
            }
        }
    });
</script>

</html>

2.4vue中的数据代理

vue2中的数据代理原理使用的是Object.defineProperty() 。

通过一个对象代理另一个对象属性的读写,具体语法如下:

Object.defineProperty('目标对象', '代理属性', {
  get() {    // getter   当读取’目标对象‘的’代理属性‘时,get函数/getter就会被调用,且返回代理属性的值
    return xxx;       
  },
  set(value) {	// setter   当修改’目标对象‘的’代理属性‘时,set函数/setter就会被调用,且收到修改的值
    xxx;
  },
});

具体示例如下:

let kobe = {
        money1:1000,
        address:'洛杉矶'
    }
    let jingjiren = {
        money2:1500
    }
    Object.defineProperty(kobe,'money1',{
        get(){
            console.log("kobe的money1被访问了");
            return jingjiren.money2
        },
        set(value){
            console.log("kobe的money1被修改了");
            jingjiren.money2 = value
        }
    })

Vue中应用的数据代理

vm{
    ...
    _data{
        name:'我的名称'
    },
    ...
    name:'我的名字',
    get name方法
    set name方法
}
  • 通过vm对象代理 data/_data 中属性的读写

  • 能更加方便的读写vue中data的数据

  • 通过Object.defineProperty()把 data 中的属性添加到vm对象上,每个属性都有setter/getter

2.5计算属性computed

计算属性,方法名可以直接在对象中使用.这个属性是通过计算得出的。 这个方法中的任意属性改变,都会触发这个方法 使用场景:希望把一些计算的业务逻辑放在方法中,例如:全名计算、地址计算、购物车合计

下面的示例,我们使用了三种方式:

  • 使用方法实现

  • 使用vue的computed读写方式实现

  • 使用vue的computed只读方式实现

注意:推荐使用computed的方式。有缓存机制。在页面重复调用多次的情况下,只执行一次

<!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>vue2</title>
</head>

<body>
    <div id="app">
        姓:<input type="text" v-model="firstName"> <br>
        名:<input type="text" v-model="lastName"> <br>
        全名:<input type="text" v-model="getFullName()"><br>
        全名:<input type="text" v-model="fullName"><br>
        全名:<input type="text" v-model="fullNameReadOnly"><br>
    </div>
</body>
<script src="./js/vue2.js"></script>
<script>
    var app = new Vue({
        el: '#app', // 用于指定当前vue实例为哪个容器使用,值为css选择器字符串
        data() {
            return {
                firstName:'张',
                lastName:'三',
            }
        },
        methods:{
            getFullName(){
                return this.firstName+"-"+this.lastName
            }
        },
        computed:{
            fullName:{
                // 读
                get(){
                    return this.firstName+"-"+this.lastName 
                },
                // 写
                set(value){
                    this.firstName = value.split("-")[0]
                    this.lastName = value.split("-")[1]
                }
            },
            // 只读方式的简写
            fullNameReadOnly(){
                return this.firstName+"-"+this.lastName 
            }
        }
    });
</script>

</html>

补充:

computed计算属性 和 methods方法(容易搞混)

omputed 计算属性:

作用:封装一段对于数据的处理,求得一个结果

语法:

①写在computed 配置项中

②作为属性,直接使用 → this.计算属性 {{ 计算属性 }}

缓存特性(提升性能)

计算属性会对计算的结果缓存,再次使用直接读取缓存,依赖项变化了,会自动重新计算 → 并再次缓存

methods方法:

作用:给实例提供一个方法,调用以处理业务逻辑

语法:

①写在 methods 配置项中

②作为方法,需要调用 → this.方法名() {{ 方法名() }} @事件名="方法名"

2.6监视属性watch

作用:监视数据变化,执行一些 业务逻辑 或 异步操作。

语法:

①简单写法 → 简单类型数据,直接监视

data:{
	words:'苹果',
	obj:{
		words:'苹果'
	}
},

watch:{
	//该方法会在数据变化时,触发执行
	数据类型名(newValue, oldValue) {
	 一些业务逻辑  或 异步操作。
	}
	对象属性。属性名(newValue,oldValue) {
	一些业务逻辑  或 异步操作。
	}
}

②完整写法 → 添加额外配置项

(1)deep:true 对复杂类型深度监视

  (2) immediate:true 初始话立即执行一次handler方法

data:{
 obj:{
 	words:'苹果',
 	lang:'italy'
 },
},

//深度监听可以用来监听整个对象的改变,但要慎重使用,因为比较消耗性能
watch:{// watch 完整写法
	数据属性名:{
		deep:true,//深度监听
		handler (newValue){
		 console.log(newVelue)
		}
	}
}

2.7 vue中class样式的动态绑定

字符串写法

  • 使用场景

    • 样式的类型不确定

  • 写法

<div :class="myclass">你好</div>
  • 手动触发样式改变

  • 注意

    字符串使用的是vue实例data中的已有属性

  • 完整示例

    <!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>vue2</title>
        <style>
            .class1{
                font-size: 24px;
            }
            .class2{
                color: red;
            }
        </style>
    </head>
    
    <body>
        <div id="app">
            <div :class="myclass" >
                你好
            </div>
        </div>
    </body>
    <script src="./js/vue2.js"></script>
    <script>
        var app = new Vue({
            el: '#app', // 用于指定当前vue实例为哪个容器使用,值为css选择器字符串
            data() {
                return {
                    myclass:'class1 class2'
                }
            },
            
        });
    </script>
    
    </html>

    运行结果:

  • 对象写法

  • 使用场景

    • 样式个数、类名确定,通过Boolean动态展示与否

  • 对象写在内联样式

    .class1{
        font-size: 24px;
    }
    .class2{
        color: red;
    }
    .class3{
        border: 1px blue solid;
    }
    <div :class="{class1:class1,class2:class2}">
        对象-你好
    </div>
    data() {
        return {
            class1:true,
            class2:false
        }
    },

    对象写在data中

    <div :class="classObject">
        对象2-你好
    </div>
    data() {
        return {
            classObject:{
                class1:'class1',
                class2:'class2'
        	}
        }
    },

    数组写法

  • 使用场景

    • 需要绑定的样式个数不确定,类名也不确定

  • 内联写法

     <div :class="[class1,class2]">数组2-你好</div>

    数组里加三元表达式

    <div :class="[isActive?class1:'',class2]">数组2-你好</div>

    写在data中

    <div :class="classList">
        数组2-你好
    </div>
    data:{
    	classList:['class1', 'class2']
    }

    2.8 vue中style样式的动态绑定

    <!-- 一个动态值,一个固定值 -->
    <div :style="{fontSize:val1,color:'red'}">
        你好
    </div>

    2.9 vue中列表过滤

    <!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>vue2</title>
    </head>
    
    <body>
        <div id="app">
            <input type="text" v-model="searchInput">
            <ul>
                <li v-for="(item,index) in searchList" :key="index">
                    {{item.name}}--{{item.price}}
                </li>
            </ul>
        </div>
    </body>
    <script src="./js/vue2.js"></script>
    <script>
        var app = new Vue({
            el: '#app',
            data() {
                return {
                    searchInput:'',
                    goodsList:[
                        { name: '牛仔裤', price: '88元' },
                        { name: '运动裤', price: '67元' },
                        { name: '羽绒服', price: '128元' },
                        { name: '运动服', price: '100元' },
                    ]
                }
            },
            computed:{
                searchList(){
                    let list = this.goodsList.filter((item)=>{
                        return item.name.indexOf(this.searchInput)!==-1
                    })
                    return list;
                }
            }
        });
    </script>
    
    </html>
  •  

2.10 vue中列表排序:

<!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>vue2</title>
</head>

<body>
    <div id="app">
        <input type="text" v-model="inputValue" />
        <button @click="keyWord=1">升序</button>
        <button @click="keyWord=2">降序</button>
        <button @click="keyWord=0">原顺序</button>
        <ul>
          <li v-for="item in newList">{{item.name}}-{{item.price}}</li>
        </ul>
    </div>
</body>
<script src="./js/vue2.js"></script>
<script>
    var app = new Vue({
        el: '#app',
        data: {
            keyWord: 0,
            inputValue: '',
            list: [
            { name: '牛仔裤', price: 88 },
            { name: '运动裤', price: 67 },
            { name: '羽绒服', price: 128 },
            { name: '运动服', price: 100 },
            ],
        },
        computed: {
            newList() {
            const arr1 = this.list.filter((i) => {
                return i.name.indexOf(this.inputValue) !== -1;
            });
            if (this.keyWord) {
                arr1.sort((a1, a2) => {
                return this.keyWord === 1
                    ? a1.price - a2.price
                    : a2.price - a1.price;
                });
            }
            return arr1;
            },
        },
    });
</script>

</html>

2.11 vue中数据更新的问题

对象新增数据更新问题

  • 描述

    • 通过普通对象添加属性方法,Vue不能监测到且不是响应式

this.obj.name= '张三'

解决

  • Vue.set() / this.$set

this.$set(this.obj,'name','张三')

注意:

this.$set不能给vue实例的根数据对象添加属性

数组数据更新问题

  • 描述

    • 直接通过数组索引值改变数组的数据,Vue监测不到改变

    • 实际在 js 内存已经把数据的第一项数据修改了

this.list[0] = { name: '李四',age: 20 };
  • 原因

    • 因为在vue中数组并没有跟对象一样封装有监测数据变化的getter、setter

  • 解决

    • Vue在数组的原始操作方法上包裹了重新解析模板的方法,

      也就是说我们在data里面的数组操作方法不是原生的,是vue封装过的

    • 哪些数组操作方法经过了封装?

push() 向数组的末尾添加一个或更多元素,并返回新的长度。
pop() 删除数组的最后一个元素并返回删除的元素
shift()  删除并返回数组的第一个元素
unshift()  向数组的开头添加一个或更多元素,并返回新的长度。
splice()  从数组中添加或删除元素。
sort()   对数组的元素进行排序
reverse() 反转数组的元素顺序。

三、生命周期

Vue生命周期:一个Vue实例从 创建 到 销毁 的整个过程。

生命周四个阶段:①创建 ②挂载 ③更新 ④销毁

Vue生命周期过程中,会自动运行一些函数,被称为 [生命周期钩子] → 让开发者可以在 [特定阶段 ] 运行自己的代码。

  • beforeCreate阶段

    • vue中data的数据和methods的方法还不能使用

  • created阶段(重点学习)

    • vue中data的数据和methods的方法已可以使用

  • beforeMount阶段

    • 页面可以展示内容,但是是未编译,最终都是不能操作的DOM结构,展示时间短

  • mounted(重点学习)

    • 页面显示编译后的DOM

    • vue的初始化过程结束

    • 此阶段可进行:定时器、网络请求、订阅消息、绑定事件等

  • beforeDestroy

    释放Vue意外的资源清除定时器,延时器...

<body>
    <div id="app">
        <h3>{{ title }}</h3>
        <div>
            <button @click="count--">-</button>
            <span>{{ count }}</span>
            <button @click="count++">+</button>
        </div>
    </div>
</body>
<script src="./JS/vue.js"></script>
<script>
    const app =new Vue({
        el:'#app',
        data:{
            count:100,
            title:'计算器'
        },
        //1.创建阶段(准备工作)
        beforeCreate() {
            console.log('beforeCreate  响应式数据准备之前',this.count)
        },
        created() {
            console.log('created  响应式数据准备之后',this.count)
            //this.数据名 = 请求回来的数据
            //可以开始发送初始话渲染的请求
        },


        //2.挂载阶段(渲染模版)
        beforeMount() {
            console.log('beforeMount  渲染模版之前',document.querySelector('h3').innerHTML)
        },
        mounted() {
            console.log('mounted  渲染模版之后',document.querySelector('h3').innerHTML)
            //可以开始操作dom了
        },

        //3.更新阶段(修改数据  → 更新视图)
        beforeUpdate() {
            console.log('beforeUpdate 数据修改视图还没跟新',document.querySelector('span').innerHTML)
        },
        updated() {
            console.log('beforeUpdate 数据修改视图数据也更新',document.querySelector('span').innerHTML)
        },


        //4.卸载阶段

        beforeDestroy() {
            console.log('beforeDestroy,卸载前')
            console.log('清除一些Vue以外的资源占用,定时器,延时器...')
        },
        destroyed() {
            console.log('destroyed,卸载后')
        },
    })
</script>

3.2 Vue的更新流程

  • beforeUpdate

    • 数据是新的,页面还没有更新

  • Updated

    • 数据是新的,页面同步更新

3.3 Vue的销毁流程

  • beforeDestroy

    • 此阶段可关闭定时器和取消订阅

    • 数据、方法可以访问但是不触发更新

  • destroy

    • 一切都结束了

3.4 回顾生命周期

  • 生命周期函数

    • 创建前、创建后(beforeCreate、created)

    • 挂载前、挂载后(beforeMount、mounted)

    • 更新前、更新后(beforeUpdate、updated)

    • 销毁前、销毁后(beforeDestroy、destroyed)

  • 常用的生命周期函数

    • mounted

      • 开启定时器

      • 发送ajax请求

      • 订阅消息

      • 绑定自定义事件

    • beforeDestroy

      • 清除定时器

      • 取消订阅、事件监听

      • 解绑自定义事件

  • vue实例销毁

    • vue开发者工具的数据为空

    • 销毁后自定义事件失效

    • 不要在beforeDestroy进行数据的操作,不会再走更新流程

四、组件开发

4.1 组件的使用

  • 创建组件的两种方式

var Header = { 
    template:'模板' , 
    data是一个函数,
    methods:功能,
    components:子组件们 
}//局部声明

Vue.component('组件名',组件对象);//全局注册 等于注册加声明了

  • 组件的分类

    • 通用组件(例如表单、弹窗、布局类等) (多个项目都可以复用)

    • 业务组件(抽奖、机器分类)(本项目中复用)

    • 页面组件(单页面开发程序的每个页面的都是一个组件、只完成功能、不复用)

  • 组件开发三部曲:声明、注册、使用

注意:子组件的命名,如果有驼峰命名,在使用子组件标签时用“-”隔开

<!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>vue2</title>
</head>

<body>
    组件的使用:组件是开发种最常用的方式。
    <div id="app01"></div>
</body>
<script src="./js/vue2.js"></script>
<script>
    // 使用语法糖来声明局部组件
    var Myheader = {
        template:`
            <div>我是头部</div>
        `
    }
    // 使用完整形式来声明局部组件
    var Mybody = Vue.extend({
        template:`
            <div>我是身体</div>
        `
    });
    // 全局组件的声明和注册是放在一起的,第一个参数是组件名字,第二个参数是组件内容
    Vue.component('myfoot',{
        template:`
            <div>我是尾部</div>
        `
    })
    new Vue({
        el:'#app01',
        // 在模板种使用标签来使用组件
        template:`
            <div>
                <Myheader></Myheader>
                <my-body></my-body>
                <myfoot></myfoot>
            </div>
        `,
        // 使用components 来注册局部组件,
        components:{
            Myheader,
            Mybody
        }
    });
</script>

</html>

4.2 插槽slot

slot就是在声明子组件时给DOM留下的坑位,以便于父组件在使用子组件的时候可以在坑位里动态的插入自己的内容。

并且,坑位是可以命名的,也就是说,子组件在声明的时候命名坑位,方便父组件在指定的坑位中插入内容

slot是动态的DOM

  • 插槽的使用:

    • 步骤有两步:a.子组件上留坑。b.父组件使用子组件的时候,给坑里赋值.

    • 要有父子组件作为前提。

    • 目的是让子组件成为动态的组件。

  • 匿名插槽

    匿名插槽就是在声明的时候没有声明name,会把全部内容都显示

<!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>vue2</title>
</head>

<body>
    插槽
    <div id="app01">
        <Myheader>
            <div>头部内容1</div>
            <div>头部内容2</div>
        </Myheader>
    </div>
</body>
<script src="./js/vue2.js"></script>
<script>
    // 使用语法糖来声明局部组件
    var Myheader = {
        template:`
            <div>
                我是头部
                <slot></slot>
            </div>
        `
    }
    new Vue({
        el:'#app01',
        // 使用components 来注册局部组件,
        components:{
            Myheader
        }
    });
</script>

</html>
  • 具名插槽

    具名插槽会在声明时,指定name。会在子组件中有选择的进行展示

<!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>vue2</title>
</head>

<body>
    插槽
    <div id="app01">
        <Myheader>
            <div>头部内容1</div>
            <div>头部内容2</div>
        </Myheader>
        <hr>
        <my-body>
            <div slot="slot1">身体内容1,旧语法,2.6.0后废弃</div>
            <template #slot2>
                <div>
                    身体内容2,新语法,缩写
                </div>
            </template>
            <template v-slot:slot3>
                身体内容3,新语法
            </template>
        </my-body>
    </div>
</body>
<script src="./js/vue2.js"></script>
<script>
    // 使用语法糖来声明局部组件
    var Myheader = {
        template:`
            <div>
                我是头部
                <slot></slot>
            </div>
        `
    }
    // 使用完整形式来声明局部组件
    var MyBody = Vue.extend({
        template:`
            <div>
                <slot name="slot1"></slot>
                我是身体
                <slot name="slot2"></slot>
                <slot name="slot3"></slot>
            </div>
        `
    });
    
    new Vue({
        el:'#app01',
        // 使用components 来注册局部组件,
        components:{
            Myheader,
            MyBody
        }
    });
</script>

</html>

4.3 $refs和$parent

这两个属性的作用是获取到子组件实例数组和父组件实例。

有了实例,就可以很方便的操作组件的属性和方法。

  • $parent

vm.$parent

$refs

$refs的使用需要,在子元素上通过ref属性声明自己的引用名称

vm.$refs

4.4 父子组件间的通信

  • 父传子 :父传子的时候,通过属性传递

    • 在子组件标签中,自定义属性和值

<Myheader ref="header" age="18" :sex="sex"></Myheader>

在子组件内部,通过props属性,获取所有的值

var Myheader = {
    template:`
        <div>
            我是头部{{age}},{{sex}}
            <slot></slot>
        </div>
    `,
    props:['age','sex']
}

子传父

子组件给父组件传递数据,采用的是$emit,自定义事件的方式完成的。

  • 子组件触发自定义事件

   var Myheader = {
        template:`
            <div>
                <button @click="func1">给父组件传值</button>
            </div>
        `,
        methods:{
            func1(){
                // 子组件触发自定义事件toParentEvent,同时传递数据
                this.$emit('to-parent-event','数据1');
            }
        }
    }

父组件监听自定义事件

<Myheader @to-parent-event="func1" ref="header" age="18" :sex="sex"></Myheader>
methods:{
    func1(val){
        // 拿到数据
        console.log(val);
    }
}

4.5 非父子组件的通信

  • 创建一个公共组件

Vue.prototype.$middleBus = new Vue();

发送方,在公共组件上,触发一个事件

this.$middleBus.$emit('sendMsg','你好child01,我是child02');

接收方,监听公共组件上的这个事件,并接受数据

this.$middleBus.$on('sendMsg',val=>{
     // 使用箭头函数,可以不改变this的指向,仍然和外部的this保持一致,指向child01
     this.msg = val;
});

4.6 混入(mixin)

  • 定义

    • 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项

  • 写法

    • 局部混入

      • 定义个混入对象


var myMixin = {
  data() {
    return {
      mixinname: '混入姓名',
    };
  },
  mounted() {
    console.log('我是混入的组件');
  },
};

引用使用

mixins: [myMixin],

全局混入

  • 定义个混入对象

  • 引入使用

Vue.mixin(myMixin);

五、路由

5.1 路由原理(hash)

单页应用的路由模式有两种

1、哈希模式(利用hashchange 事件监听 url的hash 的改变)

2、history模式(使用此模式需要后台配合把接口都打到我们打包后的index.html上)

hash模式的原理:

核心是锚点值的改变,我们监听到锚点值改变了就去局部改变页面数据,不做跳转。跟传统开发模式url改变后 立刻发起请求,响应整个页面,渲染整个页面比路由的跳转用户体验更好

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>单页面跳转的原理</title>
    </head>
    <body>
        <a href="#/login">登录</a>
        |
        <a href="#/register">注册</a>
        <div id="app"></div>
​
        <script type="text/javascript">
            var appDom = document.getElementById("app");
            window.addEventListener("hashchange",function(){
                console.log(location.hash);
                switch(location.hash){
                    case '#/login':
                    appDom.innerHTML = "登录";
                    break;
                    case '#/register':
                    appDom.innerHTML="注册";
                    break;
                }
            })
        </script>
    </body>
</html>

5.2 路由安装和使用(vue2)

  • 导入路由插件

    <script src="js/vue-router.js" type="text/javascript" charset="utf-8"></script>

  • 安装路由插件到Vue中

    Vue.use(VueRouter);

  • 创建VueRouter对象

    var Login = Vue.extends({
        template:`
            <div>
            我是登录页面
            </div>
        `
    });
    // 创建VueRouter对象,并配置路由
    var myRouter = new VueRouter({
        // 配置路由
        routes:[
            // 指定路由链接、路由名称、路由页面(组件)
            {path:'/login',name:'login',component:Login}
        ]
    });

  • 使用路由

    var myvue = new Vue({
        el:'#app',
        // 引入到vue 实例中,并在模板中使用<router-view>
        router:myRouter,
        template:`
            <div>
                头部
                <router-view></router-view>
                尾部
            </div>
        `
    })

5.3 路由跳转

路由的跳转有两种方式:

  • 使用标签

<router-link to='/login'></router-link>
  • 编程式路由,使用js

this.$router.push({path:'/login'});
this.$router.replace({path:'/login'});

说明:

1.this.$router.push(); 会向history中添加记录

2.this.$router.replace();不会向history中添加记录。

3.this.$router.go(-1)常用来做返回上一个地址。

路由中的对象:

1.this.$route 路由信息对象,只读。

2.this.$router 路由操作对象,只写。

5.4 路由的传参和取值

  • 查询参

    • 配置。查询参可以和path属性匹配,也可以和name属性匹配。

      <router-link :to="{path:'/login',query:{id:queryid}}"></router-link>

      或者

      <router-link :to="{name:'login',query:{id:queryid}}"></router-link>

      或者

      this.$router.push({path:'/login',query:{id:this.queryid}});

    • 取参

      // 此代码可以写到子组件的钩子函数中
      this.$route.query.id

  • 路由参

    • 配置路由规则

      var router = new VueRouter({
          routers:[
              // 需要在配置路由规则时,使用冒号指定参数
              {name:'login',path:'/login/:id',component:LoginVue}
          ]
      });

    • 配置。意:在这里path和params两个参数不能同时使用

      <router-link :to="{name:'login',params:{id:paramId}}"></router-link>

      或者

      this.$router.push({name:'login',params:{id:this.paramId}});

    • 取参

      this.$route.params.id;

注意:相同路由,但参数不同。造成页面不刷新的问题。

<router-view :key="$route.fullPath"></router-view>

5.5 嵌套路由

1.路由间有层级关系。他们在模板中也有嵌套关系。

2.可以一次性配置多个路由。

   
 var Nav = {
        template:`
            <div>
                <router-link :to="{name:'nav.index'}">首页</router-link>
                <router-link :to="{name:'nav.personal'}">个人中心</router-link>
                <router-link :to="{name:'nav.message'}">消息</router-link>
                <router-view></router-view>
            </div>
        `
    };
    var Index = {
        template:`
            <div>
                首页
            </div>
        `
    }
    var Personal = {
        template:`
            <div>
                个人中心
            </div>
        `
    }
    var Message = {
        template:`
            <div>
                消息
            </div>
        `
    }
         var router = new VueRouter({
            routes:[
                {
                    path:'/nav',
                    name:'nav',
                    component:Nav,
                    children:[
                        {path:'',redirect:'/nav/index'},
                        {path:'index',name:'nav.index',component:Index},
                        {path:'personal',name:'nav.personal',component:Personal},
                        {path:'message',name:'nav.message',component:Message}
                    ]
                },
                {
                    path:'',
                    redirect:'/nav'
                }
            ]
        });
var app01 = new Vue({
    el:'#app',
    template:`
        <div>
            <router-view></router-view>
        </div>
    `,
    router
});

5.6 路由守卫

可以做验证判断

使用路由的钩子函数beforeEach实现

   let app = new Vue({
        el: '#app01',
        router:myRouter,
        mounted() {
            this.$router.beforeEach((to,from,next)=>{
                console.log(to);
                if(to.path=='/nav/index'){
                    // 跳转到目标路由
                    next();
                }else{
                    setTimeout(function(){
                        next();
                    },2000);
                }
            });
        }
    });

六、状态管理Vuex

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

Vuex的核心:state、mutations、actions

  • state:存储公共的一些数据

  • mutations:定义一些方法来修改state中的数据,数据怎么改变

  • actions: 使用异步的方式来触发mutations中的方法进行提交。

此部分的代码我们在vue-cli中使用

6.1 state状态

声明

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    count:0
  },
  mutations: {

  },
  actions: {

  }
})

使用

  • 直接在模板中使用

<template>
  <div>
      vuex中的count:{{$store.state.count}}
  </div>
</template>

在方法中使用

methods:{
    func1(){
        console.log(this.$store.state.count);
    }
}

在计算属性中使用

当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性,让你少按几次键。

// 在组件中导入mapState函数。mapState函数返回的是一个对象
import {mapState} from 'vuex'
// 在组件的计算属性中使用mapState
computed:mapState({
    count:'count', // 直接使用
    mycount(state){
        // 计算后使用。initcount是本地的一个属性
        return state.count + this.initcount
    }
})

使用展开运算符

在之前的示例中,不方便和本地的计算属性混合使用,下面我们使用展开运算符,这样就可以和本地的计算属性一起使用。

 computed:{
        localCount(){
            return this.initcount-1;
        },
        ...mapState({
            count:'count',
            mycount(state){
                return state.count + this.initcount
            }
        })
    }

.2 mutations

state中的数据是只读的,不能直接进行修改。想要修改state中数据的唯一途径就是调用mutation方法。

使用commit()函数,调用mutation函数。

注意:mutation中只能执行同步方法。

  • 在mutations中定义方法,在方法中修改state

  mutations: {
    // state是状态,num是额外的参数
    add(state, num){
      state.count = state.count + num
    }
  },

直接调用

    methods:{
        func2(){
            this.$store.commit('add',2);
        }
    },

使用辅助函数(mapMutations)简化

import {mapMutations} from 'vuex'
  methods:{
        func1(){
            console.log(this.$store.state.count);
        },
        func2(){
            this.$store.commit('add',2);
        },
        ...mapMutations({
            addFunc:'add',  // 将 `this.addFunc()` 映射为 `this.$store.commit('add')`
            reduceFunc:'reduce'
        })
    },

如果暴露的方法和mutations中的方法名一致,可以使用数组的方式

methods:{
        func1(){
            console.log(this.$store.state.count);
        },
        func2(){
            this.$store.commit('add',2);
        },
        ...mapMutations([
            'add',  // 将 `this.add()` 映射为 `this.$store.commit('add')`
            'reduce'
        ])
    },

6.3 actions

  • actions中执行的方法可以是异步的

  • actions中要修改状态state中的内容,需要通过mutation。

  • 在组件中调用action需要调用dispatch()函数。

步骤如下:

  • 声明action方法

  actions: {
    delayAdd(context,num){
      // 延时两秒增加
      setTimeout(function(){
        // 调用mutations中的方法
        context.commit('add',num)
      },2000)
    }
  }

直接调用action方法

func3(){
    this.$store.dispatch('delayAdd',2)
},

使用辅助函数mapActions调用

import {mapActions} from 'vuex'
    methods:{
        func3(){
            this.$store.dispatch('delayAdd',2)
        },
        ...mapActions({
            delayAddLocal:'delayAdd' // 将 `this.delayAddLocal()` 映射为 `this.$store.dispatch('delayAdd')`
        })
    },

6.4 getters

vuex中的计算属性。

  • 在vuex中声明getter方法

  getters:{
    doubleCount(state){
      return state.count*2
    }
  }

在组件中获取getter

func4(){
    console.log(this.$store.getters.doubleCount);
},

6.5 modules

在复杂的项目中,我们不能把大量的数据堆积到store中,这样store的内容就太多,而且比较凌乱,不方便管理。所以就是出现了module。他将原有的store切割成了一个个的module。每个module中都有自己的store、mutation、actions和getter

  • 定义一个module

const storeModuleA = {
    state:{
        countA:10
    },
    mutations:{
        addA(state){
            state.countA++
            console.log("moduleA:"+state.countA);
        },
        // 此方法和root中的方法名字一致
        add(state){
            console.log("moduleA:"+state.countA);
        }
    }
}

export default storeModuleA;

在store.js中,导入并注册此module

import Vue from 'vue'
import Vuex from 'vuex'
import storeModuleA from './storeModuleA'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    count:0
  },
  mutations: {
    // state是状态,num是额外的参数
    add(state, num){
      state.count = state.count + num
    }
  },
  actions: {
  },
  getters:{
    doubleCount(state){
      return state.count*2
    }
  },
  modules:{
    a:storeModuleA
  }
})

在组件中使用子模块中的状态

func6(){
    console.log(this.$store.state.a.countA);
},
  • 在组件中使用子模块的mutation

    • 没有声明namespace的情况

      • 子模块中的mutation在没有使用namespace的情况下,这些方法会注册到root中。

      • 如果子模块中的mutation和root中的一样。则都会被调用。

func6(){
    console.log(this.$store.state.a.countA);
    this.$store.commit('addA'); // 和root中不一样的mutation方法
    this.$store.commit('add'); // 和root中一样的mutation方法
},

声明namespace的情况

在module中添加

namespaced:true

在组件中调用,加上别名即可

this.$store.commit('a/addA');

七、初识Vue3和项目搭建

  • Vue3优点

    • 更小的打包体积和内存

    • 页面第一次的渲染速度和更新速度更快

    • 更好的支持 TypeScript

    • 新增了 Composition API 和内置组件

  • 搭建vue3项目

  • 使用vue-cli创建vue3项目,需要保证vue-cli的版本在4.5.0以上。

  • 项目文件

    • 入口文件 main.js

      • 使用 createApp 解析模板,更加小巧

        import { createApp } from 'vue'
        import App from './App.vue'
        
        createApp(App).mount('#app')

        组件 <template> 标签里可以插入多个根标签

八、Vue3常用Composition API

8.1 setup

  • setup

    • setup 函数是Vue3新的配置项

    • 是使用组合API的前提,数据、方法都要放到setup 函数里声明

    • 写法

setup() {
  const name = "张三";
  const age = 18;
  function sayHello() {
    alert("你好,我是${name},今年${age}岁了");
  }
  return {
    name,
    age,
    sayHello,
  };
},

8.2 实现数据的响应式-ref函数

简介:实现数据的响应式—ref函数

在上一个示例中,age和name属性被重新赋值后,不能正确的响应到vue,自然页面也没有响应。

想要实现响应就需要使用ref函数。

ref使用后,就可以在方法中修改数据,只是需要通过value属性。

但是在HTML的插值表达式中,使用方式不需要修改。

  • 定义

    • 定义一个响应式的数据

  • 基本类型数据

    • 引入 ref 函数

import { ref } from "vue";

创建一个包含响应式数据的引用对象( ref 对象)

let name = ref("张三");
let age = ref(18);

操作数据

function changePerson() {
  name.value = "李四";
  age.value = "19";
}

对象类型数据

  • 创建一个包含响应式数据的引用对象( ref 对象)

let obj = ref({
  productName: "裤子",
  price: 40,
});

操作数据

function changePrice() {
  obj.value.price = 50;
}

注意

  • 可以处理基本类型数据、数组或者对象类型的数据

  • 基本类型数据的响应式是通过 Object.defineProperty() 实现

  • 对象类型数据的响应式是通过 ES6 中的 Proxy 实现

8.3 实现数据的响应式-reactive函数

  • 定义

    • 定义一个对象类型的响应式数据(不能处理基本类型数据)

  • 写法

    • 对象

// 定义
let obj = reactive({
  username: "youyou",
  age: 18,
});

// 修改
obj.age = 16;

 数组

// 定义
let list = reactive(["吃饭", "睡觉", "敲代码"]);

// 修改
list[2] = "打游戏"
  • reactiveref 不同点

    • 处理数据类型不同: ref 可以处理基本类型和对象(数组)类型数据,reactive 只能处理对象(数组)类型数据

    • 实现原理不同:ref 处理基本类型数据通过 Object.defineProperty() 实现,reactive 通过 Proxy 实现

    • 操作不同:ref 操作数据需要加 .value

  • 组件数据多时更加趋向使用 reactive

8.4 setup函数的运行时机和两个参数

  • 执行时机

    • 在生命周期函数 beforeCreate 之前执行一次,而且 setup 函数没有 this

  • 两个参数

    • props

      • 第一个参数接收父组件的值,是一个对象

export default {
  props: ["msg"],   //需要props声明才能在setup收到参数
  setup(props) {
    console.log(props.msg);
  },
};

context

  • 上下文对象

  • 触发自定义事件

export default {
  emits: ["testEvent"], //需要emits声明才能在setup中使用
  setup(props, context) {
    function clickMe() {
      context.emit("testEvent", "子组件的值");
    }
    return {
      clickMe,
    };
  },
};

完整测试代码如下:

父组件:

<template>
  <div>
      我是父组件
      <hr>
      <!-- child01组件 传递了自定义属性msg,绑定了自定义事件testEvent -->
      <child01 msg="abc" @testEvent="handleClick"></child01>
  </div>
</template>

<script>
import Child01 from './Child01'
export default {
    setup(){
        function handleClick(obj){
            console.log(obj);
        }
        return{
            handleClick,
        }
    },
    components:{
        Child01
    }
}
</script>

<style>

</style>

子组件:

<template>
  <div>
      我是子组件01
      <div>
          <button @click="handleClick">点击触发testEvent事件</button>
      </div>
  </div>
</template>

<script>
export default {
    props:['msg'], // 声明props的值,才能使用
    emits:['testEvent'], // 声明自定义事件
    setup(props,context){
        console.log(props.msg);
        function handleClick(){
            // 通过context触发自定义事件testEvent
            context.emit('testEvent',"子组件的值");
        }
        return {
            handleClick,
        }
    }
}
</script>

<style>

</style>

8.5 setup中的计算属性—computed函数

  • 定义

    • 通过已有的属性计算而来,跟vue2.x中的功能原理一样,使用方式有区别

  • 使用

    • 计算 ref 定义的响应式数据

<template>
  <div>
      setup中的计算属性使用:
      <br>
      <div>姓:{{firstName}}</div>
      <div>名:{{lastName}}</div>
      <div>全名:{{fullName}}  <input type="text" v-model="fullName"></div>
      
      <button @click="changeLastName">改名</button>
  </div>
</template>

<script>
import {ref, computed} from 'vue'
export default {
    setup(){
        let firstName = ref("张")
        let lastName = ref("三")
        // 使用computed函数实现计算属性,简写
        // let fullName = computed(()=>{
        //     return firstName.value+"-"+lastName.value
        // })

        // 完整写法,可以实现双向的绑定
        let fullName = computed({
            get(){
                return firstName.value+"-"+lastName.value
            },
            set(value){
                const arr = value.split("-");
                firstName.value = arr[0];
                lastName.value = arr[1];
            }
        })

        function changeLastName(){
            lastName.value = "四";
        }
        return {
            firstName,
            lastName,
            fullName,
            changeLastName
        }
    }
}
</script>

计算 reactive 定义的响应式数据

<template>
  <div>
      setup中的计算属性使用:
      <br>
      <div>姓:{{person.firstName}}</div>
      <div>名:{{person.lastName}}</div>
      <div>全名:{{fullName}}  <input type="text" v-model="fullName"></div>
      
      <button @click="changeLastName">改名</button>
  </div>
</template>

<script>
import {ref, computed, reactive} from 'vue'
export default {
    setup(){
        let person = reactive({
            firstName:'张',
            lastName:'三'
        })


        // 完整写法,可以实现双向的绑定
        let fullName = computed({
            get(){
                return person.firstName+ "-" + person.lastName
            },
            set(value){
                const arr = value.split("-");
                person.firstName= arr[0];
                person.lastName = arr[1];
            }
        })

        function changeLastName(){
            person.lastName = "四";
        }
        return {
            person,
            fullName,
            changeLastName
        }
    }
}
</script>

<style>

</style>

或者

<template>
  <div>
      setup中的计算属性使用:
      <br>
      <div>姓:{{person.firstName}}</div>
      <div>名:{{person.lastName}}</div>
      <div>全名:{{person.fullName}}  <input type="text" v-model="person.fullName"></div>
      
      <button @click="changeLastName">改名</button>
  </div>
</template>

<script>
import {ref, computed, reactive} from 'vue'
export default {
    setup(){
        let person = reactive({
            firstName:'张',
            lastName:'三'
        })

        person.fullName = computed({
            get(){
                return person.firstName+ "-" + person.lastName
            },
            set(value){
                const arr = value.split("-");
                person.firstName= arr[0];
                person.lastName = arr[1];
            }
        })

        function changeLastName(){
            person.lastName = "四";
        }
        return {
            person,
            changeLastName
        }
    }
}
</script>

<style>

</style>

8.6 setup中的监视属性—watch函数

需要先导入watch函数,具体使用如下:

  • 监听 ref 基本数据

<template>
  <div>
      num1的值:{{num1}},<button @click="addNum">增加num1</button>
  </div>
</template>

<script>
import {ref, watch} from 'vue'
export default {
    setup(){
        let num1 = ref(1);
        function addNum(){
            num1.value++;
        }

        /* 基本的监听。watch有三个参数
            1.被监听的对象。监听多个对象时,可以是数组
            2.回调函数。有两个参数:newValue, oldValue
            3.配置。是否立即执行和是否深度监听
        */
        watch(num1,(newValue, oldValue)=>{
            console.log("num1增加了", newValue, oldValue);
        },{immediate:false,deep:true})

        return {
            num1,
            addNum
        }
    }
}
</script>

<style>

</style>

监听 reactive 类型

  • 监听对象类型

// 监听reactive定义的数据,对象中的任意属性被改变,都触发。默认是深度监听,并且不能关闭
watch(numObj,(newValue, oldValue)=>{
    // 有个bug,oldValue不能正确输出,等待官方解决
    console.log("numObj被修改了", newValue, oldValue);
},{immediate:false,deep:false})

监听对象中的一个基本类型属性

// 只监听对象中的1个基本属性的改变。
watch(()=>numObj.numa,(newValue, oldValue)=>{
    console.log("numObj.numa被修改了", newValue, oldValue);
})

监听对象中的一些基本类型属性

// 只监听对象中的多个基本属性的改变。
watch([()=>numObj.numa, ()=>numObj.numb],(newValue, oldValue)=>{
    console.log("numObj.numa,numb被修改了", newValue, oldValue);
})

监听对象中的对象类型属性

// 只监听对象中的对象属性的改变。
watch(numObj.children,(newValue, oldValue)=>{
    console.log("numObj.children被修改了", newValue, oldValue);
})

watch监听整体代码如下:

<template>
  <div>
      num1的值:{{num1}},<button @click="addNum">增加num1</button>
      <hr>


      numobj中numa:{{numObj.numa}} ----- <button @click="numObj.numa++">增加numa</button>
      <br>
      numobj中numb:{{numObj.numb}} ----- <button @click="numObj.numb++">增加numb</button>
      <br>
      numObj.children.numc:{{numObj.children.numc}} ---- <button @click="numObj.children.numc++">增加numc</button>
      <br>
      
  </div>
</template>

<script>
import {ref, reactive, watch} from 'vue'
export default {
    setup(){
        let num1 = ref(1);

        let numObj = reactive({
            numa:1,
            numb:11,
            children:{
                numc:22
            }
        })

        function addNum(){
            num1.value++;
        }

        /* 基本的监听。watch有三个参数
            1.被监听的对象。监听多个对象时,可以是数组
            2.回调函数。有两个参数:newValue, oldValue
            3.配置。是否立即执行和是否深度监听。可以省略
        */
        watch(num1,(newValue, oldValue)=>{
            console.log("num1增加了", newValue, oldValue);
        },{immediate:false,deep:true})

        // 监听reactive定义的数据,对象中的任意属性被改变,都触发。默认是深度监听,并且不能关闭
        watch(numObj,(newValue, oldValue)=>{
            // 有个bug,oldValue不能正确输出,等待官方解决
            console.log("numObj被修改了", newValue, oldValue);
        },{immediate:false,deep:false})

        // 只监听对象中的某个基本属性的改变。
        watch(()=>numObj.numa,(newValue, oldValue)=>{
            console.log("numObj.numa被修改了", newValue, oldValue);
        })

        // 只监听对象中的多个基本属性的改变。
        watch([()=>numObj.numa, ()=>numObj.numb],(newValue, oldValue)=>{
            console.log("numObj.numa,numb被修改了", newValue, oldValue);
        })

        // 只监听对象中的对象属性的改变。
        watch(numObj.children,(newValue, oldValue)=>{
            console.log("numObj.children被修改了", newValue, oldValue);
        })

        return {
            num1,
            addNum,
            numObj
        }
    }
}
</script>

<style>

</style>

总结

  • 实现监听生效

    • ref 定义的数据

      • 基本类型数据作为监听值

      • 对象作为监听值,需要加 .value(用的少)

    • reactive 定义的数据

      • 对象作为监听值

      • 属性作为监听值,需要放在回调函数中

  • 注意

    • 如果监听 reactive 定义的对象,则无法正确输出 oldValue ,且深度监听是强制开启的,无法关闭 (vue3配置)

8.7 了解setup中的watchEffect函数

  • 定义

    • 在监听的回调函数中使用了属性,则监听该属性,不用在参数上指明监听哪个属性

  • 写法

watchEffect(() => {
  let na = numa.value;
  let nb = numb.value;
  console.log("watchEffect函数执行了");
});

  • watch 的区别

    • 属性监听区别:

      • watch 手动添加定向的监听属性

      • watchEffect 自动监听使用到的属性

    • 初始化执行:

      • watchEffect 会初始化执行一次

  • 建议开发中使用 watch 监听,逻辑简单、依赖属性少的场景可以使用 watchEffect

九、Vue3中的生命周期和数据处理

9.1 vue3中的生命周期

  • 创建

vue2: 创建前、后:beforeCreate、created
vue3: 创建:setup

挂载

vue2: 挂载前、后:beforeMount、mounted
vue3: 挂载:onBeforeMount、onMounted

更新

vue2: 更新前、后(beforeUpdate、updated)
vue3: 更新:onBeforeUpdate、onUpdated

卸载

vue2: 销毁前、后:beforeDestroy、destroyed
vue3: 卸载:onBeforeUnmount、onUnmounted

9.2 Vue3中的toRef和toRefs函数

  • toRef

    • 定义

      • 创建一个 ref 对象,其 value 值指向另一个对象中指定的属性

    • 写法

const name = toRef(person, "name");

    • 作用

      • 将某个响应式对象的某一个属性提供给外部使用

  • toRefs

    • 定义

      • 批量创建多个 ref 对象,其 value 值指向另一个对象中指定的属性

    • 写法

setup() {
  let person = reactive({
    name: "张三",
    age: 19,
  });
  return {
    ...toRefs(person),
  };
},
    • 作用

      • 将某个响应式对象的全部属性提供给外部使用

9.3 多层嵌套的组价间的通讯—provide和inject

  • 作用

    • 实现祖孙组件间的传值

  • 写法

    • 祖组件使用 provide 提供数据

let name = ref("youyou");
provide("myname", name);

后代组件使用 inject 使用数据

const message = inject("myname");

结束语:

首先,恭喜大家已经阅读完整个Vue2、Vue3(通俗易通),一般而言,不管书籍也好,能够完整跟下来的就已经很不容易了。所以尽量帮助初学者减少初级的困难,其实一旦掌握了之后,会发现它其实是非常容易。但大道至简,知易行难,需要大家之后不断练习,在此基础上加强知识的认知深度。虽然我尽量以通俗易通的形式,将内容体现出来,但水平毕竟有限,望大家海涵。

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值