基于Vue cli的Vue3.0初体验

作者: Mstian | 来源:发表于2020-06-24 11:39 被阅读0次

    前言

    Vue3.0的步伐越来越近了,是时候了解起来了,虽然嘴上还喊学不动了,但是,身体还得诚实起来,接着学。。。
    通过各种博客资料,还有前段时间尤雨溪大佬的直播Vue3相对Vue2的比较大的变化有以下几种:

    • 使用 TypeScript
    • 放弃 class 采用 function-based API
    • option API => Composition API
    • 重构 complier
    • 重构 virtual DOM
    • 新的响应式机制

    使用ts的话就是抛弃了谷歌的flow选择拥抱微软的ts。从这个情况也看得出来,不会ts的该学起来了,比如我。。。

    放弃class采用function-based API据说也是为了更好支持ts,为了更灵活的逻辑复用能力,代码更容易压缩等...

    option Api到Composition Api应该是对我们去写代码影响最大的一部分,稍后可以看代码体验一下。

    重构compiler与virtual DOM使Vue变得更快,也是Vue越来越优秀的原因。

    新的响应式机制采用了ES6的ProxyApi,抛弃了之前的Object.defineProperty()比较直观的解决的是Vue2中这两点问题:

    • 关于对象:Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的。

    • 关于数组:Vue 不能检测以下数组的变动:

      1. 当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
      2. 当你修改数组的长度时,例如:vm.items.length = newLength

    在官网深入响应式原理一章有较详细阐述,针对以上两种情况解决方法,官网也有给出答案,那就是使用set方法。

    Proxy可以完美的解决该问题,当然好处应该不止这些,剩下的慢慢探究吧,Proxy也有缺点,那就是兼容性问题,有一些浏览器不支持,而且无完全polyfill,浏览器支持程度可以查看https://caniuse.com/#search=Proxy

    简单了解Proxy

    Vue核心就是响应式数据,Vue3.0中的响应式采用了Proxy那就简单看看Proxy是怎么个样子的呢。

    Proxy 对象用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。。。
    from MDN,学习一个新的Api官方文档还是要读一下哈,虽然读不懂这么深奥的描述。。。

    语法:

    const p = new Proxy(target, handler)

    参数target表示要使用Proxy包装的对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)

    参数handler是一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为

    看看代码吧:

    let obj = {
        a: 1,
        b: 2
    }
    const proxy = new Proxy(obj, {
        get: function(target, prop, receiver) {
            return prop in target ? target[prop] : 0
        },
        set: function(target, prop, value, receiver) {
            target[prop] = 666
        }
    })
    console.log(proxy.a) // 1
    console.log(proxy.c) // 0
    proxy.a = 10
    console.log(proxy.a) // 666
    obj.b = 10
    console.log(proxy.b) // 不是666 而是10 
    

    以上代码中obj是我们要代理的目标对象,getset方法是参数handler的两个属性,具体如下:

    handler.get()接收三个参数,第一个参数target为代理的目标对象,第二个参数prop是代理的目标对象的属性,第三个参数是Proxy或者继承Proxy的对象,通常是proxy本身。

    handler.set()接收四个参数,其中三个参数都与get方法相同,唯独多出来一个value表示新的属性值。

    上述代码表示当访问proxy的属性时,进行拦截判断,该属性是否是目标对象的属性,如果是那么就将其值返回出来,否则就返回0。

    当对proxy上的属性进行重写时,将重写的该属性赋值为666。

    注意:此时对数据的劫持,只是劫持了代理对象proxy,而跟原对象obj没有任何关系,对obj进行操作,也不会监听到。

    proxy实现一个简易版的数据响应:

    <body>
        <h2 id="app"></h2>
      <input id="input" type="text" />
    </body>
    
    let app = document.getElementById('app')
    let input = document.getElementById('input')
    
    let obj = { // 源数据
      text:'hello world'
    }
    
    let proxy = new Proxy(obj, {
      set: function(target, prop, value){ // input事件触发进行劫持,触发update方法
        target[prop] = value
        update(value)
      }
    })
    
    function update(value){ // update方法用于同步dom更新
        app.innerHTML = value
        input.value = value
    }
    
    input.addEventListener('input',function(e){ // 监听input数据变化,并修改proxy的值
      proxy.text = e.target.value
    })
    
    proxy.text = obj.text // 初始化源数据
    

    使用Vue CLI体验Vue3.0

    第一步,安装vue-cli

    npm install -g @vue/cli
    

    安装完成后查看是否已安装成功

    vue -V
    @vue/cli 4.4.4
    

    如果cli已安装需要注意其版本应该高于cli4.x。

    第二步,初始化vue项目

    vue create vue-next-test

    输入命令后,出现命令行交互,跟之前一样,主要是在初始时勾选上vue-router,vuex,避免在升级vue3的过程中手写初始化代码,会自动生成初始化代码。
    注意:vue3.0项目目前不能直接创建,需从vue2.x升级。

    vue-cli创建
    第三步,升级成Vue3.0项目
    以上只是创建了Vue2.x的项目,需要手动升级成Vue3.0的项目
    进入vue-next-test文件夹cd vue-next-test
    输以下指令:
    vue add vue-next
    

    执行上述指令会自动安装vue-cli-plugin-vue-next插件,该插件会自动完成以下操作

    • 安装vue3.0beta版依赖
    • 配置webpack去在vue3中编译.vue文件
    • 自动迁移全局api(创建新模板)
    • 升级安装Vue Router4.0和Vuex 4.0,如果默认为未安装,则不升级。
    • 自动修改Vue Router 和Vuex模板代码

    升级完成之后就可以看代码啦!

    第四步,查看Vue3.0的部分新的东西

    • Vuex对比

    3.0版本Vuex

    import Vuex from 'vuex'
    export default Vuex.createStore({
      state: {
      },
      mutations: {
      },
      actions: {
      },
      modules: {
      }
    })
    

    2.x版Vuex

    import Vue from 'vue';
    import Vuex from 'vuex';
    Vue.use(Vuex);
    export default new Vuex.Store({
      state: {
      },
      mutations: {
      },
      actions: {
      },
      modules: {
      }
    })
    

    Vue2.x版本采用构造函数构建Vue Router实例,而Vue3.0使用createStore方法来构建Vue实例,Vuex语法和Api基本没有发生变化。和之前一样,该怎么样写state,mutations等还是怎么写,该怎么调还怎么调。

    • Vue Router对比

    3.0版本

    import { createRouter, createWebHistory } from 'vue-router'
    import Home from '../views/Home.vue'
    const routes = [
      {
        path: '/',
        name: 'Home',
        component: Home
      },
      {
        path: '/about',
        name: 'About',
        component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
      },
      {
        path: '/test',
        name: 'test',
        component: () => import('../views/Test.vue')
      }
    ]
    
    const router = createRouter({
      history: createWebHistory(),
      routes
    })
    export default router
    

    2.x版本

    import Vue from 'vue'
    import VueRouter from 'vue-router'
    Vue.use(VueRouter)
    const routes = [
      {
        path: '/',
        name: 'Home',
        component: Home
      },
      {
        path: '/about',
        name: 'About',
        component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
      },
      {
        path: '/test',
        name: 'test',
        component: () => import('../views/Test.vue')
      }
    ]
    const router = new VueRouter({
      mode:"history",
      routes
    })
    export default router
    

    同样的Vue Router也是之前采用构造函数形式,3.0采用createRouter方法去创建Vue Router实例,配置方法都一样。在路由模式配置上,之前是配置mode option,3.0则是采用vue-router中的createWebHistory方法去创建history属性,我默认选择的是history模式用的是createWebHistory方法创建history属性,如果要修改为hash模式则需要使用createWebHashHistory方法来创建。

    总结:总的来说,构建Vue Router和Vuex的方式变了,但是它们的配置方式都和之前保持一致,可以无缝衔接使用。

    • Composition API
      在3.0代码基础上继续往下看,创建一个新的组件<Test/>,在<Test/>组件中认识一下Composition API
      之前2.x版本是采用了Options API的模式,可以理解为选项式的组件代码编写,Vue官方规定好的写法,响应式数据,methods,computed,components以及生命周期这些都是规定好的,需要在哪里写,你就得在哪里写。
    <script>
      export default {
        data:() => {
          return {}
        },
        methods:{
        },
        computed:{
        },
        component:{
        },
        mounted(){
        }
      }
    </script>
    

    Vue3.0采用Composition API的模式,可以理解成组合API,怎么个组合法呢?就类似于,在组件中实现的这些东西,响应式数据,生命周期,计算属性等,都可以在Vue中获取对应方法,然后在一个方法中组合起来统一对外输出。
    Composition API提供了以下一些函数,

    • ref
    • reactive
    • toRefs
    • computed
    • watch
    • getCurrentInstance
    • 生命周期hooks
    • ...
      在体验Composition API之前需要认识一个函数叫做setup(),这个函数的主要功能是Composition API的入口,它在生命周期beforeCreate生命周期执行之前被调用,接收props对象作为第一个参数,接收来的props对象,可以通过watch监视其变化。接受context对象作为第二个参数,这个对象包含attrs,slots,emit三个属性。多说无益,直接看代码吧。
    import { ref, reactive } from 'vue'
    export default {
      setup(props, context){
        const count = ref(0) // 定义响应式数据count
        const num = ref(1) // 定义响应式数据num
        const objData = {
          name: 'erha',
          age: '1',
          skill: '拆家'
        }
        const obj = reactive(objData) // 定义响应式数据obj
        return {
          count,
          num,
          obj
        }
      },
      name:'test'
    }
    

    在Vue3.0中创建响应式数据需要引用ref,reactive这两个方法,ref一般用来创建基本数据类型的响应式数据,reactive一般用来创建引用数据类型的响应式数据。
    在模板中使用,跟之前没有区别,需要注意的是,ref属于将基本类型数据包装成应用类型,在模板中正常使用。在方法中访问的时候需要带上.value才能访问到。
    以下代码我简写了,比如有一个按钮点击会触发一个方法,该方法是让count自增,那么应该这样写:

    setup(props, context){
      const count = ref(0)
      const addCount = () => {
        count.value ++
      }
      return {
        count,
        addCount
      }
    }
    

    为什么要这么写呢?是因为Proxy的原因,Proxy要进行数据劫持的时候需要接收一个对象,所以ref就对基本数据类型的数据进行了包装,使其可以进行响应式。在方法中需要使用count.value去操作,而在模板中进行了处理,所以可以直接使用count进行渲染。

    由于Proxy的机制原因,如果将reactive中的响应式数据进行解构,那么原先的响应式数据就变成不可响应的了。

    import { reactive } from 'vue'
    const data =reactive({
      name:'lisa',
      age:18
    })
    let { name , age} = data
    data.age = 20 // 响应式
    age = 30 // 非响应式
    

    为什么将可观察对象中的属性解构出来后,变成不再可观察了呢?因为通过reactive方法创建的可观察对象,内部的属性本身并不是可观察的,而是通过Proxy代理实现的读写观察,如果将这些属性解构,这些属性就不再通过原对象的代理来访问了,就无法再进行观察。

    Composition API提供了一种方法来解决此机制带来的问题,那就是toRefs,它可以将reactive创建的可观察对象,转换成可观察的ref对象

    import {reactive, toRefs} from "vue"
    const data =reactive({
      name:'lisa',
      age:18
    })
    let { name , age} = toRefs(data)
    data.age = 20 // 响应式
    age = 30 // 响应式
    

    在模板中使用reactive生成的可观察对象的时候是这样的:

    <template>
      <div>{{obj.name}}</div>
    </template>
    <script>
    import { reactive } from "vue"
    export default{
      setup(){
        const data = {
          name :"lisa"
        }
        const obj = reactive(data)
        return {
          obj
        }
      }
    } 
    </script>
    

    当使用了toRefs的时候在模板中只需要使用name即可

    <template>
      <div>{{name}}</div>
    </template>
    <script>
    import { reactive, toRefs } from "vue"
    export default{
      setup(){
        const data = {
          name :"lisa"
        }
        const obj = reactive(data)
        return {
          ...toRefs(obj)
        }
      }
    } 
    </script>
    

    Composition API提供的computed方法就相当于2.x版本中的计算属性。使用如下:

    import { ref, computed } from "vue"
    const count = ref(0)
    const doubleCount = computed(()=>{
      return count.value*2
    })
    

    Composition API提供的watch方法相当于就是2.x的观察属性。使用如下:

    import { ref, watch } from "vue"
    const count = ref(0)
    const num = ref(1)
    watch(() => { return count.value }, (newcount) => {
      console.log('count变啦', newcount)
    })
    

    watch方法接收两个参数,第一个参数是一个函数,第二个参数也是个函数,第一个参数函数返回值表示要监听哪个数据,第二个参数函数,表示监听成功后的逻辑,该函数的第一个参数就是监听到目标数据变化后的值。
    同时watch可以监听多个数据。

    watch(
      [() => count.value, () => num.value],
      ([count, num], [oldCount, oldNum]) => { // watch 同时观察count和num两个值
        console.log(`count:${count},num:${num} oldCount:${oldCount},oldNum:${oldNum}`)
      })
    

    在Vue2.x版本中频繁出现的this,在Vue3.0中也消失了,取而代之的是Composition API提供的getCurrentInstance方法,用来获取当前组件实例,然后通过ctx获取当前上下文。

    import {getCurrentInstance} from "vue"
    export default{
      setup(){
        const {ctx} =  getCurrentInstance()
        console.log(ctx)
      }
    }
    

    大概是这么个东西


    ctx

    可以和Vue2.x中this输出对比一下。还是有不小的变动,但常用api都没有发生变化。比如切换路由

    const pushRoute = () => { // 编程导航
      ctx.$router.push({
        path: '/about'
      })
    }
    

    整体的Options API,到Composition API,大致就是之前很多挂载在Vue原型上的东西,现在都独立成一个方法然后去引用使用。之前组件中的this容易把人绕迷糊,如果采用Composition API就会好很多。之前Vue组件中强制data写在哪里,methods写在哪里,computed写在哪里,而在Vue3.0中这种规定被打破,开发者可以比较自由的组织自己的代码,两者都有自己的好处与弊端。详见可以参考文章https://juejin.im/post/5eb17a0fe51d454dd60cfe0f

    最后看一下Vue3.0中的生命周期,生命周期也是有所改动,钩子函数名称均发生变化。beforeCreate,created生命周期在setup方法中自动执行,其余生命周期钩子函数都是从Vue中引入使用(注意在setup方法中使用)

    import {
      ref,
      onBeforeMount,
      onMounted,
      onBeforeUpdate,
      onUpdated,
      onBeforeUnmount,
      onUnmounted,
      onErrorCaptured,
      onRenderTracked,
      onRenderTriggered
    } from 'vue'
    export default {
      setup (props, context) {
        // console.log(props.msg, context)
        const a = ref(0)
        const setA = () => {
          return a.value++
        }
        // 相当于 beforeMount
        onBeforeMount(() => {
          console.log('onBeforeMount')
        })
        // 相当于 mounted
        onMounted(() => {
          console.log('onMounted')
        })
        // 相当于 beforeUpdate
        onBeforeUpdate(() => {
          console.log('onBeforeUpdate')
        })
        // 相当于 updated
        onUpdated(() => {
          console.log('onUpdated')
        })
        // 相当于 beforeDestroy
        onBeforeUnmount(() => {
          console.log('onBeforeUnmount')
        })
        // 相当于 destroyed
        onUnmounted(() => {
          console.log('onUnmounted')
        })
        onErrorCaptured(() => { // 错误监控 参考文章 https://zhuanlan.zhihu.com/p/37404624
          console.log('onErrorCaptured')
        })
        onRenderTracked(() => { // 已渲染
          console.log('onRenderTracked')
        })
        onRenderTriggered(() => { // 当组件更新时会首先触发此生命周期钩子 onRenderTriggered->onRenderTracked->onBeforeUpdate->onUpdated
          console.log('onRenderTriggered')
        })
        return {
          a,
          setA
        }
      },
      name: 'HelloWorld',
      props: {
        msg: String
      }
    }
    

    onRenderTracked生命周期钩子函数表示组件已渲染。组件首次渲染经历过程为onRenderTracked->onBeforeMount->onMounted
    onErrorCaptured(err,vm,info)生命周期钩子表示捕获子孙组件中的发生错误时的异常。err:错误对象 vm:发生错误的vuez组件实例 info:Vue特定错误信息,比如发生错误的生命周期
    onRenderTriggered组件更新时会触发此钩子函数。触发生命周期钩子函数过程为onRenderTriggered->onRenderTracked->onBeforeUpdate->onUpdated

    在Vue3.0中由于外界声音反响比较大的原因,尤大以及团队考虑在3.0版本中可以持续使用2.x的东西,比如可以同时写mounted和onMounted两个生命周期,但是不建议这样做,如果使用Vue3.0那么就踏踏实实用3.0的东西去写。如果使用2.x版本的话,可以引用一些方法等,按照需要一点点向3.0慢慢过渡。总之,任何一个框架都是需要更新的,更新肯定会有变化,那么就慢慢学吧。

    我的练习源码在github上里面有我写的一些注释,有兴趣的也可以看一看https://github.com/Mstian/Vue3.0-test
    文中如有错误,还望不吝指出,谢谢。

    参考文章:
    vue 3.0 初体验 (项目搭建)
    简明扼要聊聊 Vue3.0 的 Composition API 是啥东东!
    VUE 3.0 学习探索入门系列 - Vue3.x 生命周期 和 Composition API 核心语法理解(6)
    聊聊vue3.0新特性:compositon api 用法和注意事项
    Vue源码系列(二):错误处理

    偶然发现一些比较不错的资料:
    https://www.yuque.com/vueconf/2019

    相关文章

      网友评论

        本文标题:基于Vue cli的Vue3.0初体验

        本文链接:https://www.haomeiwen.com/subject/fqkrfktx.html