姓名:
下面是关于vue3的一些新特性和新用法的讲解
一、新增内置组件:teleport
官文解释teleport:teleport 提供了一种干净的方法,允许我们控制在 DOM 中哪个父节点下呈现 HTML,而不必求助于全局状态或将其拆分为两个组件
场景:在组件A中使用alert弹窗,但不希望alert弹窗渲染的DOM结构嵌套在组件A中;而是渲染在我们指定的位置上
实现方式:
1)使用
2) 指定alert渲染的DOM结构位置,写上
注意:to属性和id的值保持一致
下面举个例子:
a)首先:定义Alert.vue
{{title}}
b)其次:在组件Page.vue中使用Alert弹窗
显示Alert
只是测试,只是测试,只是测试,只是测试,只是测试,只是测试,只是测试,只是测试,
c)最后:指定Alert最终渲染的位置。这里指定与App.vue里面的元素同级。App.vue内容如下:
App.vue页面
下面看下渲染效果:
未显示Alert组件前的DOM效果:
显示Alert组件后的DOM效果:
结论:由图1和图2可以看出,使用teleport组件,通过to属性,可以控制Alert组件渲染的位置,而Alert的显示是由内部组件Page控制的
二、新增选项:emits
场景:在Vue2.x中,子组件向父组件传递数据,一般通过$emit方式传递
举个例子:在上面的Alert.vue中,关闭Alert弹窗的时候,会调用closeAlert方法;同时在emits选项中,会添加‘on-close’
emits: ['onClose'],
methods: {
closeAlert() {
this.$emit('onClose')
}
}
如果将emits选项去掉,看下浏览器显示效果:
控制台会显示警告,大致意思是:组件内自定义事件,需要通过option:emits声明
emits作用:
1)用于声明子组件向父组件抛出的事件,便于其他开发人员知道该组件发出那些事件
2)可以对发出的事件进行验证
emits类型:Array
1)emits的值是简单的数组,如在Alert.vue中的定义
emits: ['onClose']
2)emits的值是Object,每个 property 的值可以为 null
或验证函数
父组件调用:
定义Child子组件,点击“提交”按钮,会触发submit()函数,同时会触发emits里面onSubmit的校验函数
姓名:
提交
三、新增和删除的全局API
1)vue2.x、vue3.x中的全局API对比,即vue3中全局API只保留了nextTick,其他的都是新增API;被移除的部分全局API,放到了应用实例上了
vue2.x的全局APi: vue3.x的全局API:
Vue.nextTick nextTick
Vue.extend defineComponent
defineAsyncComponent
resolveComponent
resolveDynamicComponent
Vue.directive resolveDirective
withDirective
Vue.set createApp
Vue.delete h
Vue.filter
Vue.component
Vue.use createRender
Vue.mixin
Vue.compile
Vue.observable
Vue.version
2)全局API的引进方式有差异,vue2通过默认导出一个对象,通过引用对象方法使用全局API;vue3通过具名导出方法名,使用全局API直接引进该方法名就可以使用了
vue2中使用全局API vue3中使用全局API
import Vue from 'vue' import { createApp, h } from 'vue'
Vue.component() createApp()
Vue.extend() h()
vue3中这种具名引用的方式,实现了更好的Tree-Shaking;
而vue2.x中,全局API是从Vue对象直接暴露出来的,只要我们引入了Vue对象,那么不管该对象上的全局API是否有用到,最终都会出现在我们线上的代码中
下面讲解下vue3中新增的全局API用法
1、createApp:返回一个提供应用上下文的应用实例。应用实例挂载的整个组件树共享同一个上下文;可以在 createApp
之后链式调用其它应用API
下面是vue2和vue3创建应用实例的比较:
1)vue3主要是通过createApp()创建应用实例,通过应用实例方法mount将应用实例的根组件挂载在提供的 DOM 元素上
2)vue2主要是通过newVue(options)创建应用实例,并在options里面设置相应的属性
// vue3
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
// vue2
import Vue from 'vue'
import App from './App'
import router from './router'
Vue.config.productionTip = false
new Vue({
el: '#app',
router,
components: { App },
template: ' '
})
2、h(type, props,children):返回一个”虚拟节点“,用于手动编写的渲染函数,生成组件
参数解析:
type:String | Object | Function(html标签名,组件,异步组件) 组件类型 必需
props:Object(和在模板中使用的 attribute、prop 和事件相对应) 组件属性 可选
children:String | Object |Array(子代 VNode) 组件子元素 可选
下面看一个例子:
let p = h('div', {}, [
'Some text comes first.',
h('h1', 'A headline'),
h(Page2, {
someProp: 'foobar'
})
])
console.log(p)
在控制台打印出来的p结果如下:
3、defineComponent:
官文解析:从实现上看,defineComponent
只返回传递给它的对象。但是,就类型而言,返回的值有一个合成类型的构造函数,用于手动渲染函数、TSX 和 IDE 工具支持
注意:在vue2中可以使用Vue.extend()生成组件,但在vue3中将该API移除了,取而代之的是defineComponent
作用:vue3 如果用ts,导出时候要用 defineComponent,这俩是配对的,为了类型的审查正确
4、defineAsyncComponent:创建异步组件,只在有需要的时候才会加载
1)在vue2中只在有需要的时候才加载异步组件的实现方式
page3
2)在Vue3中通过defineAsyncComponent加载异步组件实现方式
page3
通过1)、2)对比,可以看出仅仅是在定义异步组件的方式有点差异
但是defineAsyncComponent还有第二种用法,defineAsyncComponent({}),参数可以是以一个对象,官文解释如下:
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent({
// 工厂函数
loader: () => import('./Foo.vue')
// 加载异步组件时要使用的组件
loadingComponent: LoadingComponent,
// 加载失败时要使用的组件
errorComponent: ErrorComponent,
// 在显示 loadingComponent 之前的延迟 | 默认值:200(单位 ms)
delay: 200,
// 如果提供了 timeout ,并且加载组件的时间超过了设定值,将显示错误组件
// 默认值:Infinity(即永不超时,单位 ms)
timeout: 3000,
// 定义组件是否可挂起 | 默认值:true
suspensible: false,
/**
*
* @param {*} error 错误信息对象
* @param {*} retry 一个函数,用于指示当 promise 加载器 reject 时,加载器是否应该重试
* @param {*} fail 一个函数,指示加载程序结束退出
* @param {*} attempts 允许的最大重试次数
*/
onError(error, retry, fail, attempts) {
if (error.message.match(/fetch/) && attempts <= 3) {
// 请求发生错误时重试,最多可尝试 3 次
retry()
} else {
// 注意,retry/fail 就像 promise 的 resolve/reject 一样:
// 必须调用其中一个才能继续错误处理。
fail()
}
}
})
四、应用API
在vue3中,任何全局改变 Vue 行为的 API 都会移动到应用实例上,所有其他不全局改变行为的全局 API 都改成具名导出了
vue2.x全局API | vue3应用实例API |
Vue.component | app.component |
Vue.config | app.config |
Vue.config.productionTip | remove |
Vue.config.ignoredElements | app.config.isCustomElement |
Vue.directive | app.directive |
Vue.mixin | app.mixin |
Vue.use | app.use |
Vue.prototype | app.config.globalProperties |
app.mount | |
app.provide | |
app.unmount |
下面说下新增应用API和使用有改变的API
1、app.directive:自定义指令
1)vue3中directive定义(vue2同vue3)
参数:
{string} name
{Function | Object} [definition]
返回值:
如果传入 definition 参数,返回应用实例
如果不传入 definition 参数,返回指令定义
2)用法 :通过对比可以看出,调用的钩子有区别
2、app.mount:将应用实例的根组件挂载在提供的 DOM 元素上,并返回根组件实例
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount('#app')
3、app.unmount:在提供的 DOM 元素上卸载应用实例的根组件
// 挂载5秒后,应用将被卸载
setTimeout(() => app.unmount('#my-app'), 5000)
五、应用配置
vue3中的应用配置:可以在应用挂载前修改应用配置 property
import { createApp } from 'vue'
const app = createApp({})
app.config = {...}
1)vue2中的全局配置、vue3中的应用配置对比
vue3应用配置 vue2全局配置
errorHandler errorHandler
warnHandler warnHandler
optionMergeStrategies optionMergeStrategies
performance performance
// 新增配置 // vue3中删除的配置
globalProperties silent
isCustomElement devTools
ignoredElements
keyCodes
productionTip
2)配置使用方式差异:
vue2,直接在Vue.config对象上定义全局配置
vue3,直接在应用实例app.config上定义应用配置
说明:对于保留的配置项,除了挂靠对象不一样,其他都相同
// vue3应用配置
import { createApp } from 'vue'
const app = createApp({})
app.config.errorHandler = (err, vm, info) => {
// 处理错误
// `info` 是 Vue 特定的错误信息,比如错误所在的生命周期钩子
}
app.mount('#app')
// vue2全局配置
import Vue from 'vue'
Vue.config.errorHandler = function (err, vm, info) {
// handle error
// `info` 是 Vue 特定的错误信息,比如错误所在的生命周期钩子
// 只在 2.2.0+ 可用
}
new Vue({
el: '#app',
components: { App },
template: ' '
})
下面看下新增的应用配置
1、globalProperties:添加全局 property,任何组件实例都可以访问该属性;属性名冲突时,组件的 property 将具有优先权
作用:可以代替vue2中Vue.prototype扩展
// Vue 2.x
Vue.prototype.$http = () => {}
// Vue 3.x
const app = createApp({})
app.config.globalProperties.$http = () => {}
举个例子:
// main.js中定义全局应用属性
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.config.globalProperties.pageTitle = 'Page2'
// 在Page2.vue中使用应用属性pageTitle
// 有pageTitle,则渲染
{{pageTitle}}
2、isCustomElement:指定一个方法,用来识别在 Vue 之外定义的自定义元素(例如,使用 Web Components API)。如果组件符合此条件,则不需要本地或全局注册,并且 Vue 不会抛出关于 Unknown custom element
的警告
类型: (tag: string) => boolean
// 任何以“ion-”开头的元素都将被识别为自定义元素
app.config.isCustomElement = tag => tag.startsWith('ion-')
六、选项:生命周期
vue3和vue2的选项生命周期对比,从下图可以看出:vue3将vue2中的beforeDestroy、destroyed改成beforeUnmount、unmounted其实只是语义上的改变而已
vue3中新增的选项生命周期钩子还有:renderTracked renderTriggere,具体用法可以参考官文
七、移除的实例property
结论:由对比图可以看出,在vue3删除了vue2.x中的实例property有:$children $scopedSlots $isServer $listeners
下面说下移除这些实例的property原因
1、移除实例propety:$children
在vue2.x中通过$children,可以获取到当前组件的所有子组件的全部实例,所以通过$children可以访问子组件的data、methods等,常用于父子组件通讯
在vue3中可以通过$refs访问子组件实例
下面举个例子看看
1)vue3示例
App.vue页面
在控制台打印结果:
vue2,在控制台打印效果:
结论:通过上图可以看出,两者获取到的数据是一样的,只不过返回的数据结构不一样罢了,因此在vue3中将实例property:$children去掉,改用$refs获取
2、 移除实例property:$scopedSlots
vue3中将$slots和$scopedSlots统一成$slots,废除$scopedSlots
3、移除实例property:$listeners
先说下vue2.4以上版本中的$attrs、$listeners属性的用法
vm.$attrs
类型:{ [key: string]: string }
包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定
(class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件
vm.$listeners
类型:{ [key: string]: Function | Array }
包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件
场景:如下图,当组件page1需要向page3传递数据,可以通过$attrs传递数据;page3向page1传递数据可以通过$listeners实现
代码实现:
1)组件page1.vue代码,在组件page2中定义两个prop:title、tips;两个事件:on-update、on-focus
page1
2)组件page2代码,在props上定义了title属性,在组件page3中绑定$attrs(会将父作用域即page1中的tips传递给组件page3),$listeners(会将父作用域即page1中的事件监听器on-update,on-focus传给组件page3)
{{title}}
3)page3代码,定义的props:tips;$emits事件有on-focus,on-update
{{tips}}
改变标题
在vue3中,只需要在page2.vue中的page3不绑定$listeners,其他同vue2,得到的效果也是一样的
结论:vue3中将事件监听器认为是$attrs的一部分,因此将$listeners移除
4、移除实例property:$isServer
八、移除的实例方法
下图是vue3和vue2.x的实例方法对比图
从上图可以看出在vue3中移除了实例方法有:
// 数据
$set
$delete
// 事件
$on
$off
$once
// 生命周期
$mount
$destroy
下面说下废除这些实例方法的原因
1、废除$set、$delete
问题:在vue2中实现响应式数据是通过Object.defineProperty实现的,如果在页面挂载完成通过索引修改数组的数据,或者给对象新增属性,视图是不会重新渲染的,因此vue2就提供了$set方法用于解决该问题
vue3删除$set的原因:vue3是通过proxy实现响应式数据的,是对整个对象的监听,因此通过索引修改数组的数据,或者给对象新增属性,视图是会重新渲染的;同理废除了$delete
2、废除$on、$off、$once
在vue2中常用$on $off $once创建 event hub,以创建在整个应用程序中使用的全局事件侦听器,实现数据共享
vue3可以用外部库mitt来代替 $on $emit $off
九、指令
新增指令
1、v-is:类似于动态 2.x :is
绑定——因此要按组件的注册名称渲染组件,其值应为 JavaScript 字符串文本
下面举一个例子:
App.vue页面
改变页面使用的组件
页面初识化显示如图9-1,点击按钮
十、组合式API和响应性API
下图是新增的组合式API和响应性API
下面看下具体的API用法
组合式API
1、setup
1)类型:Function,是Vue3.x新增的选项;作为在组件内部使用组合式 API 的入口点(即组合式API和响应式API都在setup函数里面使用)
2)执行时机:在创建组件实例时,在初始 prop 解析之后立即调用 setup
。在生命周期方面,它是在 beforeCreate 钩子之前调用的
下面看个例子
export default {
beforeCreate() {
console.log('beforeCreate')
},
created() {
console.log('created')
},
setup() {
console.log('setup')
}
}
控制台输出结果:
3)参数: 接受两个参数---props和context
props:组件传入的属性,是响应式的,不能用ES6解构,否则会消除响应式
context:上下文对象,该对象有三个属性:slots、attrs、emit分别对应vue2中的this.$slots,this.$attrs,this.emit。由于setup中不能访问this对象,所以context提供了vue2中this常用的这三个属性,并且这几个属性都是自动同步最新的值
export default {
props: {
title: String
},
setup(props, context) {
console.log('props.title: ', props.title)
console.log('context.$attrs: ', context.$attrs)
console.log('context.$emit: ', context.$emit)
console.log('context.$slots: ', context.$slots)
let { title } = props.title // title失去响应性
}
}
4)返回值:对象、渲染函数h/JSX的方法
返回对象
标题:{{title}}
count:{{count}}
object.foo:{{object.foo}}
改变title的值
渲染函数h/JSX的方法
2、在setup函数中使用的生命周期钩子
从下图可以看出,在setup中使用的生命周期钩子都增加了on;beforeCreated和created被setup替换,但是在vue3中仍然可以使用beforeCreated、created
下面是在setup中使用生命周期钩子(钩子需要引入)的示例
标题:{{title}}
改变title的值
3、getCurrentInstance
获取当前组件的实例,只能在setup、setup中使用的生命周期钩子中使用
下面是一个demo
显示效果如下:在setup中可以直接使用getCurrentInstance
getCurrentInstance()返回的对象如下所示:
当点击uid:1的时候,控制台显示如下:
在handleClick中直接使用getCurrentInstance(),返回为null;由于handleClick中调用getCurrentInstance返回null,因此在去读返回结果的属性,浏览器就会报错
响应式API
4、reactive、ref、toRef、toRefs
在vue2.x中, 数据一般都定义在data
中, 但Vue3.x 可以使用reactive
和ref
来进行数据定义
既然reactive
和ref都可以进行数据定义,那他们的区别是什么?使用时机是什么时候?
下面先看下ref、reactive的基本定义
1)ref:接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象具有指向内部值的单个 property .value
注意:
a)在JS中访问ref定义的数据,需要通过:属性名.value方式访问
b)在DOM中访问ref定义的数据,不需要通过.value方式访问
下面是一个demo:
data中的msg:{{msg}}
setup中的
count:{{count}}
obj.num:{{obj.num}}
obj.name:{{obj.name}}
页面初始化显示效果:
8s后的显示效果
注意:从上面的demo代码可以看出,
a)ref可以代理对象和基本类型,例如字符串、数字、boolean等;
b)选型data定义的数据和组合式API中的ref定义的数据是共存的;vue3之所以推出ref来定义数据,是为了将数据、和方法放在一块,便于代码的维护等
问题:如果在data和ref中定义相同的变量会发生什么呢?
将上面的代码修改如下,只是在setup中定义一个同data中的msg变量,并返回
data中的msg:{{msg}}
setup中的
count:{{count}}
obj.num:{{obj.num}}
obj.name:{{obj.name}}
控制台抛错如下:
40:7 error Duplicated key 'msg' vue/no-dupe-keys
即data和ref中定义的返回数据不能同名
2)reactive:返回对象的响应式副本,但不能代理基本类型,例如字符串、数字、boolean等
下面看个demo
obj.num:{{obj.num}}
obj.name:{{obj.name}}
count:{{count}}
页面初始化效果:
8s后显示效果
通过上面两张图可以看出:reactive可以代理一个对象,并且是响应式的;但不能代理基本类型,否则控制台会提示定义的变量不是响应式的
因此我们可以得出ref和reactive定义的数据的区别和使用时机:
a)ref返回对象或者基础类型的响应式副本;在JS中访问ref定义的数据,需要通过:属性名.value方式访问
b)reactive只返回对象的响应式副本
c)当需要定义响应式对象的数据的时候,可以用ref和reactive定义变量;当需要定义响应式的基础类型数据的时候,用ref定义变量
3)toRefs
定义:将响应式对象转为普通对象,结果对象的每个 property 都是指向原始对象相应 property 的ref
在上面的demo中,在DOM中需要通过obj.num,obj.name访问对象的属性,这样写有点麻烦,我们可以将obj的属性结构出来吗?答案是:否,因为会消除他的响应式
问题:如果我们就是想在DOM中用结构后的数据,怎么办呢?答案是使用toRefs
下面看个demo
obj.num:{{obj.num}}
obj.name:{{obj.name}}
num:{{num}}
name:{{name}}
页面初始化显示效果如下:
8s后显示效果如下:
14s后显示效果如下:
结论:使用toRefs可以在DOM中使用对象结构出来的数据,并且还保持着响应式性质
4)toRef
定义:可以用来为源响应式对象上的 property 性创建一个 ref
。然后可以将 ref 传递出去,从而保持对其源 property 的响应式连接
下面看个demo
obj.num:{{obj.num}}
obj.name:{{obj.name}}
nameRef:{{nameRef}}
页面初始化渲染效果如下:
8s后的效果如下:
14s后的效果如下:
结论:
a)toRef是为源响应式对象上的具体的 property 创建一个 ref
,并且保持对其源 property 的响应式连接;
b)toRefs将响应式对象转为普通对象,结果对象的每个 property 都是指向原始对象相应 property 的ref
5)shallowReactive
定义:
创建一个响应式代理,该代理跟踪其自身 property 的响应性,但不执行嵌套对象的深度响应式转换 (暴露原始值)
demo1:
控制台显示效果:
当修改count的值时,会触发watchEffect
demo2:
name:{{original.info.name}}
当修改original.info.name的时候,不会触发watchEffect
6)isReactive
定义:检查对象是否是 reactive
创建的响应式 proxy;如果 proxy 是 readonly
创建的,但还包装了由 reactive
创建的另一个 proxy,它也会返回 true
7)isProxy
定义:检查对象是 reactive
还是 readonly
创建的代理
返回值:Boolean,true--是reactive创建的代理,false-
是 readonly
创建的代理
8)unref-----拆出原始值的语法糖
定义:如果参数为 ref
,则返回内部值value,否则返回参数本身;它是 val = isRef(val) ? val.value : val的语法糖
9)isRef
定义:检查一个值是否为ref对象
10)shallowRef
定义:创建一个 ref,它跟踪自己的 .value
更改,但不会使其值成为响应式的
下面看个用shallowRef定义一个对象的demo
obj.name:{{obj.name}}
obj.jobInfo.companyName:{{obj.jobInfo.companyName}}
页面初始化效果如下:
8s后的效果:
14s后的效果:
结论:从上面的效果图可以看出,shallowRef定义的对象型数据,没有响应性;但是如果给该对象的value重新赋值,可以在DOM中更新
注意:shallowRef定义的一般类型数据仍然具有响应性
问题:如果在代码中同时用shallowRef定义一般类型和object类型数据,修改一般类型属性的值,能在DOM中变更吗?
如下demo中用shallowRef同时定义了一般类型和object类型数据
obj.name:{{obj.name}}
obj.jobInfo.companyName:{{obj.jobInfo.companyName}}
count:{{count}}
页面初始化渲染效果:
8s后的效果:即数据更新了,DOM中的数据也跟着更新了-------即用shallowRef定义的一般类型数据,具有响应式
14s后的效果:
11)triggerRef
定义:手动执行与 shallowRef
关联的任何效果
问题:上述demo中,用shallowRef定义一个object,然后修改其property,在DOM中没有更新;如果我们想用shallowRef定义object数据,同时修改property值后在DOM中更新,怎么办呢?
答案:使用triggerRef
obj.name:{{obj.name}}
obj.jobInfo.companyName:{{obj.jobInfo.companyName}}
页面初始化效果:
8s后的效果:
12)customRef
定义:创建一个自定义的 ref,并对其依赖项跟踪track和更新触发trigger进行显式控制
参数:它需要一个工厂函数,该函数接收 track
和 trigger
函数作为参数
返回值:返回一个带有 get
和 set
的对象
下面看一个demo:使用 v-model
、自定义 ref 实现值绑定的示例
页面初始化效果:
改变输入框的值,显示:
13)computed
a)参数是 getter 函数,并为从 getter 返回的值返回一个不变的响应式 ref 对象,即不可直接修改其value值
b)参数具有 get
和 set
函数的对象,来创建可写的 ref 对象
下面看下参数是getter函数demo
plusOne:{{plusOne}}
页面显示效果:
下面看下参数是具有 get
和 set
函数的对象的demo
count:{{count}}
plusOne:{{plusOne}}
页面初始化效果:
8s后显示效果:
14)watch、watchEffect
watch定义:watch
API 与选项式 API this.$watch (以及相应的 watch 选项) 完全等效。watch
需要侦听特定的 data 源,并在单独的回调函数中副作用。默认情况下,它也是惰性的——即,回调是仅在侦听源发生更改时调用
watch(source, callback, [options])
参数说明:
source:ref,reactive Object,getter/effect function,Array(item是前面几个类型);用于指定要侦听的响应式变量
callback:执行的回调函数
option:支持deep、immediate 和 flush 选项
返回值:返回停止监听的函数
侦听单一数据源: data 源可以是返回值的 getter 函数,也可以是 ref
a)侦听一个getter:侦听active对象的某个属性
name:{{name}}
age:{{age}}
页面初识化效果:
7s后的效果:
如果将watch里面的source改成直接监听reactive的prop,其余代码不变,会发生什么呢?
watch(userInfo.name, (cur, pre) => {
console.log('curName: ', cur)
console.log('preName: ', pre)
})
控制台会警告:watch的source只能是下面的形式:
A watch source can only be a getter/effect function, a ref, a reactive object, or an array of these types
getter函数侦听的是ref对象的属性
const person = ref({
name: '1',
age: 1
})
setTimeout(() =>{
person.value.name = 2
}, 100)
watch(() => person.value.name, (n, o) => {
console.log(n)
console.log(o)
})
输出1 2
b)直接侦听一个ref
普通数据类型
count:{{count}}
页面初识化效果:
7s后的效果:
直接侦听一个ref引用类型对象:
setup() {
const person = ref({
name: '1',
age: 1
})
setTimeout(() =>{
person.value.name = 2
}, 100)
watch(person, (n, o) => {
console.log(n, o)
})
}
像这样是监听不到的,需要在watch里面添加第三个参数:deep:true
但是只能拿到新的值,旧的值拿不到,即n. o的值一样都是变化后的值
watch(person, (n, o) => {
console.log(n)
console.log(o)
}, {deep: true})
直接侦听ref对象的某个属性,会抛类型错误
c)直接侦听reactive
count:{{count}}
name:{{userInfo.name}}
age:{{userInfo.age}}
页面初始化效果:
2s后的显示效果如下:
可以看到:如果直接监听一个reactive,那么只会返回变化的值,之前的状态拿不到
如果reactive对象内属性是嵌套多层的对象,那么在watch中不添加第三个参数deep:true,也是可以监听到变化的,因为reactive类型是响应式的,内部是直接代理整个对象,当里面属性发生变化的时候,会反应出来的
侦听多个数据源(source使用数组)
上面两个例子中,我们分别使用了两个watch, 当我们需要侦听多个数据源时, 可以进行合并, 同时侦听多个数据
count:{{count}}
name:{{userInfo.name}}
age{{userInfo.age}}
页面初始化的时候效果:
2s后的显示效果:
侦听复杂的嵌套对象(使用第三个参数:options)
const info = reactive({
room: {
id: 200,
attrs: {
size: "400平方米",
type:"三室两厅"
}
}
});
watch(() => info.room, (newType, oldType) => {
console.log("新值:", newType, "老值:", oldType)
}, { deep: true, immediate: true })
监听复杂的嵌套对象,如果不使用第三个参数deep:true
, 是无法监听到数据变化的
默认情况下,watch是惰性的
那怎么样可以立即执行回调函数呢?答案是: 给第三个参数设置immediate: true
即可
停止侦听
在组件中创建的watch
监听,会在组件被销毁时自动停止。如果在组件销毁之前我们想要停止掉某个监听, 可以调用watch()
函数的返回值
const stopWatchCount = watch(
count,
(curCount, preCount) => {
console.log("count新值:", curCount, "count老值:", preCount)
}
)
setTimeout(() => {
stopWatchCount()
}, 100000)
watchEffect
定义:在响应式地跟踪其依赖项时立即运行一个函数,并在更改依赖项时重新运行它
count:{{count}}
name:{{name}}
age:{{age}}
num:{{num}}
从控制台打印信息可以看出,watchEffect里面的方法执行了两次,一次是页面渲染完成的时候,另一次是过了7s后修改值的时候
watchEffect可以监听到复杂数据吗?
count:{{count}}
name:{{name}}
age:{{age}}
num:{{num}}
页面初识化效果如下:
8s后的效果:
可以看到DOM更新了,但是watchEffect里面没有监听到数据的变化,即watchEffect里面监听不到复杂数据的变化
watch与watchEffect的区别
a)watchEffect 不需要手动传入依赖
b)watchEffect 会先执行一次用来自动收集依赖
c)watchEffect 无法获取到变化前的值, 只能获取变化后的值
12)readonly
定义:获取一个对象 (响应式或纯对象) 或 ref ,返回原始代理的只读代理。只读代理是深层的:访问的任何嵌套 property 也是只读的
控制台显示:
说明:执行setup函数的化,马上执行一次watchEffect函数里面的回调,打印: copy.count: 0;接着执行:original.count++,马上触发watchEffect函数里面的回调,打印: copy.count: 1;8s后对只读对象copy.count++进行加1,控制台会提示,目标表象是只读的,不能对其修改
结论:
a)readonly返回一个只读代理,不能直接对其做修改;
b)如果其原始代理做了修改,只读代理也会跟着改变;
c)如果原始代理是响应式的,或者ref,可以在watchEffect中监听到只读代理的变化
15)shallowReadonly
定义:创建一个代理,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换 (暴露原始值)
demo1:
demo2:
通过demo1和demo2的对比,可以看出:对于响应式的原对象,shallowReadonly返回的对象,可以修改其嵌套对象里面的属性值,即只读只对其首层属性有作用
demo3:
demo4:
通过demo3和demo4可知,对于原对象是ref,shallowReadonly返回的只读对象代理,可以直接修改只读对象的属性
16)isReadonly:
检查对象是否是由readonly
创建的只读代理
17)markRaw
定义:标记一个对象,使其永远不会转换为代理。返回对象本身
const foo = markRaw({})
console.log(isReactive(reactive(foo))) // false
// 嵌套在其他响应式对象中时也可以使用
const bar = reactive({ foo })
console.log(isReactive(bar.foo)) // false
18)toRaw
返回 reactive
或 readonly
代理的原始对象。这是一个转义口,可用于临时读取而不会引起代理访问/跟踪开销,也可用于写入而不会触发更改。不建议保留对原始对象的持久引用。请谨慎使用
const foo = {}
const reactiveFoo = reactive(foo)
console.log(toRaw(reactiveFoo) === foo) // true
十一、简单说下vue2.x和vue3.x的响应式
我们都知道vue2.x中实现数据的响应式重要的一环是Object.defineProperty,但在vue3中确使用Proxy代替了它
下面做个简单的对比:
1)Object.defineProperty只对对象的属性进行劫持,因此需要遍历对象的属性,如果属性是对象就还得递归,进行深度遍历;Proxy是直接代理对象,因此不需要递归
2)Object.defineProperty对新增属性,需要手动Observe;因此在vue2.x中,给数组或对象新增属性时,需要$set,才能保证新增属性是响应式,而$set内部也是通过Object.defineProperty进行处理
十二、Fragment片段
在vue2.x中,template下只允许有一个根节点;而vue3.x可以有多个根节点
test
test
test
test
十三、v-model用法升级
v-model的用法变化如下:
1)变更:在自定义组件上使用v-model
时, 属性以及事件的默认名称变了
2)变更:v-bind
的.sync
修饰符在 Vue 3 中被去掉了, 合并到了v-model
里
3)新增:同一组件可以同时设置多个 v-model
4)新增:开发者可以自定义 v-model
修饰符
1)在自定义组件上使用v-model
时, 属性以及事件的默认名称变了
vue2.x中,使用v-model绑定的默认属性:value;默认事件:input
vue3.x中,使用v-model绑定的默认属性:modelValue;默认事件:update:modelValue
// Vue2.x中, 在组件上使用 v-model相当于传递了value属性, 并触发了input事件
// vue3.x中,在组件上使用v-model相当于传递modelValue属性,并触发update:modelValue事件
2)变更:v-bind
的.sync
修饰符在 Vue 3 中被去掉了, 合并到了v-model
里
举一个双向绑定的例子:
在vue2.x中,一个modal弹窗,外部可以控制组件的属性visible,达到弹窗的显示、隐藏效果;组件内部可以控制visible属性隐藏,并将visible属性同步传输到外部
组件内部, 当我们关闭modal
时, 在子组件中以update:visible模式触发事件:
this.$emit('update:visible', false)
然后父组件可以监听这个事件进行数据更新
// 相当于
在vue3中,可以给v-model传递属性visible,达到绑定属性的目的
从而可以看出,Vue 3 中抛弃了.async
写法, 统一使用v-model
3)新增:同一组件可以同时设置多个 v-model
从上面的例子可以看出同一个组件可以设置多个v-model
十四、将具名插槽slot、作用域插槽slot-scope改成统一使用v-slot(vue2.6.0就有了)
在vue2中使用具名插槽
歌曲:成都
在vue2中使用作用域插槽(在slot上绑定数据)
// 子组件
export default {
data(){
return{
lession:['English', 'Chinese']
}
}
}
{{item}}
vue3中使用v-slot实现具名插槽和作用域插槽
// 子组件
export default {
data(){
return{
lession:['English', 'Chinese']
}
}
}
{{item}}
{{item}}
下面看下v-slot的具体用法
1)默认插槽
a)如果不设置元素的name属性,那么出口会带有隐含的名字:default;注意:默认插槽的缩写语法不能和具名插槽混用,因为它会导致作用域不明确
b)v-slot:可以简写为#;注意:简写方式只适用于有参数适合有效???
c)v-slot只能用于元素上;除了当被提供的内容只有默认插槽时,组件的标签才可以被当作插槽的模板来使用
<-- 子组件Child中 -->
<-- 父组件Parent中 -->
default
<-- 父组件Parent中写法2 -->
default
<-- 父组件Parent中写法3,有效 -->
<-- 但这与文档中说法有矛盾:文档说这种写法无效 -->
default
2)具名插槽
给元素设置name属性
<-- 子组件Child中 -->
<-- 父组件Parent中 -->
header
main
footer
3)作用域插槽
场景:在父组件作用域中访问子组件数据
<-- 子组件Child中 -->
<-- 父组件Parent中 -->
{{slotProps.lession}}
4)解构插槽Props
<-- 子组件Child中 -->
<-- 父组件Parent中 -->
{{ lession }}
<-- 父组件Parent中写法2,prop 重命名 -->
<-- 父组件Parent中写法2,lession重命名为class -->
{{ class }}
<-- 父组件Parent中写法3,定义后备内容 -->
<-- 父组件Parent中写法2,lession重命名为class -->
{{ lession }}
5)动态插槽名
<-- 父组件Parent中 -->
{{ lession }}
// 在父组件中,根据不同条件设置dynamicName的值,来展示相应的插槽内容
参考文章:
1、vue3中文文档:https://vue3js.cn/docs/zh/api/global-api.html#%E5%8F%82%E6%95%B0
2、vue3英文文档:https://v3.vuejs.org/guide/component-custom-events.html#validate-emitted-events
3、vue3中文文档:https://vue3js.cn/docs/zh/guide/migration/render-function-api.html#%E6%B8%B2%E6%9F%93%E5%87%BD%E6%95%B0%E7%AD%BE%E5%90%8D%E6%9B%B4%E6%94%B9
1、关于vue3中的render方法:https://juejin.cn/post/6844904205426098183
3、Vue3.0 新特性以及使用变更总结(实际工作用到的):https://mp.weixin.qq.com/s/OKCxvrUPoPM0hR-z9reESA
4、vue3全局API变更:https://blog.csdn.net/qq_38290251/article/details/112412522