Vue3:新特性詳解

本文目錄:

  • 1.特性函數(shù)setup
  • 2.Ref 語法
  • 3.Reactive 函數(shù)
  • 4.Vue3 生命周期
  • 5.偵測變化 - watch
  • 6.Vue3的模塊化開發(fā)
  • 7.彈窗類組件優(yōu)化:Teleport
  • 8.異步組件優(yōu)化:Suspense
  • 9.全局API優(yōu)化

1.特性函數(shù)setup

1露该、setup函數(shù)是處于 生命周期函數(shù) beforeCreate 和 Created 兩個鉤子函數(shù)之間的函數(shù) 也就說在 setup函數(shù)中是無法 使用 data 和 methods 中的數(shù)據(jù)和方法的
2、setup函數(shù)是 Composition API(組合API)的入口
3、在setup函數(shù)中定義的變量和方法最后都是需要 return 出去的 不然無法再模板中使用

setup第一個參數(shù)props會自動推論成props里面定義的類型
第二個參數(shù)context合冀,在setup中無法直接訪問vue2最常用的this對象进苍,context提供了vue2中最常用的三個屬性
context.attrs
context.slots
context.emit

2.Ref 語法

ref 是一個函數(shù),它接受一個參數(shù),返回的就是一個神奇的 響應(yīng)式對象 叔壤。我們初始化的這個 0 作為參數(shù)包裹到這個對象中去鼻忠,在未來可以檢測到改變并作出對應(yīng)的相應(yīng)涵但。

<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 函數(shù)

reactive的用法與ref的用法相似杈绸,也是將數(shù)據(jù)變成響應(yīng)式數(shù)據(jù),當(dāng)數(shù)據(jù)發(fā)生變化時UI也會自動更新矮瘟。不同的是ref用于基本數(shù)據(jù)類型瞳脓,而reactive是用于復(fù)雜數(shù)據(jù)類型,比如對象和數(shù)組

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 可以選擇這樣的準(zhǔn)則
第一澈侠,就像剛才的原生 javascript 的代碼一樣劫侧,像你平常寫普通的 js 代碼選擇原始類型和對象類型一樣來選擇是使用 ref 還是 reactive。
第二哨啃,所有場景都使用 reactive烧栋,但是要記得使用 toRefs 保證 reactive 對象屬性保持響應(yīng)性。

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的生命周期函數(shù)都是從 vue 中導(dǎo)入,再進行直接調(diào)用
從 vue 中引入 多個生命周期函數(shù)

import {onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, unMounted} from 'vue'
export default {
  name: 'App',
  setup() {
    onBeforeMount(() => {
      // 在掛載前執(zhí)行
    })
    onMounted(() => {
      // 在掛載后執(zhí)行
    })
    onBeforeUpdate(() => {
      // 在更新前前執(zhí)行
    })
    onUpdated(() => {
      // 在更新后執(zhí)行
    })
    onBeforeUnmount(() => {
      // 在組件銷毀前執(zhí)行
    })
    unMounted(() => {
      // 在組件銷毀后執(zhí)行
    })
    return {}
  }
}

onErrorCaptured可以捕捉錯誤祝峻,同時捕捉錯誤需要返回一個布爾值魔吐,表示錯誤是否向上傳播

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

5.偵測變化 - watch

watch的第一個參數(shù)是一個響應(yīng)式的對象,如果要監(jiān)聽多個對象莱找,則第一個參數(shù)也可以是一個數(shù)組
watch 多個值酬姆,返回的也是多個值的數(shù)組

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

如果是打算監(jiān)聽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的模塊化開發(fā)

使用composition API 的好處

  • 將相關(guān)的邏輯代碼組合在一起,不要分散在代碼的各個地方
  • 可以非常容易的重用代碼浮定,比mixin有更高的靈活度

實踐:記錄鼠標(biāo)的位置
利用vue3進行初步的實現(xiàn)相满,將所有的代碼都寫在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)
})

但是上方的代碼還不能做到很方便的重用,所以接下來需要對代碼進行改造桦卒,將組件內(nèi)邏輯抽象成可復(fù)用的函數(shù)
新建一個文件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'

這里引用到的就是一段函數(shù)代碼闸盔,然后調(diào)用函數(shù)悯辙,取其返回值就可以了

const { x, y } = useMousePosition()

vue3 這種實現(xiàn)方式的優(yōu)點

  • 第一:它可以清楚的知道 xy 這兩個值的來源,這兩個參數(shù)是干什么的迎吵,他們來自 useMouseTracker 的返回躲撰,那么它們就是用來追蹤鼠標(biāo)位置的值。
  • 第二:我們可以xy 可以設(shè)置任何別名击费,這樣就避免了命名沖突的風(fēng)險拢蛋。
  • 第三:這段邏輯可以脫離組件存在,因為它本來就和組件的實現(xiàn)沒有任何關(guān)系蔫巩,我們不需要添加任何組件實現(xiàn)相應(yīng)的功能谆棱。只有邏輯代碼在里面快压,不需要模版。

將請求接口的邏輯抽離出去:

import { ref } from 'vue'
import axios from 'axios'
// 添加一個參數(shù)作為要使用的 地址
const useURLLoader = (url: string) => {
// 聲明幾個ref垃瞧,代表不同的狀態(tài)和結(jié)果
  const result = ref(null)
  const loading = ref(true)
  const loaded = ref(false)
  const error = ref(null)
// 發(fā)送異步請求蔫劣,獲得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" >

模塊化結(jié)合typescript - 泛型改造:

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

在應(yīng)用中的使用个从,可以定義不同的數(shù)據(jù)類型

// 在應(yīng)用中的使用脉幢,可以定義不同的數(shù)據(jù)類型
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')

組件的定義和導(dǎo)出的都是一個普通的object

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

export default component;

而普通的對象在書寫代碼的時候自然不會獲得ts的提示支持,vue3提供了defineComponent來讓普通的object變?yōu)橹С謙s的對象

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

export default component;

7.彈窗類組件優(yōu)化:Teleport

Vue2在彈窗組件開發(fā)中嗦锐,Dialog被包裹在其他組件之中嫌松,容易被干擾,樣式也在其他組件中奕污,容易變得非澄幔混亂。
vue3 新添加了一個默認的組件就叫 Teleport碳默,我們可以拿過來直接使用外驱,它上面有一個 to 的屬性,它接受一個css query selector 作為參數(shù)腻窒,這就是代表要把這個組件渲染到哪個 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>

實際應(yīng)用中磅崭,在使用彈窗類元素的組件中需要手動控制該元素的渲染與否儿子。
定義一個isOpen來控制,
子組件內(nèi)部通過context.emit('close-modal')去觸發(fā)根組件中定義的事件砸喻,注意在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.異步組件優(yōu)化:Suspense

Suspense是Vue3推出的一個內(nèi)置的特殊組件
Suspense內(nèi)部默認有兩個自定義的slot,剛開始的會渲染#default部分割岛,達到某個條件之后才會去渲染#fallback的內(nèi)容愉适。
如果要使用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可以存放多個異步組件惠爽,這樣會等到所有的異步組件都拿到結(jié)果之后才去渲染里面的內(nèi)容癌蓖。

<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優(yōu)化

vue2全局API的寫法
1.從vue中導(dǎo)出一個全局對象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實例,然后調(diào)用$mount方法將其掛在到app節(jié)點上

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

Vue2 這樣寫在一定程度上修改了 Vue 對象的全局狀態(tài)婚肆。
第一租副,在單元測試中,全局配置非常容易污染全局環(huán)境较性,用戶需要在每次 case 之間用僧,保存和恢復(fù)配置结胀。有一些 api (vue use vue mixin)甚至沒有方法恢復(fù)配置,這就讓一些插件的測試非常的困難责循。
第二糟港,在不同的 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(/* ... */)

現(xiàn)在再設(shè)置任何的配置都會是在不同的app實例上县钥,不同的設(shè)置就不會發(fā)生沖突秀姐。
當(dāng)配置結(jié)束以后,我們再把 App 使用 mount 方法掛載到固定的 DOM 的節(jié)點上若贮。

app.mount(App, '#app')
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末省有,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子谴麦,更是在濱河造成了極大的恐慌蠢沿,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件匾效,死亡現(xiàn)場離奇詭異舷蟀,居然都是意外死亡,警方通過查閱死者的電腦和手機面哼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門野宜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人魔策,你說我怎么就攤上這事匈子。” “怎么了闯袒?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵虎敦,是天一觀的道長。 經(jīng)常有香客問我政敢,道長原茅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任堕仔,我火速辦了婚禮擂橘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘摩骨。我一直安慰自己通贞,他們只是感情好朗若,可當(dāng)我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著昌罩,像睡著了一般哭懈。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上茎用,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天遣总,我揣著相機與錄音,去河邊找鬼轨功。 笑死旭斥,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的古涧。 我是一名探鬼主播垂券,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼羡滑!你這毒婦竟也來了菇爪?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤柒昏,失蹤者是張志新(化名)和其女友劉穎凳宙,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體职祷,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡氏涩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了堪旧。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡奖亚,死狀恐怖淳梦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情昔字,我是刑警寧澤爆袍,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站作郭,受9級特大地震影響陨囊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜夹攒,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一蜘醋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧咏尝,春花似錦压语、人聲如沸啸罢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽扰才。三九已至,卻和暖如春厕怜,著一層夾襖步出監(jiān)牢的瞬間衩匣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工粥航, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留琅捏,地道東北人。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓躁锡,卻偏偏與公主長得像午绳,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子映之,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容