sunmaobin / sunmaobin.github.io

blog
https://github.com/sunmaobin/sunmaobin.github.io
174 stars 11 forks source link

VUE 3.0 学习探索入门系列 - Vue3.x 令人期待的新特性(7) #79

Open sunmaobin opened 3 years ago

sunmaobin commented 3 years ago

在前面的文章中我们也聊了许多 Vue3.x 相比 Vue2.x 有哪些变化,也介绍了一些它的特点,今天就重点介绍下它新增的这些特性。

本文主要参考:vueschool.io/articles/vu…

总览

  1. Compostion API 合成API
  2. 取消 Vue 全局变量
  3. 自定义指令 Directives API调整
  4. Component 组件支持 v-model 指令
  5. Fragments Template 支持有多个根节点
  6. Suspense Template Fallback 组件
  7. Teleport Template Dom占位传递组件

1 Compostion API 合成API

上一篇文章已经重点介绍过了。

查看:juejin.im/post/684490…

2 取消 Vue 全局变量

Vue2.x 中的代码片段:

import Vue from 'vue'
import App from './App.vue'

Vue.config.ignoredElements = [/^app-/]
Vue.use(/* ... */)
Vue.mixin(/* ... */)
Vue.component(/* ... */)
Vue.directive(/* ... */)

new Vue({
  render: h => h(App)
}).$mount('#app')

Vue3.x 中取消了全局变量 Vue,改为实例函数 createApp() 创建实例对象。

import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

app.config.ignoredElements = [/^app-/]
app.use(/* ... */)
app.mixin(/* ... */)
app.component(/* ... */)
app.directive(/* ... */)

app.mount('#app')

RFC查看网友讨论:github.com/vuejs/rfcs/…

这个变化很大,将会给我们从 Vue2.x 升级到 Vue3.x 带来不小的工作量。

为什么这么改变?其实也好理解,Vue3.x 基于函数式编程,所以:一切皆函数。 为了保证每个函数都有自己的小 圈子 能独立运行,所以从源头上就开始 开刀

3 自定义指令 Directives API调整

Vue2.x 中自定义一个指令:

const MyDirective = {
  bind(el, binding, vnode, prevVnode) {},
  inserted() {},
  update() {},
  componentUpdated() {},
  unbind() {}
}

Vue3.x 中变成了:

const MyDirective = {
  beforeMount(el, binding, vnode, prevVnode) {},
  mounted() {},
  beforeUpdate() {},
  updated() {},
  beforeUnmount() {}, // new
  unmounted() {}
}

这可以算上是一个 breaking change 了,主要是在 Vue3.x 中生命周期函数的变化导致的。

查看上一篇文章,了解 Vue3.x 的生命周期有哪些变化:juejin.im/post/684490…

RFC看这里:github.com/vuejs/rfcs/…

4 Component 组件支持 v-model 指令

Vue2.x 中我们会把 v-model 用在一些表单元素上,用于数据的双向绑定。

<input v-model="property />

但是,如果我们希望父子组件也能双向绑定时,Vue2.x 是不建议的,因为这会给父组件的维护带来灾难!

所以在 Vue2.x 中建议使用 this.$emit() 事件回传机制明确通知父组件,真正的更新还是父组件自己实现。

后来为了简化上述操作,在 Vue2.3.0 新增了 .sync 修饰符。

参考:cn.vuejs.org/v2/guide/co…

比如:父组件调用子组件 text-document 时,子组件就可以修改父组件的 doc.title

<text-document v-bind:title.sync="doc.title"></text-document>

好了,通过以上描述我们可以得出结论:

那么在 Vue3.x 中得到了统一:

:xxx.sync 将被 v-model:xxx 取代

如果你希望跟子组件直接双向绑定,则:

<text-document v-model="doc"></text-document>

或者多个属性之间一一绑定:

<text-document 
    v-model:title="doc.title"
    v-model:content="doc.content"
></text-document>

RFC查看网友讨论:github.com/vuejs/rfcs/…

5 Fragments Template 支持有多个根节点

Vue2.x 中 Template 模板你通常是这么写的:

<template>
    <div>
        <p>Hello</p>
        <p>Vue2.x</p>
    </div>
</template>

template 中只能有唯一一个根节点。原因就是每个 Vue 的实例只允许绑定到唯一的Dom树上。

如果你希望绑定两个Dom,那么你就只能在新建一个 Vue 示例,但是这样就跟当前系统脱节了,没啥意义了。

或者使用这个插件:vue-fragments,类似于 React 中的 <React.Fragment>

<template>
  <v-fragment>
    <div>Fragment 1</div>
    <div>Fragment 2</div>
  </v-fragment>
</template>

但是在 Vue3.x 中,就可以不用唯一根节点,也不用插件了,变得简单了许多:

<template>
    <p>Hello</p>
    <p>Vue3.x</p>
</template>

6 Suspense Template Fallback 组件

Vue2.x 中你应该会经常遇到这种场景:

<template>
    <div>
        <div v-if="!loading">
            ...
        </div>
        <div v-if="loading">Loading...</div>
    </div>
</template>

或者安装这个插件:vue-async-manager

然后,就变成了:

<template>
    <div>
        <Suspense>
            <div>
                ...
            </div>
            <div slot="fallback">Loading...</div>
        </Suspense>
    </div>
</template>

Vue3.x 感觉就是参考了上面这个组件的做法,现在可以这么写:

<Suspense>
  <template #default>
    ...
  </template>
  <template #fallback>
    Loading...
  </template>
</Suspense>

#fallback 其实在 Vue3.x 中就是 slot 的简写。所以,#default 可以省略。

当然 React 也有 Suspense 组件解决类似的问题。

其实,这个全局组件可能更多的会配合异步组件使用。顺便说下,在 Vue3.x 中,定义一个异步组件使用:defineAsyncComponent

7 Teleport Template Dom占位传递组件

注意: teleport3.0.0-alpha.11 刚改的名字,之前叫:portal, 查看 CHANGELOG

Vue2.x 中你应该会经常遇到这种场景:

<!-- UserCard.vue -->
<template>
  <div class="user-card">
    <b> {{ user.name }} </b>  
    <button @click="isPopUpOpen = true">删除用户</button>

    <!-- 注意这一块代码 -->
    <div v-show="isPopUpOpen">
      <p>确定删除?</p>
      <button @click="removeUser">确定</button>
      <button @click="isPopUpOpen = false">取消</button>
    </div>

  </div>
</template>

以上代码就是当我们需要做一个弹窗的时候,按照业务逻辑,弹窗和其他代码在一块写着。 但是这么写往往会出现问题,就是一旦我们点击 “删除用户”,本希望弹窗显示,但是往往这个弹出框被外边的元素挡住!由于 z-index 的原因。

那我们就想方设法让这个弹出框直接挂在到 body 节点上,这样就没问题了。比如:可以通过 Javascript 追加这个弹出框到 body 中等,处理起来比较麻烦。

关键的问题是:业务逻辑被打断了,代码也不连贯了

所以为了解决这个烦恼,Vue3新增了这个组件,现在你就可以这么写了。

在最外层的 App.vue 中:

<!-- 预留一块空地,专门用来显示这个容易被遮挡的层 -->
<div id="modal-container"></div>

<!-- app -->
<div id="app">

在你自己的组件中:

<!-- UserCard.vue -->
<template>
  <div class="user-card">
    <b> {{ user.name }} </b>  
    <button @click="isPopUpOpen = true">删除用户</button>

    <!-- 注意这一块代码 -->
    <Teleport to="#modal-container">
        <div v-show="isPopUpOpen">
          <p>确定删除?</p>
          <button @click="removeUser">确定</button>
          <button @click="isPopUpOpen = false">取消</button>
        </div>
    </Teleport>

  </div>
</template>

这样技能保证你代码的完整性、业务的连贯性,又能解决弹窗口被遮挡的问题。

另外,今天在 3.0.0-alpha.11 CHANGELOG 中又看到一条更新记录 16cd8ee

portal: portal should always remove its children when unmounted (16cd8ee)

就是说一旦组件被销毁 unmounted,Teleport 里面的元素应该被清空。如果不自动清空掉,随着你页面的切换,前一次页面遗留的弹窗可能一直存在的bug。

最后

本文也是 VUE 3.0 学习探索入门系列 里面的最后一篇,希望能对大家入门 Vue3 有所帮助。

接下来就等 Vue3.0 正式 Release 以后,再带给大家 VUE 3.0 实战上手篇 系列,欢迎大家关注我,及时了解动态。

本系列历时20天完成,再次感谢大家。

(全剧终)