Vue3.0 最新動(dòng)態(tài):script-setup 定稿,部分實(shí)驗(yàn)性 API 將棄用
最近一段時(shí)間挺忙,對(duì) Vue 3.0 的更新記錄看的比較少,今天看了一下 release 記錄,發(fā)現(xiàn)最新的 2 個(gè)小版本對(duì) script-setup 這個(gè)新特性改動(dòng)還算蠻大的,之前的用法都調(diào)整了不少。今天距離上一次發(fā)文討論 script-setup 新特性已經(jīng)有 4 個(gè)多月了(回顧上一篇[1]),雖然截止至 7 月 2 日的 3.1.4 版本,script-setup 還是處于實(shí)驗(yàn)性階段,但在同一天,尤大在 twitter[2] 上發(fā)布了一條推文,預(yù)告了它將會(huì)在 3.2.0 版本脫離實(shí)驗(yàn)狀態(tài),正式進(jìn)入 Vue 3.0 的隊(duì)伍。
先簡(jiǎn)單梳理一下本次定稿下來(lái)的一些調(diào)整:
useContext API 被棄用
在原先,可以通過(guò)該 API 來(lái)獲取組件的上下文信息,包含了 attrs 、slots 、emit、expose 等父子組件通信數(shù)據(jù)和方法。
- // 導(dǎo)入 useContext 組件
- import { useContext } from "vue";
- // 獲取 context
- const ctx = useContext();
該 API 將在 3.2 版本之后刪除,context 里面的數(shù)據(jù),會(huì)用新的 useSlots 和 useAttrs API 來(lái)代替。
新增 useSlots API 和 useAttrs API
在 useContext API 被刪除后,原先的上下文數(shù)據(jù),將由這兩個(gè)新 API 獲取到。
useAttrs
顧名思義, useAttrs 可以是用來(lái)獲取 attrs 數(shù)據(jù)的(也就是非 props 的屬性值)。
- // 導(dǎo)入 useAttrs 組件
- import { useAttrs } from "vue";
- // 獲取 attrs
- const attrs = useAttrs();
- // attrs是個(gè)對(duì)象,和 props 一樣,需要通過(guò) key 來(lái)得到對(duì)應(yīng)的單個(gè) attr
- console.log(attrs.msg);
如果當(dāng)前組件里沒(méi)有將某個(gè)屬性指定為 props,那么父組件綁定下來(lái)的屬性值,都會(huì)進(jìn)入到 attrs 里,通過(guò)這個(gè)新 API 來(lái)拿到。
useSlots
同樣,通過(guò) API 的命名也能了解它是用來(lái)獲取插槽數(shù)據(jù)的。但這個(gè) API 對(duì)大部分同學(xué)來(lái)說(shuō)應(yīng)該用的比較少,因?yàn)榇蟛糠?Vue 開(kāi)發(fā)者應(yīng)該都是用的 SFC 模式(單組件),插槽可以直接在 template 里使用 <slot /> 標(biāo)簽渲染。所以,我個(gè)人覺(jué)得這個(gè) API 的目標(biāo)用戶是面向 JSX / TSX 的開(kāi)發(fā)者,簡(jiǎn)單的用法參考如下:父組件,可以傳入默認(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 的子組件,通過(guò) useSlots 來(lái)獲取父組件傳進(jìn)來(lái)的 slots 數(shù)據(jù)進(jìn)行渲染:
- 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;
新增 defineExpose API
在標(biāo)準(zhǔn)組件寫(xiě)法里,子組件的數(shù)據(jù)都是默認(rèn)隱式暴露給父組件的,但在 script-setup 模式下,所有數(shù)據(jù)只是默認(rèn) return 給 template 使用,不會(huì)暴露到組件外,所以父組件是無(wú)法直接通過(guò)掛載 ref 變量獲取子組件的數(shù)據(jù)。如果要調(diào)用子組件的數(shù)據(jù),需要先在子組件顯示的暴露出來(lái),才能夠正確的拿到,這個(gè)操作,就是由 expose 來(lái)完成。expose 也是 context 的一個(gè)組件成員,原來(lái)的用法,是從 useContext 里導(dǎo)出:
- // 導(dǎo)入 useContext 組件
- import { useContext } from "vue";
- // 啟用expose組件
- const { expose } = useContext();
- // 定義一個(gè)想提供給父組件拿到的數(shù)據(jù)
- const msg: string = "Hello World!";
- // 顯示暴露的數(shù)據(jù),才可以在父組件拿到
- expose({
- msg,
- });
由于 useContext 會(huì)在未來(lái)版本里移除,所以新增了 defineExpose API 來(lái)實(shí)現(xiàn) expose 的功能。新的 API 用法:
- // 導(dǎo)入 defineExpose 組件
- import { defineExpose } from "vue";
- // 定義數(shù)據(jù)
- const msg: string = "Hello World!";
- // 暴露給父組件
- defineExpose({
- msg,
- });
父組件就可以通過(guò) ref API 去拿到子組件暴露出來(lái)的 msg 數(shù)據(jù)了。
改名 defineEmits API
使用 defineEmits 取待原來(lái)的 defineEmit API ,也就是改名了。好吧,我之前的文章還特地強(qiáng)調(diào)了 defineProps 是復(fù)數(shù)結(jié)尾,帶有 s,而 defineEmit 沒(méi)有,如今,都統(tǒng)一了,都是復(fù)數(shù)形式。從尤大的更新說(shuō)明里看,大約只是一個(gè) typo 更新,對(duì)比原來(lái)的 defineEmit ,目的是使用新的 defineEmits 與標(biāo)準(zhǔn)組件的 emits 命名上更為接近,和 defineProps 也更統(tǒng)一。╮(╯▽╰)╭ 所以用法方面和原來(lái)是沒(méi)什么區(qū)別的:
- // 導(dǎo)入 defineEmits 組件
- import { defineEmits } from "vue";
- // 獲取 emit
- const emit = defineEmits(["say-hi", "chang-name"]);
- // 調(diào)用 emit 打招呼
- emit("say-hi", "Hello!");
- // 調(diào)用 emit 改名
- emit("chang-name", "Tom");
新增 withDefaults API
說(shuō)完 emits,經(jīng)常與之同時(shí)出現(xiàn)的 props 也有一些變化,本次是帶來(lái)了一個(gè)全新的 withDefaults API,用于輔助 defineProps 來(lái)指定 prop 的默認(rèn)值。在以前的文章我有提及到,當(dāng)你用 TypeScript 編程時(shí),defineProps 有兩種類型指定方式:
1. 通過(guò)構(gòu)造函數(shù)進(jìn)行檢查(傳統(tǒng)方法)
第一種方式是使用 JavaScript 原生構(gòu)造函數(shù)進(jìn)行類型規(guī)定,使用這種方法時(shí),如果你要限制 props 的類型和默認(rèn)值,需要通過(guò)一個(gè) “對(duì)象” 入?yún)?lái)傳遞給 defineProps,比如:
- // 導(dǎo)入 defineProps 組件
- import { defineProps } from "vue";
- // 定義 props
- defineProps({
- name: {
- type: String,
- required: false,
- default: "Petter",
- },
- userInfo: Object,
- tags: Array,
- });
1. 使用類型注解進(jìn)行檢查(TS 專屬)
第二種方式是按照 TS 的書(shū)寫(xiě)習(xí)慣來(lái)定義數(shù)據(jù)類型,這種情況下需要遵循 TypeScript 的類型規(guī)范,比如字符串是 string,而不是 String。
- // 導(dǎo)入 defineProps 組件
- import { defineProps } from "vue";
- // 對(duì)象類型接口
- interface UserInfo {
- id: number;
- age: number;
- }
- // 定義 props
- defineProps<{
- name: string;
- phoneNumber: number;
- userInfo: UserInfo;
- tags: string[];
- }>();import { defineProps, withDefaults } from "vue";
- withDefaults(
- defineProps<{
- size?: number;
- labels?: string[];
- }>(),
- {
- size: 3,
- labels: () => ["default label"],
- }
- );
- import { defineProps, withDefaults } from "vue";
- withDefaults(
- defineProps<{
- size?: number;
- labels?: string[];
- }>(),
- {
- size: 3,
- labels: () => ["default label"],
- }
- );
在此之前,使用第二種方法,是無(wú)法指定默認(rèn)值的(在當(dāng)時(shí)的 RFC 的文檔里也有說(shuō)明無(wú)法指定)。如今,這個(gè)新的 withDefaults API 可以讓你在使用 TS 類型系統(tǒng)時(shí),也可以指定 props 的默認(rèn)值。它接收兩個(gè)入?yún)ⅲ?/p>
可能缺乏一些官方描述,還是看參考用法可能更直觀:
- import { defineProps, withDefaults } from "vue";
- withDefaults(
- defineProps<{
- size?: number;
- labels?: string[];
- }>(),
- {
- size: 3,
- labels: () => ["default label"],
- }
- );
頂級(jí) await 的支持
不必再配合 async 就可以直接使用 await 了,這種情況下,組件的 setup 會(huì)自動(dòng)變成 async setup 。
- <script setup lang="ts">
- const post = await fetch(`/api/post/1`).then((r) => r.json());
- </script>
它轉(zhuǎn)換成標(biāo)準(zhǔn)組件的寫(xiě)法就是:
- <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>
參考資料
以上所有的資料都來(lái)自于尤大在 PR 227 的評(píng)論通告……傳送門(mén):script setup by yyx990803 · Pull Request #227 · vuejs/rfcsgithub.com[3]好隱蔽的說(shuō),而且原來(lái)的 RFC 倉(cāng)庫(kù)的文檔也刪除了,換了新的文檔也是找了好久才翻到新的,本文先根據(jù)尤大的通告做一波簡(jiǎn)單的說(shuō)明,文章首發(fā)在博客,只同步在知乎。Vue3.0 最新動(dòng)態(tài):script-setup 定稿 部分實(shí)驗(yàn)性 API 將棄用 - 程沛權(quán) - 養(yǎng)了三只貓 chengpeiquan.com[4]后續(xù)將會(huì)詳細(xì)更新到 Vue3.0 學(xué)習(xí)教程與實(shí)戰(zhàn)案例[5] 里。