一定要知道的 25 個 Vue 技巧
目錄
- 1. 將 prop 限制為型別列表
- 2. 預設內容和擴充套件點
- 3. 使用引號觀察巢狀值
- 4. 知道何時使用 v-if(以及何時避免使用)
- 5. 單作用域 slot 的簡寫(不需要模板標籤!)
- 6. 有條件地渲染slot
- 6.1 為什麼我們希望能夠有條件地渲染slot呢?
- 7. 如何觀察slot的變化
- 8. 將本地和全域性風格混合在一起
- 9. 覆蓋子元件的樣式——正確的方法
- 10. 用上下文感知元件創造魔法
- 10.1 狀態共享
- 10.2 配置
- 10.3 造型
- 11. 如何使在 之外建立的變數具有響應性?
- 12. 在 v-for 中解構
- 13. 在 Vue 中迴圈一個範圍
- 14. 觀察元件中的任何內容
- 15. 竊取道具型別
- 16. 檢測元素外部(或內部)的點選
- 17. 遞迴槽http://www.cppcns.com
- 18. 元件元資料
- 19. 多檔案單檔案元件
- 20. 可重用元件不是你想的那樣
- 21. 從元件外部呼叫方法
- 22. 觀察陣列和物件
- 23. 與 Vue Router 深度連結
- 24. 模板標籤的另一種用途
- 25. 處理錯誤(和警告)的更好方法
1. 將 prop 限制為型別列表
在 prop
定義中使用 validator
選項,你可以將 prop
限制為一組特定的值:
export default { name: 'Image',props: { src: { type: String,},style: { type: String,validator: s => ['square','rounded'].includes(s) } } };
這個 validator
函式接受一個 prop
,返回 true
或 false
。當你需要比布林值允許的更多選項時,也可以使用它。按鈕型別或警報型別(資訊、成功、危險、警告)是一些比較常見的用途。
2. 預設內容和擴充套件點
Vue 中的slot可以具有預設內容,這使你可以製作更易於使用的元件:
<button class="button" @click="$emit('click')"> <slot> <!-- 如果沒有提供slot則使用 --> Click me </slot> </button>
基本上你可以獲取元件的任何部分,將其包裝在一個slot
<template> <button class="button" @click="$emit('click')"> <!-- 一開始在 slot 標籤中新增什麼都不做 --> <!-- 我們可以通過向 slot 提供內容來覆蓋它 --> <slot> <div class="formatting"> {{ text }} </div> </slot> </button> </template>
現在你可以以多種不同的方式使用該元件。
簡單的預設方式或你自己的自定義方式:
<!-- 使用元件的預設功能 --> <ButtonWithExtensionPoint text="Formatted text" /> <!-- 使用擴充套件點建立自定義行為 --> <ButtonWithExtensionPoint> <div class="different-formatting"> 在這裡做一些不同的事情 </div> </ButtonWithExtensionPoint>
3. 使用引號觀察巢狀值
你可能不知道這一點:只需使用引號就可以輕鬆地直接檢視巢狀值:
watch { '$route.query.id'() { // ... } }
這對於處理深度巢狀的物件非常有用
4. 知道何時使用 v-if(以及何時避免使用)
有時不使用v-if
,使用v-show會
更高效:
<ComplicatedChart v-show="chartEnabled" />
當v-if
開啟和關閉時,它將完全建立和銷燬元素。v-show
不同的是將建立元素並將其留在那裡,通過將其樣式設定為display: none
來隱藏它。
如果你需要切換的元件渲染起來代價比較高,那麼這樣做會更有效率。另一方面,如果你不需要立即使用那個元件,可以使用v-if以便它跳過渲染它並更快地載入頁面。
5. 單作用域 slot 的簡寫(不需要模板標籤!)
Scoped slot
比較有趣,但為了使用它們,你也必須使用很多template
標籤。
然而有一個速記可以讓我們擺脫它,但前提是我們使用單個作用域slot
。
可以不用這樣寫:
<DataTable> <template #header="tableAttributes"> <TableHeader v-bind="tableAttributes" /> </template> </DataTable>
我們可以這樣寫:
<DataTable #header="tableAttributes"> <TableHeader v-bind="tableAttributes" /> </DataTable>
這樣更簡單、直接。
6. 有條件地渲染slot
每個 Vue
元件都有一個特殊的 $slots
物件,其中包含所有slot
。 預設slot
具有預設鍵,名字命名的slot都使用它們的名稱作為鍵:
const $slots = { default: <default slot>,icon: <icon slot>,button: <button slot>,};
但是這個$slots
物件只有應用於元件的slot
,而不是每個定義的slot
。
以這個定義了幾個slot的元件為例,包括幾個命名的slot:
<!-- Slots.vue --> <template> <div> <h2>這裡是一些slots</h2> <slot /> <slot name="second" /> <slot name="third" /> </div> </template>
如果我們只對元件應用一個slot,則只有該slot會出現在我們的$slots物件中:
<template> <Slots> <template #second> 這將應用於第二個slot </template> </Slots> </template> $slots = { second: <vnode> }
我們可以在我們的元件中使用它來檢測哪些slot
已應用於元件,
例如:通過隱藏slot
的包裝元素:\
<template> <div> <h2>一個包裹的slot</h2> <div v-if="$slots.default" class="styles"> <slot /> </div> </div> </template>
現在div
,應用樣式的包裝器只有在我們實際用某些東西填充該slot
時才會呈現。
如果我們不使用v-if
,div
如果我們沒有slot
,我們最終會得到一個空的和不必要的。根據所div具有的樣式,這可能會弄亂我們的佈局並使事情看起來很奇怪。
6.1 為什麼我們希望能夠有條件地渲染slot呢?
使用條件slot主要有以下三個原因:
- 使用
wrapper div
來新增預設樣式時 slot
是空的- 當我們將預設內容與巢狀
slot
組合
例如,當我們新增預設樣式時,我們會在slot
周圍新增一個div:
<template> <div> <h2>This is a pretty great component,amirite?</h2> <div class="default-styling"> <slot > </div> <button @click="$emit('click')">Click me!</button> </div> </template>
但是,如果父元件沒有將內容應用到該slot,我們最終會在頁面div上呈現一個空的:\
<div> <h2>這是一個非常棒的元件</h2> <div class="default-styling"> <!-- slot中沒有內容,但仍會呈現此 div--> </div> <button @click="$emit('click')">Click me!</button> </div>
v-if在包裝上新增它div可以解決問題。沒有應用到slot的內容?像這樣:\
<div> <h2>這是一個非常棒的元件</h2> <button @click="$emit('click')">Click me!</button> </div>
7. 如何觀察slot的變化
有時我們需要知道slot內的內容什麼時候發生了變化:
<!-- 可惜這個活動不存在 --> <slot @change="update" />
可惜 Vue 沒有內建的方法來檢測這一點,使用突變觀察器一種非常簡潔的方法:
export default { mounted() { // 當事情發生變化時呼叫`update` const observer = new MutationObserver(this.update); // 觀察這個元件的變化 observer.observe(this.$el,{ childList: true,subtree: true }); } };
8. 將本地和全域性風格混合在一起
通常在使用樣式時我們希望它們被限定為單個元件:
<style scoped> .component { background: green; } </style>
如果你需要還可以新增一個非作用域樣式塊來新增全域性樣式:
<style> /*全域性應用*/ .component p { margin-bottom: 16px; } </style> <style scoped> /*範圍限定於此特定元件*/ .component { background: green; } </style>
9. 覆蓋子元件的樣式——正確的方法
Scoped
比較容易保持整潔,並且不會意外地將樣式滲入應用程式的其他部分。但有時你需要覆蓋子元件的樣式,並突破該範圍。
Vue 有一個deep專門用於此的選擇器:
<style scoped> /* 覆蓋子元件的 CSS,同時保持樣式範圍*/ .my-component >>> .child-component { font-size: 24px; } </style>
注意:如果你使用的是 SCSS 之類的 CSS 前處理器,則可能需要改用/deep/。
10. 用上下文感知元件創造魔法
上下文感知元件是“神奇的”——它們可以自動適應周圍發生的事情,處理邊緣情況,狀態共享等等。有 3 種主要型別的上下文感知元件,但是我覺得配置是其中最有趣的一種。
10.1 狀態共享
當你將一個大元件分解成多個小元件時,它們通常仍然需要共享狀態。你可以“在幕後”實現這一點,而不是將這項工作推給使用元件的人。
可以將一個Dropdown
元件分解為Select
和Option
元件以提供更大的靈活性。但是為了更容易使用,Select和Option元件彼此共享selected
狀態:
<!-- 為簡單起見用作單個元件 --> <Dropdown v-model="selected" :options="[]" /> <!-- 拆分以獲得更大的靈活性 --> <Select v-model="selected"> <Option value="mustard">Mustard</Option> <Option value="ketchup">Ketchup</Option> <div class="relish-wrapper"> <Option value="relish">Relish</Option> </div> </Select>
10.2 配置
有時需要根據應用程式其餘部分的情況更改元件的行為。這樣做通常是為了自動處理邊緣情況,否則會很麻煩。Popup或者Tooltip
應該重新定位自己,這樣它就不會溢位頁面。但是,如果該元件位於 modal 內部,則它應該重新定位自身,以免溢位modal
。如果Tooltip
知道它何時在模態內,這可以自動完成。
10.3 造型
當你建立了上下文感知 CSS
,根據父元素或兄弟元素中發生的情況應用不同的樣式。
.statistic { color: black; font-size: 24px; font-weight: bold; } /* 在彼此相鄰的統計資料之間進行一些分離*/ .statistic + .statistic { margin-left: 10px; }
CSS 中變數讓我們更進一步允許我們在頁面的不同部分設定不同的值。
11. 如何使在 Vue 之外建立的變數具有響應性?
如果你從 Vue
外部獲得一個變數,那麼能夠使其具有響應性就很好。這樣你就可以在計算道具、觀察者和其他任何地方使用它,它就像 Vue
中的任何其他狀態一樣工作。
當你正在使用 options API,你只需將它放在data元件的部分中:
const externalVariable = getValue(); export default { data() { return { reactiveVariable: externalVariable,}; } };
當你在 Vue 3
中使用組合 API
,則可以使用ref
或reactive
這樣:
import { ref } from 'vue'; // 可以完全在 Vue 元件之外完成 const externalVariable = getValue(); const reactiveVariable = ref(externalVariable); // 使用 .value 訪問 console.log(reactiveVariable.value); 使用reactive來代替:\ import { reactive } from 'vue'; // 可以完全在 Vue 元件之外完成 const externalVariable = getValue(); // Reactive 僅適用於物件和陣列 const anotherReactiveVariable = reactive(externalVariable); // 直接訪問 console.log(anotherReactiveVariable);
如果你仍在使用 Vue 2
(就像我們中的許多人一樣),你可以使用observable
而不是reactive
獲得完全相同的結果。
12. 在 v-for 中解構
你知道你可以在 v-for
中解構嗎?
<li v-for="{ name,id } in users" :key="id" > {{ name }} </li>
眾所周知,你可以使用這樣的元組從 v-for 中獲取索引:
<li v-for="(value,key) in [ 'Hai Yong','Frozen','Web Beginner' ]"> {{ index + 1 }} - {{ value }} </li>
使用物件時,你還可以抓住key:
<li v-for="(value,key) in { name: 'Hai Yong',released: 2021,director: 'A blogger',}"> {{ key }}: {{ value }} </li>
也可以結合這兩種方法,獲取屬性的鍵和索引:
<li v-for="(value,key,index) in { name: 'Hai Yong',}"> #{{ index + 1 }}. {{ key }}: {{ value }} </li>
13. 在 Vue 中迴圈一個範圍
v-for指令允許我們遍歷一個數組,但它也讓我們遍歷一個範圍:
<template> <ul> <li v-for="n in 5">專案#{{ n }}</li> </ul> </template>
顯示效果:
專案#1
專案#2
專案#3
專案#4
專案#5
當我們使用v-for
範圍時,它將從 1 開始並以我們指定的數字結束。
14. 觀察元件中的任何內容
你的元件中的任何響應都可以被觀察到:
export default { computed: { someComputedProperty() { // 更新計算道具 },watch: { someComputedProperty() { // 當計算的 prop 更新時做一些事情 } } };
你可以看:
- 計算道具
- 道具
- 巢狀值
如果你使用組合 API,只要它是一個ref
或reactive
物件就可以監視任何值,。
15. 竊取道具型別
從子元件複製 prop
型別,只是為了在父元件中使用它們。但竊取這些道具型別比只是複製它們要好得多。
例如,我們Icon在這個元件中使用了一個元件:
<template> <div> <h2>{{ heading }}</h2> <Icon :type="iconType" :size="iconSize" :colour="iconColour" /> </div> </template>
為了讓它工作,我們需要新增正確的道具型別,從Icon元件中複製:\
import Icon from './Icon'; export default { components: { Icon },props: { iconType: { type: String,required: true,iconSize: { type: String,default: 'medium',validator: size => [ 'small','medium','large','x-lahttp://www.cppcns.comrge' ].includes(size),iconColour: { type: String,default: 'black',heading: { type: String,};
當Icon
元件的 prop
型別更新時,你肯定你會忘記回到這個元件並更新它們。隨著時間的推移,隨著該元件的 prop
型別開始偏離元件中的 prop
型別,將引入錯誤Icon
。
所以這就是為什麼我們會竊取它們:
import Icon from './Icon'; export default { components: { Icon },props: { ...Icon.props,};
除了在我們的示例中,我們在每個道具名稱的開頭添加了“icon”
所以我們必須做一些額外的工作來實現這一點:
import Icon from './Icon'; const iconProps = {}; // Do some processing beforehand Object.entries(Icon.props).forEach((key,val) => { iconProps[`icon${key[0].toUpperCase()}${key.substring(1)}`] = val; }); export default { components: { Icon },props: { ...iconProps,};
現在,如果Icon
元件中的 prop
型別被修改,我們的元件將保持最新。
但是如果在Icon
元件中新增或刪除了一個 prop
型別呢?為了涵蓋這些情況,我們可以使用v-bind
計算道具來保持動態。
16. 檢測元素外部(或內部)的點選
有時我們需要檢測點選是發生在特定元素el的內部還是外部。這是我們通常使用的方法:
window.addEventListener('mousedown',e => { // 獲取被點選的元素 const clickedEl = e.target; // `el` 是你正在檢測外部點選的元素 if (el.contains(clickedEl)) { // 單擊“el”內部 } else { // 在`el`之外點選 } });
17. 遞迴槽
我們是否可以v-for
只使用模板來制作一個元件?在此過程中,我發現瞭如何遞迴地使用slot
。
這是元件的樣子:
<!-- VFor.vue --> <template> <div> <!-- 渲染第一項 --> {{ list[0] }} <!-- 如果我們有更多的專案可以繼續,但需要離開我們剛剛渲染的專案 --> <v-for v-if="list.length > 1" :list="list.slice(1)" /> </div> </template>
如果你想用作用域slot來做這件事——為什麼不呢?!— 只需要進行一些調整:<
template>
<div>
<!-- 將專案傳遞到要渲染的slot中 -->
<slot v-bind:item="list[0]">
<!-- Default -->
{{ list[0] }}客棧
</slot>
<v-for
v-if="list.length > 1"
:list="list.slice(1)"
>
<!-- 遞歸向下傳遞作用域slot -->
<template v-slot="{ item }">
<slot v-bind:item="item" />
</template>
</v-for>
</div>
</template>
以下是該元件的使用方法:
<template> <div> <!-- 常規列表 --> <v-for :list="list" /> <!-- 帶有粗體專案的列表 --> <v-for :list="list"> <template v-slot="{ item }"> <strong>{{ item }}</strong> </template> </v-for> </div> </template>
18. 元件元資料
並不是你新增到元件的每一點資訊都是狀態。有時你需要新增一些元資料來為其他元件提供更多資訊。
例如:如果你要為 Google Analytics 等分析儀表板構建一堆不同的小部件:
如果你希望佈局知道每個小部件應占用多少列,你可以將其作為元資料直接新增到元件上:
export default { name: 'LiveUsersWidget',// 👇 只需將其新增為額外屬性 columns: 3,props: { // ... },data() { return { //... }; },};
你會發現此元資料是元件上的一個屬性:
import LiveUsersWidget from './LiveUsersWidget.vue'; const { columns } = LiveUsersWidget;
你還可以通過特殊$options屬性從元件內部訪問元資料:
export default { name: 'LiveUsersWidget',columns: 3,created() { // 👇 `$options` 包含元件的所有元資料 console.log(`Using ${this.$options.metadata} columns`); },};
請記住,此元資料對於元件的每個例項都是相同的,並且不是響應式的。
其他用途包括(但不限於):
- 保留各個元件的版本號
- 用於構建工具的自定義標誌以區別對待元件
- 向元件新增自定義功能,超出計算道具、資料、觀察者等。
19. 多檔案單檔案元件
這是 SFC 的一個鮮為人知的功能。你可以像使用常規 HTML 檔案一樣匯入檔案:
<!-- "single" 檔案元件 --> <template src="./template.html"></template> <script src="./script."></script> <style scoped src="./styles.css"></style>
如果你需要共享樣式、文件或其他任何內容這會很方便。也非常適合那些因滾動而磨損手指的超長元件檔案
20. 可重用元件不是你想的那樣
可重用元件不一定是大的或複雜的東西,我經常使小而短的元件可重複使用。因為我不會到處重寫這段程式碼,更新它變得容易得多,而且我可以確保每個OverflowMenu
看起來和工作完全一樣——因為它們是一樣的!
<!-- OverflowMenu.vue --> <template> <Menu> <!-- 新增自定義按鈕來觸發我們的選單 --> <template #button v-slot="bind"> <!-- 使用 bind 傳遞點選處理程式、a11y 屬性等。 --> <Button v-bind="bind"> <!-- 使用我們自己的“...”圖示,此按鈕沒有文字 --> <template #icon> <svg src="./ellipsis.svg" /> </template> </Button> </template> </Menu> </template>
這裡我們使用了一個Menu
元件,但是在觸發它開啟的按鈕上添加了一個“...”(省略號)圖示。可能並不不值得用它來製作可重用的元件,因為它只有幾行。每次我們想使用Menu這樣的時候,我們不能只新增圖示嗎?但這OverflowMenu
將使用數十次,現在如果我們想要更新圖示或其行為,我們可以很容易地做到。而且使用起來也簡單多了!
<template> <OverflowMenu :menu-items="items" @click="handleMenuClick" /> </template>
21. 從元件外部呼叫方法
你可以通過給它一個從元件外部呼叫方法ref:
<!-- Parent.vue --> <template> <ChildComponent ref="child" /> </template> // Parent.vue 中的某個地方 this.$refs.child.method();
通常,我們使用道具和事件在元件之間進行通訊。道具被髮送到子元件,事件被髮送回父元件。
<template> <ChildComponent :tell-me-what-to-do="someInstructions" @something-happened="hereIWillHelpYouWithThat" /> </template>
但有時你可能會遇到需要父元件觸發子元件中的方法的情況。這是隻有向下傳遞道具不起作用的地方。
可以向下傳遞一個布林值並讓子元件監視它:
<!-- Parent.vue --> <template> <ChildComponent :trigger="shouldCallMethod" /> </template> // Child.vue export default { props: ['trigger'],watch: { shouldCallMethod(newVal) { if (newVal) { // 當觸發器設定為 `true` 時呼叫該方法 this.method(); } } } }
這工作正常,但僅限於第一次呼叫。如果你需要多次觸發此操作,則必須清理並重置狀態。然後邏輯看起來像這樣:
Parent
元件傳遞true
給triggerprop
Watch
被觸發,Child
元件呼叫方法Child
元件發出一個事件告訴Parent
元件該方法已成功觸發Parent
元件重置trigger
回false
,因此我們可以再次執行此操作
相反,如果我們在子元件上設定ref ,我們可以直接呼叫該方法:
<!-- Parent.vue --> <template> <ChildComponent ref="child" /> </template> // Parent.vue 中的某個地方 this.$refs.child.method();
我們打破了“props down,events up
”規則,打破了封裝,但它更清晰、更容易理解值得這樣做!
有時,“最佳”解決方案最終會成為最差的解決方案。
22. 觀察陣列和物件
使用觀察者最棘手的部分是有時候它似乎不能正確觸發。一般都是因為你試圖檢視一個數組或一個物件,但沒有設定deep
為true
:
export default { name: 'ColourChange',props: { colours: { type: Array,watch: { // 使用物件語法而不僅僅是方法 colours: { // 這將讓 Vue 知道檢視陣列內部 deep: true,// 我們必須將我們的方法移動到處理程式欄位 handler() console.log('顏色列表已更改!'); } } } }
使用 Vue 3 的反應式 API 看起來像這樣:
watch( colours,() => { console.log('顏色列表已更改!'); },{ deep: true,} );
如果你想了解更多資訊,可以參閱Vue 3和Vue 2的文件。
23. 與 Vue Router 深度連結
你可以在 URL 中儲存(一些)狀態,允許你直接跳轉到頁面上的特定狀態。
比如你可以載入一個已選擇日期範圍過濾器的頁面:
someurl.com/edit?date-range=last-week
這對於使用者可能共享大量連結的應用程式部分、伺服器呈現的應用程式或在兩個獨立應用程式之間傳遞比常規連結通常提供的資訊更多的資訊非常有用。
你可以儲存過濾器、搜尋值、模式是開啟還是關閉,或者我們滾動到的列表中的位置——非常適合無限分頁。
使用vue-router這樣的方式獲取查詢(這也適用於 Nuxt 和 Vuepress 等大多數 Vue 框架):
const dateRange = this.$route.query.dateRange;
要更改它,我們使用RouterLink元件並更新query:
<RouterLink :to="{ query: { dateRange: newDateRange } }">
24程式設計客棧. 模板標籤的另一種用途
該template
標籤可以在模板內的任何地方使用,以更好地組織程式碼。
我喜歡用它來簡化v-if邏輯,有時v-for也是。
在這個例子中,我們有幾個元素都使用相同的v-if條件:\
<template> <div class="card"> <img src="imgPath" /> <h3> {{ title }} </h3> <h4 v-if="expanded"> {{ subheading }} </h4> <div v-if="expanded" class="card-content"> <slot/> </div> <SocialShare v-if="expanded" /> </div> </template>
這有點笨拙,一開始並不明顯,一堆這些元素被顯示和隱藏在一起。在更大、更復雜的元件上,這可能是更糟糕的情況!
但我們可以解決這個問題。
我們可以使用template標籤對這些元素進行分組,並將其提升v-if到template標籤本身:\
<template> <div class="card"> <img src="imgPath" /> <h3> {{ title }} </h3> <template v-if="expanded"> <h4> {{ subheading }} </h4> <div class="card-content"> <slot/> </div> <SocialShare/> </template> </div> </template>
25. 處理錯誤(和警告)的更好方法
你可以為 Vue 中的錯誤和警告提供自定義處理程式:
// Vue 3 const app = createApp(App); app.config.errorHandler = (err) => { alert(err); }; // Vue 2 Vue.config.errorHandler = (err) => { alert(err); };
Bugsnag
和 Rollbar
等錯誤跟蹤服務掛接到這些處理程式中以記錄錯誤,但你也可以使用它們來更優雅地處理錯誤以獲得更好的使用者體驗。
例如,如果錯誤未得到處理,應用程式不僅會崩潰,還可以顯示整頁錯誤螢幕並讓使用者重新整理或嘗試其他操作。
在 Vue 3 中,錯誤處理程式僅適用於模板和觀察程式錯誤,但 Vue 2 錯誤處理程式幾乎可以捕獲所有內容。兩個版本中的警告處理程式僅適用於開發。
到此這篇關於一定要知道的 25 個 Vue 技巧的文章就介紹到這了,更多相關Vue 技巧內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!