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

原文: 本人掘金文章

关注公众号: 微信搜索 前端工具人 ; 收货更多的干货

一、 官方中文文档链接

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. 本编文章部分内容参考链接有:
posted @ 2020-09-29 17:26  会写代码的赖先生  阅读(1208)  评论(0编辑  收藏  举报