Vue3.0新特性

本文主要讲解Vue3常用的新特性,详细可以查看Vue3.0官网教程

Vue3 比 Vue2 到底强在哪里?

  • Vue3 具有更明显的性能的提升(具体体现在:打包大小更小,初次渲染更快,更新更快,内存使用减少等优点)
  • Vue3 的 composition API 解决了组件碎片化的问题,使组件更具逻辑化,方便后期维护。
  • Vue3 增加了一些好用的新特性,如片段(Fragment)、Tree-shaking 和 Teleport 等。

Fragment(片段)

在 Vue 2.x 中,由于不支持多根节点组件,当开发者意外创建一个时会发出警告
The template root requires exactly one element.)。

在 Vue 3.x 中,组件可以包含多个根节点

<!-- Layout.vue -->
<template>
  <header>...</header>
  <main v-bind="$attrs">...</main>
  <footer>...</footer>
</template>
复制代码

一个新的全局 API:createApp

在 Vue 2.x 中使用 new 的方式来创建一个Vue实例,而 Vue 3.x 则用 Vue.createApp 来初创建一个Vue实例。

const app = Vue.createApp({
  /* 选项 */
})
复制代码

以下是 Vue2 全局 API 对应 Vue3 全局API的表

2.x 全局 API 3.x 实例 API (app)
Vue.config app.config
Vue.config.productionTip 移除
Vue.config.ignoredElements app.config.isCustomElement
Vue.component app.component
Vue.directive app.directive
Vue.mixin app.mixin
Vue.use app.use
Vue.prototype app.config.globalProperties

Tree-shaking

Tree-shaking的本质是消除无用的js代码。无用代码消除在广泛存在于传统的编程语言编译器中,编译器可以判断出某些代码根本不影响输出,然后消除这些代码,这个称之为DCE(dead code elimination)。
Tree-shaking 是 DCE 的一种新的实现,Javascript同传统的编程语言不同的是,javascript绝大多数情况需要通过网络进行加载,然后执行,加载的文件大小越小,整体执行时间更短,所以去除无用代码以减少文件体积,对javascript来说更有意义。

在 Vue 3.x 中,全局和内部 API 都经过了重构,并考虑到了 tree-shaking 的支持。因此,全局 API 现在只能作为 ES 模块构建的命名导出进行访问。例如:

// vue 2
import Vue from 'vue'

Vue.nextTick(() => {
  // 一些和DOM有关的东西
})

// vue 3
import { nextTick } from 'vue'

nextTick(() => {
  // 一些和DOM有关的东西
})
复制代码
//vue 2
import { shallowMount } from '@vue/test-utils'
import { MyComponent } from './MyComponent.vue'

test('an async feature', async () => {
  const wrapper = shallowMount(MyComponent)

  // 执行一些DOM相关的任务

  await wrapper.vm.$nextTick()

  // 运行你的断言
})

//vue 3
import { shallowMount } from '@vue/test-utils'
import { MyComponent } from './MyComponent.vue'
import { nextTick } from 'vue'

test('an async feature', async () => {
  const wrapper = shallowMount(MyComponent)

  // 执行一些DOM相关的任务

  await nextTick()

  // 运行你的断言
})

复制代码

通过这一更改,如果模块打包工具支持 tree-shake,则 Vue 应用程序中未使用的全局 API 将从最终的打包产物中排除,从而获得最佳的文件大小。

Vue 2.x 中的这些全局 API 受此更改的影响

  • Vue.nextTick
  • Vue.observable (用 Vue.reactive 替换)
  • Vue.version
  • Vue.compile (仅完整构建版本)
  • Vue.set (仅兼容构建版本)
  • Vue.delete (仅兼容构建版本)

生命周期

Vue2 和 Vue3 生命周期的对比
image.png

emits选项(新增)

和 prop 类似,组件可触发的事件可以通过 emits 选项被定义

<template>
  <div>
    <p>{{ text }}</p>
    <button v-on:click="$emit('accepted')">OK</button>
  </div>
</template>
<script>
  export default {
    props: ['text'],
    emits: ['accepted']
  }
</script>
复制代码

该选项也可以接收一个对象,该对象允许开发者定义传入事件参数的验证器,和 props 定义里的验证器类似。

Teleport

Teleport 是一种能够将我们的模板移动到 DOM 中 Vue app 之外的其他位置的技术,就有点像哆啦A梦的“任意门”。下面举个?:

//index.html

  <div id="app"></div>
+  <div id="teleport-target"></div>
  <script type="module" src="/src/main.js"></script>
复制代码
//src/components/HelloWorld.vue 中,添加如下,留意 to 属性跟上面的 id 选择器一致

  <button @click="showToast" class="btn">打开 toast</button>
  <!-- to 属性就是目标位置 -->
  <teleport to="#teleport-target">
    <div v-if="visible" class="toast-wrap">
      <div class="toast-msg">我是一个 Toast 文案</div>
    </div>
  </teleport>
  
<script>
  import { ref } from 'vue';
  export default {
  setup() {
    // toast 的封装
    const visible = ref(false);
    let timer;
    const showToast = () => {
      visible.value = true;
      clearTimeout(timer);
      timer = setTimeout(() => {
        visible.value = false;
      }, 2000);
    }
    return {
      visible,
      showToast
    }
  }
}
</script>
复制代码

效果图以及更佳详细地理解可以看这篇文章(文章清晰易懂)Vue 3 任意传送门——Teleport

移除了 $children

在 Vue2.x 中,开发者可以使用 this.$children 直接访问当前实例的子组件:

<template>
  <div>
    <img alt="Vue logo" src="./assets/logo.png">
    <my-button>Change logo</my-button>
  </div>
</template>

<script>
import MyButton from './MyButton'

export default {
  components: {
    MyButton
  },
  mounted() {
    console.log(this.$children) // [VueComponent]
  }
}
</script>
复制代码

在 Vue3.x 中,$childrens 用法已移除,不再支持。

移除了过滤器 filter

在 Vue2.x 中可以使用过滤器来处理通用文本格式。

<template>
  <h1>Bank Account Balance</h1>
  <p>{{ accountBalance | currencyUSD }}</p>
</template>

<script>
  export default {
    props: {
      accountBalance: {
        type: Number,
        required: true
      }
    },
    filters: {
      currencyUSD(value) {
        return '$' + value
      }
    }
  }
</script>
复制代码

在 Vue3.x 中,过滤器已删除,不再支持。相反地,Vue官方建议用方法调用或计算属性来替换它们。

移除了 .sync 修饰符

在 Vue2.x 中,我们有时候需要对某一个 prop 进行 “双向绑定” ,可以进行以下操作。

<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />
// 简写
<ChildComponent :title.sync="pageTitle" />
复制代码

相当于 Vue3.x 中以v-model修饰符代替.sync修饰符。

<ChildComponent v-model:title="pageTitle" />
复制代码

移除 v-on.native 修饰符

在 Vue2.x 中,要将原生 DOM 监听器添加到子组件的根元素中,可以使用 .native 修饰符

<my-component
  v-on:close="handleComponentEvent"
  v-on:click.native="handleNativeClickEvent"
/>
复制代码

在 Vue3.x 中,v-on 的 .native 修饰符已被移除。同时,新增的 emits 选项允许子组件定义真正会被触发的事件。

<my-component
  v-on:close="handleComponentEvent"
  v-on:click="handleNativeClickEvent"
/>
复制代码
// MyComponent.vue
<script>
  export default {
    emits: ['close']
  }
</script>
复制代码

移除 $listeners

$listeners 对象在 Vue 3 中已被移除。具体可以看官方文档

移除 $(on、off、once)

在 Vue3 中$(on,off,once) 实例方法已被移除,应用实例不再实现事件触发接口。

<script>
	created() {
        console.log(this.$on, this.$once, this.$off) // undefined undefined undefined
	}
</script>

复制代码

!!!重头戏来了,Composition API是 Vue3里最重要的特性之一!!!

Composition API

什么是Composition API?

为了更好地理解Composition API,我找到了 大帅老猿 关于Composition API的4张动画图,图出自 做了一夜动画,就为让大家更好的理解Vue3的Composition Api

我们先来看看 Vue 2 中的Options API是什么样子的:
1bd101840df446c78d52e9c14711aae7_tplv-k3u1fbpfcp-watermark.gif

当我们的项目有了新的需求时,往往是需要在Options API中添加如下功能代码:

568b0ced69f241d282cf2c512e4e5f33_tplv-k3u1fbpfcp-watermark.gif

当我们的组件开始变得更大时,逻辑关注点的列表也会增长。尤其对于那些一开始没有编写这些组件的人来说,这会导致组件难以阅读和理解。因此Vue3 Composition API就是为了解决这种问题。
当我们使用了Vue3的 Composition API后可以使同一个逻辑关注点相关代码收集在一起,这样就增加了可阅读性:

d05799744a6341fd908ec03e5916d7b6_tplv-k3u1fbpfcp-watermark.gif

4146605abc9c4b638863e9a3f2f1b001_tplv-k3u1fbpfcp-watermark.gif

既然我们知道了为什么,我们就可以知道怎么做。为了开始使用组合式 API,我们首先需要一个可以实际使用它的地方。在 Vue 组件中,我们将此位置称为 setup

新的 setup 选项在组件创建之前执行,一旦 props 被解析,就将作为组合式 API 的入口。setup 选项是一个接收 propscontext 的函数。

(⚠️注意:在 setup 中你应该避免使用 this,因为它不会找到组件实例。setup 的调用发生在 data property、computed property 或 methods 被解析之前,所以它们无法在 setup 中被获取。)

export default {
  components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  props: {
    user: {
      type: String,
      required: true
    }
  },
  setup(props) {
    console.log(props) // { user: '' }

    return {} // 这里返回的任何内容都可以用于组件的其余部分
  }
  // 组件的“其余部分”
}
复制代码

带 ref 的响应式变量

在 Vue 3.0 中,我们可以通过一个新的 ref 函数使任何响应式变量在任何地方起作用,如下所示:

import { ref } from 'vue'

const counter = ref(0)
复制代码

ref 接收参数并将其包裹在一个带有 value property 的对象中返回,然后可以使用该 property 访问或更改响应式变量的值:

import { ref } from 'vue'

const counter = ref(0)

console.log(counter) // { value: 0 }
console.log(counter.value) // 0

counter.value++
console.log(counter.value) // 1
复制代码

将值封装在一个对象中,看似没有必要,但为了保持 JavaScript 中不同数据类型的行为统一,这是必须的。这是因为在 JavaScript 中,Number 或 String 等基本类型是通过值而非引用传递的,在任何值周围都有一个封装对象,这样我们就可以在整个应用中安全地传递它,而不必担心在某个地方失去它的响应性。

在 setup 内注册生命周期钩子

为了使组合式 API 的功能和选项式 API 一样完整,我们还需要一种在 setup 中注册生命周期钩子的方法。

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted } from 'vue'

// 在我们的组件中
setup (props) {
  const repositories = ref([])
  const getUserRepositories = async () => {
    repositories.value = await fetchUserRepositories(props.user)
  }

  onMounted(getUserRepositories) // 在 `mounted` 时调用 `getUserRepositories`

  return {
    repositories,
    getUserRepositories
  }
}
复制代码

watch 响应式更改

就像我们在组件中使用 watch 选项并在 user property 上设置侦听器一样,我们也可以使用从 Vue 导入的 watch 函数执行相同的操作。它接受 3 个参数:

  • 一个想要侦听的响应式引用或 getter 函数
  • 一个回调
  • 可选的配置选项
import { ref, watch } from 'vue'

const counter = ref(0)
watch(counter, (newValue, oldValue) => {
  console.log('The new counter value is: ' + counter.value)
})
复制代码

每当 counter 被修改时,例如 counter.value=5,侦听将触发并执行回调 (第二个参数),在本例中,它将把 ‘The new counter value is:5’ 记录到控制台中。

独立的 computed 属性

与 ref 和 watch 类似,也可以使用从 Vue 导入的 computed 函数在 Vue 组件外部创建计算属性。让我们回到 counter 的例子:

import { ref, computed } from 'vue'

const counter = ref(0)
const twiceTheCounter = computed(() => counter.value * 2)

counter.value++
console.log(counter.value) // 1
console.log(twiceTheCounter.value) // 2
复制代码

这里我们给 computed 函数传递了第一个参数,它是一个类似 getter 的回调函数,输出的是一个只读的响应式引用。为了访问新创建的计算变量的 value,我们需要像 ref 一样使用 .value property。

接下来我们来看由Options API 转换成 Composition API的两段代码:

// Options API

export default {
  components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  props: {
    user: { 
      type: String,
      required: true
    }
  },
  data () {
    return {
      repositories: [], // 1
      filters: { ... }, // 3
      searchQuery: '' // 2
    }
  },
  computed: {
    filteredRepositories () { ... }, // 3
    repositoriesMatchingSearchQuery () { ... }, // 2
  },
  watch: {
    user: 'getUserRepositories' // 1
  },
  methods: {
    getUserRepositories () {
      // 使用 `this.user` 获取用户仓库
    }, // 1
    updateFilters () { ... }, // 3
  },
  mounted () {
    this.getUserRepositories() // 1
  }
}
复制代码
// src/composables/useRepositoryNameSearch.js

import { ref, computed } from 'vue'

export default function useRepositoryNameSearch(repositories) {
  const searchQuery = ref('')
  const repositoriesMatchingSearchQuery = computed(() => {
    return repositories.value.filter(repository => {
      return repository.name.includes(searchQuery.value)
    })
  })

  return {
    searchQuery,
    repositoriesMatchingSearchQuery
  }
}


// Composition API
import { toRefs } from 'vue'
import useUserRepositories from '@/composables/useUserRepositories'
import useRepositoryNameSearch from '@/composables/useRepositoryNameSearch'
import useRepositoryFilters from '@/composables/useRepositoryFilters'

export default {
  components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  props: {
    user: {
      type: String,
      required: true
    }
  },
  setup(props) {
    const { user } = toRefs(props)

    const { repositories, getUserRepositories } = useUserRepositories(user)

    const {
      searchQuery,
      repositoriesMatchingSearchQuery
    } = useRepositoryNameSearch(repositories)

    const {
      filters,
      updateFilters,
      filteredRepositories
    } = useRepositoryFilters(repositoriesMatchingSearchQuery)

    return {
      // 因为我们并不关心未经过滤的仓库
      // 我们可以在 `repositories` 名称下暴露过滤后的结果
      repositories: filteredRepositories,
      getUserRepositories,
      searchQuery,
      filters,
      updateFilters
    }
  }
}
复制代码

setup

使用 setup 函数时,它将接收两个参数:
1、props
2、context

Props

setup 函数中的第一个参数是 props。正如在一个标准组件中所期望的那样,setup 函数中的 props 是响应式的,当传入新的 prop 时,它将被更新。

// MyBook.vue

export default {
  props: {
    title: String
  },
  setup(props) {
    console.log(props.title)
  }
}
复制代码

但是,因为 props 是响应式的,你不能使用 ES6 解构,它会消除 prop 的响应性。
如果需要解构 prop,可以在 setup 函数中使用 toRefs 函数来完成此操作:

// MyBook.vue

import { toRefs } from 'vue'

setup(props) {
  const { title } = toRefs(props)

  console.log(title.value)
}
复制代码

如果 title 是可选的 prop,则传入的 props 中可能没有 title 。在这种情况下,toRefs 将不会为 title 创建一个 ref 。你需要使用 toRef 替代它:

// MyBook.vue
import { toRef } from 'vue'
setup(props) {
  const title = toRef(props, 'title')
  console.log(title.value)
}
复制代码

Context

传递给 setup 函数的第二个参数是 context。context 是一个普通的 JavaScript 对象,它暴露组件的三个 property:

// MyBook.vue

export default {
  setup(props, context) {
    // Attribute (非响应式对象)
    console.log(context.attrs)

    // 插槽 (非响应式对象)
    console.log(context.slots)

    // 触发事件 (方法)
    console.log(context.emit)
  }
}
复制代码

context 是一个普通的 JavaScript 对象,也就是说,它不是响应式的,这意味着你可以安全地对 context 使用 ES6 解构。

// MyBook.vue
export default {
  setup(props, { attrs, slots, emit }) {
    ...
  }
}
复制代码

WatchEffect

该方法接收一个函数并且立即执行,并当该函数里的变量变更时,重新执行该函数。该方法无法获取到原值,只能是改变之后的值。

watchEffect(() => {
	console.log(nameObj.name) 
})
复制代码

取消监听

const stop = watchEffect(() => {
	console.log(nameObj.name) 
    setTimeout(() => {
    	stop()
    }, 5000)
})
复制代码

总结一下 watch 和 watchEffect 的区别

watch:
1.具有一定的惰性lazy 第一次页面展示的时候不会执行,只有数据变化的时候才会执行
2.参数可以拿到当前值和原始值
3.可以侦听多个数据的变化,用一个侦听起承载

watchEffect:
1.立即执行,没有惰性,页面的首次加载就会执行。
2.不能获取之前数据的值 只能获取当前值

有关响应性API的可以查看
官方文档响应性API

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享