生命周期钩子
我们可以直接看生命周期图来认识都有哪些生命周期钩子(图片是根据官网翻译后绘制的):
image.png
从图中我们可以看到Vue3.0新增了 setup ,这个在前面我们也详细说了, 然后是将Vue2.x中的 beforeDestroy 名称变更成 beforeUnmount; destroyed 表更为 unmounted ,作者说这么变更纯粹是为了更加语义化,因为一个组件是一个 mountunmount 的过程。其他Vue2中的生命周期仍然保留。

上边 生命周期图 中并没包含全部的生命周期钩子, 还有其他的几个, 全部生命周期钩子如图所示:
image.png
我们可以看到 beforeCreatecreatedsetup 替换了(但是Vue3中你仍然可以使用, 因为Vue3是向下兼容的, 也就是你实际使用的是vue2的)。其次,钩子命名都增加了 on ; Vue3.x还新增用于调试的钩子函数 onRenderTriggeredonRenderTricked

1.性能

  • 双向响应原理由Object.defineProperty改为基于ES6Proxy,使其颗粒度更大,速度更快,且消除了之前存在的警告;
  • 重写了 Vdom ,突破了Vdom` 的性能瓶颈
  • 进行了模板编译的优化
  • 进行了更加高效的组件初始化

2.Tree-Shaking 的支持

支持了 tree-shaking (剪枝):像修剪树叶一样把不需要的东西给修剪掉,使 Vue3 的体积更小。

需要的模块才会打入到包里,优化后的 Vue3.0 的打包体积只有原来的一半(13kb)。哪怕把所有的功能都引入进来也只有23kb,依然比 Vue2.x 更小。像 keep-alivetransition 甚至 v-for 等功能都可以按需引入。

3.Composition API

composition-api 是一个 Vue3 中新增的功能,它的灵感来自于 ReactHooks ,是比 mixin 更强大的存在。
语法糖介绍

compositon-api提供了一下几个函数

  • reactive
  • watchEffect
  • computed
  • ref
  • toRefs

composition-api 可以提高代码逻辑的可复用性,从而实现与模板的无关性;同时使代码的可压缩性更强。另外,把 Reactivity 模块独立开来,意味着 Vue3.0 的响应式模块可以与其他框架相组合。

4.Fragments

不再限制 template 只有一个根节点。
render函数也可以返回数组了,有点像 React.Fragments

5.Better TypeScript Support

更好的类型推导,使得 Vue3TypeScript 支持得非常好

6.Custom Renderer API

实现用DOM的方式进行 WebGL 编程

体验Vue3.0

初始化项目

  1. 使用脚手架创建项目
vue create my-Project
  1. 安装composition-api,体验新特性
npm i @vue/composition-api -s
  1. 使用插件
// main.js

import Vue from 'vue'
import VueCompositionAPI from '@vue/composition-api'

Vue.use(VueCompositionAPI)

Setup函数

setup()函数是Vue3.0中,专门为组件提供的新属性。它为基于Composition API的新特性提供了统一的入口。

在Vue3中,定义methodswatchcomputeddata数据都放在了setup()函数中

  1. 执行时机
    setup()函数会在created()生命周期之前执行。

setup执行时机.png
2. 接收props数据
propssetup()函数的一个形参,组件接收的props数据可以在setup()函数内访问到。

setup(props) {
    console.log(props.p1)
}
  1. context上下文对象
    contextsetup() 的第二个形参,它是一个上下文对象,可以通过 context 来访问Vue的实例 this。
setup(props,context) {
    console.log(this)
    console.log(context)
}

image.png
=======setup的context参数.png=========

注意:在 setup() 函数中访问不到Vue的 this 实例

Teleport

如果用过React的同学,可能对于 Portals 比较熟悉,详见。React 的 Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案,我理解,Vue 3 中的 Teleport 跟这个其实是类似的

在 Vue2,如果想要实现类似的功能,需要通过第三方库 portal-vue 去实现,感兴趣可以了解一下

本篇文章主要来探讨以下两个点:

  • Teleport是什么?它解决的是什么问题?
  • 通过一个小实例介绍Teleport的使用

为什么我们需要 Teleport

Teleport 是一种能够将我们的模板移动到DOMVue app之外的其他位置的技术,就有点像哆啦A梦的“任意门”

场景:像modals,toast等这样的元素,很多情况下,我们将它完全的和我们的Vue应用的DOM`完全剥离,管理起来反而会方便容易很多

原因在于如果我们嵌套在 Vue 的某个组件内部,那么处理嵌套组件的定位z-index和样式就会变得很困难

另外,像modals,toast等这样的元素需要使用到Vue组件的状态(data 或者 props)的值

这就是Teleport派上用场的地方。我们可以在组件的逻辑位置写模板代码,这意味着我们可以使用组件的 data 或 props。然后在 Vue 应用的范围之外渲染它
Teleport的使用
准备
快速搭建一个vue3的项目

$ npm init vite-app learn-vue3
$ cd learn-vue3
$ npm install
$ npm run dev

yarn

$ yarn create vite-app learn-vue3
$ cd learn-vue3
$ yarn
$ yarn dev

打开: http://localhost:3000/,看到如下页面,说明成功了
image.png

toast

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>
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
    }
  }
}

效果展示:
image.png
可以看到,我们使用 teleport 组件,通过 to 属性,指定该组件渲染的位置与

同级,也就是在 body 下,但是 teleport 的状态 visible 又是完全由内部Vue组件控制

与 Vue components 一起使用 —— modal

如果<teleport>包含Vue组件,则它仍将是 父组件的逻辑子组件

接下来我们以一个 modal 组件为例

 <div id="app"></div>
  <div id="teleport-target"></div>
+  <div id="modal-container"></div>
  <script type="module" src="/src/main.js"></script>
  <teleport to="#modal-container">
    <!-- use the modal component, pass in the prop -->
    <modal :show="showModal" @close="showModal = false">
      <template #header>
        <h3>custom header</h3>
      </template>
    </modal>
  </teleport>

JS 核心代码如下:

import { ref } from 'vue';
import Modal from './Modal.vue';
export default {
  components: {
    Modal
  },
  setup() {
    // modal 的封装
    const showModal = ref(false);
    return {
      showModal
    }
  }
}

在这种情况下,即使在不同的地方渲染Modal,它仍将是当前组件(调用 Modal 的组件)的子级,并将从中接收show prop

这也意味着来自父组件的注入按预期工作,并且子组件将嵌套在 Vue Devtools 中的父组件之下,而不是放在实际内容移动到的位置

看实际效果以及在Vue Devtool中。
image.png

总结

本文主要介绍了 Vue 3的新特性——Teleport,从为什么要使用Teleport,以及通过两个小 demo,演示它的基础使用,希望能够对你有帮助

emits选项

  • 官方文档传送门
  • Vue官方建议我们在组件中所有的emit事件都能在组件的emits选项中声明
  • emits参数有俩种形式对象和数组,对象里面可以配置带校验emit事件,为null的时候代表不校验,校验的时候,会把emit事件的参数传到校验函数的参数里面
  • 当校验函数不通过的时候,控制台会输出一个警告,但是emit事件会继续执行
  • 记录一个坑:比如你emit事件的名称正好和原生事件的名字重复了,那么这个事件会执行俩次,那么配置了emits这个选项的话,就能很好的解决这个问题,下去自己实验一下,这篇文章中不做演示
    我们看一下带校验和不带校验的emit事件一个例子

子组件Emiter.vue

<template>
  <button @click="handleClick">点击emit-click事件</button>
  <button @click="handleOpen">点击emit-open事件</button>
</template>
<script lang="ts">
import {defineComponent} from "vue";
export default defineComponent({
  emits: {
    click: null,//click事件没有检验
    open: (value) => {
      if (typeof value === "string") {
        return true;
      } else {
        return false;
      }
    },
  },
  setup(props, {emit}) {
    const handleClick = function() {
      emit("click");
    };
    const handleOpen = function() {
      emit("open", 1);
    };
    return {
      handleClick,
      handleOpen,
    };
  },
  data() {
    return {};
  },
  methods: {},
});
</script>
<style scoped></style>

父组件Emit.vue

<template>
  <emiter @click="onClick" @open="onOpen"></emiter>
</template>
<script lang="ts">
import {defineComponent} from "vue";
import Emiter from "@/components/Emiter.vue";
export default defineComponent({
  components: {
    Emiter,
  },
  data() {
    return {};
  },
  methods: {
    onClick() {
      console.log("click me!");
    },
    onOpen() {
      console.log("open me!");
    },
  },
});
</script>
<style scoped></style>

看一下结果,控制台输出警告信息
image.png

状态和事件绑定

Vue 3.0 中定义状态的方法改为类似 React Hooks 的方法,下面我们在 Test.vue 中定义一个状态 count:

<template>
  <div class="test">
    <h1>test count: {{count}}</h1>
  </div>
</template>

<script>
  import { ref } from 'vue'

  export default {
    setup () {
      const count = ref(0)
      return {
        count
      }
    }
  }
</script>

Vue 3.0 中初始化状态通过setup方法,

定义状态需要调用ref方法。接下来我们定义一个事件,用来更新count状态:

<template>
  <div class="test">
    <h1>test count: {{count}}</h1>
    <button @click="add">add</button>
  </div>
</template>

<script>
  import { ref } from 'vue'

  export default {
    setup () {
      const count = ref(0)
      const add = () => {
        count.value++
      }
      return {
        count,
        add
      }
    }
  }
</script>

这里的add方法不再需要定义在methods中,
但注意更新count值的时候不能直接使用count++,而应使用 count.value++
更新代码后,点击按钮count的值就会更新了:

计算属性和监听器

Vue 3.0 中计算属性和监听器的实现依赖computedwatch方法:

computed

<template>
  <div class="test">
    <h1>test count: {{count}}</h1>
    <div>count * 2 = {{doubleCount}}</div>
    <button @click="add">add</button>
  </div>
</template>

<script>
  import { ref, computed, watch } from 'vue'

  export default {
    setup () {
      const count = ref(0)
      const add = () => {
        count.value++
      }
      watch(() => count.value, val => {
        console.log(`count is ${val}`)
      })
      const doubleCount = computed(() => count.value * 2)
      return {
        count,
        doubleCount,
        add
      }
    }
  }
</script>

计算属性computed是一个方法,里面需要包含一个回调函数,当我们访问计算属性返回结果时,会自动获取回调函数的值:

const doubleCount = computed(() => count.value * 2)

watch

监听器watch同样是一个方法,它包含2个参数,2个参数都是 function:

watch(() => count.value, 
  val => {
    console.log(`count is ${val}`)
  })

第一个参数是监听的值,count.value表示当count.value发生变化就会触发监听器的回调函数,即第二个参数,第二个参数可以执行监听时候的回调
如果是2个以上的监听属性 就这样

watch(
  [refA, () => refB.value],
  ([a, b], [prevA, prevB]) => {
    console.log(`a is: ${a}`)
    console.log(`b is: ${b}`)
  }
)

持续更新,未完待续....

Q.E.D.


学而不厌 不耻下问