前言 React在很早之前的版本中加了useId,用於生成唯一ID。在Vue3.5版本中,終於也有了期待已久的useId。這篇文章來帶你搞清楚useId有哪些應用場景,以及他是如何實現的。 關註公眾號:【前端歐陽】,給自己一個進階vue的機會 useId的作用 他的作用也是生成唯一ID,同一個Vue ...
前言
React在很早之前的版本中加了useId
,用於生成唯一ID。在Vue3.5版本中,終於也有了期待已久的useId
。這篇文章來帶你搞清楚useId
有哪些應用場景,以及他是如何實現的。
關註公眾號:【前端歐陽】,給自己一個進階vue的機會
useId的作用
他的作用也是生成唯一ID,同一個Vue應用裡面每次調用useId
生成的ID都不同。
使用方法也很簡單,代碼如下:
<script setup lang="ts">
import { useId } from 'vue'
const id0 = useId();
console.log(id0); // v-0
const id1 = useId();
console.log(id1); // v-1
const id2 = useId();
console.log(id2); // v-2
</script>
看到這裡有的小伙伴會有問題,你上面的例子都是在同一組件裡面調用useId
。那如果我在不同的組件裡面分別調用useId
,這些組件生成的ID還是唯一的嗎?
比如下麵這個例子,父組件代碼如下:
<template>
<div>
<UseIdChild1 />
<UseIdChild2 />
</div>
</template>
子組件UseIdChild1
代碼如下:
<script setup lang="ts">
import { useId } from "vue";
const id0 = useId();
const id1 = useId();
console.log(id0);
console.log(id1);
</script>
子組件UseIdChild2
代碼如下:
<script setup lang="ts">
import { useId } from "vue";
const id0 = useId();
const id1 = useId();
console.log(id0);
console.log(id1);
</script>
從上面的代碼可以看到兩個子組件裡面的代碼實際是一樣的,那你猜猜子組件UseIdChild1
中列印的id0
、id1
和子組件UseIdChild2
中列印的id0
、id1
是不是一樣的呢?
答案是:不一樣。
UseIdChild1
中列印的id0
的值為v-0
,id1
的值為v-1
。
UseIdChild2
中列印的id0
的值為v-2
,id1
的值為v-3
。
通過上面的這兩個例子,我想你應該猜出來useId
函數生成唯一ID的規律:“字元串v-
加上自增的數字
”。
其中的首碼v
可以通過app.config.idPrefix
進行自定義。
有的時候我們要渲染一個列表數據,需要列表的每一個item中有一個唯一的id,此時我們就可以使用useId
去給每個item生成唯一的id。
這個是最簡單的使用場景,接下來我們看看在服務端渲染(SSR)中useId
的使用場景。
在服務端渲染(SSR)中使用useId
首先我們要搞清楚服務端渲染時有哪些痛點?
我們來看一個服務端渲染的例子,代碼如下:
<template>
<div>
<label :htmlFor="id">Do you like Vue3.5?</label>
<input type="checkbox" name="vue3.5" :id="id" />
</div>
</template>
<script setup lang="ts">
const id = Math.random();
</script>
上面的代碼如果是跑在客戶端渲染時沒有任何問題,但是如果在服務端渲染時就會有警告了。如下圖:
上面的警告意思是,在服務端時生成的id的值為0.4050816845323888
。但是在客戶端時生成的id的值卻是0.4746900241123273
,這兩次生成的id值不同,所以才會出現警告。
可能有的小伙伴會有疑問,為什麼在服務端生成一次id後,在客戶端又去生成一次id呢?
為瞭解答上面這個問題,我們先來瞭解一下服務端渲染(SSR)的流程:
-
首先會在服務端(Node.js環境)發起介面請求,從後端拿到頁面渲染需要的數據。
-
根據拿到的數據去生成頁面的HTML字元串,此時就會在服務端生成一次id,這一步叫
dehydrate
(脫水)。 -
將服務端生成的HTML字元串發送給客戶端(瀏覽器)。
-
瀏覽器拿到了服務端生成的HTML字元串可以將其作為首屏內容,直接渲染到頁面上。但是此時click之類的事件還沒綁定在DOM上,所以在客戶端需要再渲染一次。就會在客戶端再次生成一次id,這一步叫
hydrate
(註水)。
由於我們這裡是使用Math.random()
去生成的id,在服務端和客戶端每次執行Math.random()
生成的id值當然就不同了,所以才會出現上面的警告。
有了useId
後,解決上面的警告就很簡單了,只需要把Math.random()
改成useId()
就可以了。代碼如下:
<template>
<div>
<label :htmlFor="id">Do you like Vue3.5?</label>
<input type="checkbox" name="vue3.5" :id="id" />
</div>
</template>
<script setup lang="ts">
const id = useId();
</script>
因為useId
在服務端渲染時會生成v-0
,在客戶端渲染時依然還是v-0
。
可能有的小伙伴有疑問,前面不是講的useId
每執行一次會給後面的數字+1
。那麼服務端執行一次後,再去客戶端執行一次,講道理應該生成的ID不一樣吧??
useId
生成的“自增數字部分”是維護在vue實例上面的ids
屬性上,服務端渲染時會在Node.js端生成一個vue實例。但是客戶端渲染時又會在瀏覽器中重新生成一個新的vue實例,此時vue實例上的ids
屬性也會被重置,所以在服務端和客戶端執行useId
生成的值是一樣的。
useId是如何實現的
我們來看看useId
的源碼,非常簡單!!簡化後的代碼如下:
function useId(): string {
const i = getCurrentInstance()
if (i) {
return (i.appContext.config.idPrefix || 'v') + '-' + i.ids[0] + i.ids[1]++
}
return ''
}
這個getCurrentInstance
函數我想很多同學都比較熟悉,他的作用是返回當前vue實例。
給useId
打個斷點,來看一下當前vue實例i
,如下圖:
從上圖中可以看到vue實例上的ids
屬性是一個數組,數組的第一項是空字元串,第二項是數字0,第三項也是數字0
我們再來看看useId
是如何返回唯一ID的,如下:
return (i.appContext.config.idPrefix || 'v') + '-' + i.ids[0] + i.ids[1]++
生成的唯一ID由三部分組成:
-
第一部分為首碼,從
app.config.idPrefix
中取的。如果沒有配置,那麼就是字元串v
。 -
第二部分為寫死的字元串
-
。 -
第三部分為
i.ids[0] + i.ids[1]++
,其中ids[0]
的值為空字元串。i.ids[1]++
這裡是先取值,然後再執行++
,所以第三部分的值為數字0
。再次調用useId
時,由於上一次執行過一次++
了。此時的數字值為1
,並且再次執行++
。
看到這裡有的小伙伴又有疑問了,這裡看上去ids
屬性是存在vue實例上面的。每個vue組件都有一個vue實例,那麼每個組件都有各自維護的ids
屬性。
那你前面的那個例子中UseIdChild1
子組件和UseIdChild2
子組件中各自生成的id0
的值應該是一樣的v-0
吧,為什麼一個是v-0
,另外一個是v-2
呢?
答案其實很簡單,所有vue實例上面的ids
屬性都是同一個數組,指向的是頂層組件實例上面的那個ids
屬性。創建vue實例的源碼如下圖:
從上圖中可以看到當沒有父組件時,也就是最頂層的vue組件實例,就將其ids
屬性設置為數組['', 0, 0]
。
當生成子組件的vue實例時,由於父組件上面有ids
屬性,所以就用父組件上面的了。指針都是指向的是最頂層vue實例上面的ids
屬性,所以才會說所有的vue組件實例上面的ids
屬性都是指向同一個數組。
這也就是為什麼UseIdChild1
子組件和UseIdChild2
子組件中各自生成的id0
的值一個是v-0
,另外一個是v-2
。
總結
Vue3.5新增的useId
可以在Vue應用內生成唯一的ID,我們可以使用useId
給列表數據中的每一個item生成一個唯一的id。
並且在服務端渲染(SSR)場景中,服務端和客戶端執行useId
生成的是同一個ID。利用這個特點我們可以使用useId
解決一些在 SSR 應用中,伺服器端和客戶端生成的 ID 不一致導致的警告。
最後我們講了useId
的實現也很簡單,生成的ID分為三部分:
-
第一部分為首碼:
app.config.idPrefix
,如果沒有配置,那麼就是字元串v
。 -
第二部分字元串:
-
。 -
第三部分的值為一個自增的數字,存在vue實例上面的
ids
屬性,所有的vue實例上面的ids
屬性都是指向同一個數組。這也就是為什麼說useId
可以在Vue應用內
生成唯一的ID,而不是在Vue組件內
生成唯一的ID。
關註公眾號:【前端歐陽】,給自己一個進階vue的機會
另外歐陽寫了一本開源電子書vue3編譯原理揭秘,看完這本書可以讓你對vue編譯的認知有質的提升。這本書初、中級前端能看懂,完全免費,只求一個star。