秒转Vue3教程

1,513 阅读8分钟

20210609103414.jpg

1. Vue3的介绍

Vue.js 设计的初衷就包括可以被渐进式地采用 ,所以Vue3支持2中的大部分特性 (迁移指南)。

Vue3相比于Vue2有哪些进步?

巧黑板,实际开发没有多大用,面试极大概率问道。💪

e331220d4440436f8b7eabddc6e4a841.jpeg

Vue3新增特性:

  • 性能

    • 虚拟DOM的重写

    • 打包大小减少( tree shaking)

    • 初次渲染快

    • 内存使用减少(compositionAPI)

  • 新增语法(compositionAPI)

    • ref和reactive

    • computed和watch

    • 新的生命周期函数

    • 自定义函数-Hooks函数(像React Hooks)

  • 其它新赠特性

    • Teleport 瞬移组件
    • Suspense 异步加载组件
    • 全局API的修改和优化
    • Custom renderer
  • 任何的修改都已经移到全局的实例上去

    全局配置

    • Vue.config 改成 app.config

    全局注册类API

    • Vue.component 改为 app.component

    • Vue.directive 改为 app.directive

    行为扩展类API

    • Vue.mixin 改为 app.mixin

    • Vue.use 改为 app.use

  • 更好的Typescript支持

Vue2遇到的难题

Vue2的组件 API 设计所面对的核心问题之一就是如何组织逻辑,以及如何在多个组件之间抽取和复用逻辑。基于 Vue 2 目前的 API 我们有一些常见的逻辑复用模式,但都或多或少存在一些问题。这些模式包括:

  • Mixins
    • 命名冲突
    • 不清楚暴露出来变量的作用
    • 重用到其他component经常会遇到问题
  • 高阶组件 (Higher-order Components, aka HOCs)
  • Renderless Components (基于 scoped slots / 作用域插槽封装逻辑的组件)

总体来说,以上这些模式存在以下问题:

  • 模版中的数据来源不清晰

    ​ 举例来说,当一个组件中使用了多个 mixin 的时候,光看模版会很难分清一个属性到底是来自哪一个 mixin。HOC 也有类似的问题。

  • 命名空间冲突

    ​ 由不同开发者开发的 mixin 无法保证不会正好用到一样的属性或是方法名。HOC 在注入的 props 中也存在类似问题。

  • 性能

    ​ HOC 和 Renderless Components 都需要额外的组件实例嵌套来封装逻辑,导致无谓的性能开销。

Vue3解决的问题

Function-based API 受 React Hooks 的启发,提供了一个全新的逻辑复用方案,且不存在上述问题。使用基于函数的 API,我们可以将相关联的代码抽取到一个 "composition function"(组合函数)中 —— 该函数封装了相关联的逻辑,并将需要暴露给组件的状态以响应式的数据源的方式返回出来。这里是一个用组合函数来封装鼠标位置侦听逻辑的例子:

function useMouse() {
  const x = ref(0)
  const y = ref(0)
  const update = e => {
    x.value = e.pageX
    y.value = e.pageY
  }
  onMounted(() => {
    window.addEventListener('mousemove', update)
  })
  onUnmounted(() => {
    window.removeEventListener('mousemove', update)
  })
  return { x, y }
}

// 在组件中使用该函数
const Component = {
  setup() {
    const { x, y } = useMouse()
    // 与其它函数配合使用
    const { z } = useOtherLogic()
    return { x, y, z }
  },
  template: `<div>{{ x }} {{ y }} {{ z }}</div>`
}

从以上例子中可以看到:

  • 暴露给模版的属性来源清晰(从函数返回)
  • 返回值可以被任意重命名,所以不存在命名空间冲突
  • 没有创建额外的组件实例所带来的性能损耗
类型问题:

Vue3原本期望Class类解决类型推倒问题,最后依然存在类型问题.最后采用了天然对类型很友好的基于函数的API,因为基于函数API在使用TS或是原生JS时写出来的代码几乎一摸一样

打包尺寸:

基于函数的 API 每一个函数都可以被 ES export 被单独引入,主要这样对tree-shaking很友好

没有使用的API打包的时候会被移除,基于函数API有更好的压缩效率,但是Class的属性和方法名不行

2. 体验Vue3

安装三种方式:

  1. 在页面上以 CDN 包的形式导入。
  2. 使用 npm 安装它。
  3. 使用官方的 CLI 来构建一个项目。
1. cdn
<script src="https://unpkg.com/vue@next"></script>
2. npm
// 最新稳定版
npm install vue@next
3. 命令行工具 (CLI)
// 最新稳定版
yarn global add @vue/cli
// 然后在Vue项目中运行(更新相关插件)
vue upgrade --next
// 创建Vue3项目跟2一样
vue create vue3_demo
4. Vite:
// yarn
yarn create @vitejs/app <project-name>‘
cd <project-name>
yarn
yarn dev

hello,word!!!

<script src="https://unpkg.com/vue@next"></script>
<body>
    <div id="app">
        {{msg}} == {{count}}
    </div>
</body>
<script>
   // 没用compositionAPI跟传统写法一样
    const CountApp = {
        data() {
            return {
                msg: 'hello,word',
                count: 0
            }
        },
        mounted() {
            setInterval(() => {
                this.count++
            }, 1000)
        }
    }
    // 声明式渲染
    Vue.createApp(CountApp).mount('#app')
</script>

CompositionAPI:

const { ref, computed, watch, onMounted } = Vue
const App = {
  template: `
    <div>
    <span> 铁蛋儿很帅!!!{{ count }}</span>
    <span> 你也是这么认为的!!!! {{ plusOne }}</span>
    <button @click="increment">点赞+++</button>
    </div> 
`,
  setup() {
    // 响应的数据
    const count = ref(0)
    // 计算属性
    const plusOne = computed(() => count.value + 1)
    // method方法
    const increment = () => { count.value++ }
    // 观察
    watch(() => count.value * 2, val => {
      console.log(`count * 2 is ${val}`)
    })
    // 生命周期
    onMounted(() => {
      console.log(`mounted`)
    })
    // 双向绑定的数据必须返回
    return {
      count,
      plusOne,
      increment
    }
  }
}
// 声明式渲染
Vue.createApp(App).mount('#app')

3. 响应式对象

1. setup函数

setup函数是组件逻辑的地方,它在组件实例被创建时,初始化 props 之后调用。

// setup() 会接收到初始的 props 作为参数:
const MyComponent = {
  props: {
    name: String
  },
  setup(props) {
    console.log(props.name)
  }
}

注意:

传进来的 props 对象虽然是响应式的可以被当作数据源去观测,但是后续props 发生变动时它也会被框架内部同步更新,所以不要直接修改。(会导致警告)

组件状态 :

setup() 可以返回一个对象类似data,这个对象上的属性将会被暴露给模版的渲染上下文:

const MyComponent = {
  props: {
    name: String
  },
  setup(props) {
    return {
      msg: `hello ${props.name}!`
    }
  },
  template: `<div>{{ msg }}</div>`
}

2. ref

创建一个可以在 setup() 内部被管理的值,可以使用 ref 函数。

ref函数返回的是只有一个value属性的包装对象(主要是用来包装原始类型的数据)。

import { ref } from 'vue'

const MyComponent = {
  setup(props) {
    const msg = ref('hello')
    const appendName = () => {
      msg.value = `hello ${props.name}`
    }
    return {
      msg,
      appendName
    }
  },
  // 当包装对象被暴露给模版渲染上下文,或是被嵌套在另一个响应式对象中的时候,它会被自动展开为内部的值。
  template: `<div @click="appendName">{{ msg }}</div>`
}

// 这样包装对象的值就可以直接修改了
console.log(msg.value) // 'hello'
msg.value = 'tiedan' // 修改为tiedan

为什么需要包装对象?

我们知道在 JavaScript 中,原始值类型如 string 和 number 是只有值,没有引用的。如果在一个函数中返回一个字符串变量,接收到这个字符串的代码只会获得一个值,是无法追踪原始变量后续的变化的。

因此,包装对象的意义就在于提供一个让我们能够在函数之间以引用的方式传递任意类型值的容器。

3. reactive

返回一个没有包装的响应对象(主要用于非原始类型的数据

import { reactive } from 'vue'

const object = reactive({
  count: 0
})

object.count++

4. toRefs

​ 将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的ref

说人话就是: 响应式对象转为普通对象用它可以再变成响应式的

 const { reactive, toRefs } = Vue
 const MyComponent = {
   setup() {
     const obj = reactive({
       count: 0
     })
     const inc = () => {
       obj.count++
     }
     return {
       // 
       // 如果直接用ES6扩展运算符,会取消双向数据绑定的特性,使用toRefs(),转为响应式数据
       ...toRefs(obj),
       inc
     }
   },
   template: `<h1 @click="inc">{{ count }}</h1>`
 }

5. Watchers

watchEffect

自动收集依赖源,依赖源更新时重新执行自身

  1. 立即执行,没有惰性,页面的首次加载就会执行。

  2. 自动检测内部代码,代码中有依赖就会执行。

  3. 不需要传递要侦听的内容, 会自动感知代码依赖。

const { watchEffect, ref } = Vue

Vue.createApp({
  setup() {
    const userId = ref(0)
    // 只要数据数据发生改变就触发
    watchEffect(() => console.log(userId.value))
    setTimeout(() => {
      userId.value = 1
    }, 1000)
    return {
      userId
    }
  }

}).mount('#app')

watch

显式指定依赖源,依赖源更新时执行回调函数

  1. 具有一定的惰性lazy 第一次页面展示的时候不会执行,只有数据变化的时候才会执行。
  2. 参数可以拿到当前值和原始值。
  3. 可以侦听多个数据的变化。
// 侦听一个 getter
const state = reactive({ count: 0 })
watch(
  () => state.count,
  (count, prevCount) => {
    /* ... */
  }
)

// 直接侦听ref
const count = ref(0)
watch(count, (count, prevCount) => {
  /* ... */
})

// 侦听多个数据源
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
  /* ... */
})

两着的区别 :

  • watchEffect和computed比较像,都是通过执行副作用函数获取要监听的数据源,而watch是通过第一参数获取要监听的数据源(可以监听多个)
  • watch的参数有3个(数据源,副作用函数,配置),watchEffect只有两个(副作用函数,配置)
  • watch的副作用函数接收的参数比watchEffect多两个(新旧值)

相同点 :

  • watch 与 watchEffect共享停止侦听(源码都是调用doWatch方法只是传的参数不同)

  • 相同的副作用刷新时机

  • 都能通过返回的停止函数停止监听

4. 生命周期

  • 除去 beforeCreatecreated之外,在我们的 setup 方法中,有9个旧的生命周期钩子,我们可以在setup方法中访问

    • onBeforeMount——挂载开始前调用

    • onMount——挂载后调用

    • onBeforeUpdate——当响应数据改变,且重新渲染前调用

    • onUpdated——重新渲染后调用

    • onBeforeUnmount——Vue实例销毁前调用

    • onUnmounted——实例销毁后调用

    • onActivated——当keep-alive组件被激活时调用

    • onDeactivated——当keep-alive组件取消激活时调用

    • onErrorCaptured——从子组件中捕获错误时调用

  • 改变两个销毁生命周期更具有语义化

    • beforeDestory 换成 beforeUnmount
    • destoryed 换成 unmounted
  • 新增两个方便调试生命周期

    • onRenderTracked
    • onRenderTriggered

5. Teleport瞬移组件

​ 一个常见的场景是创建一个包含全屏模式的组件。在大多数情况下,你希望模态框的逻辑存在于组件中,但是模态框的快速定位就很难通过 CSS 来解决,或者需要更改组件组合

​ 当在初始的 HTML 结构中使用这个组件时,我们可以看到一个问题——模态框是在深度嵌套的 div 中渲染的,而模态框的 position:absolute 以父级相对定位的 div 作为引用。

​ Teleport 提供了一种干净的方法,允许我们控制在 DOM 中哪个父节点下渲染了 HTML,而不必求助于全局状态或将其拆分为两个组件。

6. Suspense异步组件

Suspense组件用于在等待某个异步组件解析时显示后备内容

应用实例:

  • 在页面加载之前显示加载动画
  • 显示占位符内容
  • 处理延迟加载的图像

定义异步:

 app.component('component-a', {
        setup() {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    resolve({
                        result: "异步组件"
                    });
                }, 2000)
            })
        },
        template: `<h1>{{result}}</h1>`,         
 })

Suspense组件应用:

<div id="app">
  <Suspense>
    <template #default>
      <component-a></component-a>
    </template>
    <template #fallback>
      <h1>Loadding...</h1>
    </template>
  </Suspense>
</div>

以上就是我对Vue3的简单总结