可能很多同學(xué)(包括我)剛上手 Vue 3.0 之后迂猴,都會覺得開發(fā)過程似乎變得更繁瑣了,Vue 官方團(tuán)隊(duì)當(dāng)然不會無視群眾的呼聲,如果你基于腳手架和 .vue 文件開發(fā)躺屁,那么可以享受到更高效率的開發(fā)體驗(yàn)。
在閱讀這篇文章之前经宏,需要對 Vue 3.0 的單組件有一定的了解犀暑,如果還處于完全沒有接觸過的階段,請先抽點(diǎn)時間閱讀 單組件的編寫 一章烁兰。
WARNING
本章節(jié)的部分方案屬于實(shí)驗(yàn)性方案耐亏,或者是剛進(jìn)入定稿階段,所以在官網(wǎng)文檔上還暫時看不到使用說明沪斟,期間可能還會有一些功能調(diào)整和 BUG 修復(fù)广辰,請留意版本號說明。
所以要體驗(yàn)以下新特性币喧,請確保項(xiàng)目下 package.json 里的 vue (opens new window)和 @vue/compiler-sfc (opens new window)都在 v3.1.4 版本以上轨域,最好同步 NPM 上當(dāng)前最新的 @next 版本,否則在編譯過程中可能出現(xiàn)一些奇怪的問題(這兩個依賴必須保持同樣的版本號)杀餐。
#script-setup
這是一個比較有爭議的新特性干发,作為 setup 函數(shù)的語法糖,褒貶不一史翘,不過經(jīng)歷了幾次迭代之后枉长,目前在體驗(yàn)上來說,感受還是非常棒的琼讽。
TIP
截止至 2021-07-16 必峰,<script setup>
方案已在 Vue3.2.0-beta.1
版本中脫離實(shí)驗(yàn)狀態(tài),正式進(jìn)入 Vue 3.0 的隊(duì)伍钻蹬,在新的版本中已經(jīng)可以作為一個官方標(biāo)準(zhǔn)的開發(fā)方案使用(但初期仍需注意與開源社區(qū)的項(xiàng)目兼容性問題吼蚁,特別是 UI 框架)。
另外问欠,Vue 的 3.1.2
版本是針對 script-setup 的一個分水嶺版本肝匆,自 3.1.4
開始 script-setup 進(jìn)入定稿狀態(tài),部分舊的 API 已被舍棄顺献,本章節(jié)內(nèi)容將以最新的 API 為準(zhǔn)進(jìn)行整理說明旗国,如果您需要查閱舊版 API 的使用,請參閱 這里 (opens new window)注整。
#新特性的產(chǎn)生背景
在了解它怎么用之前能曾,可以先了解一下它被推出的一些背景度硝,可以幫助你對比開發(fā)體驗(yàn)上的異同點(diǎn),以及了解為什么會有這一章節(jié)里面的新東西寿冕。
在 Vue 3.0 的 .vue 組件里蕊程,遵循 SFC 規(guī)范要求(注:SFC,即 Single-File Component蚂斤,.vue 單組件)存捺,標(biāo)準(zhǔn)的 setup 用法是,在 setup 里面定義的數(shù)據(jù)如果需要在 template 使用曙蒸,都需要 return 出來捌治。
如果你使用的是 TypeScript ,還需要借助 defineComponent 來幫助你對類型的自動推導(dǎo)纽窟。
<!-- 標(biāo)準(zhǔn)組件格式 -->
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
setup () {
// ...
return {
// ...
}
}
})
</script>
關(guān)于標(biāo)準(zhǔn) setup 和 defineComponent 的說明和用法肖油,可以查閱 全新的 setup 函數(shù) 一節(jié)。
script-setup 的推出是為了讓熟悉 3.0 的用戶可以更高效率的開發(fā)組件臂港,減少一些心智負(fù)擔(dān)森枪,只需要給 script 標(biāo)簽添加一個 setup 屬性,那么整個 script 就直接會變成 setup 函數(shù)审孽,所有頂級變量县袱、函數(shù),均會自動暴露給模板使用(無需再一個個 return 了)佑力。
Vue 會通過單組件編譯器式散,在編譯的時候?qū)⑵涮幚砘貥?biāo)準(zhǔn)組件,所以目前這個方案只適合用 .vue 文件寫的工程化項(xiàng)目打颤。
<!-- 使用 script-setup 格式 -->
<script setup lang="ts">
// ...
</script>
對建芙,就是這樣档悠,代碼量瞬間大幅度減少……
TIP
因?yàn)?script-setup 的大部分功能在書寫上和標(biāo)準(zhǔn)版是一致的藏姐,這里只提及一些差異化的表現(xiàn)厅篓。
#全局編譯器宏
在 script-setup 模式下,新增了 4 個全局編譯器宏透且,他們無需 import 就可以直接使用撕蔼。
但是默認(rèn)的情況下直接使用,項(xiàng)目的 eslint 會提示你沒有導(dǎo)入秽誊,但你導(dǎo)入后罕邀,控制臺的 Vue 編譯助手又會提示你不需要導(dǎo)入,就很尷尬…
哈哈哈哈不過不用著急养距,可以配置一下 lint ,把這幾個編譯助手寫進(jìn)全局規(guī)則里日熬,就可以了棍厌,不需要導(dǎo)入也不會報(bào)錯了。
// 項(xiàng)目根目錄下的 .eslintrc.js
module.exports = {
// 原來的lint規(guī)則,補(bǔ)充下面的globals...
globals: {
defineProps: 'readonly',
defineEmits: 'readonly',
defineExpose: 'readonly',
withDefaults: 'readonly',
},
}
關(guān)于幾個宏的說明都在下面的文檔部分有說明耘纱,你也可以從這里導(dǎo)航過去直接查看敬肚。
宏 | 說明 |
---|---|
defineProps | 點(diǎn)擊查看 |
defineEmits | 點(diǎn)擊查看 |
defineExpose | 點(diǎn)擊查看 |
withDefaults | 點(diǎn)擊查看 |
下面我們繼續(xù)了解 script-setup 的變化。
#template 操作簡化
如果使用 JSX / TSX 寫法束析,這一點(diǎn)沒有太大影響艳馒,但對于習(xí)慣使用 <template />
的開發(fā)者來說,這是一個非常爽的體驗(yàn)员寇。
主要體現(xiàn)在這兩點(diǎn):
#變量無需進(jìn)行 return
標(biāo)準(zhǔn)組件模式下弄慰,setup 里定義的變量,需要 return 后蝶锋,在 template 部分才可以正確拿到:
<!-- 標(biāo)準(zhǔn)組件格式 -->
<template>
<p>{{ msg }}</p>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
setup () {
const msg: string = 'Hello World!';
// 要給 template 用的數(shù)據(jù)需要 return 出來才可以
return {
msg
}
}
})
</script>
在 script-setup 模式下陆爽,你定義了就可以直接使用。
<!-- 使用 script-setup 格式 -->
<template>
<p>{{ msg }}</p>
</template>
<script setup lang="ts">
const msg: string = 'Hello World!';
</script>
#子組件無需手動注冊
子組件的掛載扳缕,在標(biāo)準(zhǔn)組件里的寫法是需要 import 后再放到 components 里才能夠啟用:
<!-- 標(biāo)準(zhǔn)組件格式 -->
<template>
<Child />
</template>
<script lang="ts">
import { defineComponent } from 'vue'
// 導(dǎo)入子組件
import Child from '@cp/Child.vue'
export default defineComponent({
// 需要啟用子組件作為模板
components: {
Child
},
// 組件里的業(yè)務(wù)代碼
setup () {
// ...
}
})
</script>
在 script-setup 模式下慌闭,只需要導(dǎo)入組件即可,編譯器會自動識別并啟用躯舔。
<!-- 使用 script-setup 格式 -->
<template>
<Child />
</template>
<script setup lang="ts">
import Child from '@cp/Child.vue'
</script>
#props 的接收方式變化
由于整個 script 都變成了一個大的 setup function 驴剔,沒有了組件選項(xiàng),也沒有了 setup 入?yún)⒅嘧詻]辦法和標(biāo)準(zhǔn)寫法一樣去接收 props 了丧失。
這里需要使用一個全新的 API :defineProps
。
defineProps
是一個方法飒赃,內(nèi)部返回一個對象利花,也就是掛載到這個組件上的所有 props ,它和普通的 props 用法一樣载佳,如果不指定為 prop炒事, 則傳下來的屬性會被放到 attrs 那邊去。
TIP
前置知識點(diǎn):接收 props - 組件之間的通信蔫慧。
#defineProps 的基礎(chǔ)用法
所以挠乳,如果只是單純在 template 里使用,那么其實(shí)就這么簡單定義就可以了:
defineProps([
'name',
'userInfo',
'tags'
])
使用 string[]
數(shù)組作為入?yún)⒐枚悖?prop 的名稱作為數(shù)組的 item 傳給 defineProps
就可以了睡扬。
如果 script 里的方法要拿到 props 的值,你也可以使用字面量定義:
const props = defineProps([
'name',
'userInfo',
'tags'
])
console.log(props.name);
但在作為一個 Vue 老玩家黍析,都清楚不顯性的指定 prop 類型的話卖怜,很容易在協(xié)作中引起程序報(bào)錯,那么應(yīng)該如何對每個 prop 進(jìn)行類型檢查呢阐枣?
有兩種方式來處理類型定義马靠。
#通過構(gòu)造函數(shù)檢查 prop
這是第一種方式:使用 JavaScript 原生構(gòu)造函數(shù)進(jìn)行類型規(guī)定奄抽。
也就是跟我們平時定義 prop 類型時一樣, Vue 會通過 instanceof
來進(jìn)行 類型檢查 (opens new window)甩鳄。
使用這種方法逞度,需要通過一個 “對象” 入?yún)韨鬟f給 defineProps
,比如:
defineProps({
name: String,
userInfo: Object,
tags: Array
});
所有原來 props 具備的校驗(yàn)機(jī)制妙啃,都可以適用档泽,比如你除了要限制類型外,還想指定 name
是可選揖赴,并且?guī)в幸粋€默認(rèn)值:
defineProps({
name: {
type: String,
required: false,
default: 'Petter'
},
userInfo: Object,
tags: Array
});
更多的 props 校驗(yàn)機(jī)制馆匿,可以點(diǎn)擊 帶有類型限制的 props 和 可選以及帶有默認(rèn)值的 props 了解更多。
#使用類型注解檢查 prop
這是第二種方式:使用 TypeScript 的類型注解储笑。
和 ref 等 API 的用法一樣甜熔,defineProps
也是可以使用尖括號 <> 來包裹類型定義,緊跟在 API 后面突倍,另外腔稀,由于 defineProps
返回的是一個對象(因?yàn)?props 本身是一個對象),所以尖括號里面的類型還要用大括號包裹羽历,通過 key: value
的鍵值對形式表示焊虏,如:
defineProps<{ name: string }>();
注意到了嗎?這里使用的類型秕磷,和第一種方法提到的指定類型時是不一樣的诵闭。
TIP
在這里,不再使用構(gòu)造函數(shù)校驗(yàn)澎嚣,而是需要遵循使用 TypeScript 的類型疏尿。
比如字符串是 string
,而不是 String
易桃。
如果有多個 prop 褥琐,就跟寫 interface 一樣:
defineProps<{
name: string;
phoneNumber: number;
userInfo: object;
tags: string[];
}>();
其中,舉例里的 userInfo
是一個對象晤郑,你可以簡單的指定為 object敌呈,也可以先定義好它對應(yīng)的類型,再進(jìn)行指定:
interface UserInfo {
id: number;
age: number;
}
defineProps<{
name: string;
userInfo: UserInfo;
}>();
如果你想對某個數(shù)據(jù)設(shè)置為可選造寝,也是遵循 TS 規(guī)范磕洪,通過英文問號 ?
來允許可選:
// name 是可選
defineProps<{
name?: string;
tags: string[];
}>();
如果你想設(shè)置可選參數(shù)的默認(rèn)值,需要借助 withDefaults API诫龙。
WARNING
需要強(qiáng)調(diào)的一點(diǎn)是:在 構(gòu)造函數(shù) 和 類型注解 這兩種校驗(yàn)方式只能二選一析显,不能同時使用,否則會引起程序報(bào)錯
#withDefaults 的基礎(chǔ)用法
這個新的 withDefaults API 可以讓你在使用 TS 類型系統(tǒng)時签赃,也可以指定 props 的默認(rèn)值叫榕。
它接收兩個入?yún)ⅲ?/p>
參數(shù) | 類型 | 含義 |
---|---|---|
props | object | 通過 defineProps 傳入的 props |
defaultValues | object | 根據(jù) props 的 key 傳入默認(rèn)值 |
可能缺乏一些官方描述浑侥,還是看參考用法可能更直觀:
withDefaults(defineProps<{
size?: number
labels?: string[]
}>(), {
size: 3,
labels: () => ['default label']
})
如果你要在 TS / JS 再對 props 進(jìn)行獲取,也可以通過字面量來拿到這些默認(rèn)值:
// 如果不習(xí)慣上面的寫法晰绎,你也可以跟平時一樣先通過interface定義一個類型接口
interface Props {
msg?: string
}
// 再作為入?yún)魅?const props = withDefaults(defineProps<Props>(), {
msg: 'hello'
})
// 這樣就可以通過props變量拿到需要的prop值了
console.log(props.msg)
#emits 的接收方式變化
和 props 一樣,emits 的接收也是需要使用一個全新的 API 來操作括丁,這個 API 就是 defineEmits
荞下。
和 defineProps
一樣, defineEmits
也是一個方法史飞,它接受的入?yún)⒏袷胶蜆?biāo)準(zhǔn)組件的要求是一致的尖昏。
TIP
注意:從3.1.3
版本開始,該 API 已被改名构资,加上了復(fù)數(shù)結(jié)尾抽诉,帶有 s,在此版本之前是沒有 s 結(jié)尾吐绵!
前置知識點(diǎn):接收 emits - 組件之間的通信迹淌。
#defineEmits 的基礎(chǔ)用法
由于 emit 并非提供給模板直接讀取,所以需要通過字面量來定義 emits己单。
最基礎(chǔ)的用法也是傳遞一個 string[]
數(shù)組進(jìn)來唉窃,把每個 emit 的名稱作為數(shù)組的 item 。
// 獲取 emit
const emit = defineEmits(['chang-name']);
// 調(diào)用 emit
emit('chang-name', 'Tom');
由于 defineEmits
的用法和原來的 emits 選項(xiàng)差別不大纹笼,這里也不重復(fù)說明更多的諸如校驗(yàn)之類的用法了纹份,可以查看 接收 emits 一節(jié)了解更多。
#attrs 的接收方式變化
attrs
和 props
很相似廷痘,也是基于父子通信的數(shù)據(jù)蔓涧,如果父組件綁定下來的數(shù)據(jù)沒有被指定為 props
,那么就會被掛到 attrs
這邊來笋额。
在標(biāo)準(zhǔn)組件里元暴, attrs
的數(shù)據(jù)是通過 setup
的第二個入?yún)?context
里的 attrs
API 獲取的。
// 標(biāo)準(zhǔn)組件的寫法
export default defineComponent({
setup (props, { attrs }) {
// attrs 是個對象鳞陨,每個 Attribute 都是它的 key
console.log(attrs.class);
// 如果傳下來的 Attribute 帶有短橫線昨寞,需要通過這種方式獲取
console.log(attrs['data-hash']);
}
})
但和 props
一樣,由于沒有了 context
參數(shù)厦滤,需要使用一個新的 API 來拿到 attrs
數(shù)據(jù)援岩。
這個 API 就是 useAttrs
。
TIP
請注意掏导,useAttrs
API 需要 Vue3.1.4
或更高版本才可以使用享怀。
#useAttrs 的基礎(chǔ)用法
顧名思義, useAttrs 可以是用來獲取 attrs 數(shù)據(jù)的趟咆,它的用法非常簡單:
// 導(dǎo)入 useAttrs 組件
import { useAttrs } from 'vue'
// 獲取 attrs
const attrs = useAttrs()
// attrs是個對象添瓷,和 props 一樣梅屉,需要通過 key 來得到對應(yīng)的單個 attr
console.log(attrs.msg);
對 attrs
不太了解的話,可以查閱 獲取非 Prop 的 Attribute
#slots 的接收方式變化
slots
是 Vue 組件的插槽數(shù)據(jù)鳞贷,也是在父子通信里的一個重要成員坯汤。
對于使用 template 的開發(fā)者來說,在 script-setup 里獲取插槽數(shù)據(jù)并不困難搀愧,因?yàn)楦鷺?biāo)準(zhǔn)組件的寫法是完全一樣的惰聂,可以直接在 template 里使用 <slot />
標(biāo)簽渲染。
<template>
<div>
<!-- 插槽數(shù)據(jù) -->
<slot />
<!-- 插槽數(shù)據(jù) -->
</div>
</template>
但對使用 JSX / TSX 的開發(fā)者來說咱筛,就影響比較大了搓幌,在標(biāo)準(zhǔn)組件里,想在 script 里獲取插槽數(shù)據(jù)迅箩,也是需要在 setup
的第二個入?yún)⒗锬玫?slots
API 溉愁。
// 標(biāo)準(zhǔn)組件的寫法
export default defineComponent({
// 這里的 slots 就是插槽
setup (props, { slots }) {
// ...
}
})
新版本的 Vue 也提供了一個全新的 useSlots
API 來幫助 script-setup 用戶獲取插槽。
TIP
請注意饲趋,useSlots
API 需要 Vue3.1.4
或更高版本才可以使用拐揭。
#useSlots 的基礎(chǔ)用法
先來看看父組件,父組件先為子組件傳入插槽數(shù)據(jù)篙贸,支持 “默認(rèn)插槽” 和 “命名插槽” :
<template>
<!-- 子組件 -->
<ChildTSX>
<!-- 默認(rèn)插槽 -->
<p>I am a default slot from TSX.</p>
<!-- 默認(rèn)插槽 -->
<!-- 命名插槽 -->
<template #msg>
<p>I am a msg slot from TSX.</p>
</template>
<!-- 命名插槽 -->
</ChildTSX>
<!-- 子組件 -->
</template>
<script setup lang="ts">
import ChildTSX from '@cp/context/Child.tsx'
</script>
在使用 JSX / TSX 編寫的子組件里投队,就可以通過 useSlots
來獲取父組件傳進(jìn)來的 slots
數(shù)據(jù)進(jìn)行渲染:
// 注意:這是一個 .tsx 文件
import { defineComponent, useSlots } from 'vue'
const ChildTSX = defineComponent({
setup() {
// 獲取插槽數(shù)據(jù)
const slots = useSlots()
// 渲染組件
return () => (
<div>
{/* 渲染默認(rèn)插槽 */}
<p>{ slots.default ? slots.default() : '' }</p>
{/* 渲染命名插槽 */}
<p>{ slots.msg ? slots.msg() : '' }</p>
</div>
)
},
})
export default ChildTSX
#ref 的通信方式變化
在標(biāo)準(zhǔn)組件寫法里,子組件的數(shù)據(jù)都是默認(rèn)隱式暴露給父組件的爵川,也就是父組件可以通過 childComponent.value.foo
這樣的方式直接操作子組件的數(shù)據(jù)(參見:DOM 元素與子組件 - 響應(yīng)式 API 之 ref)敷鸦。
但在 script-setup 模式下,所有數(shù)據(jù)只是默認(rèn)隱式 return 給 template 使用寝贡,不會暴露到組件外扒披,所以父組件是無法直接通過掛載 ref 變量獲取子組件的數(shù)據(jù)。
在 script-setup 模式下圃泡,如果要調(diào)用子組件的數(shù)據(jù)碟案,需要先在子組件顯示的暴露出來,才能夠正確的拿到颇蜡,這個操作价说,就是由 defineExpose
來完成。
#defineExpose 的基礎(chǔ)用法
defineExpose
的用法非常簡單风秤,它本身是一個函數(shù)鳖目,可以接受一個對象參數(shù)。
在子組件里缤弦,像這樣把需要暴露出去的數(shù)據(jù)通過 key: value
的形式作為入?yún)ⅲㄏ旅娴睦邮怯玫搅?ES6 的 屬性的簡潔表示法 (opens new window)):
<script setup lang="ts">
// 定義一個想提供給父組件拿到的數(shù)據(jù)
const msg: string = 'Hello World!';
// 顯示暴露的數(shù)據(jù)领迈,才可以在父組件拿到
defineExpose({
msg
});
</script>
然后你在父組件就可以通過掛載在子組件上的 ref 變量,去拿到暴露出來的數(shù)據(jù)了。
#頂級 await 的支持
在 script-setup 模式下狸捅,不必再配合 async 就可以直接使用 await 了衷蜓,這種情況下,組件的 setup 會自動變成 async setup 尘喝。
<script setup lang="ts">
const post = await fetch(`/api/post/1`).then((r) => r.json())
</script>
它轉(zhuǎn)換成標(biāo)準(zhǔn)組件的寫法就是:
<script lang="ts">
import { defineComponent, withAsyncContext } from 'vue'
export default defineComponent({
async setup() {
const post = await withAsyncContext(
fetch(`/api/post/1`).then((r) => r.json())
)
return {
post
}
}
})
</script>
點(diǎn)贊加關(guān)注磁浇,永遠(yuǎn)不迷路
每天一更新,創(chuàng)作拿命拼