Vue2+Vue3学习笔记

Vue学习笔记

一、关于Vue2

什么是Vue?

一套用于构建用户界面的渐进式javascript框架
特点:
1、采用组件化模式,提高代码复用率、且让代码更好维护。
2、声明式编码,让编码人员无需直接操作DOM,提高开发效率
3、使用虚拟DOM+

初识Vue

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

<body>
    <div id="root">
        <h1>Hello ,{{name}}</h1>
    </div>
    <script>
        Vue.config.productionTip=false

        //创建Vue实例
        new Vue({
            //用于指定当前Vue示例为哪个容器服务,值通常为css选择器字符串,也可以为document.getelementById("root");
            el:'#root', 
            //data中用于存储数据,数据供el所指定的容器去使用,值暂时先写成一个对象
            data:{
                name:'周深',
            }
        })
    </script>
</body>

补充
js表达式和Js代码的区别
JS表达式会产生一个值,可以放在任何一个需要值的地方:
比如:a,a+b,demo(0)、x==y?x:y
而JS代码是语句,一般用于控制程序的走向:
比如:for循环、ifelse语句

模板语法

Vue模板语法有两大类:
1、插值语法:
功能:用于解析标签体内容
写法:{{xxx}},其中xxx是JS表达式,且可以直接读取到Vue实例中data的所有属性

2、指令语法:
功能:用于解析标签(包括:标签属性、标签体内容、绑定事件)
比如: v-bind:href="url.toUpperCase()",或也可以简写为:href="url.toUpperCase()",其中双引号中的要写JS表达式,且可以直接读取到Vue实例中data的所有属性。

举例:

<body>
    <div id="root">
        <h1>Hello ,{{name}},{{address}}</h1>
        <button><a :href="more.url">点我去了解他</a></button>
    </div>
    <script>
        Vue.config.productionTip=false

        //创建Vue实例
        new Vue({
            //用于指定当前Vue示例为哪个容器服务,值通常为css选择器字符串,也可以为document.getelementById("root");
            el:'#root', 
            //data中用于存储数据,数据供el所指定的容器去使用,值暂时先写成一个对象
            data:{
                name:'周深',
                address:'贵阳',
                more:{
                    url: 'https://zh.wikipedia.org/wiki/周深',
                }
            }
        })
    </script>
</body>

数据绑定

Vue中有两种数据绑定的方式:

1、单向绑定(v-bind):数据只能从data流向页面。

2、双向绑定(v-model):数据不仅能从data流向页面,还可以从页面流向data

注:
1、双向绑定一般都应用在表单类元素上(如:input、select)
2、v-model:value可以简写为v-model,因为v-model默认收集的就是value值

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

el和data的两种写法

el的两种写法:
1、在new vue创建实例的时候配置el属性
2、先创建Vue实例,随再通过vm.$mount(’#root’)指定el的值

Vue.config.productionTip=false
        const vm=new Vue({
            //el: '#root',
            data:function(){
                return {
                    name:'周深'
                }
            }
        })
        v.$mount('#root')

data的两种写法:
1、对象式,在Vue实例中直接配置
2、函数式,例如:
对于如何选择编写,在学习到组件时,data就必须要用第二种函数式!

new Vue({
            el: '#root',
            data:function(){
                return {
                    name:'周深'
                }
            }
        })

注:由Vue管理的函数中的this是Vue的实例对象,所以一定不要写箭头函数,一旦写了箭头函数,this就不是Vue实例了。

MVVM

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

特点
1、data中所有的属性,最后都会出现在vm里
2、vm中的所有属性及Vue原型上所有属性,在Vue模板中都可以直接使用

数据代理

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

enumerable:控制属性是否可以枚举,默认值为false

writable:控制属性是否可以被删除,默认值为false

configurable:控制属性是否可以被删除,默认值为false

getter函数:当有人读取person中的age属性时getter函数会被调用,且返回值就是age的值

setter函数:当有人修改person中的age属性时,setter函数会被调用,且收到的就是新修改的值

<body>
    <script>
        let number=20
        let person={
            name:'周深',
            gender:'男'
        }
        Object.defineProperty(person,'age',{
            /*value:18,
            //控制属性是否可以枚举,默认值为false
            enumerable:true,
            //控制属性是否被修改,默认值为false
            writable: true,
            //控制属性是否可以被删除,默认值为false
            configurable: true*/

            //当有人读取person中的age属性时getter函数会被调用,且返回值就是age的值
            get(){
                console.log("dwkasnd")
                return number;
            },

            //当有人修改person中的age属性时,setter函数会被调用,且收到的就是新修改的值
            set(value){
                console.log('有人读取age属性,值为',value)
            }
        })
    </script>
</body>

要求:用obj2来数据代理obj1中的x

<body>
    <script>
        let obj1={x: 100}
        let obj2={y:200}

        Object.defineProperty(obj2,'x',{
            get(){
                return obj1.x
            },

            set(value){
                obj1.x=value
            }
        })
    </script>
</body>

总结
数据代理:
1、Vue中的数据代理:通过Vue对象来代理data对象中属性的操作(读/写)
2、Vue中数据代理的好处:更加方便的操作data中的数据
3、基本原理:通过Object.defineProperty()把data对象中所有的属性添加到vm上,为每一个添加到vm上的属性都指定一个getter/setter。在getter/setter内部去操作(读/写)data中对应的属性。
在这里插入图片描述

二、事件

事件处理

事件的基本使用:
1、使用v-on:xxx或@xxx 绑定事件,其中xxx是事件名
2、事件的回调需要配置在methods对象中,最终会在vm上
3、methods中配置的函数,不要用箭头函数!否则this就不是vm了而是window
4、methods中配置的函数,都是被Vue管理的,其中this指的是vm或组件实例对象
5、@click=demo这样写就表示不传参,只默认传event
如果写成@click=demo(num,$event)就可以传参,但是event前必须加 $

<body>
    <div id="root">
    <button @click="showinfo1">点我不传参数</button>
    <button @click="showinfo2(66,$event)">点我传参数</button>
</div>
    <script>
        Vue.config.productionTip=false
        new Vue({
            el:'#root',
            data:{
                name:'周深',
                gender:'男',
            },
            methods:{
                showinfo1(event){
                    alert("hello")
                },
                showinfo2(event,number){
                    alert("kcdkn")
                }
            }
        })
    </script>
</body>


事件修饰符

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


键盘事件

1、Vue中常用的按键别名:
回车:enter
删除:delete(捕获删除和退格键)
退出:esc
空格:space
换行:tab(特殊,必须配合keydown使用)
上:up
下:down
左:left
右:right

<input type="text" palceholder="按下某键输入" @keyup.enter="demo">

2、Vue未提供别名的按键,可以使用按键原始的key值去绑定,但要注意格式的不同,一定要写成caps-lock(大小写切换按键)这种短横线命名

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

4、也可以使用keyCode去指定具体的按键(可能马上永久不能使用所以不推荐)

5、可以用Vue.config.keyCodes.自定义修饰符名称=键码 这种方式去定制按键的别名

Vue.config.keyCodes.huiche=13 //给按键编码为13的回车按键名称设置为huiche

三、计算属性与监视

计算属性

1、定义:要用的属性不存在,要通过已有的属性计算得来(比如将两个vm实例中属性的属性值拼接等)

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

3、关于get函数什么时候被调用?
初次读取时调用一次,然后就一直在缓存中,接下来再读取时就直接在缓存中找,直到其被依赖的数据发生改变时才会重新调用一次。
4、优点:与methods实现相比,内部有缓存机制(复用),效率更高,调试更方便

注:计算属性最终会出现在vm里,直接读取即可如果计算属性要修改,则必须要编写setter去响应修改,且set中要引起计算时依赖的数据发生改变。

<body>
    <div id="root">
    firstName: <input type="text" v-model="firstName"><br><br>
    lastName: <input type="text" v-model="lastName"><br><br>
    fullName: <span>{{fullName}}</span>
    </div>
    <script>
        Vue.config.productionTip=false
        const vm=new Vue({
            el:'#root',
            data:{
                firstName:'周',
                lastName:'深',
            },
            computed:{
                fullName:{
                    get(){
                        return this.firstName+'-'+this.lastName
                    },
                    set(value){
                        const arr=value.split('-')
                        this.firstname=arr[0]
                        this.lastname=arr[1]
                    }
                }
            }
        })
    </script>
</body>

以上代码如果fullName属性中没有setter,那么可以进行简写:
特别注意:就算是简写成了fullName(),在上面的{{ }}中还是得要写fullName,不可写成fullName()

<script>
        Vue.config.productionTip=false
        const vm=new Vue({
            el:'#root',
            data:{
                firstName:'周',
                lastName:'深',
            },
            computed:{
                fullName(){
                    return this.firstName+'-'+this.lastName
                }
            }
        })
</script>

监视属性

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

深度监视

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

注:Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以!
使用watch时根据数据的具体结构,决定是否采用深度监视。

总结:

计算属性computed和监视属性watch的总结:
1、computed能完成的功能,watch也可以完成
2、watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作

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

四、绑定与渲染

绑定

class绑定

方法:
1、字符串写法:
适用于:样式的类名不确定,需要动态指定

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

3、对象写法:
适用于:要绑定的样式个数确定,名字也确定,但要动态决定用不用

<span :class="classObj"> </span> <!--对象写法-->

style绑定

1、对象写法

2、数组写法:(用的比较少)

<span :style="{fontSize: xxx;}"> </span>  <!--其中xxx是动态值-->
<span :style="[a,b]"> </span> <!--其中a.b是样式对象-->

渲染

条件渲染

1、v-if
语法: v-if=“表达式”
v-else-if=“表达式”
v-else=“表达式”
适用于:切换频率较低的场景
特点:不展示DOM元素直接被移除
注:v-if可以和v-else-if、v-else一起使用,但要求中间不能有其他东西

2、v-show:
语法:
v-show=“表达式”
适用于:切换频率较高的场景
特点:不展示的DOM的元素未被移除,仅仅是使用样式隐藏

注:使用v-if时,元素可能无法获取到,v-show一定可以获取到。因为v-if若它的表达式值为false,那么这个节点也会被移除!

列表渲染

v-for指令:
1、用于展示列表数据
2、语法: v-for=“(value,key) in xxx” :key=“yyy”
3、可遍历:数组、对象、字符串(用的少)、指定次数(用的很少)

key

对于v-for=“(value,key) in xxx” :key=“yyy”中的key:

Vue中key有什么作用?(key的内部原理)

1、虚拟DOM中key的作用:key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据新数据生成新的虚拟DOM,随后Vue会根据新的虚拟DOM和旧的虚拟DOM差异进行比较,比较规则如下:

2、对比规则:
旧的虚拟DOM中找到了与新的虚拟DOM相同的key:若虚拟DOM中的内容没变,则直接用之前的真实DOM的内容;若虚拟DOM中的内容发生改变,则生成新的真实DOM,随后替换掉页面中之前的真实DOM

旧的虚拟DOM中未找到与新的虚拟DOM相同的key,则创建新的真实DOM,随后渲染到页面。

3、用index作为key可能引发一些问题:
若对数据进行逆序添加、逆序删除等破坏顺序的操作,会产生没有必要的真实DOM更新,虽然界面效果没问题但是进行比对算法时,会新生成很多其实旧的虚拟DOM中已经存在的内容,效率很低

若结构中还包含一些输入类的DOM:则会产生错误DOM更新并且界面也会出问题。

4、开发中如何选择key?
首先最好使用每条数据的唯一标识作为key,比如:id、手机号、身份证号、学号等唯一值。
如果不存在对数据的逆序添加、逆序删除等破坏顺序的操作,仅用于渲染列表用于展示,使用index作为key是没有问题的

Vue监视数据的总结

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

2、Vue是如何监测对象中的数据?
通过setter实现监视,且要在new Vue时就传入要监测的数据。
如果对象中后追加的属性,Vue默认不做响应式处理。
如果想要给后添加的属性做响应式处理,必须使用如下API:

Vue.set(target,PropertyName/index,value)

vm.$set(target,PropertyName/index,value)

3、Vue是如何监测数组中的数据?
通过包裹数组更新元素的方法进行实现,本质就是做了两件事:
调用原生对应的方法对数组进行更新
重新解析模板,进而更新页面
例如:unshift就是原生方法,在数组的第一个元素前添加新元素

this.student.friends.unshift({name:'Lucy',age:'19',gender:'女'})

4、在Vue修改数组中的某个元素一定要使用如下方法:
使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reserve()。
使用Vue.set()或vm.$set()进行修改

this.student.hobby.splice(0,1,'开车')
Vue.set(this.student.hobby,0,'开车')

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

收集表单数据

收集表单数据:
若:<input type="text">,则v-model收集的是value值,用户输入的就是value值
<input type="radio">,则v-model收集的是value值,且要给标签配置value值。
<input type="checkbox">
如果没有配置value属性,那么收集的是checked(勾选或未勾选,是一个布尔值)
如果配置了value属性,那么:v-model的初始值是非数组那么收集的就是checked(勾选或未勾选,是一个布尔值);v-model的初始值是数组,那么收集的就是value组成的数组。

注:v-model的三个修饰符:
1、lazy:失去焦点时再收集数据
2、trim:输入首尾空格忽略
3、number:输入字符串转为有效的数字

过滤器

定义:对显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)
语法:
注册过滤器:Vue.filter(name,callback)(全局)或 new Vue( filter:{ } ) (局部)
使用过滤器:{{ xxx | 过滤器名 }} 或 v-bind:属性=“ xxx | 过滤器名 ”

注:
1、过滤器也可以接收额外参数,多个过滤器也可以串联
2、并没有改变原本的数据,是产生新的对应的数据

内置指令

v-bind:单向绑定解析表达式

v-model:双向数据绑定

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

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

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

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

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

v-text:向其所在的节点中渲染文本内容
与插值语法的区别:v-text会完全替换中节点中的内容,{{ xxx}}则不会

v-html:向指定节点中渲染包含html结构的内容。与插值语法的区别是:v-html会替换掉节点中所有的内容,{{ xx }}则不会;v-html可以识别html结构
注:v-html有安全性问题,在网站上动态渲染任意html是非常危险的,容易导致XSS攻击。一定要在可信的内容上使用v-html,永远不要用在用户提交的内容上!

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

<style>
    [v-cloak]{
         display: none;
    }
</style>

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

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

自定义指令

1、定义语法:
(1)局部指令:

new Vue({
     directives:{指令名:配置对象}
})

new Vue({
     directives:{指令名: 回调函数}
})

(2)全局指令

Vue.directive(指令名,配置对象)

Vue.directive(指令名,回调函数)

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

注:指令定义时不加v-,使用时加v-;指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名。

生命周期

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

总结:
常用的生命周期的钩子:
1、mounted:发送ajax请求、启动定时器、绑定自定义事件、订阅消息等(初始化操作)
2、beforeDestroy:清除定时器,解绑自定义事件、取消订阅消息等(收尾工作)

关于销毁Vue实例:
1、销毁后借助Vue开发者工具看不到任何信息
2、销毁后自定义事件会失效,但原生DOM事件依然有效
3、一般不会在BeforeDestroy操作数据,因为即便操作数据也不会再触发更新流程了。

五、组件

组件:实现应用中局部功能代码和资源的集合。
优点:复用编码,简化项目编码、提高运行效率

非单文件组件

非单文件组件:一个文件中包含有n个组件。

Vue使用组件的三大步骤:
1、定义组件
2、注册组件
3.使用组件(写组件标签)

如何定义一个组件?
使用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>

组件名的编写

1、关于组件名:
只有一个单词的:第一种写法(首字母小写),例如:school;第二种写法(首字母大写),例如:School
多个单词组成:第一种写法(kebab-case命名),例如:my-school;第二种法(CamelCase命名),例如;MySchool(需要Vue脚手架支持)

注:组件名尽可能回避html中已有的元素名称,例如:h2、H2都不行;可以使用name配置项指定组件在开发者工具中呈现的名字。

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

3、一个简写的方式:const school=Vue.extend(ooptions)可以简写为:const school=options

VueComponent

关于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(也称为组件实例对象)

内置关系

一个重要的内置关系:VueComponent.prototype._proto_===Vue.prototype,让组件实例对象vc可以访问到Vue原型上的属性和方法。

render函数

关于不同版本的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函数去指定具体内容

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、使用方式:打标识+获取
打标识:

//给html标签上添加ref属性
<h1 v-text="msg" ref="title"></h1>
//给组件上添加ref属性
<SchoolName ref="sch"/>

获取:

console.log(this.$refs.title)//获取到的是真实DOM元素
console.log(this.$refs.sch)//获取到的是组件实例对象vc

props配置

配置项props:
功能:让组件接收外部传过来的数据。
(1)传递数据:

<Demo name="xxx" />

(2)接受数据:
第一种方式:只接收

props['name']

第二种方式:限制类型

props:{
	name:String, //限制name传进来的必须是字符串
	age:Number//限制age传进来的必须是数字
}

第三种方法:限制类型+限制必要性+指定默认值

props:{
	name:{
		type:String, //限制类型为字符串
		required:true,//为真则表示必须要传name
		default: '张三'  //如果不传则默认为张三 
	}
}

注:props是只读的,不可以修改props中的内容,Vue底层会监测你对props的修改。如果需要修改,可以将props中的内容复制一份到data中,然后去修改data中的属性。

mixin混入

功能:可以把多个组件共用的配置提取成一个混入对象
使用方式:
第一步:定义混合:

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

第二步:使用混入,例如:
(1)全局混入:Vue.mixin( xxx )
(2)局部混入:mixins:[ ‘xxx’ ]

插件

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

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

使用插件:

Vue.use()

scoped样式

作用:让样式在局部生效,防止冲突
写法:

<style lang="less" scoped></style>

总结

分析案例总结:ToDoList
1、组件化编码流程:
(1)拆分静态组件:组件要按照功能点拆分,命名不要和html元素冲突
(2)实现动态组件:考虑好数据存放的位置,数据是一个组件在用,还是一些组件在用:
a.一个组件在用:放在组件自身即可
b.一些组件在用:放在他们共同的父组件上(状态提升)
(3)实现交互:从绑定时间开始

2、props适用于:
(1)父组件 = = > 子组件 通信
(2)子组件 = => 父组件 通信(但是要求父先给子一个函数)

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

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

webStorage

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

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

3、相关的API:
(1)xxxxStorage.setItem('key','value');该方法是接收一个键和值作为参数,会把键值对添加到村初中,如果键名存在,则更新其对应的值
(2)xxxx.Storage.getItem('person');该方法接收一个键名作为参数,返回键名对应的值
(3)xxxxxStorage.removeItem('key');该方法接收一个键名作为参数,并把该键名从存储中删除
(4)xxxxStorage.clear();该方法会清空存储中所有数据

注:
(1)SessionStorage存储的内容会随着浏览器窗口的关闭而消失
(2)LocalStorage存储的内容,需要手动清除才会消失
(3)xxxx.Storage.getItem('person');如果xxx对应的value获取不到,则返回的是null
(4)JSON.parse(null)的结果仍然是null

六、组件间通信

自定义事件

1、定义:一种组件间的通信方式:子组件 == > 父组件

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

3、绑定自定义事件:
(1)第一种方式:
在父组件中<Demo @事件名="test"><Demo v-on:事件名="test">
(2)第二种方式:
在父组件中:

<Demo ref="demo" />
.....
mounted(){
	this.$ref.xxx.$on('事件名',this.test)
}

(3)若想让自定义时间只触发一次,可以使用once修饰符,或$once方法

4、触发自定义事件:this.$emit('事件名',数据)

5、解绑自定义事件:this.$off('事件名')

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

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

全局事件总线

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

2、安装全局事件总线:

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

3、使用全局事件总线
(1)接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件中

methods(){
	demo(data){......}
}
......
mounted(){
	this.$bus.$on('xxx',demo)
}

(2)提供数据:this.$bus.$emit('xxx',数据)

4、最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。

消息订阅与发布

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

2、使用步骤:
(1)终端安装第三方库pubsub:npm i pubsub-js
(2)引入:import pubsub from 'pubsub-js'
(3)接收数据:A组件想接收数据则在A组件中订阅消息,订阅的回调留在A组件自身。

methods(){
	//第一个参数为订阅名,但是由于使用不到,可以用_来占位
	demo(_,data){......}
}
......
mounted(){
	this.pid=punsub.subscribe('xxx',this.demo) //订阅消息
}

(4)提供数据:pubsub.publish('xxx',数据)
(5)最好在beforeDestroy钩子中,用pubsub.unsubscribe(pid)去取消订阅

补充:

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

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

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

七、Vue动画

Vue封装的过渡和动画:
1、作用:在插入、更新或移除DOM元素时,在合适的时候给元素添加样式类名。

2、图示:
在这里插入图片描述
写法:

1、准备好样式:

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

2、使用< transition >包裹要过度的元素,并配置name属性

<transition name="hello">
	<h1 v-show="isshow">你好啊</h1>
</transition>

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

八、Vue配置代理

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

devServer: {
    proxy: 'http://localhost:5000'  //端口号为服务器
  }

注:
1、优点:配置简单,请求资源时直接发给前端即可。
2、缺点:不能配置多个代理,不能灵活的控制请求是否走代理
3、工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器(优先配置前端资源)

方法二
编写vue.config.js配置具体代理规则:

module.exports = {
  devServer: {
    proxy: {
      '/api': {  //匹配所有以‘/api’开头的请求路径
        target: 'http://localhost:5000',  //代理目标的基础路径
        changeOrigin: true
        pathRewrite:{'^/api':''}
      },
      '/foo': {  //匹配所有以‘/foo’开头的请求路径
        target: 'http://localhost:5001'  //代理目标的基础路径
        changeOrigin: true
        pathRewrite:{'^/foo':''}
      }
    }
  }
}

changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:8080
changeOrigin默认值为true

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

九、Vue插槽

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

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

默认插槽

使用方式:

//父组件中
<Category>
	<div>html结构1</div>
</Category>
//子组件中
<template>
	<div>
		//定义插槽
		<slot></slot>
	</div>
</template>

具名插槽

//父组件中
<Category>
	<template slot="center">
		<div>html结构1</div>
	</template>
	<template v-slot:footer>
		<div>html结构2</div>
	</template>
</Category>

//子组件中
<template>
	<div>
		//定义插槽
		<slot name="center">插槽默认内容1</slot>
		<slot name="footer">插槽默认内容2</slot>
	</div>
</template>

作用域插槽

定义:数据在组件自身,但根据数据生成的结构需要组件的使用者来决定。(数据在组件自身中,但使用数据所遍历出来的结构是在使用者组件中的)

十、Vuex

关于Vuex

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

2、何时使用Vuex?
多个组件依赖于同一状态;来自不同组件的行为需要变更同一状态

3、Vue工作原理图:
在这里插入图片描述

Vuex搭建

1、下载:npm i vuex@3
注:如果是使用Vue2,则应该下载Vuex3;使用Vue3的,应该下载Vuex4

2、在src下创建文件: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
 })

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

......
//引入store
import store from './store'
......

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

Vuex的使用

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

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

map方法的使用

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

computed: {
    //借助mapState生成计算属性:sum、school、subject(对象写法)
     ...mapState({sum:'sum',school:'school',subject:'subject'}),
         
    //借助mapState生成计算属性:sum、school、subject(数组写法)
    ...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(对象形式)
    ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})

    //靠mapActions生成:incrementOdd、incrementWait(数组形式)
    ...mapActions(['jiaOdd','jiaWait'])
}

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

methods:{
    //靠mapActions生成:increment、decrement(对象形式)
    ...mapMutations({increment:'JIA',decrement:'JIAN'}),
    
    //靠mapMutations生成:JIA、JIAN(对象形式)
    ...mapMutations(['JIA','JIAN']),
}

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

模块化

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是组件。

基本使用

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>

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

嵌套路由

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>

路由传参

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

命名路由

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

2、使用方式:

给路由命名:

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

简化跳转:

 <!--简化前,需要写完整的路径 -->
      <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>

路由的props配置

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

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

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

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

replace属性

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

2、浏览器的历史记录有两种写入方式:分别为pushreplacepush是追加历史记录,replace是替换当前记录。路由跳转时候默认为push

3、如何开启replace模式:<router-link replace .......>News</router-link>

路由导航

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() //可前进也可后退,传一个正负数,正数表示前进几步,负数表示后退几步

缓存路由组件

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

2、具体编码:

 <keep-alive include="News"> 
       <router-view></router-view>
   </keep-alive>

两个新的生命周期钩子

1、 作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
2、 具体名字:
activated路由组件被激活时触发。
deactivated路由组件失活时触发。

路由守卫

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

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

全局守卫

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

独享守卫

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()
	}
}

组件内守卫

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

两种工作模式

1、 对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。
2、hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
3、hash模式:

  1. 地址中永远带着#号,不美观 。
  2. 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
  3. 兼容性较好。
  4. history模式:
    1. 地址干净,美观 。
    2. 兼容性和hash模式相比略差。
    3. 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。



Vue3


十一、关于Vue3


setup配置

1、 理解:Vue3.0中一个新的配置项,值为一个函数。

2、setup是所有Composition API(组合API)“ 表演的舞台 ”

3、组件中所用到的:数据、方法等等,均要配置在setup中。

4、setup函数的两种返回值:

  1. 若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。(重点关注!)
  2. 若返回一个渲染函数:则可以自定义渲染内容。(了解)

注:
1、 尽量不要与Vue2.x配置混用
- Vue2.x配置(data、methos、computed…)中可以访问到setup中的属性、方法。
- 但在setup中不能访问到Vue2.x配置(data、methos、computed…)。
- 如果有重名, setup优先。

2、setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合

ref函数

1、作用: 定义一个响应式的数据
2、语法: const xxx = ref(initValue)

  • 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)
  • JS中操作数据: xxx.value
  • 模板中读取数据: 不需要.value,直接:<div>{{xxx}}</div>

注:
1、 接收的数据可以是:基本类型、也可以是对象类型。
2、基本类型的数据:响应式依然是靠Object.defineProperty()getset完成的。
3、对象类型的数据:内部 “ 求助 ”了Vue3.0中的一个新函数—— reactive函数。

reactive函数

1、作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数)

2、语法:const 代理对象= reactive(源对象)接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)

3、reactive定义的响应式数据是“深层次的”。

4、内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。

Vue3的响应式原理

Vue2的响应式处理

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

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

    Object.defineProperty(data, 'count', {
        get () {}, 
        set () {}
    })

3、存在问题:

  • 新增属性、删除属性, 界面不会更新。(Vue2中使用this.$set()this.$delete()进行属性的新增和删除)
  • 直接通过下标修改数组, 界面不会自动更新。

Vue3的响应式处理

实现原理:
1、通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。

2、通过Reflect(反射): 对源对象的属性进行操作。

 new Proxy(data, {
      	// 拦截读取属性值
          get (target, prop) {
          	return Reflect.get(target, prop)
          },
          // 拦截设置属性值或添加新属性
          set (target, prop, value) {
          	return Reflect.set(target, prop, value)
          },
          // 拦截删除属性
          deleteProperty (target, prop) {
          	return Reflect.deleteProperty(target, prop)
          }
      })
      
      proxy.name = 'tom'   

ref和reactive对比

1、从定义数据角度对比:

  • ref用来定义:基本类型数据
  • reactive用来定义:对象(或数组)类型数据
  • 备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过reactive转为代理对象

2、 从原理角度对比:

  • ref通过Object.defineProperty()getset来实现响应式(数据劫持)。
  • reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。

3、 从使用角度对比:

  • ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value
  • reactive定义的数据:操作数据与读取数据:均不需要.value

关于setup

1、setup执行的时机: 在beforeCreate之前执行一次,this是undefined。

2、 setup的参数

  • props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
  • context:上下文对象
    • attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于 this.$attrs
    • slots: 收到的插槽内容, 相当于 this.$slots
    • emit: 分发自定义事件的函数, 相当于 this.$emit

十二、计算和监视属性

计算属性

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

2、写法

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]
        }
    })
}

监视属性

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

  • 两个小“坑”:

1、监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。

2、监视reactive定义的响应式数据中某个属性时:deep配置有效。

//情况一:监视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.job,(newValue,oldValue)=>{
	console.log('person的job变化了',newValue,oldValue)
},{immediate:true,deep:true}) 

//情况五:监视reactive定义的响应式数据中的某些属性
watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{
	console.log('person的job变化了',newValue,oldValue)
},{immediate:true,deep:true})

//特殊情况
watch(()=>person.job,(newValue,oldValue)=>{
    console.log('person的job变化了',newValue,oldValue)
},{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效

watchEffect函数

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

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

注:watchEffect有点像computed:

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

十三、生命周期

十四、组合式API

hook函数

1、定义:本质是一个函数,把setup函数中使用的Composition API进行了封装。(类似于vue2.x中的mixin)

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

toref

1、 作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。语法:const name = toRef(person,'name')

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

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

shallowReactive 和shallowRef

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

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

应用场景:

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

readonly 和 shallowreadonly

readonly: 让一个响应式数据变为只读的(深只读)。

shallowReadonly:让一个响应式数据变为只读的(浅只读)。

应用场景: 不希望数据被修改时。

toRaw和markRow

toRaw

  • 作用:将一个由reactive生成的响应式对象转为普通对象

  • 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。

markRaw

  • 作用:标记一个对象,使其永远不会再成为响应式对象。
  • 应用场景:
    1. 有些值不应被设置为响应式的,例如复杂的第三方类库等。
    2. 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。

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值是需要被“追踪”的
  							return value
  						},
  						set(newValue){
  							clearTimeout(timer)
  							timer = setTimeout(()=>{
  								value = newValue
  								trigger() //告诉Vue去更新界面
  							},delay)
  						}
  					}
  				})
  			}
  			let keyword = myRef('hello',500) //使用程序员自定义的ref
  			return {
  				keyword
  			}
  		}
  	}
  </script>

provide 与 inject

1、作用:实现祖与后代组件间通信。父组件有一个 provide 选项来提供数据,后代组件有一个 inject 选项来开始使用这些数据
具体写法:
在祖组件中:

     setup(){
     	......
         let car = reactive({name:'奔驰',price:'40万'})
         provide('car',car)
         ......
     }

在后代组件中:

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

响应式数据的判断

isRef: 检查一个值是否为一个 ref 对象

isReactive: 检查一个对象是否是由 reactive 创建的响应式代理

isReadonly: 检查一个对象是否是由 readonly 创建的只读代理

isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理

Fragment 组件

在Vue2中: 组件必须有一个根标签

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

Teleport组件

定义: Teleport 是一种能够将我们的组件html结构移动到指定位置的技术。

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

Suspense组件

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

2、使用步骤:

  • 异步引入组件

    import {defineAsyncComponent} from 'vue'
    const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
    
  • 使用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>

十五、其他变化

全局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.xxxxapp.config.xxxx
    Vue.config.productionTip移除
    Vue.componentapp.component
    Vue.directiveapp.directive
    Vue.mixinapp.mixin
    Vue.useapp.use
    Vue.prototypeapp.config.globalProperties

其它改变

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

2、过度类名的更改:

  • 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;
    }
    

3、移除keyCode作为 v-on 的修饰符,同时也不再支持config.keyCodes

4、移除v-on.native修饰符

  • 父组件中绑定事件

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

    <script>
      export default {
        emits: ['close']
      }
    </script>
    

5、移除过滤器(filter)

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值