Vue3:新特性详解

本文目录:

  • 1.特性函数setup
  • 2.Ref 语法
  • 3.Reactive 函数
  • 4.Vue3 生命周期
  • 5.侦测变化 - watch
  • 6.Vue3的模块化开发
  • 7.弹窗类组件优化:Teleport
  • 8.异步组件优化:Suspense
  • 9.全局API优化

1.特性函数setup

1、setup函数是处于 生命周期函数 beforeCreate 和 Created 两个钩子函数之间的函数 也就说在 setup函数中是无法 使用 data 和 methods 中的数据和方法的
2、setup函数是 Composition API(组合API)的入口
3、在setup函数中定义的变量和方法最后都是需要 return 出去的 不然无法再模板中使用

setup第一个参数props会自动推论成props里面定义的类型
第二个参数context,在setup中无法直接访问vue2最常用的this对象,context提供了vue2中最常用的三个属性
context.attrs
context.slots
context.emit

2.Ref 语法

ref 是一个函数,它接受一个参数,返回的就是一个神奇的 响应式对象 。我们初始化的这个 0 作为参数包裹到这个对象中去,在未来可以检测到改变并作出对应的相应。

<template>
  <h1>{{count}}</h1>
  <h1>{{double}}</h1>
  <button @click="increase">+1</button>
</template>
import { ref } from "vue"
setup() {
  const count = ref(0)
  const double = computed(() => {
    return count.value * 2
  })
  const increase = () => {
    count.value++
  }
  return {
    count,
    increase,
    double
  }
}

3.Reactive 函数

reactive的用法与ref的用法相似,也是将数据变成响应式数据,当数据发生变化时UI也会自动更新。不同的是ref用于基本数据类型,而reactive是用于复杂数据类型,比如对象和数组

import { ref, computed, reactive, toRefs } from 'vue'

interface DataProps {
  count: number;
  double: number;
  increase: () => void;
}

setup() {
  const data: DataProps  = reactive({
    count: 0,
    increase: () => { data.count++},
    double: computed(() => data.count * 2)
  })
  const refData = toRefs(data)
  return {
    ...refData
  }
}

使用 ref 还是 reactive 可以选择这样的准则
第一,就像刚才的原生 javascript 的代码一样,像你平常写普通的 js 代码选择原始类型和对象类型一样来选择是使用 ref 还是 reactive。
第二,所有场景都使用 reactive,但是要记得使用 toRefs 保证 reactive 对象属性保持响应性。

4.Vue3 生命周期

在vue3中,生命周期在hook中的变化

  • beforeCreate -> 不需要
  • created -> 不需要
  • beforeMount -> onBeforeMount
  • mounted -> onMounted
  • beforeUpdate -> onBeforeUpdate
  • updated -> onUpdated
  • beforeUnmount -> onBeforeUnmount
  • unmounted -> onUnmounted
  • errorCaptured -> onErrorCaptured
  • renderTracked -> onRenderTracked
  • renderTriggered -> onRenderTriggered

为了更好的Tree-shaking,Vue3的生命周期函数都是从 vue 中导入,再进行直接调用
从 vue 中引入 多个生命周期函数

import {onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, unMounted} from 'vue'
export default {
  name: 'App',
  setup() {
    onBeforeMount(() => {
      // 在挂载前执行
    })
    onMounted(() => {
      // 在挂载后执行
    })
    onBeforeUpdate(() => {
      // 在更新前前执行
    })
    onUpdated(() => {
      // 在更新后执行
    })
    onBeforeUnmount(() => {
      // 在组件销毁前执行
    })
    unMounted(() => {
      // 在组件销毁后执行
    })
    return {}
  }
}

onErrorCaptured可以捕捉错误,同时捕捉错误需要返回一个布尔值,表示错误是否向上传播

import { onErrorCaptured } from 'vue'
setup(){
  const error = ref(null)
  onErrorCaptured ((e:any)=>{
    error.value = e
    return true
  })
}

5.侦测变化 - watch

watch的第一个参数是一个响应式的对象,如果要监听多个对象,则第一个参数也可以是一个数组
watch 多个值,返回的也是多个值的数组

watch([greetings, data], (newValue, oldValue) => {
  console.log('old', oldValue)
  console.log('new', newValue)
  document.title = 'updated' + greetings.value + data.count
})

如果是打算监听data中的一个属性,比如data.count,则不能直接去watch([greetings, data.count]这样写,而是要使用 getter 的写法 watch reactive 对象中的一项

watch([greetings, () => data.count], (newValue, oldValue) => {
  console.log('old', oldValue)
  console.log('new', newValue)
  document.title = 'updated' + greetings.value + data.count
})

6.Vue3的模块化开发

使用composition API 的好处

  • 将相关的逻辑代码组合在一起,不要分散在代码的各个地方
  • 可以非常容易的重用代码,比mixin有更高的灵活度

实践:记录鼠标的位置
利用vue3进行初步的实现,将所有的代码都写在setup中

const x = ref(0)
const y = ref(0)
const updateMouse = (e: MouseEvent) => {
  x.value = e.pageX
  y.value = e.pageY
}
onMounted(() => {
  document.addEventListener('click', updateMouse)
})
onUnmounted(() => {
  document.removeEventListener('click', updateMouse)
})

但是上方的代码还不能做到很方便的重用,所以接下来需要对代码进行改造,将组件内逻辑抽象成可复用的函数
新建一个文件hooks文件夹,里面就存放我们抽离出来的所有的逻辑模块,新建一个useMousePosition.ts

import { ref, onMounted, onUnmounted } from 'vue'
function useMouseTracker() {
  // const positions = reactive<MousePostion>({
  //   x: 0,
  //   y: 0
  // })
  const x = ref(0)
  const y = ref(0)
  const updatePosition = (event: MouseEvent) => {
    x.value = event.clientX
    y.value = event.clientY 
  }
  onMounted(() => {
    document.addEventListener('click', updatePosition)
  })
  onUnmounted(() => {
    document.removeEventListener('click', updatePosition)
  })
  return { x, y }
}
export default useMouseTracker

在需要使用这个逻辑的页面进行引用

import useMousePosition from './hooks/useMousePosition'

这里引用到的就是一段函数代码,然后调用函数,取其返回值就可以了

const { x, y } = useMousePosition()

vue3 这种实现方式的优点

  • 第一:它可以清楚的知道 xy 这两个值的来源,这两个参数是干什么的,他们来自 useMouseTracker 的返回,那么它们就是用来追踪鼠标位置的值。
  • 第二:我们可以xy 可以设置任何别名,这样就避免了命名冲突的风险。
  • 第三:这段逻辑可以脱离组件存在,因为它本来就和组件的实现没有任何关系,我们不需要添加任何组件实现相应的功能。只有逻辑代码在里面,不需要模版。

将请求接口的逻辑抽离出去:

import { ref } from 'vue'
import axios from 'axios'
// 添加一个参数作为要使用的 地址
const useURLLoader = (url: string) => {
// 声明几个ref,代表不同的状态和结果
  const result = ref(null)
  const loading = ref(true)
  const loaded = ref(false)
  const error = ref(null)
// 发送异步请求,获得data
// 由于 axios 都有定义,所以rawData 可以轻松知道其类型
  axios.get(url).then((rawData) => {
    loading.value = false
    loaded.value = true
    result.value = rawData.data
  }).catch((e) => {
    error.value = e
  })
  // 将这些ref 一一返回
  return {
    result,
    loading,
    error,
    loaded
  }
}
export default useURLLoader

在要使用接口的页面进行引用:

const { result, loading, loaded } = useURLLoader('https://dog.ceo/api/breeds/image/random')
...
<h1 v-if="loading">Loading!...</h1>
<img v-if="loaded" :src="result.message" >

模块化结合typescript - 泛型改造:

function useURLLoader<T>(url: string) {
  const result = ref<T | null>(null)

在应用中的使用,可以定义不同的数据类型

// 在应用中的使用,可以定义不同的数据类型
interface DogResult {
  message: string;
  status: string;
}
interface CatResult {
  id: string;
  url: string;
  width: number;
  height: number;
}
const { result, loading, loaded } = useURLLoader<CatResult[]>('https://api.thecatapi.com/v1/images/search?limit=1')

组件的定义和导出的都是一个普通的object

const component = {
  name: 'HelloWorld',
  props: {
    ...
  }
}

export default component;

而普通的对象在书写代码的时候自然不会获得ts的提示支持,vue3提供了defineComponent来让普通的object变为支持ts的对象

const component = 
  defineComponent({
    name: 'HelloWorld',
    props: {
      ...
    }
})

export default component;

7.弹窗类组件优化:Teleport

Vue2在弹窗组件开发中,Dialog被包裹在其他组件之中,容易被干扰,样式也在其他组件中,容易变得非常混乱。
vue3 新添加了一个默认的组件就叫 Teleport,我们可以拿过来直接使用,它上面有一个 to 的属性,它接受一个css query selector 作为参数,这就是代表要把这个组件渲染到哪个 dom 元素中

<template>
  <teleport to="#modal">
    <div id="center">
      <h1>this is a modal</h1>
    </div>
  </teleport>
</template>

特别适合弹窗类的元素渲染,这样页面的所有元素就不用所有的元素都挤在一个div中了
在index.html添加一个modal元素

<div id-"app"></div>
<div id-"modal"></div>

实际应用中,在使用弹窗类元素的组件中需要手动控制该元素的渲染与否。
定义一个isOpen来控制,
子组件内部通过context.emit('close-modal')去触发根组件中定义的事件,注意在vue3中子组件需要通过emit引入该事件。

<template>
<teleport to="#modal">
  <div id="center" v-if="isOpen">
    <h2><slot>this is a modal</slot></h2>
    <button @click="buttonClick">Close</button>
  </div>
</teleport>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
  props: {
    isOpen: Boolean,
  },
  emits: {
    'close-modal': null
  },
  setup(props, context) {
    const buttonClick = () => {
      context.emit('close-modal')
    }
    return {
      buttonClick
    }
  }
})

在 App.vue 组件中使用

const modalIsOpen = ref(false)
const openModal = () => {
  modalIsOpen.value = true
}
const onModalClose = () => {
  modalIsOpen.value = false
}

<button @click="openModal">Open Modal</button><br/>
<modal :isOpen="modalIsOpen" @close-modal="onModalClose"> My Modal !!!!</modal>

8.异步组件优化:Suspense

Suspense是Vue3推出的一个内置的特殊组件
Suspense内部默认有两个自定义的slot,刚开始的会渲染#default部分,达到某个条件之后才会去渲染#fallback的内容。
如果要使用Suspense,组件中要返回一个Promise
可以非常方便为异步请求的等待界面进行个性化的定制。
定义一个异步组件,在 setup 返回一个 Promise,AsyncShow.vue

<template>
  <h1>{{result}}</h1>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
  setup() {
    return new Promise((resolve) => {
      setTimeout(() => {
        return resolve({
          result: 42
        })
      }, 3000)
    })
  }
})
</script>

在 App.vue 中使用

<Suspense>
  <template #default>
    <async-show />
  </template>
  <template #fallback>
    <h1>Loading !...</h1>
  </template>
</Suspense>

default可以存放多个异步组件,这样会等到所有的异步组件都拿到结果之后才去渲染里面的内容。

<template>
  <img :src="result && result.message">
</template>

<script lang="ts">
import axios from 'axios'
import { defineComponent } from 'vue'
export default defineComponent({
  async setup() {
    const rawData = await axios.get('https://dog.ceo/api/breeds/image')
    return {
      result: rawData.data
    }
  }
})
</script>

Suspense 中可以添加多个异步组件

<Suspense>
  <template #default>
    <async-show />
    <dog-show />
  </template>
  <template #fallback>
    <h1>Loading !...</h1>
  </template>
</Suspense>

9.全局API优化

vue2全局API的写法
1.从vue中导出一个全局对象Vue

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

2.然后在Vue中进行一系列的配置

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

3.新建vue实例,然后调用$mount方法将其挂在到app节点上

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

Vue2 这样写在一定程度上修改了 Vue 对象的全局状态。
第一,在单元测试中,全局配置非常容易污染全局环境,用户需要在每次 case 之间,保存和恢复配置。有一些 api (vue use vue mixin)甚至没有方法恢复配置,这就让一些插件的测试非常的困难。
第二,在不同的 APP 中,如果想共享一份有不同配置的 vue 对象,也变得非常困难。

vue3新增加了createApp,先将createApp引入,然后利用createApp生成一个app实例,

const app = createApp(App)

接下来增加配置就不需要直接给Vue添加了,而是给app添加就行了

app.config.isCustomElement = tag => tag.startsWith('app-')
app.use(/* ... */)
app.mixin(/* ... */)
app.component(/* ... */)
app.directive(/* ... */)

现在再设置任何的配置都会是在不同的app实例上,不同的设置就不会发生冲突。
当配置结束以后,我们再把 App 使用 mount 方法挂载到固定的 DOM 的节点上。

app.mount(App, '#app')
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容