一文學(xué)會(huì)使用Vue3

一文學(xué)會(huì)使用Vue3

本文適合Vue初學(xué)者,或者Vue2遷移者,當(dāng)然還是建議Vue3官網(wǎng)完全過(guò)一遍吵取。不適合精通原理锄列,源碼的大佬們鸽嫂。

先推薦兩個(gè)vscode插件

Volar

首先推薦Volar赂摆,使用vscode開(kāi)發(fā)Vue項(xiàng)目的小伙伴肯定都認(rèn)識(shí)Vetur這個(gè)神級(jí)插件揽涮,有了它可以讓我們得開(kāi)發(fā)如魚(yú)得水猴娩。 那么Volar可以理解為Vue3版本的Vetur阴幌,代碼高亮勺阐,語(yǔ)法提示,基本上Vetur有的它都有矛双。

特色功能

當(dāng)然作為新的插件出山渊抽,肯定有它獨(dú)有的功能。

多個(gè)根節(jié)點(diǎn)編輯器不會(huì)報(bào)錯(cuò)

Vue3是允許我們有多個(gè)根節(jié)點(diǎn)的议忽,但是我們?nèi)绻褂肰etur就會(huì)報(bào)錯(cuò)懒闷,不會(huì)影響運(yùn)行,但是看起來(lái)就很煩。 所以當(dāng)我們轉(zhuǎn)向Volar那么就不會(huì)出現(xiàn)這個(gè)問(wèn)題了须误。

image

編輯器分隔

即便Vue的組件化開(kāi)發(fā)议慰,可以將單文件的代碼長(zhǎng)度大幅縮短,但還是動(dòng)輒幾百行甚是上千行灵疮。那么我們切換templatescriptstyle的時(shí)候就要頻繁上下翻壳繁,雖然有的插件可以直接定位到css震捣,但是你回不去啊闹炉!所以這個(gè)功能簡(jiǎn)直是太人性化了蒿赢。

安裝完Volar以后,打開(kāi)一個(gè).vue文件渣触,看vscode的右上角羡棵,有這么一個(gè)圖標(biāo),點(diǎn)一下嗅钻。

image

它就會(huì)自動(dòng)給你分隔成三個(gè)頁(yè)面皂冰,分別對(duì)應(yīng)templatescriptstyle养篓,這樣就太舒服了有沒(méi)有秃流。

image

Vue 3 Snippets

推薦的第二個(gè)插件叫做Vue 3 Snippets,同樣的柳弄,他也有自己的Vue2版本舶胀。它是干什么的呢,可以看一下下面這張圖碧注,我只輸入了“v3”嚣伐,它有很多提示,我們就先選擇v3computed萍丐,選中回車(chē)即可轩端。

image

然后它就給自動(dòng)給我們寫(xiě)了如下代碼

image

是不是超級(jí)省事,摸魚(yú)的時(shí)間又增加了逝变!還有更多有趣的使用方式基茵,小伙伴們自行探索吧刻撒。

創(chuàng)建Vue3項(xiàng)目

那么正式開(kāi)始學(xué)習(xí)我們的Vue3,先從創(chuàng)建項(xiàng)目開(kāi)始耿导。

使用 vue-cli 創(chuàng)建

輸入下面的命令然后選擇配置項(xiàng)進(jìn)行安裝即可声怔,這里注意vue-cli的版本一定要在4.5.0以上

// 安裝或者升級(jí)
npm install -g @vue/cli
//查看版本 保證 vue cli 版本在 4.5.0 以上
vue --version
// 創(chuàng)建項(xiàng)目
vue create my-project
//然后根據(jù)提示一步一步傻瓜式操作就行了
... 

使用 Vite 創(chuàng)建

都說(shuō)Vue3.0Vite2更配,各種優(yōu)化舱呻,各種快醋火,但都不屬于本文的內(nèi)容,本文的目的我們只需要知道它特別好用箱吕,怎么用就行了芥驳。我這里是多選擇了TS,每行都有注釋?zhuān)荒苛巳弧?/p>

// 初始化viete項(xiàng)目
npm init vite-app <project-name>
// 進(jìn)入項(xiàng)目文件夾
cd <project-name>
// 安裝依賴(lài)
npm install
//啟動(dòng)項(xiàng)目
npm run dev 

創(chuàng)建完以后我們先來(lái)看看入口文件main.ts

// 引入createApp函數(shù),創(chuàng)建對(duì)應(yīng)的應(yīng)用,產(chǎn)生應(yīng)用的實(shí)例對(duì)象
import { createApp } from 'vue';
// 引入app組件(所有組件的父級(jí)組件)
import App from './App.vue';
// 創(chuàng)建app應(yīng)用返回對(duì)應(yīng)的實(shí)例對(duì)象,調(diào)用mount方法進(jìn)行掛載  掛載到#app節(jié)點(diǎn)上去
createApp(App).mount('#app'); 

然后看看根組件app.vue

//Vue2組件中的html模板中必須要有一對(duì)根標(biāo)簽,Vue3組件的html模板中可以沒(méi)有根標(biāo)簽
<template>
  <img alt="Vue logo" src="./assets/logo.png">
  <!-- 使用子級(jí)組件 -->
  <HelloWorld msg="Welcome to Your Vue.js + TypeScript App" />
</template>

<script lang="ts"> // 這里可以書(shū)寫(xiě)TS代碼

// defineComponent函數(shù),目的是定義一個(gè)組件 內(nèi)部可以傳入一個(gè)配置對(duì)象
import { defineComponent } from 'vue';
//引入子級(jí)組件
import HelloWorld from './components/HelloWorld.vue';

// 暴露出去一個(gè)定義好的組件
export default defineComponent({
  // 當(dāng)前組件的名字
  name: 'App',
  // 注冊(cè)組件
  components: {
    // 注冊(cè)一個(gè)子級(jí)組件
    HelloWorld,
  },
}); </script> 

Composition API

接下來(lái)到了重頭戲茬高,Vue3的招牌特性兆旬,Composition API

Composition API可以更方便的抽取共通邏輯,但是不要過(guò)于在意邏輯代碼復(fù)用怎栽,以功能提取代碼也是一種思路丽猬。

順便提一句,Vue3兼容大部分Vue2語(yǔ)法熏瞄,所以在Vue3中書(shū)寫(xiě)Vue2語(yǔ)法是沒(méi)有問(wèn)題的(廢除的除外)脚祟,但是既然我們已經(jīng)升級(jí)Vue3了,不建議混合使用强饮,除非一些大型特殊項(xiàng)目需要兼容兩個(gè)版本由桌。

setup

setup是組合Composition API中的入口函數(shù),也是第一個(gè)要使用的函數(shù)邮丰。

setup只在初始化時(shí)執(zhí)行一次行您,所有的Composition API函數(shù)都在此使用。

setup() {
  console.log('我執(zhí)行了') //我執(zhí)行了
}, 

可以通過(guò)console.log看到setup是在beforeCreate生命周期之前執(zhí)行的(只執(zhí)行一次)

 beforeCreate() {
    console.log('beforeCreate執(zhí)行了');
  },
  setup() {
    console.log('setup執(zhí)行了');
    return {};
  },
  //setup執(zhí)行了
  //beforeCreate執(zhí)行了 

由此可以推斷出setup執(zhí)行的時(shí)候,組件對(duì)象還沒(méi)有創(chuàng)建,組件實(shí)例對(duì)象this還不可用,此時(shí)thisundefined, 不能通過(guò)this來(lái)訪問(wèn)data/computed/methods/props剪廉。

返回對(duì)象中的屬性會(huì)與data函數(shù)返回對(duì)象的屬性合并成為組件對(duì)象的屬性娃循,返回對(duì)象中的方法會(huì)與methods中的方法合并成功組件對(duì)象的方法,如果有重名, setup優(yōu)先妈经。因?yàn)樵?code>setup中this不可用淮野,methods中可以訪問(wèn)setup提供的屬性和方法, 但在setup方法中不能訪問(wèn)datamethods里的內(nèi)容捧书,所以還是不建議混合使用吹泡。

setup函數(shù)如果返回對(duì)象, 對(duì)象中的 屬性方法 , 模板 中可以直接使用

//templete
<div>{{number}}</div>

//JS
setup() {
  const number = 18;
  return {
    number,
  };
}, 

注意:setup不能是一個(gè)async函數(shù): 因?yàn)榉祷刂挡辉偈?code>return的對(duì)象, 而是promise, 模板中就不可以使用return中返回對(duì)象的數(shù)據(jù)了。

setup的參數(shù)(props,context)`

props: 是一個(gè)對(duì)象,里面有父級(jí)組件向子級(jí)組件傳遞的數(shù)據(jù),并且是在子級(jí)組件中使用props接收到的所有的屬性

context:上下文對(duì)象经瓷,可以通過(guò)es6語(yǔ)法解構(gòu) setup(props, {attrs, slots, emit})

  • attrs: 獲取當(dāng)前組件標(biāo)簽上所有沒(méi)有通過(guò)props接收的屬性的對(duì)象, 相當(dāng)于 this.$attrs
  • slots: 包含所有傳入的插槽內(nèi)容的對(duì)象, 相當(dāng)于 this.$slots
  • emit: 用來(lái)分發(fā)自定義事件的函數(shù), 相當(dāng)于 this.$emit

演示attrsprops

//父組件
<template>
  <child :msg="msg" msg2='哈哈哈' />
</template>
<script lang='ts'> import { defineComponent, ref } from 'vue';
// 引入子組件
import Child from './components/Child.vue';
export default defineComponent({
  name: 'App',
  components: {
    Child,
  },
  setup() {
    const msg = ref('hello,vue3');
    return {
      msg,
    };
  },
}); </script>

//子組件
<template>
  <h2>子組件</h2>
  <h3>msg:{{ msg }}</h3>
</template>

<script lang='ts'> import { defineComponent } from 'vue';
export default defineComponent({
  name: 'Child',
  props: ['msg'],
  setup(props, {attrs, slots, emit}) {
    console.log('props:', props);//msg: "hello,vue3"
    console.log('attrs:', attrs);//msg2: "哈哈哈"
    return {};
  },
}); </script> 

演示emit

//父組件
<template>
  <child @show="show" />
</template>

<script lang='ts'> setup() {
    const show = () => {
      console.log('name:', 'hzw');
    };
    return {
      show,
    };
  }, </script>

//子組件
<template>
  <button>事件分發(fā)</button>
</template>
<script lang='ts'> import { defineComponent } from 'vue';

export default defineComponent({
  name: 'Child',
  setup(props, { emit }) {
    const emitFn = () => {
      emit('show');
    };
    return {
      emitFn,
    };
  },
}); </script> 

ref

作用

定義一個(gè)響應(yīng)式的數(shù)據(jù)(一般用來(lái)定義一個(gè)基本類(lèi)型的響應(yīng)式數(shù)據(jù)Undefined爆哑、NullBoolean舆吮、NumberString)

語(yǔ)法

const xxx = ref(initValue): 

注意script中操作數(shù)據(jù)需要使用xxx.value的形式揭朝,而模板中不需要添加.value

用一個(gè)例子來(lái)演示:實(shí)現(xiàn)一個(gè)按鈕,點(diǎn)擊可以增加數(shù)字

<template>
  <div>{{count}}</div>
  <button @click='updateCount'>增加</button>
</template> 

在Vue2中

data() {
  return {
    conunt: 0,
  };
},
methods: {
  updateCount() {
    this.conunt++;
  },
}, 

在Vue3中

 setup() {
    // ref用于定義一個(gè)響應(yīng)式的數(shù)據(jù)队贱,返回的是一個(gè)Ref對(duì)象,對(duì)象中有一個(gè)value屬性
    //如果需要對(duì)數(shù)據(jù)進(jìn)行操作潭袱,需要使用該Ref對(duì)象的value屬性
    const count = ref(0);
    function updateCount() {
      count.value++;
    }
    return {
      count,
      updateCount,
    };
  }, 

Vue2中我們通過(guò)this.$refs來(lái)獲取dom節(jié)點(diǎn)柱嫌,Vue3中我們通過(guò)ref來(lái)獲取節(jié)點(diǎn)

首先需要在標(biāo)簽上添加ref='xxx',然后再setup中定義一個(gè)初始值為nullref類(lèi)型,名字要和標(biāo)簽的ref屬性一致

const xxx = ref(null) 

注意:一定要在setupreturn中返回屯换,不然會(huì)報(bào)錯(cuò)编丘。

還是用一個(gè)例子來(lái)演示:讓輸入框自動(dòng)獲取焦點(diǎn)

<template>
  <h2>App</h2>
  <input type="text">---
  <input type="text" ref="inputRef">
</template>

<script lang="ts">
import { onMounted, ref } from 'vue'
/* 
ref獲取元素: 利用ref函數(shù)獲取組件中的標(biāo)簽元素
功能需求: 讓輸入框自動(dòng)獲取焦點(diǎn)
*/
export default {
  setup() {
    const inputRef = ref<HTMLElement|null>(null)

    onMounted(() => {
      inputRef.value && inputRef.value.focus()
    })

    return {
      inputRef
    }
  },
}
</script> 

reactive

語(yǔ)法

const proxy = reactive(obj) 

作用

定義多個(gè)數(shù)據(jù)的響應(yīng)式,接收一個(gè)普通對(duì)象然后返回該普通對(duì)象的響應(yīng)式代理器對(duì)象(Proxy)彤悔,響應(yīng)式轉(zhuǎn)換是“深層的”:會(huì)影響對(duì)象內(nèi)部所有嵌套的屬性,所有的數(shù)據(jù)都是響應(yīng)式的嘉抓。

代碼演示

<template>
  <h3>姓名:{{user.name}}</h3>
  <h3>年齡:{{user.age}}</h3>
  <h3>wife:{{user.wife}}</h3>
  <button @click="updateUser">更新</button>
</template>

 setup() {
    const user = reactive({
      name: 'hzw',
      age: 18,
      wife: {
        name: 'xioaohong',
        age: 18,
        books: ['紅寶書(shū)', '設(shè)計(jì)模式', '算法與數(shù)據(jù)結(jié)構(gòu)'],
      },
    });
    const updateUser = () => {
      user.name = '小紅';
      user.age += 2;
      user.wife.books[0] = '西游記';
    };
    return {
      user,
      updateUser,
    };
  }, 

computed函數(shù):

Vue2中的computed配置功能一致,返回的是一個(gè)ref類(lèi)型的對(duì)象

計(jì)算屬性的函數(shù)中如果只傳入一個(gè)回調(diào)函數(shù) 表示的是get操作

import { computed } from 'vue';
const user = reactive({
  firstName: '韓',
  lastName: '志偉',
});
const fullName1 = computed(() => {
  return user.firstName + user.lastName;
});
return {
  user,
  fullName1,
}; 

計(jì)算屬性的函數(shù)中可以傳入一個(gè)對(duì)象,可以包含setget函數(shù),進(jìn)行讀取和修改的操作

const fullName2 = computed({
  get() {
    return user.firstName + '_' + user.lastName;
  },
  set(val: string) {
    const names = val.split('_');
    user.firstName = names[0];
    user.lastName = names[1];
  },
});
return {
  user,
  fullName2,
}; 

watch函數(shù):

Vue2中的watch配置功能一致,

  • 參數(shù)1:要監(jiān)聽(tīng)的數(shù)據(jù)
  • 參數(shù)2:回調(diào)函數(shù)
  • 參數(shù)3:配置

作用

監(jiān)視指定的一個(gè)或多個(gè)響應(yīng)式數(shù)據(jù), 一旦數(shù)據(jù)變化, 就自動(dòng)執(zhí)行監(jiān)視回調(diào)

默認(rèn)初始時(shí)不執(zhí)行回調(diào), 但可以通過(guò)配置immediatetrue, 來(lái)指定初始時(shí)立即執(zhí)行第一次

通過(guò)配置deeptrue, 來(lái)指定深度監(jiān)視

import { watch, ref } from 'vue';
const user = reactive({
  firstName: '韓',
  lastName: '志偉',
});
const fullName3 = ref('');
watch(
  user,
  ({ firstName, lastName }) => {
    fullName3.value = firstName + '_' + lastName;
  },
  { immediate: true, deep: true }
);
return {
  user,
  fullName3,
}; 

watch監(jiān)聽(tīng)多個(gè)數(shù)據(jù),使用數(shù)組

watch監(jiān)聽(tīng)非響應(yīng)式數(shù)據(jù)的時(shí)候需要使用回調(diào)函數(shù)的形式

watch([()=>user.firstName,()=>user.lastName,fullName3],()=>{console.log('我執(zhí)行了')}) 

watchEffect函數(shù):

作用

監(jiān)視數(shù)據(jù)發(fā)生變化時(shí)執(zhí)行回調(diào)晕窑,不用直接指定要監(jiān)視的數(shù)據(jù), 回調(diào)函數(shù)中使用的哪些響應(yīng)式數(shù)據(jù)就監(jiān)視哪些響應(yīng)式數(shù)據(jù)抑片,默認(rèn)初始時(shí)就會(huì)執(zhí)行第一次, 從而可以收集需要監(jiān)視的數(shù)據(jù)。

import { watchEffect, ref } from 'vue';
const user = reactive({
  firstName: '韓',
  lastName: '志偉',
});
const fullName4 = ref('');
watchEffect(() => {
  fullName4.value = user.firstName + '_' + user.lastName;
});
return {
  user,
  fullName4,
};
watchEffect可以實(shí)現(xiàn)計(jì)算屬性set方法
watchEffect(() => {
    const names = fullName3.value.split('_');
    user.firstName = names[0];
    user.lastName = names[1];
}); 

生命周期對(duì)比:

image

注意:3.0中的生命周期鉤子要比2.X中相同生命周期的鉤子要快

Composition API還新增了以下調(diào)試鉤子函數(shù):但是不怎么常用

  • onRenderTracked
  • onRenderTriggered

代碼演示

setup() {

  onBeforeMount(() => {
    console.log('--onBeforeMount')
  })

  onMounted(() => {
    console.log('--onMounted')
  })

  onBeforeUpdate(() => {
    console.log('--onBeforeUpdate')
  })

  onUpdated(() => {
    console.log('--onUpdated')
  })

  onBeforeUnmount(() => {
    console.log('--onBeforeUnmount')
  })

  onUnmounted(() => {
    console.log('--onUnmounted')
  })
} 

toRefs

作用

把一個(gè)響應(yīng)式對(duì)象轉(zhuǎn)換成普通對(duì)象杨赤,該普通對(duì)象的每個(gè)屬性都是一個(gè) ref

應(yīng)用

我們使用reactive創(chuàng)建的對(duì)象敞斋,如果想在模板中使用,就必須得使用xxx.xxx的形式,如果大量用到的話還是很麻煩的,但是使用es6解構(gòu)以后,會(huì)失去響應(yīng)式,那么toRefs的作用就體現(xiàn)在這,疾牲,利用toRefs可以將一個(gè)響應(yīng)式 reactive 對(duì)象的所有原始屬性轉(zhuǎn)換為響應(yīng)式的ref屬性渺尘。當(dāng)然小伙伴們可以自行開(kāi)發(fā)更多應(yīng)用場(chǎng)景。

代碼演示

<template>
  <div>
    name:{{name}}
  </div>
</template>

<script lang='ts'> import { defineComponent, reactive, toRefs } from 'vue';

export default defineComponent({
  name: '',
  setup() {
    const state = reactive({
      name: 'hzw',
    });
    const state2 = toRefs(state);
    setInterval(() => {
      state.name += '===';
    }, 1000);
    return {
      //通過(guò)toRefs返回的對(duì)象,解構(gòu)出來(lái)的屬性也是響應(yīng)式的
      ...state2,
    };
  },
}); </script> 

provide 與 inject

作用

實(shí)現(xiàn)跨層級(jí)組件(祖孫)間通信

代碼演示

父組件

<template>
  <h1>父組件</h1>
  <p>當(dāng)前顏色: {{color}}</p>
  <button @click="color='red'">紅</button>
  <button @click="color='yellow'">黃</button>
  <button @click="color='blue'">藍(lán)</button>
  <hr>
  <Son />
</template>
<script lang="ts"> import { provide, ref } from 'vue'
import Son from './Son.vue'
export default {
  name: 'ProvideInject',
  components: {
    Son
  },
  setup() {
    const color = ref('red')
    provide('color', color)
    return {
      color
    }
  }
} </script> 

子組件

<template>
  <div>
    <h2>子組件</h2>
    <hr>
    <GrandSon />
  </div>
</template>

<script lang="ts"> import GrandSon from './GrandSon.vue'
export default {
  components: {
    GrandSon
  },
} </script> 

孫子組件

<template>
  <h3 :style="{color}">孫子組件: {{color}}</h3>
</template>

<script lang="ts"> import { inject } from 'vue'
export default {
  setup() {
    const color = inject('color')
    return {
      color
    }
  }
} </script> 

其他特性

Teleport(瞬移)

作用

Teleport 提供了一種干凈的方法, 讓組件的html在父組件界面外的特定標(biāo)簽(很可能是body)下插入顯示 換句話說(shuō)就是可以把 子組件 或者 dom節(jié)點(diǎn) 插入到任何你想插入到的地方去说敏。

語(yǔ)法

使用to屬性 引號(hào)內(nèi)使用選擇器

 <teleport to="body">
  </teleport> 

代碼演示

//父組件

<template>
  <div class="father">
    <h2>App</h2>
    <modal-button></modal-button>
  </div>
</template>

<script lang="ts"> import ModalButton from './components/ModalButton.vue'
export default {
  setup() {
    return {}
  },
  components: {
    ModalButton,
  },
} </script>

//子組件
<template>
  <div class="son">
    <button @click="modalOpen = true">
      點(diǎn)我打開(kāi)對(duì)話框
    </button>

    <teleport to="body">
      <div v-if="modalOpen"
           class="looklook">
        看看我出現(xiàn)在了哪里
        <button @click="modalOpen = false">
          Close
        </button>
      </div>
    </teleport>
  </div>
</template>

<script> import { ref } from 'vue'
export default {
  name: 'modal-button',
  setup() {
    const modalOpen = ref(false)
    return {
      modalOpen,
    }
  },
} </script> 

可以看到在子組件中的looklook元素跑到了body下面,而之前的位置默認(rèn)出現(xiàn)了兩行注釋

image

Suspense(不確定的)

作用

它們?cè)试S我們的應(yīng)用程序在等待異步組件時(shí)渲染一些后備內(nèi)容鸥跟,可以讓我們創(chuàng)建一個(gè)平滑的用戶體驗(yàn)

語(yǔ)法

 <Suspense>
    <template v-slot:default>
      <!-- 異步組件 -->
      <AsyncComp />
    </template>

    <template v-slot:fallback>
      <!-- 后備內(nèi)容 -->
      <h1>LOADING...</h1>
    </template>
  </Suspense> 

vue3中引入異步組件的方式

const AsyncComp = defineAsyncComponent(() => import('./AsyncComp.vue')) 

代碼演示

父組件

<template>
  <Suspense>
     <!-- v-slot:defaul可以簡(jiǎn)寫(xiě)成#defaul -->
    <template v-slot:default>
      <AsyncComp/>
    </template>

    <template v-slot:fallback>
      <h1>LOADING...</h1>
    </template>
  </Suspense>
</template>

<script lang="ts"> import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() => import('./AsyncComp.vue'))
export default {
  setup() {
    return {
    }
  },

  components: {
    AsyncComp,
  }
} </script> 

子組件

<template>
  <h2>AsyncComp22</h2>
  <p>{{msg}}</p>
</template>

<script lang="ts"> export default {
  name: 'AsyncComp',
  setup () {
     return new Promise((resolve, reject) => {
       setTimeout(() => {
         resolve({
           msg: 'abc'
         })
       }, 2000)
     })
  }
} </script> 

通過(guò)下圖可以看到在異步組件加載出來(lái)之前,顯示的是fallback中的內(nèi)容

image

響應(yīng)式數(shù)據(jù)的判斷

作用

  • isRef: 檢查一個(gè)值是否為一個(gè) ref 對(duì)象
  • isReactive: 檢查一個(gè)對(duì)象是否是由 reactive 創(chuàng)建的響應(yīng)式代理
  • isReadonly: 檢查一個(gè)對(duì)象是否是由 readonly 創(chuàng)建的只讀代理
  • isProxy: 檢查一個(gè)對(duì)象是否是由 reactive 或者 readonly 方法創(chuàng)建的代理

代碼演示

setup() {
    const state1 = ref(1);
    console.log('isref:', isRef(state1));//isref: true
    const state2 = reactive({});
    console.log('isReactive:', isReactive(state2));//isReactive: true
    const state3 = readonly({});
    console.log('isReadonly:', isReadonly(state3));//isReadonly: true
    const state4 = reactive({});
    console.log('isProxy:', isProxy(state2));//isProxy: true
    console.log('isProxy:', isProxy(state4));//isProxy: true
    return {};
  }, 

其他不常用特性

還有很多很多不常用的新特性,我在日常開(kāi)發(fā)中是沒(méi)有用到的盔沫,很多都是用來(lái)做優(yōu)化的医咨,感興趣的小伙伴們自行去官網(wǎng)查看,或者大佬們可以介紹一下應(yīng)用場(chǎng)景架诞。

  • shallowReactive
  • shallowRef
  • readonly
  • shallowReadonly
  • markRaw
  • customRef
  • ...

語(yǔ)法糖

雖然Composition API用起來(lái)已經(jīng)非常方便了拟淮,但是我們還是有很煩的地方,比如

  • 組件引入了還要注冊(cè)
  • 屬性和方法都要在setup函數(shù)中返回谴忧,有的時(shí)候僅一個(gè)return就十幾行甚至幾十行
  • ...
  • 不想寫(xiě)啊怎么辦

好辦很泊,Vue3官方提供了script setup語(yǔ)法糖

只需要在script標(biāo)簽中添加setup,組件只需引入不用注冊(cè)沾谓,屬性和方法也不用返回委造,setup函數(shù)也不需要,甚至export default都不用寫(xiě)了均驶,不僅是數(shù)據(jù)昏兆,計(jì)算屬性和方法,甚至是自定義指令也可以在我們的template中自動(dòng)獲得妇穴。

但是這么過(guò)癮的語(yǔ)法糖爬虱,還是稍微添加了一點(diǎn)點(diǎn)心智負(fù)擔(dān)隶债,因?yàn)闆](méi)有了setup函數(shù),那么props跑筝,emit死讹,attrs怎么獲取呢,就要介紹一下新的語(yǔ)法了曲梗。

setup script語(yǔ)法糖提供了三個(gè)新的API來(lái)供我們使用:defineProps回俐、defineEmituseContext

  • defineProps 用來(lái)接收父組件傳來(lái)的值props
  • defineEmit 用來(lái)聲明觸發(fā)的事件表稀并。
  • useContext 用來(lái)獲取組件上下文context仅颇。

代碼演示

父組件

<template>
  <div>
    <h2>我是父組件!</h2>
    <Child msg="hello"
           @child-click="handleClick" />
  </div>
</template>

<script setup> import Child from './components/Child.vue'

const handleClick = (ctx) => {
  console.log(ctx)
} </script> 

子組件

<template>
  <span @click="sonClick">msg: {{ props.msg }}</span>
</template>

<script setup> import { useContext, defineProps, defineEmit } from 'vue'

const emit = defineEmit(['child-click'])
const ctx = useContext()
const props = defineProps({
  msg: String,
})

const sonClick = () => {
  emit('child-click', ctx)
} </script> 

我們點(diǎn)擊一下子組件

image

可以看到context被打印了出來(lái)碘举,其中的attrs忘瓦、emitslots引颈、expose屬性和方法依然可以使用耕皮。 props也可以輸出在頁(yè)面上,事件也成功派發(fā)蝙场。

其他知識(shí)點(diǎn)

接下來(lái)介紹一下我使用Vue3過(guò)程中遇到的問(wèn)題或者小技巧凌停,不全面,想起什么就寫(xiě)什么吧

script setup語(yǔ)法糖請(qǐng)注意

如果在父組件中通過(guò)ref='xxx'的方法來(lái)獲取子組件實(shí)例售滤,子組件使用了script setup語(yǔ)法糖,那么子組件的數(shù)據(jù)需要用expose的方式導(dǎo)出罚拟,否則會(huì)因?yàn)楂@取不到數(shù)據(jù)而報(bào)錯(cuò)。

代碼演示

父組件

<template>
  <div>
    <h2>我是父組件完箩!</h2>
    <Child ref='son' />
  </div>
</template>

<script setup> import Child from './components/Child.vue'
import { ref } from 'vue'
const son = ref(null)
console.log('????~ son:', son) </script> 

子組件先不使用語(yǔ)法糖

<template>
  <div>
    我是子組件{{msg}}
  </div>
</template>

<script > import { ref } from 'vue'

export default {
  setup() {
    const msg = ref('hello')
    return {
      msg,
    }
  },
} 

可以看到是可以獲取到我們?cè)谧咏M件中定義的msg屬性的

image

現(xiàn)在把子組件換成script setup語(yǔ)法糖再來(lái)試一試

<template>
  <div>
    我是子組件{{msg}}
  </div>
</template>

<script setup> import { ref } from 'vue'
const msg = ref('hello') </script> 

可以看到現(xiàn)在是獲取不到子組件定義的msg屬性的

image

我們可以看看尤大大怎么說(shuō)

image

Emit派發(fā)事件可以對(duì)參數(shù)進(jìn)行驗(yàn)證

父組件

<template>
  <div>
    <h2>我是父組件赐俗!</h2>
    <Child @sonClick='sonClick' />
  </div>
</template>

<script setup> import Child from './components/Child.vue'
import { ref } from 'vue'
const sonClick = (value) => {
  console.log(value)
} </script> 

子組件

 <template>
  <div>
    我是子組件{{ msg }}
  </div>
  <button @click="handleClick(1)">我是按鈕1</button>
  <button @click="handleClick(2)">我是按鈕2</button>
</template>

<script> import { ref } from 'vue'
export default {
  name: '',
  emits: {
    sonClick: (value) => {
      if (value === 1) {
        return true
      } else {
        return false
      }
    },
  },
  setup(props, { emit }) {
    const msg = ref('hello')
    const handleClick = (value) => {
      emit('sonClick', value)
    }
    return {
      msg,
      handleClick,
    }
  },
} </script> 

我們分別點(diǎn)一下按鈕1和按鈕2,可以看到當(dāng)我們點(diǎn)了按鈕2的時(shí)候弊知,控制臺(tái)會(huì)發(fā)出警告阻逮,但是程序會(huì)繼續(xù)執(zhí)行,還沒(méi)想到什么適合的應(yīng)用場(chǎng)景秩彤,但是要知道這個(gè)知識(shí)點(diǎn)叔扼,小伙伴們可以在這搞事情。

image

跨組件通訊mitt.js

Vue2中怎么實(shí)現(xiàn)跨組件通訊呢,很多人第一想法就是event bus漫雷。但是Vue3移除了$on,$once,$off導(dǎo)致不能使用這個(gè)方法瓜富。但是Vue官方給大家推薦了mitt.js,它的原理就是event bus

代碼演示

先安裝

npm i mitt -s 

然后封裝一個(gè)hook

//mitt.js
import mitt from 'mitt'
const emitter = mitt();

export default emitter; 

父組件

<template>
  <div>
    <h2>我是父組件珊拼!</h2>
    <Child1 />
    <Child2 />

  </div>
</template>

<script setup> import Child1 from './components/Child1.vue'
import Child2 from './components/Child2.vue' </script> 

子組件1

<template>
  <div>
    我是子組件1
    <h1>{{msg}}</h1>
  </div>
</template>

<script> import { ref, onUnmounted } from 'vue'
import emitter from '../mitt'
export default {
  name: '',

  setup() {
    //初始化
    const msg = ref('hello')
    const changeMsg = () => {
      msg.value = 'world'
    }
    // 監(jiān)聽(tīng)事件,更新數(shù)據(jù)
    emitter.on('change-msg', changeMsg)
    // 顯式卸載
    onUnmounted(() => {
      emitter.off('change-msg', changeMsg)
    })
    return {
      msg,
      changeMsg,
    }
  },
} </script> 

子組件2

<template>
  <div>
    我是子組件2
  </div>
  <button @click='changeMsg'>點(diǎn)擊修改msg</button>
</template>

<script> import { ref } from 'vue'
import emitter from '../mitt'

export default {
  name: '',

  setup() {
    const changeMsg = () => {
      emitter.emit('change-msg')
    }
    return {
      changeMsg,
    }
  },
} </script> 

演示

image

自定義指令

先看看Vue2自定義指令的鉤子

  • bind:當(dāng)指令綁定在對(duì)應(yīng)元素時(shí)觸發(fā)食呻。只會(huì)觸發(fā)一次。
  • inserted:當(dāng)對(duì)應(yīng)元素被插入到 DOM 的父元素時(shí)觸發(fā)澎现。
  • update:當(dāng)元素更新時(shí)仅胞,這個(gè)鉤子會(huì)被觸發(fā)(此時(shí)元素的后代元素還沒(méi)有觸發(fā)更新)。
  • componentUpdated:當(dāng)整個(gè)組件(包括子組件)完成更新后剑辫,這個(gè)鉤子觸發(fā)干旧。
  • unbind:當(dāng)指令被從元素上移除時(shí),這個(gè)鉤子會(huì)被觸發(fā)妹蔽。也只觸發(fā)一次椎眯。

Vue3 中,官方為了更有助于代碼的可讀性和風(fēng)格統(tǒng)一胳岂,把自定義指令的鉤子名稱(chēng)改的更像是組件生命周期编整,盡管他們是兩回事

  • bind => beforeMount
  • inserted => mounted
  • beforeUpdate: 新的鉤子,會(huì)在元素自身更新前觸發(fā)
  • update => 移除乳丰!
  • componentUpdated => updated
  • beforeUnmount: 新的鉤子掌测,當(dāng)元素自身被卸載前觸發(fā)
  • unbind => unmounted

過(guò)渡動(dòng)畫(huà)

這個(gè)沒(méi)有什么大的改動(dòng),只是修改了兩個(gè)class名字产园,正是因?yàn)闆](méi)有什么大的改動(dòng)汞斧,導(dǎo)致我曾經(jīng)在這里栽了大跟頭,寫(xiě)完了怎么都不對(duì)什燕,后來(lái)查官網(wǎng)才知道粘勒。

以下是直接引用 官網(wǎng)的原文

  • v-enter-from:定義進(jìn)入過(guò)渡的開(kāi)始狀態(tài)。在元素被插入之前生效屎即,在元素被插入之后的下一幀移除庙睡。
  • v-enter-active:定義進(jìn)入過(guò)渡生效時(shí)的狀態(tài)。在整個(gè)進(jìn)入過(guò)渡的階段中應(yīng)用技俐,在元素被插入之前生效埃撵,在過(guò)渡/動(dòng)畫(huà)完成之后移除。這個(gè)類(lèi)可以被用來(lái)定義進(jìn)入過(guò)渡的過(guò)程時(shí)間虽另,延遲和曲線函數(shù)暂刘。
  • v-enter-to:定義進(jìn)入過(guò)渡的結(jié)束狀態(tài)。在元素被插入之后下一幀生效 (與此同時(shí) v-enter-from 被移除)捂刺,在過(guò)渡/動(dòng)畫(huà)完成之后移除谣拣。
  • v-leave-from:定義離開(kāi)過(guò)渡的開(kāi)始狀態(tài)。在離開(kāi)過(guò)渡被觸發(fā)時(shí)立刻生效族展,下一幀被移除森缠。
  • v-leave-active:定義離開(kāi)過(guò)渡生效時(shí)的狀態(tài)。在整個(gè)離開(kāi)過(guò)渡的階段中應(yīng)用仪缸,在離開(kāi)過(guò)渡被觸發(fā)時(shí)立刻生效贵涵,在過(guò)渡/動(dòng)畫(huà)完成之后移除。這個(gè)類(lèi)可以被用來(lái)定義離開(kāi)過(guò)渡的過(guò)程時(shí)間,延遲和曲線函數(shù)宾茂。
  • v-leave-to:離開(kāi)過(guò)渡的結(jié)束狀態(tài)瓷马。在離開(kāi)過(guò)渡被觸發(fā)之后下一幀生效 (與此同時(shí) v-leave-from 被刪除),在過(guò)渡/動(dòng)畫(huà)完成之后移除跨晴。

特別注意的是v-enter改成了v-enter-form欧聘,v-leave改成了v-leave-from

其他小知識(shí)

Vue3移除了filter

獲取組件實(shí)例方法getCurrentInstance()

這個(gè)方法可以獲取到當(dāng)前組件的實(shí)例端盆,相當(dāng)于Vue2中的this怀骤,但是要注意在 開(kāi)發(fā)環(huán)境生產(chǎn)環(huán)境 的使用方法是不同的。

畢竟是個(gè)人總結(jié)焕妙,難免會(huì)出現(xiàn)紕漏和錯(cuò)誤蒋伦,期待各路大神的補(bǔ)充和糾正。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末焚鹊,一起剝皮案震驚了整個(gè)濱河市痕届,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌寺旺,老刑警劉巖爷抓,帶你破解...
    沈念sama閱讀 216,843評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異阻塑,居然都是意外死亡蓝撇,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)陈莽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)渤昌,“玉大人,你說(shuō)我怎么就攤上這事走搁《栏蹋” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,187評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵私植,是天一觀的道長(zhǎng)忌栅。 經(jīng)常有香客問(wèn)我,道長(zhǎng)曲稼,這世上最難降的妖魔是什么索绪? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,264評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮贫悄,結(jié)果婚禮上瑞驱,老公的妹妹穿的比我還像新娘。我一直安慰自己窄坦,他們只是感情好唤反,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,289評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布凳寺。 她就那樣靜靜地躺著,像睡著了一般彤侍。 火紅的嫁衣襯著肌膚如雪肠缨。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,231評(píng)論 1 299
  • 那天拥刻,我揣著相機(jī)與錄音怜瞒,去河邊找鬼父泳。 笑死般哼,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的惠窄。 我是一名探鬼主播蒸眠,決...
    沈念sama閱讀 40,116評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼杆融!你這毒婦竟也來(lái)了楞卡?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,945評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤脾歇,失蹤者是張志新(化名)和其女友劉穎蒋腮,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體藕各,經(jīng)...
    沈念sama閱讀 45,367評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡池摧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,581評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了激况。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片作彤。...
    茶點(diǎn)故事閱讀 39,754評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖乌逐,靈堂內(nèi)的尸體忽然破棺而出竭讳,到底是詐尸還是另有隱情,我是刑警寧澤浙踢,帶...
    沈念sama閱讀 35,458評(píng)論 5 344
  • 正文 年R本政府宣布绢慢,位于F島的核電站,受9級(jí)特大地震影響洛波,放射性物質(zhì)發(fā)生泄漏胰舆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,068評(píng)論 3 327
  • 文/蒙蒙 一奋岁、第九天 我趴在偏房一處隱蔽的房頂上張望思瘟。 院中可真熱鬧,春花似錦闻伶、人聲如沸滨攻。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,692評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)光绕。三九已至女嘲,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間诞帐,已是汗流浹背欣尼。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,842評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留停蕉,地道東北人愕鼓。 一個(gè)月前我還...
    沈念sama閱讀 47,797評(píng)論 2 369
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像慧起,于是被迫代替她去往敵國(guó)和親菇晃。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,654評(píng)論 2 354

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