vue3.0 Composition API上手初体验

475 阅读2分钟

vue3.0 对比 vue2.0 优势

  • vue3.0 相比 vue2.0 性能提升了近 50%,框架内部做了大量性能优化,包括:虚拟dom,编译模板,Proxy 的新数据监听,更小的打包文件等
  • vue3.0 新的组合式 API(即 Composition API)根据逻辑相关性组织代码,相比于 vue2.0 的 Options API,提高了代码的可读性和可维护性,更适合大型项目的编写方式
  • vue3.0 对 Typescript 支持更好,去除了繁琐的 this 操作,更强大的类型推导

vue3.0 对比 vue2.0 语法区别

  • vue3.0 支持组件中使用多个根节点,可以减少节点层级深度,也希望开发者能够明确语义

  • vue3.0 删除了过滤器(filters),将不在支持,官方建议使用计算属性(computed)替换过滤器

  • vue3.0 暴露出很多 API 供开发者使用,可以根据需求,将所需要的 API 从 Vue 中导入。考虑到 tree-shaking,利用了 import 和 export 的语法,实现了按需打包模块的功能

  • vue3.0 中新增了一个新的全局 API createApp,调用 createApp 返回一个应用实例,该应用实例暴露出来全局 API(vue3.0 将可以全局改变 Vue 行为的 API 从原来的 Vue 构造函数上转移到了实例上了)

    //vue2.0创建
    import Vue from 'vue'
    import App from './App.vue'
    
    Vue.config.productionTip = false
    
    new Vue({
      render: h => h(App),
    }).$mount('#app')
    
    
    //vue3.0创建
    import { createApp } from 'vue'
    import App from './App.vue'
    
    createApp(App).mount('#app');
    

vue3.0 对比 vue2.0 响应式

Object.defineProperty 只能监听到属性的读写,而 Proxy 除读写外还可以监听属性的删除,方法的调用等

  • vue2.0 数据响应式的原理是通过遍历 data 属性,利用 Object.definePrototype 将其转化成 setter/getter,在数据变动时发布消息给订阅者,触发相应的监听回调,由于 js 的限制,vue 不能检测数组和对象属性的添加和删除
  • vue3.0 基于 Proxy 来做数据的劫持代理,可以原生支持到数组的响应式,不需要重写数组的原型,还可以直接检测数组和对象属性的新增和删除(解决了 vue2.0 不能检测数组和对象属性的添加和删除的问题)

vue3.0 对 Typescript 支持更好

  • Typescript 是 javascript 类型的超集,它可以编译成纯的 javascript
  • Typescript 可以在任何浏览器、任何计算机和任何操作系统上运行,并且是开源的
  • Typescript 可以提供静态类型检查,规范团队的编码及使用方式,适合大型项目的开发
  • IDE 的友好提示,也是适合大型项目的开发

vue3.0 常用 API

Vue 组合式 API:composition-api.vuejs.org/zh/api.html…

setup

setup 函数是一个新的组件选项,作为在组件内使用 Composition API 的入口函数,变量、方法都定义在该函数中

  • setup 函数代替了 beforeCreate 和 created 两个生命周期函数,在生命周期 beforeCreate 之前被调用
  • this 在 setup() 中不可用,直接使用声明的变量名来访问数据(由于 setup() 在解析 2.x 选项前被调用,setup() 中的 this 将与 2.x 选项中的 this 完全不同。同时在 setup() 和 2.x 选项中使用 this 时将造成混乱)
  • setup 函数有两个参数:propscontext
    • props 对象为当前组件允许外界传递过来的参数名称以及对应的响应式的值
    • context 是一个上下文对象,暴露出三个属性:attrs 、slots、emit
  • setup 函数内定义的变量使用 return 暴露出去,供 template 中使用
<template>
   <div>
      {{ count }}
      {{ obj.count }}
   </div>
</template>

<script>
//引入需要的API
import { ref, reactive } from 'vue'

export default {
   name: 'home',
   setup() {
      const count = ref(0)
      export let obj = reactive({ count: 0 })
      
      //通过return暴露出去,供template中使用
      return {
         count,
         obj
      }
   }
}
</script>

<style scoped>
  
</style>

使用 <script setup>

  • < script> 标签具有 setup 属性时,组件在编译的过程中代码运行的上下文是 setup() 函数中
  • 定义的变量使用 export 暴露出去,供 template 中使用
<template>
   <div>
      {{ count }}
      {{ obj.count }}
   </div>
</template>

<script setup>
//引入需要的API
import { ref, reactive } from 'vue'

//通过export暴露出去,供template中使用
export let num = ref(0)
export let obj = reactive({ count: 0 })
</script>

<style scoped>
  
</style>

reactive

reactive 接收一个普通对象然后返回该普通对象的响应式代理,创建一个响应式的数据对象

<template>
   <div>
      {{ obj.count }}
   </div>
</template>

<script>
//引入需要的API
import { reactive } from 'vue'

export default {
   name: 'home',
   setup() {
      //通过reactive定义响应式对象
      const obj = reactive({ count: 0 }) 

      //通过return暴露出去,供template中使用
      return {
         obj
      }
   }
}
</script>

ref

ref 接受一个参数值并返回一个响应式且可改变的 ref 对象。ref 对象拥有一个指向内部值的单一属性 .value

  • 可以简单地把 ref(obj) 理解为 reactive({ value: obj })
  • 在 setup 函数中访问 ref 包装后的对象时才需要使用 .value,在 template 模板中访问会自动识别其是否为 ref 包装过,无需在模板内额外书写 .value
<template>
   <div>
      {{ count }}
   </div>
</template>

<script>
//引入需要的API
import { ref } from 'vue'

export default {
   name: 'home',
   setup() {
      //通过ref定义响应式对象
      const count = ref(0) 

      //通过value属性访问值
      console.log(count.value)   //0

      //通过return暴露出去,供template中使用
      return {
         count
      }
   }
}
</script>

toRef

toRef 可以用来为一个 reactive 对象的属性创建一个 ref,这个 ref 可以被传递并且能够保持响应性(将 reactive 对象中的某个值转化为响应式数据)

与 ref 的区别:

  • ref 是对传入数据的拷贝,toRef 是对传入数据的引用
  • ref 的值改变会更新视图,toRef 的值改变不会更新视图
<script>
//引入需要的API
import { reactive, toRef  } from 'vue'

export default {
   name: 'home',
   setup() {
      //通过reactive定义响应式对象
      const obj = reactive({ count: 0, name: '廊坊吴彦祖' }) 

      //通过toRef将obj中的count属性创建为ref
      const count = toRef(obj, 'count')

      //通过return暴露出去,供template中使用
      return {
         obj,
         count
      }
   }
}
</script>

toRefs

toRefs 把一个响应式对象转换成普通对象,该普通对象的每个属性都是一个 ref ,和响应式对象属性一一对应(将 reactive 对象中的所有值转化为响应式数据)

<script>
//引入需要的API
import { reactive, toRefs  } from 'vue'

export default {
   name: 'home',
   setup() {
      //通过reactive定义响应式对象
      const obj = reactive({ count: 0, name: '廊坊吴彦祖' }) 

      //通过toRefs将obj中的所有属性创建为ref
      const refObj = toRefs(obj)

      console.log(refObj)

      //通过return暴露出去,供template中使用
      return {
         obj,
         refObj
      }
   }
}
</script>

console.log(refObj): 在这里插入图片描述

readonly

readonly 传入一个对象(响应式或普通)或 ref,返回一个原始对象的只读代理(无法修改)

  • 只读的代理是“深层的”,对象内部任何嵌套的属性也都是只读的
<template>
   <div>
      {{ count }}
   </div>
</template>

<script>
//引入需要的API
import { readonly } from 'vue'

export default {
   name: 'home',
   setup() {
      //通过readonly定义只读对象
      const count = readonly(0) 

      count.value++   //修改报错!Cannot create property 'value' on number '0'
      
      //通过return暴露出去,供template中使用
      return {
         count
      }
   }
}
</script>

computed

computed(计算属性)传入一个 getter 函数,返回一个默认不可手动修改的 ref 对象,使用和 vue 2.0 区别不大

<template>
   <div>
      {{ count }}
      {{ addCount }}
   </div>
</template>

<script>
//引入需要的API
import { ref, computed } from 'vue'

export default {
   name: 'home',
   setup() {
      //通过ref定义响应式对象
      const count = ref(0) 

      //通过computed方法实现计算属性
      const addCount = computed(() => count.value + 1)

      console.log(addCount.value)   //1        

      //通过return暴露出去,供template中使用
      return {
         count,
         addCount
      }
   }
}
</script>

watch

watch(侦听器)需要侦听特定的数据源,并在回调函数中执行副作用,使用和 vue 2.0 区别不大

  • 默认情况是懒执行的,仅在侦听的源变更时才执行回调
<script>
//引入需要的API
import { ref, reactive, watch } from 'vue'

export default {
   name: 'home',
   setup() {
      //通过ref定义响应式对象
      const count = ref(0) 

      //通过watch方法实现对count的数据侦听
      watch(count, (newVal, oldVal) => {
         console.log('newVal:' + newVal)
         console.log('oldVal:' + oldVal)
      })

      //通过reactive定义响应式对象
      const obj = reactive({ count: 0 }) 
      
      //通过watch方法实现对obj.count的数据侦听
      watch(() => obj.count, (newVal, oldVal) => {
         console.log('newVal:' + newVal)
         console.log('oldVal:' + oldVal)
      })

	  //通过watch方法实现同时对count和obj.count的数据侦听
      watch([count, () => obj.count], ([newVal1, oldVal1], [newVal2, oldVal2]) => {
         console.log('newVal1:' + newVal1)
         console.log('oldVal1:' + oldVal1)
         console.log('newVal2:' + newVal2)
         console.log('oldVal3:' + oldVal2)
      })
      
      //通过return暴露出去,供template中使用
      return {
         count,
         obj
      }
   }
}
</script>

watchEffect

watchEffect 立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更时重新运行该函数

  • 当 watchEffect 在组件的 setup() 函数或生命周期钩子被调用时, 侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止(也可以显式调用返回值以停止侦听)
    //watchEffect方法返回stop方法,执行该方法停止侦听
    const stop = watchEffect(() => {
      ......
    })
    
    //停止侦听
    stop()
    

与 watch 的区别:

  • 不需要手动传入依赖(不需要手动指定侦听对象)
  • 每次初始化时会执行一次回调函数来自动获取依赖
  • 只能得到变化后的值,无法得到原值(变化前的值)
<script>
//引入需要的API
import { ref, reactive, watchEffect } from 'vue'

export default {
   name: 'home',
   setup() {
      //通过ref定义响应式对象
      const count = ref(0) 

      //通过reactive定义响应式对象
      const obj = reactive({ count: 0 }) 
      
      //通过watchEffect方法实现数据侦听(只能得到变化后的值)
      watchEffect(() => {
         console.log(count.value)
         console.log(obj.name)
      })
      
      //通过return暴露出去,供template中使用
      return {
         count,
         obj
      }
   }
}
</script>

模板 Refs

使用组合式 API 时,reactive refs 和 template refs 的概念已经是统一的。为了获得对模板内元素或组件实例的引用,需要在 setup() 中声明一个值为 null 的 ref 并返回它

  • 变量名必须和元素 ref 属性设置的名相同(获取 ref = box 的元素:const box = ref(null) )
<template>
   <div ref="box"></div>
</template>

<script>
//引入需要的API
import { ref, onMounted } from 'vue'

export default {
   name: 'home',
   setup() {
      //通过ref(null)获取指定元素
      const box = ref(null) 

      onMounted(() => {
         console.log(box.value)   //<div></div>
      })
      
      //通过return暴露出去,供template中使用
      return {
         box
      }
   }
}
</script>

vue3.0 生命周期

  • vue3.0 生命周期部分有所变化(setup 函数代替了 beforeCreate 和 created 两个生命周期函数,在生命周期 beforeCreate 之前被调用)

    vue 2.0vue3.0
    beforeCreatesetup
    createdsetup
    beforeMountonBeforeMount
    mountedonMounted
    beforeUpdateonBeforeUpdate
    updatedonUpdated
    beforeDestoryonBeforeUnmount
    destoryedonUnmounted
  • 使用 vue3.0 的生命周期,同样需要先从 vue 中导入,再在 setup 函数中使用

    <script>
    //引入生命周期
    import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, unMounted } from 'vue'
    
    export default {
       name: 'home',
       setup() {
          //组件挂载前
          onBeforeMount(() => {
             ......
          }) 
          //组件挂载前后
          onMounted(() => {
             ......
          })
          //组件更新前
          onBeforeUpdate(() => {
             ......   
          })
          //组件更新后
          onUpdated(() => {
     	     ......
          })
          //组件销毁前
          onBeforeUnmount(() => {
             ......  
          })
          //组件销毁后
          unMounted(() => {
             ......  
          })
          
          return {
             ......
          }
       }
    }
    </script>
    

vue2.0 项目中使用 vue3.0 新语法

安装 @vue/composition-api:

npm install @vue/composition-api --save

main.js:
import Vue from 'vue'
import App from './App.vue'

//引入@vue/composition-api
import VueCompositionAPI from '@vue/composition-api'
Vue.use(VueCompositionAPI)

Vue.config.productionTip = false

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

vue-cli4 创建 vue3.0 项目

  • 脚手架要求:vue-cli4.0 以上

    npm install -g @vue/cli

使用 vuecli4 脚手架创建 vue3.0 项目:

vue create vue3.0-demo

选择 Vue 3 在这里插入图片描述

路由配置:

vue3.0 下 vue-router4.0 相关 API:

  • createRouter:创建路由实例
  • createWebHashHistory:创建 hash 路由模式
  • createWebHistory:创建 history 路由模式
路由文件 router.js:
//引入路由相关API
import { createRouter, createWebHashHistory } from 'vue-router'
import Home from '../views/home/index.vue'
import About from '../views/about/index.vue'
//创建hash路由模式
const routerHashHistory = createWebHashHistory()

//创建路由实例
const router = createRouter({
    history: routerHashHistory,
    routes: [
      {
        name: 'home',
        path: '/',
        component: Home
      },
      {
        name: 'about',
        path: '/about',
        component: About
      }
    ]
})

export default router
main.js:
import { createApp } from 'vue'
import App from './App.vue'
//引入路由实例
import router from './router'

//将路由实例注入到vue根实例中
createApp(App).use(router).mount('#app')