包裹自定义的alert组件,to属性:是有效的查询选择器.._vue3 new vue">
当前位置:   article > 正文

vue3新特性和新用法_vue3 new vue

vue3 new vue

下面是关于vue3的一些新特性和新用法的讲解

一、新增内置组件:teleport

官文解释teleport:teleport 提供了一种干净的方法,允许我们控制在 DOM 中哪个父节点下呈现 HTML,而不必求助于全局状态或将其拆分为两个组件

场景:在组件A中使用alert弹窗,但不希望alert弹窗渲染的DOM结构嵌套在组件A中;而是渲染在我们指定的位置上

实现方式

    1)使用<teleport to="#alert">包裹自定义的alert组件,to属性:是有效的查询选择器或 HTMLElement

     2)  指定alert渲染的DOM结构位置,写上<div id="alert"></div>

注意:to属性和id的值保持一致

下面举个例子:

a)首先:定义Alert.vue

  1. <template>
  2. <teleport to="#alert">
  3. <div class="alert">
  4. <div class="alert-mask" @click="closeAlert"></div>
  5. <div class="alert-content">
  6. <div class="alert_header" v-if="title">
  7. <slot name="header">
  8. <h3>{{title}}</h3>
  9. </slot>
  10. </div>
  11. <div class="alert_content">
  12. <slot></slot>
  13. </div>
  14. <div class="alert_footer">
  15. <slot name="footer"></slot>
  16. </div>
  17. </div>
  18. </div>
  19. </teleport>
  20. </template>
  21. <script>
  22. export default {
  23. name: 'alert',
  24. props: {
  25. title: {
  26. type: String,
  27. default: '测试alert组件'
  28. }
  29. },
  30. emits: ['onClose'],
  31. methods: {
  32. closeAlert() {
  33. this.$emit('onClose')
  34. }
  35. }
  36. }
  37. </script>

b)其次:在组件Page.vue中使用Alert弹窗

  1. <template>
  2. <div class="page">
  3. <div class="page-btn">
  4. <div @click="showAlert" class="btn-item">显示Alert</div>
  5. </div>
  6. <Alert v-if="isShowAlert" @on-close="closeAlert">
  7. <div>
  8. 只是测试,只是测试,只是测试,只是测试,只是测试,只是测试,只是测试,只是测试,
  9. </div>
  10. </Alert>
  11. </div>
  12. </template>

c)最后:指定Alert最终渲染的位置。这里指定与App.vue里面的元素同级。App.vue内容如下:

  1. <template>
  2. <div class="app">
  3. <p class="app-title">App.vue页面</p>
  4. <div id="alert"></div>
  5. <Page />
  6. </div>
  7. </template>

下面看下渲染效果:

未显示Alert组件前的DOM效果:

                                                                                                                    

显示Alert组件后的DOM效果:

                                                                                                                           

结论:由图1和图2可以看出,使用teleport组件,通过to属性,可以控制Alert组件渲染的位置,而Alert的显示是由内部组件Page控制的

二、新增选项:emits

场景:在Vue2.x中,子组件向父组件传递数据,一般通过$emit方式传递

举个例子:在上面的Alert.vue中,关闭Alert弹窗的时候,会调用closeAlert方法;同时在emits选项中,会添加‘on-close’

  1. emits: ['onClose'],
  2. methods: {
  3. closeAlert() {
  4. this.$emit('onClose')
  5. }
  6. }

如果将emits选项去掉,看下浏览器显示效果:

控制台会显示警告,大致意思是:组件内自定义事件,需要通过option:emits声明

emits作用

1)用于声明子组件向父组件抛出的事件,便于其他开发人员知道该组件发出那些事件

2)可以对发出的事件进行验证

emits类型:Array<string> | Object

1)emits的值是简单的数组,如在Alert.vue中的定义

emits: ['onClose']

2)emits的值是Object,每个 property 的值可以为 null 或验证函数

父组件调用:

<Child @on-submit="onSubmit"/>

定义Child子组件,点击“提交”按钮,会触发submit()函数,同时会触发emits里面onSubmit的校验函数

  1. <template>
  2. <div class="child">
  3. <div class="content">
  4. <p>姓名:</p>
  5. <input type="text" v-model="value" class="content-value">
  6. </div>
  7. <div class="btn" @click="submit">提交</div>
  8. </div>
  9. </template>
  10. <script>
  11. export default {
  12. name: 'child',
  13. data() {
  14. return {
  15. value: ''
  16. }
  17. },
  18. emits: {
  19. onSubmit: payload => {
  20. // payload为$emit触发的onSubmit事件的参数
  21. if (payload) {
  22. return true
  23. } else {
  24. return false
  25. }
  26. }
  27. },
  28. methods: {
  29. submit() {
  30. this.$emit('onSubmit', this.value)
  31. }
  32. }
  33. }
  34. </script>

三、新增和删除的全局API

1)vue2.x、vue3.x中的全局API对比,即vue3中全局API只保留了nextTick,其他的都是新增API;被移除的部分全局API,放到了应用实例上了

  1. vue2.x的全局APi: vue3.x的全局API:
  2. Vue.nextTick nextTick
  3. Vue.extend defineComponent
  4. defineAsyncComponent
  5. resolveComponent
  6. resolveDynamicComponent
  7. Vue.directive resolveDirective
  8. withDirective
  9. Vue.set createApp
  10. Vue.delete h
  11. Vue.filter
  12. Vue.component
  13. Vue.use createRender
  14. Vue.mixin
  15. Vue.compile
  16. Vue.observable
  17. Vue.version

2)全局API的引进方式有差异,vue2通过默认导出一个对象,通过引用对象方法使用全局API;vue3通过具名导出方法名,使用全局API直接引进该方法名就可以使用了

  1. vue2中使用全局API vue3中使用全局API
  2. import Vue from 'vue' import { createApp, h } from 'vue'
  3. Vue.component() createApp()
  4. 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里面设置相应的属性

  1. // vue3
  2. import { createApp } from 'vue'
  3. import App from './App.vue'
  4. createApp(App).mount('#app')
  5. // vue2
  6. import Vue from 'vue'
  7. import App from './App'
  8. import router from './router'
  9. Vue.config.productionTip = false
  10. new Vue({
  11. el: '#app',
  12. router,
  13. components: { App },
  14. template: '<App/>'
  15. })

2、h(type, props,children):返回一个”虚拟节点“,用于手动编写的渲染函数,生成组件

参数解析:

type:String | Object | Function(html标签名,组件,异步组件)       组件类型          必需

props:Object(和在模板中使用的 attribute、prop 和事件相对应)    组件属性         可选

children:String | Object |Array(子代 VNode)                             组件子元素     可选

下面看一个例子:

  1. let p = h('div', {}, [
  2. 'Some text comes first.',
  3. h('h1', 'A headline'),
  4. h(Page2, {
  5. someProp: 'foobar'
  6. })
  7. ])
  8. console.log(p)

在控制台打印出来的p结果如下:

3、defineComponent

官文解析:从实现上看,defineComponent 只返回传递给它的对象。但是,就类型而言,返回的值有一个合成类型的构造函数,用于手动渲染函数、TSX 和 IDE 工具支持

注意:在vue2中可以使用Vue.extend()生成组件,但在vue3中将该API移除了,取而代之的是defineComponent

作用:vue3 如果用ts,导出时候要用 defineComponent,这俩是配对的,为了类型的审查正确

  1. <script lang="ts">
  2. import { defineComponent } from 'vue'
  3. export default defineComponent({
  4. name: 'Page3',
  5. props: {
  6. msg: String
  7. },
  8. setup(props, ctx) {
  9. console.log(props, ctx)
  10. }
  11. })
  12. </script>

4、defineAsyncComponent:创建异步组件,只在有需要的时候才会加载

1)在vue2中只在有需要的时候才加载异步组件的实现方式

  1. <template>
  2. <div class="page3">
  3. <h2 v-if="isShowPage">page3</h2>
  4. <AsyncPage2 v-else-if="isShowPage2"/>
  5. </div>
  6. </template>
  7. <script>
  8. export default {
  9. name: 'Page3',
  10. components: {
  11. AsyncPage2: () => import('./Page2') // 定义异步组件
  12. },
  13. data() {
  14. return {
  15. isShowPage2: false,
  16. isShowPage: false
  17. }
  18. },
  19. mounted() {
  20. // 控制是否加载异步组件
  21. this.isShowPage2 = true
  22. }
  23. }
  24. </script>

2)在Vue3中通过defineAsyncComponent加载异步组件实现方式

  1. <template>
  2. <div class="page3">
  3. <h2 v-if="isShowPage">page3</h2>
  4. <AsyncPage2 v-else-if="isShowPage2"/>
  5. </div>
  6. </template>
  7. <script>
  8. import { defineAsyncComponent } from 'vue'
  9. export default {
  10. name: 'Page3',
  11. components: {
  12. AsyncPage2: defineAsyncComponent(() => import('./Page2'))
  13. },
  14. data() {
  15. return {
  16. isShowPage2: false,
  17. isShowPage: false
  18. }
  19. },
  20. mounted() {
  21. this.isShowPage = true
  22. }
  23. }
  24. </script>
  25. <style>
  26. </style>

通过1)、2)对比,可以看出仅仅是在定义异步组件的方式有点差异

但是defineAsyncComponent还有第二种用法,defineAsyncComponent({}),参数可以是以一个对象,官文解释如下:

  1. import { defineAsyncComponent } from 'vue'
  2. const AsyncComp = defineAsyncComponent({
  3. // 工厂函数
  4. loader: () => import('./Foo.vue')
  5. // 加载异步组件时要使用的组件
  6. loadingComponent: LoadingComponent,
  7. // 加载失败时要使用的组件
  8. errorComponent: ErrorComponent,
  9. // 在显示 loadingComponent 之前的延迟 | 默认值:200(单位 ms)
  10. delay: 200,
  11. // 如果提供了 timeout ,并且加载组件的时间超过了设定值,将显示错误组件
  12. // 默认值:Infinity(即永不超时,单位 ms)
  13. timeout: 3000,
  14. // 定义组件是否可挂起 | 默认值:true
  15. suspensible: false,
  16. /**
  17. *
  18. * @param {*} error 错误信息对象
  19. * @param {*} retry 一个函数,用于指示当 promise 加载器 reject 时,加载器是否应该重试
  20. * @param {*} fail 一个函数,指示加载程序结束退出
  21. * @param {*} attempts 允许的最大重试次数
  22. */
  23. onError(error, retry, fail, attempts) {
  24. if (error.message.match(/fetch/) && attempts <= 3) {
  25. // 请求发生错误时重试,最多可尝试 3
  26. retry()
  27. } else {
  28. // 注意,retry/fail 就像 promise 的 resolve/reject 一样:
  29. // 必须调用其中一个才能继续错误处理。
  30. fail()
  31. }
  32. }
  33. })

四、应用API

在vue3中,任何全局改变 Vue 行为的 API 都会移动到应用实例上,所有其他不全局改变行为的全局 API 都改成具名导出了

vue2.x全局APIvue3应用实例API
Vue.componentapp.component
Vue.configapp.config
Vue.config.productionTipremove
Vue.config.ignoredElementsapp.config.isCustomElement
Vue.directiveapp.directive
Vue.mixinapp.mixin
Vue.useapp.use
Vue.prototypeapp.config.globalProperties
app.mount
app.provide
app.unmount

下面说下新增应用API和使用有改变的API

1、app.directive:自定义指令

1)vue3中directive定义(vue2同vue3)                                                                        

  1. 参数:     
  2. {string} name
  3. {Function | Object} [definition]   
  4. 返回值:
  5. 如果传入 definition 参数,返回应用实例     
  6. 如果不传入 definition 参数,返回指令定义

2)用法 :通过对比可以看出,调用的钩子有区别      

 

2、app.mount:将应用实例的根组件挂载在提供的 DOM 元素上,并返回根组件实例

  1. import { createApp } from 'vue'
  2. import App from './App.vue'
  3. const app = createApp(App)
  4. app.mount('#app')

3、app.unmount:在提供的 DOM 元素上卸载应用实例的根组件

  1. // 挂载5秒后,应用将被卸载
  2. setTimeout(() => app.unmount('#my-app'), 5000)

五、应用配置

vue3中的应用配置:可以在应用挂载前修改应用配置 property

  1. import { createApp } from 'vue'
  2. const app = createApp({})
  3. app.config = {...}

1)vue2中的全局配置、vue3中的应用配置对比

  1. vue3应用配置 vue2全局配置
  2. errorHandler errorHandler
  3. warnHandler warnHandler
  4. optionMergeStrategies optionMergeStrategies
  5. performance performance
  6. // 新增配置 // vue3中删除的配置
  7. globalProperties silent
  8. isCustomElement devTools
  9. ignoredElements
  10. keyCodes
  11. productionTip

2)配置使用方式差异:

     vue2,直接在Vue.config对象上定义全局配置

     vue3,直接在应用实例app.config上定义应用配置

    说明:对于保留的配置项,除了挂靠对象不一样,其他都相同

  1. // vue3应用配置
  2. import { createApp } from 'vue'
  3. const app = createApp({})
  4. app.config.errorHandler = (err, vm, info) => {
  5. // 处理错误
  6. // `info` 是 Vue 特定的错误信息,比如错误所在的生命周期钩子
  7. }
  8. app.mount('#app')
  9. // vue2全局配置
  10. import Vue from 'vue'
  11. Vue.config.errorHandler = function (err, vm, info) {
  12. // handle error
  13. // `info` 是 Vue 特定的错误信息,比如错误所在的生命周期钩子
  14. // 只在 2.2.0+ 可用
  15. }
  16. new Vue({
  17. el: '#app',
  18. components: { App },
  19. template: '<App/>'
  20. })

下面看下新增的应用配置

1、globalProperties:添加全局 property,任何组件实例都可以访问该属性;属性名冲突时,组件的 property 将具有优先权

作用:可以代替vue2中Vue.prototype扩展

  1. // Vue 2.x
  2. Vue.prototype.$http = () => {}
  3. // Vue 3.x
  4. const app = createApp({})
  5. app.config.globalProperties.$http = () => {}

举个例子:

  1. // main.js中定义全局应用属性
  2. import { createApp } from 'vue'
  3. import App from './App.vue'
  4. const app = createApp(App)
  5. app.config.globalProperties.pageTitle = 'Page2'
  6. //Page2.vue中使用应用属性pageTitle
  7. <template>
  8. <div class="page2">
  9. // 有pageTitle,则渲染
  10. <p v-if="pageTitle">{{pageTitle}}</p>
  11. <Child @on-submit="onSubmit"/>
  12. </div>
  13. </template>

2、isCustomElement:指定一个方法,用来识别在 Vue 之外定义的自定义元素(例如,使用 Web Components API)。如果组件符合此条件,则不需要本地或全局注册,并且 Vue 不会抛出关于 Unknown custom element 的警告

类型: (tag: string) => boolean

  1. // 任何以“ion-”开头的元素都将被识别为自定义元素
  2. app.config.isCustomElement = tag => tag.startsWith('ion-')

六、选项:生命周期

vue3和vue2的选项生命周期对比,从下图可以看出:vue3将vue2中的beforeDestroy、destroyed改成beforeUnmount、unmounted其实只是语义上的改变而已

vue3生命周期图谱
vue3生命周期钩子函数图谱
 

           

vue2.x生命周期钩子函数图谱
 

                                                                                                                                                                        

vue3中新增的选项生命周期钩子还有:renderTracked        renderTriggere,具体用法可以参考官文

七、移除的实例property

vue3和vue2.x的实例property对比图

结论:由对比图可以看出,在vue3删除了vue2.x中的实例property有:$children     $scopedSlots     $isServer     $listeners

下面说下移除这些实例的property原因

1、移除实例propety:$children 

在vue2.x中通过$children,可以获取到当前组件的所有子组件的全部实例,所以通过$children可以访问子组件的data、methods等,常用于父子组件通讯

在vue3中可以通过$refs访问子组件实例

下面举个例子看看

1)vue3示例

  1. <template>
  2. <div class="app">
  3. <p class="app-title">App.vue页面</p>
  4. <div id="alert"></div>
  5. <Page2 ref="Page2"/>
  6. <Page ref="Page" class="app-page"/>
  7. </div>
  8. </template>
  9. <script>
  10. import Page2 from './components/Page2.vue'
  11. import Page from './components/Page.vue'
  12. export default {
  13. name: 'App',
  14. components: {
  15. Page2,
  16. Page
  17. },
  18. mounted() {
  19. console.log('this.$children: ', this.$children)
  20. console.log('this.$refs: ', this.$refs)
  21. }
  22. }
  23. </script>

在控制台打印结果:

vue2,在控制台打印效果:

结论:通过上图可以看出,两者获取到的数据是一样的,只不过返回的数据结构不一样罢了,因此在vue3中将实例property:$children去掉,改用$refs获取

2、 移除实例property:$scopedSlots 

vue3中将$slots和$scopedSlots统一成$slots,废除$scopedSlots

3、移除实例property:$listeners

先说下vue2.4以上版本中的$attrs、$listeners属性的用法

  1. vm.$attrs
  2. 类型:{ [key: string]: string }
  3. 包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定
  4. (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件
  5. vm.$listeners
  6. 类型:{ [key: string]: Function | Array<Function> }
  7. 包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件

场景:如下图,当组件page1需要向page3传递数据,可以通过$attrs传递数据;page3向page1传递数据可以通过$listeners实现

代码实现:

1)组件page1.vue代码,在组件page2中定义两个prop:title、tips;两个事件:on-update、on-focus

  1. <template>
  2. <div class="hello">
  3. <p class="title">page1</p>
  4. <page2 :title="title" :tips="tips" @on-update="onUpdate" @on-focus="onFocus"/>
  5. </div>
  6. </template>
  7. <script>
  8. import page2 from './page2.vue'
  9. export default {
  10. name: 'HelloWorld',
  11. components: {
  12. page2
  13. },
  14. data () {
  15. return {
  16. title: 'page2',
  17. tips: 'page3'
  18. }
  19. },
  20. methods: {
  21. onUpdate (val) {
  22. this.tips = val
  23. },
  24. onFocus () {
  25. console.log('onfocus')
  26. }
  27. }
  28. }
  29. </script>

2)组件page2代码,在props上定义了title属性,在组件page3中绑定$attrs(会将父作用域即page1中的tips传递给组件page3),$listeners(会将父作用域即page1中的事件监听器on-update,on-focus传给组件page3)

  1. <template>
  2. <div class="page2">
  3. <p class="title">{{title}}</p>
  4. <page3 v-bind="$attrs" v-on="$listeners"/>
  5. </div>
  6. </template>
  7. <script>
  8. import page3 from './page3.vue'
  9. export default {
  10. name: 'page2',
  11. components: {
  12. page3
  13. },
  14. props: {
  15. title: String
  16. }
  17. }
  18. </script>

3)page3代码,定义的props:tips;$emits事件有on-focus,on-update

  1. <template>
  2. <div class="page3">
  3. <p class="tips">{{tips}}</p>
  4. <input type="text" v-model="value" @focus="handleFocus" class="input">
  5. <p @click="changeTitle" class="btn">改变标题</p>
  6. </div>
  7. </template>
  8. <script>
  9. export default {
  10. name: 'page3',
  11. props: {
  12. tips: String
  13. },
  14. data () {
  15. return {
  16. value: ''
  17. }
  18. },
  19. methods: {
  20. handleFocus () {
  21. this.$emit('on-focus')
  22. },
  23. changeTitle () {
  24. this.$emit('on-update', this.value)
  25. }
  26. }
  27. }
  28. </script>

在vue3中,只需要在page2.vue中的page3不绑定$listeners,其他同vue2,得到的效果也是一样的

结论:vue3中将事件监听器认为是$attrs的一部分,因此将$listeners移除

4移除实例property:$isServer 

八、移除的实例方法

下图是vue3和vue2.x的实例方法对比图

从上图可以看出在vue3中移除了实例方法有:

  1. // 数据
  2. $set
  3. $delete
  4. // 事件
  5. $on
  6. $off
  7. $once
  8. // 生命周期
  9. $mount
  10. $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 字符串文本

下面举一个例子:

  1. <template>
  2. <div class="app">
  3. <p class="app-title">App.vue页面</p>
  4. <div id="alert"></div>
  5. <div v-is="page[index]"></div>
  6. <div @click="changeUsePage" class="btn">改变页面使用的组件</div>
  7. </div>
  8. </template>
  9. <script>
  10. import Page2 from './components/Page2.vue'
  11. import Page1 from './components/Page1.vue'
  12. export default {
  13. name: 'App',
  14. components: {
  15. Page2,
  16. Page1
  17. },
  18. data() {
  19. return {
  20. tips: 'page3',
  21. page: ['Page2', 'Page1'],
  22. index: 1
  23. }
  24. },
  25. methods: {
  26. changeUsePage() {
  27. if (this.index === 0) {
  28. this.index = 1
  29. } else {
  30. this.index = 0
  31. }
  32. }
  33. }
  34. }
  35. </script>

页面初识化显示如图9-1,点击按钮

十、组合式API和响应性API

下图是新增的组合式API和响应性API

下面看下具体的API用法

组合式API

1、setup

1)类型:Function,是Vue3.x新增的选项;作为在组件内部使用组合式 API 的入口点(即组合式API和响应式API都在setup函数里面使用)

2)执行时机:在创建组件实例时,在初始 prop 解析之后立即调用 setup。在生命周期方面,它是在 beforeCreate 钩子之前调用的

下面看个例子

  1. export default {
  2. beforeCreate() {
  3. console.log('beforeCreate')
  4. },
  5. created() {
  6. console.log('created')
  7. },
  8. setup() {
  9. console.log('setup')
  10. }
  11. }

控制台输出结果:

3)参数:  接受两个参数---props和context

props:组件传入的属性,是响应式的,不能用ES6解构,否则会消除响应式

context:上下文对象,该对象有三个属性:slots、attrs、emit分别对应vue2中的this.$slots,this.$attrs,this.emit。由于setup中不能访问this对象,所以context提供了vue2中this常用的这三个属性,并且这几个属性都是自动同步最新的值

  1. export default {
  2. props: {
  3. title: String
  4. },
  5. setup(props, context) {
  6. console.log('props.title: ', props.title)
  7. console.log('context.$attrs: ', context.$attrs)
  8. console.log('context.$emit: ', context.$emit)
  9. console.log('context.$slots: ', context.$slots)
  10. let { title } = props.title // title失去响应性
  11. }
  12. }

4)返回值:对象、渲染函数h/JSX的方法

返回对象

  1. <template>
  2. <div class="page2">
  3. <div>标题:{{title}}</div>
  4. <!-- 从 setup 返回的 ref 在模板中访问时会自动展开,因此模板中不需要 .value -->
  5. <div>count:{{count}}</div>
  6. <div>object.foo:{{object.foo}}</div>
  7. <div class="btn" @click="changeTitle">改变title的值</div>
  8. </div>
  9. </template>
  10. <script>
  11. import { ref, reactive } from 'vue'
  12. export default {
  13. name: 'page2',
  14. props: {
  15. title: String
  16. },
  17. methods: {
  18. changeTitle() {
  19. this.$emit('on-chang-title', '改变后的标题')
  20. }
  21. },
  22. setup(props) {
  23. const count = ref(0)
  24. const object = reactive({ foo: 'page' })
  25. console.log('props.title: ', props.title)
  26. // 暴露到template中
  27. return {
  28. count,
  29. object
  30. }
  31. }
  32. }
  33. </script>
  34. <style scoped>
  35. .page2{
  36. background: #fff;
  37. margin-top: 30px;
  38. padding: 15px;
  39. }
  40. .btn{
  41. width: 100px;
  42. background: #cac6c6;
  43. line-height: 40px;
  44. margin: 20px auto;
  45. }
  46. </style>

渲染函数h/JSX的方法

  1. <script>
  2. import { h, ref, reactive } from 'vue'
  3. export default {
  4. name: 'page2',
  5. setup() {
  6. const count = ref(0)
  7. const object = reactive({ foo: 'page' })
  8. return () => h('div', [count.value, object.foo])
  9. }
  10. }
  11. </script>

2、在setup函数中使用的生命周期钩子

从下图可以看出,在setup中使用的生命周期钩子都增加了on;beforeCreated和created被setup替换,但是在vue3中仍然可以使用beforeCreated、created

下面是在setup中使用生命周期钩子(钩子需要引入)的示例

  1. <template>
  2. <div class="page2">
  3. <div>标题:{{title}}</div>
  4. <div class="btn" @click="changeTitle">改变title的值</div>
  5. </div>
  6. </template>
  7. <script>
  8. import { onBeforeMount, onMounted, onBeforeUpdate,onUpdated, onBeforeUnmount, onUnmounted, onErrorCaptured, onRenderTracked, onRenderTriggered
  9. } from "vue"
  10. export default {
  11. name: 'page2',
  12. props: {
  13. title: String
  14. },
  15. methods: {
  16. changeTitle() {
  17. this.$emit('on-chang-title', '改变后的标题')
  18. }
  19. },
  20. setup() {
  21. console.log('-----setup-----')
  22. // vue3中组合式API的生命周期钩子放在setup中调用
  23. onBeforeMount(() => {
  24. console.log('-----onBeforeMount-----')
  25. })
  26. onMounted(() => {
  27. console.log('-----onMounted-----')
  28. })
  29. onBeforeUpdate(() => {
  30. console.log('-----onBeforeUpdate-----')
  31. })
  32. onUpdated(() => {
  33. console.log('-----onUpdated-----')
  34. })
  35. onBeforeUnmount(() => {
  36. console.log('-----onBeforeUnmount-----')
  37. })
  38. onUnmounted(() => {
  39. console.log('-----onUnmounted-----')
  40. })
  41. onErrorCaptured(() => {
  42. console.log('-----onErrorCaptured-----')
  43. })
  44. onRenderTracked(() => {
  45. console.log('-----onRenderTracked-----')
  46. })
  47. onRenderTriggered(() => {
  48. console.log('-----onRenderTriggered-----')
  49. })
  50. }
  51. }
  52. </script>

3、getCurrentInstance

获取当前组件的实例,只能在setup、setup中使用的生命周期钩子中使用

下面是一个demo

  1. <script>
  2. import { h, onMounted, getCurrentInstance } from "vue"
  3. export default {
  4. name: 'page2',
  5. setup() {
  6. // also works if called on a composable
  7. function useComponentId() {
  8. return getCurrentInstance().uid
  9. }
  10. const internalInstance = getCurrentInstance() // works
  11. console.log('setup getCurrentInstance(): ', getCurrentInstance())
  12. const id = useComponentId() // works
  13. console.log('setup useComponentId(): ', useComponentId())
  14. const handleClick = () => {
  15. getCurrentInstance() // doesn't work
  16. console.log('handleClick getCurrentInstance(): ', getCurrentInstance())
  17. useComponentId() // doesn't work
  18. console.log('handleClick useComponentId(): ', useComponentId())
  19. console.log('handleClick internalInstance: ', internalInstance) // works
  20. }
  21. onMounted(() => {
  22. console.log('onMounted getCurrentInstance(): ', getCurrentInstance()) // works
  23. console.log('onMounted useComponentId(): ', useComponentId()) // works
  24. })
  25. return () => h('button', {onClick: handleClick}, `uid: ${id}`)
  26. }
  27. }
  28. </script>

显示效果如下:在setup中可以直接使用getCurrentInstance

getCurrentInstance()返回的对象如下所示:

当点击uid:1的时候,控制台显示如下:

在handleClick中直接使用getCurrentInstance(),返回为null;由于handleClick中调用getCurrentInstance返回null,因此在去读返回结果的属性,浏览器就会报错

响应式API

4、reactive、ref、toRef、toRefs

在vue2.x中, 数据一般都定义在data中, 但Vue3.x 可以使用reactiveref来进行数据定义

既然reactiveref都可以进行数据定义,那他们的区别是什么?使用时机是什么时候?

下面先看下ref、reactive的基本定义

1)ref:接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象具有指向内部值的单个 property .value

注意

a)在JS中访问ref定义的数据,需要通过:属性名.value方式访问

b)在DOM中访问ref定义的数据,不需要通过.value方式访问

下面是一个demo:

  1. <template>
  2. <div class="page2">
  3. <div>data中的msg:{{msg}}</div>
  4. <div>setup中的</div>
  5. <div>count:{{count}}</div>
  6. <div>obj.num:{{obj.num}}</div>
  7. <div>obj.name:{{obj.name}}</div>
  8. </div>
  9. </template>
  10. <script>
  11. import { ref } from "vue"
  12. export default {
  13. name: 'page2',
  14. data() {
  15. return {
  16. msg: '测试'
  17. }
  18. },
  19. setup() {
  20. const count = ref(0)
  21. let timer = null
  22. let obj = ref({
  23. num: 1,
  24. name: '张三'
  25. })
  26. timer = setTimeout(() => {
  27. clearTimeout(timer)
  28. count.value += 1
  29. obj.value.num += 1
  30. obj.value.name = '李四'
  31. }, 8000)
  32. return {
  33. count,
  34. obj
  35. }
  36. }
  37. }
  38. </script>

页面初始化显示效果:

8s后的显示效果

注意:从上面的demo代码可以看出,

a)ref可以代理对象和基本类型,例如字符串、数字、boolean等;

b)选型data定义的数据和组合式API中的ref定义的数据是共存的;vue3之所以推出ref来定义数据,是为了将数据、和方法放在一块,便于代码的维护等

问题:如果在data和ref中定义相同的变量会发生什么呢?

将上面的代码修改如下,只是在setup中定义一个同data中的msg变量,并返回

  1. <template>
  2. <div class="page2">
  3. <div>data中的msg:{{msg}}</div>
  4. <div>setup中的</div>
  5. <div>count:{{count}}</div>
  6. <div>obj.num:{{obj.num}}</div>
  7. <div>obj.name:{{obj.name}}</div>
  8. </div>
  9. </template>
  10. <script>
  11. import { ref } from "vue"
  12. export default {
  13. name: 'page2',
  14. data() {
  15. return {
  16. msg: '测试'
  17. }
  18. },
  19. setup() {
  20. const count = ref(0) // count虽然定义成const,但是countref对象,因此可以修改属性value的值
  21. let timer = null
  22. let obj = ref({
  23. num: 1,
  24. name: '张三'
  25. })
  26. let msg = '测试2' // 新增
  27. timer = setTimeout(() => {
  28. clearTimeout(timer)
  29. count.value += 1
  30. obj.value.num += 1
  31. obj.value.name = '李四'
  32. }, 8000)
  33. return {
  34. count,
  35. obj,
  36. msg // 新增
  37. }
  38. }
  39. }
  40. </script>

控制台抛错如下:

40:7  error  Duplicated key 'msg'  vue/no-dupe-keys

即data和ref中定义的返回数据不能同名

2)reactive:返回对象的响应式副本,但不能代理基本类型,例如字符串、数字、boolean等

下面看个demo

  1. <template>
  2. <div class="page2">
  3. <div>obj.num:{{obj.num}}</div>
  4. <div>obj.name:{{obj.name}}</div>
  5. <div>count:{{count}}</div>
  6. </div>
  7. </template>
  8. <script>
  9. import { reactive } from "vue"
  10. export default {
  11. name: 'page2',
  12. setup() {
  13. let timer = null
  14. let obj = reactive({
  15. num: 1,
  16. name: '张三'
  17. })
  18. let count = reactive(0)
  19. timer = setTimeout(() => {
  20. clearTimeout(timer)
  21. obj.num += 1
  22. obj.name = '李四'
  23. count += 1
  24. }, 8000)
  25. return {
  26. obj,
  27. count
  28. }
  29. }
  30. }
  31. </script>

页面初始化效果:

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

  1. <template>
  2. <div class="page2">
  3. <div>obj.num:{{obj.num}}</div>
  4. <div>obj.name:{{obj.name}}</div>
  5. <div>num:{{num}}</div>
  6. <div>name:{{name}}</div>
  7. </div>
  8. </template>
  9. <script>
  10. import { reactive, toRefs } from "vue"
  11. export default {
  12. name: 'page2',
  13. setup() {
  14. let timer = null
  15. let timer2 = null
  16. let obj = reactive({
  17. num: 1,
  18. name: '张三'
  19. })
  20. const objAsRefs = toRefs(obj)
  21. timer = setTimeout(() => {
  22. clearTimeout(timer)
  23. obj.num += 1
  24. obj.name = '李四'
  25. }, 8000)
  26. timer2 = setTimeout(() => {
  27. clearTimeout(timer2)
  28. objAsRefs.num.value += 1
  29. objAsRefs.name.value = '王老五'
  30. }, 14000)
  31. return {
  32. obj,
  33. ...objAsRefs
  34. }
  35. }
  36. }
  37. </script>

页面初始化显示效果如下:

8s后显示效果如下:

14s后显示效果如下:

结论:使用toRefs可以在DOM中使用对象结构出来的数据,并且还保持着响应式性质

4)toRef

定义:可以用来为源响应式对象上的 property 性创建一个 ref。然后可以将 ref 传递出去,从而保持对其源 property 的响应式连接

下面看个demo

  1. <template>
  2. <div class="page2">
  3. <div>obj.num:{{obj.num}}</div>
  4. <div>obj.name:{{obj.name}}</div>
  5. <div>nameRef:{{nameRef}}</div>
  6. </div>
  7. </template>
  8. <script>
  9. import { reactive, toRef } from "vue"
  10. export default {
  11. name: 'page2',
  12. setup() {
  13. let timer = null
  14. let timer2 = null
  15. let obj = reactive({
  16. num: 1,
  17. name: '张三'
  18. })
  19. const nameRef = toRef(obj, 'name')
  20. timer = setTimeout(() => {
  21. clearTimeout(timer)
  22. obj.num += 1
  23. obj.name = '李四'
  24. }, 8000)
  25. timer2 = setTimeout(() => {
  26. clearTimeout(timer2)
  27. nameRef.value = '王老五'
  28. }, 14000)
  29. return {
  30. obj,
  31. nameRef
  32. }
  33. }
  34. }
  35. </script>

页面初始化渲染效果如下:

8s后的效果如下:

14s后的效果如下:

结论

a)toRef是为源响应式对象上的具体的 property 创建一个 ref,并且保持对其源 property 的响应式连接;

b)toRefs将响应式对象转为普通对象,结果对象的每个 property 都是指向原始对象相应 property 的ref

5)shallowReactive

定义:创建一个响应式代理,该代理跟踪其自身 property 的响应性,但不执行嵌套对象的深度响应式转换 (暴露原始值)

demo1:

  1. <script>
  2. import { watchEffect, shallowReactive } from "vue"
  3. export default {
  4. name: 'page2',
  5. setup() {
  6. const original = shallowReactive({
  7. count: 0,
  8. info: {
  9. name: '张三'
  10. }
  11. })
  12. watchEffect(() => {
  13. // 适用于响应性追踪
  14. console.log('original.count', original.count)
  15. })
  16. // 变更original 会触发侦听器依赖副本
  17. original.count = 1
  18. }
  19. }
  20. </script>

控制台显示效果:

当修改count的值时,会触发watchEffect

demo2:

  1. <template>
  2. <div>name:{{original.info.name}}</div>
  3. </template>
  4. <script>
  5. import { watchEffect, shallowReactive } from "vue"
  6. export default {
  7. name: 'page2',
  8. setup() {
  9. const original = shallowReactive({
  10. count: 0,
  11. info: {
  12. name: '张三'
  13. }
  14. })
  15. watchEffect(() => {
  16. // 适用于响应性追踪
  17. console.log('original.info.name', original.info.name)
  18. })
  19. // 变更original 会触发侦听器依赖副本
  20. original.info.name = '李四'
  21. return {
  22. original
  23. }
  24. }
  25. }
  26. </script>

当修改original.info.name的时候,不会触发watchEffect

6)isReactive

定义:检查对象是否是 reactive创建的响应式 proxy;如果 proxy 是 readonly 创建的,但还包装了由 reactive 创建的另一个 proxy,它也会返回 true

7)isProxy

定义:检查对象是 reactive 还是 readonly创建的代理

返回值:Boolean,true--是reactive创建的代理,false-是 readonly创建的代理

  1. <script>
  2. import { reactive, ref, isProxy } from "vue"
  3. export default {
  4. name: 'page2',
  5. setup() {
  6. const reactiveObj = reactive({
  7. count: 0,
  8. info: {
  9. name: '张三'
  10. }
  11. })
  12. const refNum = ref(0)
  13. console.log('reactiveObj: ', isProxy(reactiveObj))
  14. console.log('refNum: ', isProxy(refNum))
  15. }
  16. }
  17. </script>

8)unref-----拆出原始值的语法糖

定义:如果参数为 ref,则返回内部值value,否则返回参数本身;它是 val = isRef(val) ? val.value : val的语法糖

9)isRef

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

10)shallowRef

定义:创建一个 ref,它跟踪自己的 .value 更改,但不会使其值成为响应式的

下面看个用shallowRef定义一个对象的demo

  1. <template>
  2. <div class="page2">
  3. <div>obj.name:{{obj.name}}</div>
  4. <div>obj.jobInfo.companyName:{{obj.jobInfo.companyName}}</div>
  5. </div>
  6. </template>
  7. <script>
  8. import { shallowRef } from "vue"
  9. export default {
  10. name: 'page2',
  11. setup() {
  12. const obj = shallowRef({
  13. name: '张三',
  14. jobInfo: {
  15. companyName: '张三的公司'
  16. }
  17. })
  18. let timer = null
  19. let timer2 = null
  20. timer = setTimeout(() => {
  21. clearTimeout(timer)
  22. obj.value.name = '李四'
  23. obj.value.jobInfo.companyName = '李四的公司'
  24. console.log('8s后的obj:', obj.value)
  25. }, 8000)
  26. timer2 = setTimeout(() => {
  27. clearTimeout(timer2)
  28. obj.value = {
  29. name: '王老五',
  30. jobInfo: {
  31. companyName: '王老五的公司'
  32. }
  33. }
  34. console.log('14s后的obj:', obj.value)
  35. }, 14000)
  36. return {
  37. obj
  38. }
  39. }
  40. }
  41. </script>

页面初始化效果如下:

8s后的效果:

14s后的效果:

结论:从上面的效果图可以看出,shallowRef定义的对象型数据,没有响应性;但是如果给该对象的value重新赋值,可以在DOM中更新

注意:shallowRef定义的一般类型数据仍然具有响应性

问题:如果在代码中同时用shallowRef定义一般类型和object类型数据,修改一般类型属性的值,能在DOM中变更吗?

如下demo中用shallowRef同时定义了一般类型和object类型数据

  1. <template>
  2. <div class="page2">
  3. <div>obj.name:{{obj.name}}</div>
  4. <div>obj.jobInfo.companyName:{{obj.jobInfo.companyName}}</div>
  5. <div>count:{{count}}</div>
  6. </div>
  7. </template>
  8. <script>
  9. import { shallowRef } from "vue"
  10. export default {
  11. name: 'page2',
  12. setup() {
  13. let count = shallowRef(0)
  14. const obj = shallowRef({
  15. name: '张三',
  16. jobInfo: {
  17. companyName: '张三的公司'
  18. }
  19. })
  20. let timer = null
  21. let timer2 = null
  22. timer = setTimeout(() => {
  23. clearTimeout(timer)
  24. obj.value.name = '李四'
  25. obj.value.jobInfo.companyName = '李四的公司'
  26. console.log('8s后的obj:', obj.value)
  27. count.value = 1
  28. console.log('8s后的count:', count.value)
  29. }, 8000)
  30. timer2 = setTimeout(() => {
  31. clearTimeout(timer2)
  32. obj.value = {
  33. name: '王老五',
  34. jobInfo: {
  35. companyName: '王老五的公司'
  36. }
  37. }
  38. console.log('14s后的obj:', obj.value)
  39. count.value = 2
  40. console.log('14s后的count:', count.value)
  41. }, 19000)
  42. return {
  43. obj,
  44. count
  45. }
  46. }
  47. }
  48. </script>

页面初始化渲染效果:

8s后的效果:即数据更新了,DOM中的数据也跟着更新了-------即用shallowRef定义的一般类型数据,具有响应式

14s后的效果:

11)triggerRef

定义:手动执行与 shallowRef 关联的任何效果

问题:上述demo中,用shallowRef定义一个object,然后修改其property,在DOM中没有更新;如果我们想用shallowRef定义object数据,同时修改property值后在DOM中更新,怎么办呢?

答案:使用triggerRef

  1. <template>
  2. <div class="page2">
  3. <div>obj.name:{{obj.name}}</div>
  4. <div>obj.jobInfo.companyName:{{obj.jobInfo.companyName}}</div>
  5. </div>
  6. </template>
  7. <script>
  8. import { shallowRef, triggerRef } from "vue"
  9. export default {
  10. name: 'page2',
  11. setup() {
  12. const obj = shallowRef({
  13. name: '张三',
  14. jobInfo: {
  15. companyName: '张三的公司'
  16. }
  17. })
  18. let timer = null
  19. timer = setTimeout(() => {
  20. clearTimeout(timer)
  21. obj.value.name = '李四'
  22. obj.value.jobInfo.companyName = '李四的公司'
  23. console.log('8s后的obj:', obj.value)
  24. triggerRef(obj) // 触发DOM更新
  25. }, 8000)
  26. return {
  27. obj
  28. }
  29. }
  30. }
  31. </script>

页面初始化效果:

8s后的效果:

12)customRef

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

参数:它需要一个工厂函数,该函数接收 track 和 trigger 函数作为参数

返回值:返回一个带有 get 和 set 的对象

下面看一个demo:使用 v-model 、自定义 ref 实现值绑定的示例

  1. <template>
  2. <div class="page2">
  3. <input class="input" type="text" v-model="text" @change="changeVal">
  4. </div>
  5. </template>
  6. <script>
  7. import { customRef } from "vue"
  8. export default {
  9. name: 'page2',
  10. setup() {
  11. function useDebouncedRef(value, delay= 200) {
  12. let timeout
  13. return customRef((track, trigger) => {
  14. return {
  15. get() {
  16. track()
  17. // do something
  18. return value
  19. },
  20. set(newValue) {
  21. clearTimeout(timeout)
  22. timeout = setTimeout(() => {
  23. value = newValue
  24. // do something
  25. trigger()
  26. }, delay)
  27. }
  28. }
  29. })
  30. }
  31. function changeVal(val) {
  32. console.log('val.target.value', val.target.value)
  33. console.log('val: ', val)
  34. }
  35. let text = useDebouncedRef('hello')
  36. return {
  37. text,
  38. changeVal
  39. }
  40. }
  41. }
  42. </script>

页面初始化效果:

改变输入框的值,显示:

13)computed

a)参数是 getter 函数,并为从 getter 返回的值返回一个不变的响应式 ref 对象,即不可直接修改其value值

b)参数具有 get 和 set 函数的对象,来创建可写的 ref 对象

下面看下参数是getter函数demo

  1. <template>
  2. <div class="page2">
  3. <div>plusOne:{{plusOne}}</div>
  4. </div>
  5. </template>
  6. <script>
  7. import { ref, computed } from "vue"
  8. export default {
  9. name: 'page2',
  10. setup() {
  11. const count = ref(1)
  12. const plusOne = computed(() => count.value + 1) // count.value改变时,plusOne.value也跟着改变
  13. console.log(plusOne.value) // 2
  14. plusOne.value++ // error,不能直接修改plusOne.value的值,浏览器会有警告
  15. return {
  16. plusOne
  17. }
  18. }
  19. }
  20. </script>

页面显示效果:

下面看下参数是具有 get 和 set 函数的对象的demo

  1. <template>
  2. <div class="page2">
  3. <div>count:{{count}}</div>
  4. <div>plusOne:{{plusOne}}</div>
  5. </div>
  6. </template>
  7. <script>
  8. import { ref, computed } from "vue"
  9. export default {
  10. name: 'page2',
  11. setup() {
  12. const count = ref(1)
  13. const plusOne = computed({
  14. get: () => count.value + 1,
  15. set: val => {
  16. count.value = val - 1
  17. }
  18. })
  19. setTimeout(() => {
  20. plusOne.value = 3
  21. console.log(count.value)
  22. }, 8000)
  23. return {
  24. plusOne,
  25. count
  26. }
  27. }
  28. }
  29. </script>

页面初始化效果:

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对象的某个属性

  1. <template>
  2. <div class="page2">
  3. <div>name:{{name}}</div>
  4. <div>age:{{age}}</div>
  5. </div>
  6. </template>
  7. <script>
  8. import { reactive, toRefs, watch } from "vue"
  9. export default {
  10. name: 'page2',
  11. setup() {
  12. const userInfo = reactive({ name: "张三", age: 10 })
  13. setTimeout(() =>{
  14. userInfo.name = '李四'
  15. userInfo.age = 12
  16. },7000)
  17. // 修改age值时会触发 watch的回调
  18. watch(
  19. () => userInfo.age,
  20. (curAge, preAge) => {
  21. console.log("age新值:", curAge, "age老值:", preAge)
  22. }
  23. )
  24. return {
  25. ...toRefs(userInfo)
  26. }
  27. }
  28. }
  29. </script>

页面初识化效果:

7s后的效果:

如果将watch里面的source改成直接监听reactive的prop,其余代码不变,会发生什么呢?

  1. watch(userInfo.name, (cur, pre) => {
  2. console.log('curName: ', cur)
  3. console.log('preName: ', pre)
  4. })

控制台会警告: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对象的属性

  1. const person = ref({
  2. name: '1',
  3. age: 1
  4. })
  5. setTimeout(() =>{
  6. person.value.name = 2
  7. }, 100)
  8. watch(() => person.value.name, (n, o) => {
  9. console.log(n)
  10. console.log(o)
  11. })

输出1    2

b)直接侦听一个ref

普通数据类型

  1. <template>
  2. <div class="page2">
  3. <div>count:{{count}}</div>
  4. </div>
  5. </template>
  6. <script>
  7. import { ref, watch } from "vue"
  8. export default {
  9. name: 'page2',
  10. setup() {
  11. const count = ref(0)
  12. setTimeout(() =>{
  13. count.value++
  14. }, 7000)
  15. watch(
  16. count,
  17. (curCount, preCount) => {
  18. console.log("count新值:", curCount, "count老值:", preCount)
  19. }
  20. )
  21. return {
  22. count
  23. }
  24. }
  25. }
  26. </script>

页面初识化效果:

7s后的效果:

直接侦听一个ref引用类型对象:

  1. setup() {
  2. const person = ref({
  3. name: '1',
  4. age: 1
  5. })
  6. setTimeout(() =>{
  7. person.value.name = 2
  8. }, 100)
  9. watch(person, (n, o) => {
  10. console.log(n, o)
  11. })
  12. }

像这样是监听不到的,需要在watch里面添加第三个参数:deep:true

但是只能拿到新的值,旧的值拿不到,即n. o的值一样都是变化后的值

  1. watch(person, (n, o) => {
  2. console.log(n)
  3. console.log(o)
  4. }, {deep: true})

直接侦听ref对象的某个属性,会抛类型错误

c)直接侦听reactive

  1. <template>
  2. <div class="page2">
  3. <div>count:{{count}}</div>
  4. <div>name:{{userInfo.name}}</div>
  5. <div>age:{{userInfo.age}}</div>
  6. </div>
  7. </template>
  8. <script>
  9. import { ref, watch, reactive } from "vue"
  10. export default {
  11. name: 'page2',
  12. setup() {
  13. const count = ref(0)
  14. const userInfo = reactive({ name: "张三", age: 10 })
  15. setTimeout(() =>{
  16. count.value++
  17. userInfo.name = '李四'
  18. userInfo.age = 12
  19. }, 2000)
  20. watch(userInfo, (cur, pre) => {
  21. console.log('curUserInfo: ', cur)
  22. console.log('preUserInfo: ', pre)
  23. })
  24. return {
  25. count,
  26. userInfo
  27. }
  28. }
  29. }
  30. </script>

页面初始化效果:

2s后的显示效果如下:

可以看到:如果直接监听一个reactive,那么只会返回变化的值,之前的状态拿不到

如果reactive对象内属性是嵌套多层的对象,那么在watch中不添加第三个参数deep:true,也是可以监听到变化的,因为reactive类型是响应式的,内部是直接代理整个对象,当里面属性发生变化的时候,会反应出来的

侦听多个数据源(source使用数组)

上面两个例子中,我们分别使用了两个watch, 当我们需要侦听多个数据源时, 可以进行合并, 同时侦听多个数据

  1. <template>
  2. <div class="page2">
  3. <div>count:{{count}}</div>
  4. <div>name:{{userInfo.name}}</div>
  5. <div>age{{userInfo.age}}</div>
  6. </div>
  7. </template>
  8. <script>
  9. import { ref, watch, reactive } from "vue"
  10. export default {
  11. name: 'page2',
  12. setup() {
  13. const count = ref(0)
  14. const userInfo = reactive({ name: "张三", age: 10 })
  15. setTimeout(() =>{
  16. count.value++
  17. userInfo.name = '李四'
  18. userInfo.age = 12
  19. }, 2000)
  20. watch([() => userInfo.age, count], ([curAge, curCount], [preAge, preCount]) => {
  21. console.log("curAge:", curAge, "preAge:", preAge)
  22. console.log("curCount:", curCount, "preCount:", preCount)
  23. })
  24. return {
  25. count,
  26. userInfo
  27. }
  28. }
  29. }
  30. </script>

页面初始化的时候效果:

2s后的显示效果:

侦听复杂的嵌套对象(使用第三个参数:options)

  1. const info = reactive({
  2. room: {
  3. id: 200,
  4. attrs: {
  5. size: "400平方米",
  6. type:"三室两厅"
  7. }
  8. }
  9. });
  10. watch(() => info.room, (newType, oldType) => {
  11. console.log("新值:", newType, "老值:", oldType)
  12. }, { deep: true, immediate: true })

监听复杂的嵌套对象,如果不使用第三个参数deep:true, 是无法监听到数据变化的

默认情况下,watch是惰性的

那怎么样可以立即执行回调函数呢?答案是: 给第三个参数设置immediate: true即可

停止侦听

在组件中创建的watch监听,会在组件被销毁时自动停止。如果在组件销毁之前我们想要停止掉某个监听, 可以调用watch()函数的返回值

  1. const stopWatchCount = watch(
  2. count,
  3. (curCount, preCount) => {
  4. console.log("count新值:", curCount, "count老值:", preCount)
  5. }
  6. )
  7. setTimeout(() => {
  8. stopWatchCount()
  9. }, 100000)

watchEffect

定义:在响应式地跟踪其依赖项时立即运行一个函数,并在更改依赖项时重新运行它

  1. <template>
  2. <div class="page2">
  3. <div>count:{{count}}</div>
  4. <div>name:{{name}}</div>
  5. <div>age:{{age}}</div>
  6. <div>num:{{num}}</div>
  7. </div>
  8. </template>
  9. <script>
  10. import { ref, watchEffect, reactive, toRefs } from "vue"
  11. export default {
  12. name: 'page2',
  13. setup() {
  14. const count = ref(0)
  15. const userInfo = reactive({
  16. name: '张三',
  17. age: '11'
  18. })
  19. let num = 2
  20. setTimeout(() =>{
  21. count.value++
  22. userInfo.age++
  23. num++
  24. }, 7000)
  25. watchEffect(() => {
  26. console.log('count: ', count.value)
  27. console.log('userInfo:', userInfo)
  28. console.log('num: ', num)
  29. })
  30. return {
  31. ...toRefs(userInfo),
  32. count,
  33. num
  34. }
  35. }
  36. }
  37. </script>

从控制台打印信息可以看出,watchEffect里面的方法执行了两次,一次是页面渲染完成的时候,另一次是过了7s后修改值的时候

watchEffect可以监听到复杂数据吗?

  1. <template>
  2. <div class="page2">
  3. <div>count:{{count}}</div>
  4. <div>name:{{name}}</div>
  5. <div>age:{{age}}</div>
  6. <div>num:{{num}}</div>
  7. </div>
  8. </template>
  9. <script>
  10. import { ref, watchEffect, reactive, toRefs } from "vue"
  11. export default {
  12. name: 'page2',
  13. setup() {
  14. const count = ref(0)
  15. const userInfo = reactive({
  16. name: '张三',
  17. age: '11',
  18. job: {
  19. title: 'test'
  20. }
  21. })
  22. let num = 2
  23. setTimeout(() =>{
  24. count.value++
  25. userInfo.job.title = 'change'
  26. userInfo.name = 'change'
  27. num++
  28. }, 8000)
  29. watchEffect(() => {
  30. console.log('userInfo:', userInfo)
  31. console.log('userInfo.job:', userInfo.job)
  32. })
  33. return {
  34. ...toRefs(userInfo),
  35. count,
  36. num
  37. }
  38. }
  39. }
  40. </script>

页面初识化效果如下:

8s后的效果:

可以看到DOM更新了,但是watchEffect里面没有监听到数据的变化,即watchEffect里面监听不到复杂数据的变化

watch与watchEffect的区别

a)watchEffect 不需要手动传入依赖

b)watchEffect 会先执行一次用来自动收集依赖

c)watchEffect 无法获取到变化前的值, 只能获取变化后的值

12)readonly

定义:获取一个对象 (响应式或纯对象) 或 ref ,返回原始代理的只读代理。只读代理是深层的:访问的任何嵌套 property 也是只读的

  1. <script>
  2. import { reactive, watchEffect, readonly } from "vue"
  3. export default {
  4. name: 'page2',
  5. setup() {
  6. const original = reactive({ count: 0 })
  7. const copy = readonly(original)
  8. watchEffect(() => {
  9. // 适用于响应性追踪
  10. console.log('copy.count:', copy.count)
  11. })
  12. // 变更original 会触发侦听器依赖副本
  13. original.count++
  14. // 变更副本将失败并导致警告
  15. setTimeout(() => {
  16. copy.count++ // 不能对只读变量做修改,否则控制台会警告
  17. }, 8000)
  18. }
  19. }
  20. </script>

控制台显示:

页面初始化控制台打印信息

8s后控制台打印的信息

说明:执行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:

  1. <script>
  2. import { reactive, watchEffect, shallowReadonly } from "vue"
  3. export default {
  4. name: 'page2',
  5. setup() {
  6. const original = reactive({
  7. count: 0,
  8. info: {
  9. name: '张三'
  10. }
  11. })
  12. const copy = shallowReadonly(original)
  13. watchEffect(() => {
  14. // 适用于响应性追踪
  15. console.log('copy.count:', copy.count)
  16. })
  17. // 变更original 会触发侦听器依赖副本
  18. original.count++ // 触发watchEffect
  19. // 变更副本将失败并导致警告
  20. setTimeout(() => {
  21. copy.count++
  22. }, 3000)
  23. }
  24. }
  25. </script>
页面初始化,控制台打印信息

3s后控制台打印信息

demo2:

  1. <script>
  2. import { reactive, watchEffect, shallowReadonly } from "vue"
  3. export default {
  4. name: 'page2',
  5. setup() {
  6. const original = reactive({
  7. count: 0,
  8. info: {
  9. name: '张三'
  10. }
  11. })
  12. const copy = shallowReadonly(original)
  13. watchEffect(() => {
  14. // 适用于响应性追踪
  15. console.log('copy.info.name', copy.info.name)
  16. })
  17. // 变更original 会触发侦听器依赖副本
  18. original.info.name = '李四'
  19. setTimeout(() => {
  20. copy.info.name = '王老五' // 正常
  21. }, 3000)
  22. }
  23. }
  24. </script>
页面初识化,控制台打印信息

3s后控制台信息

通过demo1和demo2的对比,可以看出:对于响应式的原对象,shallowReadonly返回的对象,可以修改其嵌套对象里面的属性值,即只读只对其首层属性有作用

demo3:

  1. <script>
  2. import { ref, watchEffect, shallowReadonly } from "vue"
  3. export default {
  4. name: 'page2',
  5. setup() {
  6. const original = ref({
  7. count: 0,
  8. info: {
  9. name: '张三'
  10. }
  11. })
  12. const copy = shallowReadonly(original)
  13. watchEffect(() => {
  14. // 适用于响应性追踪
  15. console.log('copy.count', copy.value.count)
  16. })
  17. // 变更original 会触发侦听器依赖副本
  18. original.value.count = 1
  19. setTimeout(() => {
  20. copy.value.count = 2 // 正常
  21. }, 3000)
  22. }
  23. }
  24. </script>
页面初始化控制台信息

3s后控制台信息
​​​​​​

demo4:

  1. <script>
  2. import { ref, watchEffect, shallowReadonly } from "vue"
  3. export default {
  4. name: 'page2',
  5. setup() {
  6. const original = ref({
  7. count: 0,
  8. info: {
  9. name: '张三'
  10. }
  11. })
  12. const copy = shallowReadonly(original)
  13. watchEffect(() => {
  14. // 适用于响应性追踪
  15. console.log('copy.info.name', copy.value.info.name)
  16. })
  17. // 变更original 会触发侦听器依赖副本
  18. original.value.info.name = '李四'
  19. setTimeout(() => {
  20. copy.value.info.name = '王老五'
  21. }, 3000)
  22. }
  23. }
  24. </script>
页面初始化控制台信息
3s后控制台信息

通过demo3和demo4可知,对于原对象是ref,shallowReadonly返回的只读对象代理,可以直接修改只读对象的属性

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

17)markRaw

定义:标记一个对象,使其永远不会转换为代理。返回对象本身

  1. const foo = markRaw({})
  2. console.log(isReactive(reactive(foo))) // false
  3. // 嵌套在其他响应式对象中时也可以使用
  4. const bar = reactive({ foo })
  5. console.log(isReactive(bar.foo)) // false

18)toRaw

返回 reactive 或 readonly 代理的原始对象。这是一个转义口,可用于临时读取而不会引起代理访问/跟踪开销,也可用于写入而不会触发更改。不建议保留对原始对象的持久引用。请谨慎使用

  1. const foo = {}
  2. const reactiveFoo = reactive(foo)
  3. 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可以有多个根节点

  1. <!-- vue2.x写法 -->
  2. <template>
  3. <div>
  4. <div>test</div>
  5. <div>test </div>
  6. </div>
  7. </template>
  8. <!-- vue3.x写法 -->
  9. <template>
  10. <div>test</div>
  11. <div>test </div>
  12. </template>

十三、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

  1. // Vue2.x中, 在组件上使用 v-model相当于传递了value属性, 并触发了input事件
  2. <search-input v-model="searchValue"><search-input>
  3. <!-- 相当于 -->
  4. <search-input :value="searchValue" @input="searchValue=$event"><search-input>
  5. // vue3.x中,在组件上使用v-model相当于传递modelValue属性,并触发update:modelValue事件
  6. <search-input v-model="searchValue"><search-input>
  7. <!-- 相当于 -->
  8. <search-input :modelValue="searchValue" @update:modelValue="searchValue=$event"><search-input>

2)变更:v-bind.sync修饰符在 Vue 3 中被去掉了, 合并到了v-model

举一个双向绑定的例子:

在vue2.x中,一个modal弹窗,外部可以控制组件的属性visible,达到弹窗的显示、隐藏效果;组件内部可以控制visible属性隐藏,并将visible属性同步传输到外部

组件内部, 当我们关闭modal时, 在子组件中以update:visible模式触发事件:

this.$emit('update:visible', false)

然后父组件可以监听这个事件进行数据更新

  1. <modal :visible.async="isVisible"></modal>
  2. // 相当于
  3. <modal :visible="isVisible" @update:visible="isVisible = $event"></modal>

在vue3中,可以给v-model传递属性visible,达到绑定属性的目的

  1. <modal v-model:visible="isVisible" v-model:content="content"></modal>
  2. <!-- 相当于 -->
  3. <modal
  4. :visible="isVisible"
  5. :content="content"
  6. @update:visible="isVisible"
  7. @update:content="content"
  8. />

从而可以看出,Vue 3 中抛弃了.async写法, 统一使用v-model

3)新增:同一组件可以同时设置多个 v-model

从上面的例子可以看出同一个组件可以设置多个v-model

十四、将具名插槽slot、作用域插槽slot-scope改成统一使用v-slot(vue2.6.0就有了)

在vue2中使用具名插槽

  1. <!-- 子组件中:-->
  2. <slot name="title"></slot>
  3. <!-- 父组件中:-->
  4. <div slot="title">
  5. <h1>歌曲:成都</h1>
  6. <div>

在vue2中使用作用域插槽(在slot上绑定数据)

  1. // 子组件
  2. <slot name="content" :lesson="lesson"></slot>
  3. export default {
  4. data(){
  5. return{
  6. lession:['English', 'Chinese']
  7. }
  8. }
  9. }
  10. <!-- 父组件中使用 -->
  11. <template slot="content" slot-scope="scoped">
  12. <div v-for="item in scoped.lession">{{item}}</div>
  13. <template>

vue3中使用v-slot实现具名插槽和作用域插槽

  1. // 子组件
  2. <slot name="content" :lesson="lesson"></slot>
  3. export default {
  4. data(){
  5. return{
  6. lession:['English', 'Chinese']
  7. }
  8. }
  9. }
  10. <!-- 父组件中使用 -->
  11. <template v-slot:content="scoped">
  12. <div v-for="item in scoped.lession">{{item}}</div>
  13. </template>
  14. <!-- 也可以简写成: -->
  15. <template #content="{ lession }">
  16. <div v-for="item in lession">{{item}}</div>
  17. </template>

下面看下v-slot的具体用法

1)默认插槽

a)如果不设置<slot>元素的name属性,那么出口会带有隐含的名字:default;注意:默认插槽的缩写语法不能和具名插槽混用,因为它会导致作用域不明确

b)v-slot:可以简写为#;注意:简写方式只适用于有参数适合有效???

c)v-slot只能用于<template>元素上;除了当被提供的内容只有默认插槽时,组件的标签才可以被当作插槽的模板来使用

  1. <-- 子组件Child中 -->
  2. <main>
  3. <slot></slot>
  4. </main>
  5. <-- 父组件Parent中 -->
  6. <Child>
  7. <template v-slot:defalut>
  8. <div>default</div>
  9. </tempalte>
  10. </Child>
  11. <-- 父组件Parent中写法2 -->
  12. <Child>
  13. <template v-slot>
  14. <div>default</div>
  15. </tempalte>
  16. </Child>
  17. <-- 父组件Parent中写法3,有效 -->
  18. <-- 但这与文档中说法有矛盾:文档说这种写法无效 -->
  19. <Child #>
  20. <div>default</div>
  21. </Child>

2)具名插槽

给<slot>元素设置name属性

  1. <-- 子组件Child中 -->
  2. <header>
  3. <slot name=""header></slot>
  4. </header>
  5. <main>
  6. <slot></slot>
  7. </main>
  8. <footer>
  9. <slot name="footer"></slot>
  10. </footer>
  11. <-- 父组件Parent中 -->
  12. <Child>
  13. <template #header>
  14. <div>header</div>
  15. </tempalte>
  16. <template #default>
  17. <div>main</div>
  18. </tempalte>
  19. <template #footer>
  20. <div>footer</div>
  21. </tempalte>
  22. </Child>

3)作用域插槽

场景:在父组件作用域中访问子组件数据

  1. <-- 子组件Child中 -->
  2. <main>
  3. <slot :lession="lession"></slot>
  4. </main>
  5. <-- 父组件Parent中 -->
  6. <Child>
  7. <template #default="slotProps">
  8. <div>{{slotProps.lession}}</div>
  9. </tempalte>
  10. </Child>

4)解构插槽Props

  1. <-- 子组件Child中 -->
  2. <main>
  3. <slot :lession="lession"></slot>
  4. </main>
  5. <-- 父组件Parent中 -->
  6. <Child>
  7. <template #default="{ lession }">
  8. <div>{{ lession }}</div>
  9. </tempalte>
  10. </Child>
  11. <-- 父组件Parent中写法2,prop 重命名 -->
  12. <Child>
  13. <template #default="{ lession: class }">
  14. <-- 父组件Parent中写法2,lession重命名为class -->
  15. <div>{{ class }}</div>
  16. </tempalte>
  17. </Child>
  18. <-- 父组件Parent中写法3,定义后备内容 -->
  19. <Child>
  20. <template #default="{ lession = ['French] }">
  21. <-- 父组件Parent中写法2,lession重命名为class -->
  22. <div>{{ lession }}</div>
  23. </tempalte>
  24. </Child>

5)动态插槽名

  1. <-- 父组件Parent中 -->
  2. <Child>
  3. <template #default="dynamicName">
  4. <div>{{ lession }}</div>
  5. </tempalte>
  6. </Child>
  7. // 在父组件中,根据不同条件设置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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/494313
推荐阅读
相关标签
  

闽ICP备14008679号