• Vue3.0 进阶、环境搭建、相关API的使用


    原文: 本人掘金文章

    关注公众号: 微信搜索 web全栈进阶 ; 收货更多的干货

    一、 官方中文文档链接

    https://vue3js.cn/docs/zh/

    二、开篇

    • vue3.0 从去年预热到9月18号晚上,已正式发布 vue3.0 beta 版本;
    • beta 版意味着 vue3.0 开业正式投入到项目中了;大家可以开心的学习了(前端新技术你继续出,我还学得动...因为要生活要吃饭!!!);
    • 前端技术生态一直不断的更新换代,许多人都觉得亚历山大,学不动了;但不是学不动就可以不学了么。。。 0.0;学不动也得学,不然适应自己的只会是淘汰

    三、vue2.0 项目的建议

    引用官方文档作者的话:

    提示:
    我们仍在开发 Vue 3 的专用迁移版本,该版本的行为与 Vue 2 兼容,运行时警告不兼容。如果你计划迁移一个非常重要的 Vue 2 应用程序,我们强烈建议你等待迁移版本完成以获得更流畅的体验

    目前作者的意思是:对于 vue2.0 的项目强烈不建议升到 vue3.0;因为目前的beta版本以及现有的框架及插件,不是很支持和兼容vue3.0语法; 所以肯定有很多预想不到的问题;对于跃跃欲试升级vue3.0的小伙伴们,只能等待官方的兼容版本开发完,在做迁移; 毕竟线上项目不是开玩笑的,出了一个bug都可能是重大损失。 这个锅家里没矿的基本背不动...

    四、介绍

    Vue3带来些什么? 参考至: 公众号:前端早读课文章

    详细文档请参考: 官方中文文档链接

    • 更快
      • 重构了Virtual DOM
        • 标记静态内容,并区分动态内容
        • 更新时只diff动态的部分
      • 重构了 双向数据绑定
        • Object.defineProperty() --> Proxy API
        • Proxy 对于复杂的数据结构减少了循环递归的监听;初始渲染循环递归是非常耗性能的;
        • Proxy 对于数组的变异方法(会修改原数组),不在需要单独用数组原生方法重写、处理
        • 语法也比defineProperty简洁多了,直接监听某个属性即可;
      • 事件缓存
        • vue2中,针对绑定事件,每次触发都要重新生成全新的function去更新;
        • Vue3中,提供了事件缓存对象cacheHandlers,当cacheHandlers开启的时候,编译会自动生成一个内联函数,将其变成一个静态节点,这样当事件再次触发时,就无需重新创建函数直接调用缓存的事件回调方法即可
    • 更小 (Tree shaking支持)
      • 简而言之: 不会把所有的都打包进来,只会打包你用到的api;大项目你会发现热加载、初始渲染提升了很多
      • 很大程度的减少了开发中的冗余代码,提升编译速度
    • 更易于维护
      • Vue3Flow迁移到TypeScript
        • 多人协同开发的情况下,用了 TypeScript 之后的酸爽你会吐槽,为什么早不出现 TypeScript
      • 代码目录结构遵循monorepo
        • 核心观点: 代码分割到一个个小的模块中, 开发者大部分只是工作在少数的几个文件夹,并且也只会编译自己负责的模块;而不是整个项目编译
    • 新功能和特性 Composition API
      • 不要在意越来越像react-hook;毕竟别人的优点是值得自己学习的;
      • Composition API 函数式开发,很大程度的提高组件、业务逻辑的复用性;高度解耦;提升代码质量、开发效率;减少代码体积
    • 提升开发效率 vite 的支持 (当然目前来说 vite 功能还不够强大和稳定, 但尤大把它作为vue3官方构建工具,那肯定尤大会完善它的; 可自由选择webpack还是vite
      • vite在开发环境下基于浏览器原生 ES imports 开发,在生产环境下基于Rollup打包
        • 快速的冷启动
        • 即时的模块热更新
        • 真正的按需编译
      • vue2.0 相信很多小伙伴都是结合webpack开发; 但是有没有发现初期项目小的时候很爽运行、编译、热加载都很快; 项目一大... 打包、运行、改个功能热加载的时候... 我们先去上个厕所/接个水;忙的时候挺烦这环节
      • vue3.0 结合 vite作者的介绍是不跟项目体积庞大而影响,开始啥样现在也啥样; 当然夸张是夸张了点, 但是相差应该不大;

    五、 环境搭建

    // 对于 Vue 3,应该 npm 上可用的 Vue CLI v4.5 作为 @vue/cli@next
    yarn global add @vue/cli@next
    # OR
    npm install -g @vue/cli@next
    
    // 创建项目
    npm init vite-app <project-name>
    # OR
    yarn create vite-app <project-name>
    
    // 下载依赖及运行项目, 已 npm 方式为例; 详细步骤官方文档的安装页都有
    cd <project-name>
    npm install
    npm run dev // 项目就能跑起来并且访问了
    

    拓展: 项目引入其他插件比如 vue-router4.0、vuex4.0、typescript等请参考 Vue3.0环境搭建

    六、 语法介绍

    上手之前应该先阅读一遍 vue3 对于 vue2 的一些变更; 详情点击 官网文档重大变更

    vue2中使用的是Options API; vue3中的是Composition API 简称纯函数式API

    6.1、 setup

    vue3 组件入口为 setup(){} 函数作为入口, 默认只执行一次;执行顺序在 beforeCreate 之后 created 之前;

    ...
    // 使用props和this
    setup (props, ctx) {
      // props 组件间传递的参数; 
      // ctx 组件的实例的执行上下文(可以理解为 vue2 this) 
         /* 可执行 下面等操作:例 ctx.$emit() 
         attrs: Object
         emit: ƒ ()
         listeners: Object
         parent: VueComponent
         refs: Object
         root: Vue 
         */
      // 注意 steup 中没有this了, 拿不到this
    }
    

    6.2、 生命周期

    我记得早期是说 vue3 中是移除掉了 beforeCreatecreated两个生命周期; 但是实践的时候我发现还是可以写的; 因为vue2vue3 写法目前相兼容;

    created () { console.log('created') }
    setup (props, ctx) {
      console.log('setup')
      // mounted 新写法 记住一句话 所有的方式都是以函数的形式呈现
      onMounted(() => {})
    }
    mounted () { console.log('mounted') }
    // 执行顺序 setup created mounted
    

    虽然兼容但尽量不要这样写;向前看齐嘛; 强烈推荐全部都放在steup函数中

    6.3、 reactive、ref、tofefs、isRef

    创建响应式对象 reactive、ref、tofefs 用法, 对应 vue2 中的 data 推荐写法3

    // 写法一:响应式数据一多, return 要很多次; 使用数据的时候要通过state拿到
    <template>
      <div>
        <p>{{state.count}}</p>
      </div>
    </template>
    import {reactive} from 'vue'
    ...
    setup(props, ctx) {
      const state = reactive({ 
        count: 0
      })
      return { state }
    }
    
    // 写法二
    <template>
      <div>
        <p>{{count}}</p>
      </div>
    </template>
    import {reactive} from 'vue'
    ...
    setup(props, ctx) {
      const state = reactive({ 
        count: 0
      })
      return {
        count: state.count
      }
    }
    
    // 写法三:推荐 通过 toRefs 代理对象, 再通过解构的方式取值
    <template>
      <div>
        <p>{{count}}</p>
      </div>
    </template>
    import {reactive, toRefs} from 'vue'
    ...
    setup(props, ctx) {
      const state = reactive({ 
        count: 0
      })
      return {
        ...toRefs(state)
      }
    }
    
    // 写法四:通过 ref() 函数包装, 返回值是一个对象,对象上只包含一个 value 属性, 就是要的属性值
    <template>
      <div>
        <p>{{count}}</p>
        <p>{{count1}}</p>
      </div>
    </template>
    import {reactive, toRefs, ref} from 'vue'
    ...
    setup(props, ctx) {
      // 父组件传递count属性
      // 写法1
      const count = ref(props.count)
      console.log(count.value) // 对应props.count的值
      // 写法2 
      const state = reactive({ 
        count1: ref(props.count)
      })
      return {
        count,
        ...toRefs(state)
      }
    }
    
    // isRef 来判断某个值是否为 ref() 创建出来的对象
    import { ref, isRef } from 'vue';
    export default {
      setup(props, ctx) {
        const refCount = ref(0)
        const count = isRef(refCount) ? refCount : 1
      }
    };
    

    6.4、 computed

    例子场景:结合 vue-router 根据当前路劲为count赋值, 也扩展下vue-router的用法

    <template>
      <div>
        <p>{{count}}</p>
        <p>{{count1}}</p>
      </div>
    </template>
    import {reactive, toRefs, computed} from 'vue'
    import {useRoute} from 'vue-router'
    ...
    setup(props, ctx) {
      const route = useRoute()
      const state = reactive({ 
        // 计算属性 写法1
        count: computed(() => {
          return route.path
        })
      })
      // 计算属性 写法2
      const count1 = computed(() => {
        return route.path
      })
      return {
        ...toRefs(state),
        // 计算属性不需要通过 toRefs 结构, 因为他就是一个具体的值就是响应式的
        count1
      }
    }
    

    6.5、watch 、watchEffect

    例子场景:同 computed 一样

    watchEffectwatch 有什么不同:

    1. watchEffect 不需要指定监听的属性,他会自动的收集依赖, 只要我们回调中引用到了 响应式的属性, 那么当这些属性变更的时候,这个回调都会执行
    2. watch 只能监听指定的属性
    3. watch 可以获取到新值与旧值,而 watchEffect 不行
    4. watchEffect 在组件初始化的时候就会执行一次用以收集依赖(与computed同理),后续收集的依赖发生变化,这个回调才会再次执行
    // watch 用法 监听单个属性
    <template>
      <div>
        <p>{{count}}</p>
      </div>
    </template>
    import {reactive, toRefs, watch} from 'vue'
    import {useRoute} from 'vue-router'
    ...
    setup(props, ctx) {
      const route = useRoute()
      const state = reactive({ 
        count: 0,
      })
      // 监听路由路劲, immediate 是否立即执行一次
      watch(() => route.path, (newValue) => {
        state.count = newValue
      }, { immediate: true })
      
      return {
        ...toRefs(state),
      }
    }
    
    // watch 用法 监听ref数据源
    <template>
      <div>
        <p>{{count}}</p>
      </div>
    </template>
    import {reactive, toRefs, ref, watch} from 'vue'
    ...
    setup(props, ctx) {
      // 定义数据源
      let count = ref(0);
      // 指定要监视的数据源
      watch(count, (count, prevCount) => {
        console.log(count, prevCount)
      })
      setInterval(() => {
        count.value += 2
      }, 2000)
      console.log(count.value)
      return {
        count
      }
    }
    
    // watch 用法 监听多个属性
    <template>
      <div>
        <p>{{count}}</p>
      </div>
    </template>
    import {reactive, toRefs, watch} from 'vue'
    ...
    setup(props, ctx) {
      const state = reactive({ 
        name: 'vue',
        age: 3
      })
      watch(
        // 监听name、 age
        [() => state.name, () => state.age],
        // 如果属性改变、则执行以下回调
        ([newName, newAge], [oldname, oldAge]) => {
          console.log(oldname, oldname)
          console.log(oldAge, oldAge)
        },
        { lazy: true} // 在 watch 被创建的时候,不执行回调函数中的代码
      )
      setTimeout(() => {
        state.name = 'react'
        state.age += 1
      }, 3000)
      return {
        ...toRefs(state),
      }
    }
    
    // watchEffect 用法
    <template>
      <div>
        <p>{{count}}</p>
      </div>
    </template>
    import {reactive, toRefs, ref, watchEffect} from 'vue'
    import {useRoute} from 'vue-router'
    ...
    setup(props, ctx) {
      const route = useRoute()
      const state = reactive({ 
        count: 0,
      })
      // 当 route.path 变化时就会执行打印, 有点类似 react-hook 的 useEffect 第二个参数效果
      watchEffect(() => {
        count = route.path
        console.log(route.path)
      })
      // watchEffect、 watch 都可以主动停止监听
      const stop = watchEffect(() => { 
        count = route.path
        console.log(route.path)
      })
      // 在某个时机下 执行 stop() 停止watchEffect监听
      if (...) { stop() }
      return {
        ...toRefs(state),
      }
    }
    

    七、 Vue3中移除的一些API和方法

    7.1 取消KeyboardEvent.keyCode

    Vue2.x中,绑定键盘事件会用到如下代码:

    <!-- keyCode version -->
    <input v-on:keyup.13="submit" />
    
    <!-- alias version -->
    <input v-on:keyup.enter="submit" />
    

    或者是:

    Vue.config.keyCodes = {
      f1: 112
    }
    <!-- keyCode version -->
    <input v-on:keyup.112="showHelpText" />
    
    <!-- custom alias version -->
    <input v-on:keyup.f1="showHelpText" />
    

    在事件中,给keyup配置一个指定按钮的keyCode(数字)在Vue3中将不会生效,但是依然可以使用别名,例如:

    <input v-on:keyup.delete="confirmDelete" />
    

    7.2 移除 $on,$off 和 $once方法

    Vue2.x中可以通过EventBus的方法来实现组件通信:

    var EventBus = new Vue()
    Vue.prototype.$EventBus = EventBus
    ...
    this.$EventBus.$on()  this.$EventBus.$emit()
    

    这种用法在Vue3中就不行了,在Vue3中移除了 $on,$off等方法(参考rfc),而是推荐使用mitt方案来代替:

    import mitt from 'mitt'
    const emitter = mitt()
    // listen to an event
    emitter.on('foo', e => console.log('foo', e) )
    // fire an event
    emitter.emit('foo', { a: 'b' })
    

    7.3 移除filters

    Vue3中,移除了组件的filters项,可以使用methods的或者computed来进行替代:

    <template>
      <p>{{ accountBalance | currencyUSD }}</p>
    </template>
    <script>
      export default {
        filters: {
          currencyUSD(value) {
            return '$' + value
          }
        }
      }
    </script>
    

    替换为:

    <template>
      <p>{{ accountInUSD }}</p>
    </template>
    <script>
      export default {
        props: {
          accountBalance: {
            type: Number,
            required: true
          }
        },
        computed: {
          accountInUSD() {
            return '$' + this.accountBalance
          }
        }
      }
    </script>
    

    八、Vue3中改变的API和写法

    8.1 实例初始化

    vue2.x中通过new Vue()的方法来初始化:

    import App from './App.vue'
    new Vue({
      store,
      render: h => h(App)
    }).$mount('#app')
    

    vue3Vue不再是一个构造函数,通过createApp方法初始化:

    import App from './App.vue'
    createApp(App).use(store).mount('#app')
    

    8.2 全局API调用方式改变

    Vue2.x中,大部分全局API都是通过Vue.xxx或者Vue.abc()方式调用,例如:

      import Vue from 'vue'
      Vue.mixin()
      Vue.use()
    

    而在Vue3中,这些方式将会改变,取而代之的是如下:

      import { createApp } from 'vue'
      const app = createApp({})
      app.mixin()
      app.use()
    

    同时,可以只引入一些需要的API,不需要的不用引入,这样也符合Three Shaking的要求,例如:

      import { nextTick,reactive,onMounted } from 'vue'
      nextTick(() => {
      })
      onMounted(() => {
      })
    

    由于Vue3中全局API都会通过app.xxx的方法调用,所以之前通过Vue.prototype.xxx绑定的全局方法和变量将无法使用,可以采用如下方式来代替:

      //在main.js中:
      app.config.globalProperties.http = function(){}
    
      //在vue组件中:
      this.http()
    

    8.3 render方法修改

    Vue2.x中,有时会自定义render方法来返回模板内容,如下:

    export default {
      render(h) {
        return h('div')
      }
    }
    

    Vue3中,h通过vue来引入,如下:

    import { h } from 'vue'
    export default {
      render() {
        return h('div')
      }
    }
    

    8.4 新的异步组件创建方式

    Vue2.x中,尤其是在Vue Router中,会经常使用到异步组件,借助webpack的打包方式,可以将一个组件的代码进行异步获取,例如:

    const asyncPage = () => import('./NextPage.vue')
    const asyncPage = {
      component: () => import('./NextPage.vue'),
      delay: 200,
      timeout: 3000,
      error: ErrorComponent,
      loading: LoadingComponent
    }
    

    Vue3中,提供了defineAsyncComponent()方法创建异步组件,同时可以返回一个Promise对象来自己控制加载完成时机,如下:

    import { defineAsyncComponent } from 'vue'
    const asyncPageWithOptions = defineAsyncComponent({
      loader: () => import('./NextPage.vue'),
      delay: 200,
      timeout: 3000,
      error: ErrorComponent,
      loading: LoadingComponent
    })
    const asyncComponent = defineAsyncComponent(
      () =>
        new Promise((resolve, reject) => {
          /* ... */
        })
    )
    

    九、暂时性的结尾:

    1. 写了大半天,写的有点啰嗦,新API基本都写了几种编码方式,根据自己的爱好取舍
    2. 后续慢慢加入自己对一些新API认识及用法
    3. 本编文章部分内容参考链接有:
  • 相关阅读:
    关于前端复用的构思
    react-redux单元测试(基于react-addons-test-utils,mocha)
    关于windows下NODE_ENV=test无效的情况解决办法
    javascript柯里化及组合函数~
    如何使用函数式编程?
    redux源码解析-函数式编程
    react案例->新闻移动客户端--(react+redux+es6+webpack+es6的spa应用)
    jQuery高级技巧——性能优化篇
    jQuery高级技巧——DOM操作篇
    使用checkbox实现纯CSS下拉框
  • 原文地址:https://www.cnblogs.com/ljx20180807/p/13743703.html
Copyright © 2020-2023  润新知