1. 程式人生 > 程式設計 >Vue 技巧之控制父類的 slot

Vue 技巧之控制父類的 slot

首先來思考一個問題:是否有一種方法可以從子元件填充父元件的插槽?

最近一位同事問我這個問題,答案很簡單:可以的。但我的解決方案可能和你想的完全不一樣,這是涉及一個棘手的Vue架構問題,但也是一個非常有趣的問題。

為什麼會有這個問題

Vue 技巧之控制父類的 slot

在我們的應用程式中,我們有一個頂部欄,其中包含不同的按鈕、搜尋欄和其他一些控制元件。根據每個人所在的頁面,它可能略有不同,因此我們需要一種基於每個頁面配置它的方法。

Vue 技巧之控制父類的 slot

為此,我們希望每個頁面都能夠配置操作欄。看起來很簡單,但這裡有個問題

這個頂部欄(我們稱之為ActionBar)實際上是我們的主佈局的一部分,結構如下:

<template>
 <div>
  <FullPageError />
  <ActionBar />
  <App />
 </div>
</template>

根據你所在的頁面/路線動態注入App的位置。

我們可以使用ActionBar上的一些插槽來配置它。 但是,我們如何從App元件中控制這些插槽?

定義問題

首先,最好是儘可能清楚地知道我們要解決的問題。

我們來看一個具有一個子元件和一個插槽的元件:

// Parent.vue
<template>
 <div>
  <Child />
  <slot />
 </div>
</template>

我們可以這樣填充Parent的插槽:

// App.vue
<template>
 <Parent>
  <p>This content goes into the slot</p>
 </Parent>
</template>

這裡沒什麼特別的。。。

填充子元件的插槽很容易,這也是使用插槽的最常見方式。

但是,有沒有一種方法可以控制從Child元件內部進入Parent元件slot的內容呢?

換種說法:我們可以讓子元件填充父元件的插槽嗎?來看看我想到的第一個解決方案。

向下使用 props,向上使用 event

資料流經元件樹的唯一途徑是使用props。 而向上通訊的方法是使用事件。這意味著,如果要讓子元件與父元件進行通訊,我們需要使用事件來實現。

因此,我們將使用事件來將內容傳遞到ActionBars槽中

import SlotContent from './SlotContent';

export default {
 name: 'Application',created() {
  // As soon as this component is created we'll emit our events
  this.$emit('slot-content',SlotContent);
 }
};

我們將要放入插槽中的所有內容打包到SlotContent元件中。 一旦建立了應用程式元件,我們就會發出slot-content事件,並傳遞我們要使用的元件。

我們的元件結構如下:

<template>
 <div>
  <FullPageError />
  <ActionBar>
   <Component :is="slotContent" />
  </ActionBar>
  <App @slot-content="component => slotContent = component" />
 </div>
</template>

監聽該事件,並將slotContent設定為我們的App元件傳送給我們的任何內容。 然後,使用內建的Component,就可以動態地渲染該元件。

但是,通過事件傳遞元件感覺很奇怪,並非是主流的做法。幸運的是,還有一種方法可以完全避免使用事件。

使用 $options

由於Vue元件只是 JS 物件,因此我們可以向它們新增所需的任何屬性。無需使用事件傳遞插槽內容,我們只需將其作為欄位新增到元件中即可:

 // App.vue
import SlotContent from './SlotContent';

export default {
 name: 'Application',slotContent: SlotContent,props: { /***/ },computed: { /***/ },};

在主頁中通過 App.slotContent 獲取對應的元件

<template>
 <div>
  <FullPageError />
  <ActionBar>
   <Component :is="slotContent" />
  </ActionBar>
  <App />
 </div>
</template>

import App from './App';
import FullPageError from './FullPageError';
import ActionBar from './ActionBar';

export default {
 name: 'Scaffold',components: {
  App,FullPageError,ActionBar,}
 data() {
  return {
   slotContent: App.slotContent,}
 },};

這更像是靜態配置,更美觀、更簡潔,但這仍然是不對的。

理想情況下,我們不會在程式碼中混合使用正規化,所有操作應該都是以宣告方式完成。

但是在這裡,我們沒有將我們的元件組合在一起,而是將它們作為 JS 物件傳遞。如果我們能以正常的Vue方式把我們想要的寫在插槽裡就好了。

考慮 Portal(傳送門)

Vue 中的 Portal 技術 在 Vue 專案中,我們使用模板來宣告 dom

巢狀關係,然而有時候一些元件需要脫離固定的層級關係,不再受制與層疊上下文,比如說 Modal 和 Dialog
這種元件就希望能夠脫離當前模板所在的層疊上下文。

在 Vue 中有兩種方式來實現這種效果,一種是使用指令,操作真實 dom,使用熟知的 dom 操作方法將指令所在的元素 append
到另外一個 dom 節點上去。另一種方式就是定義一套元件,將元件內的 vnode 轉移到另外一個元件中去,然後各自渲染。

它們的工作方式和你想象的完全一樣。你可以把任何東西從一個地方傳送到另一個地方。在我們的例子中,我們將元素從DOM中的一個位置“傳送”到另一個位置。

無論元件樹如何顯示,我們都可以控制組件在DOM中的顯示位置。

例如,假設我們想要填充一個modal。但是我們的modal必須在根頁面處渲染,這樣我們才能正確地覆蓋它。首先,我們要在modal中指定我們想要的:

<template>
 <div>
  <!-- Other components -->
  <Portal to="modal">
   Rendered in the modal.
  </Portal>
 </div>
</template>

然後,在我們的modal元件中,我們將擁有另一個將內容渲染出來的 portal:

<template>
 <div>
  <h1>Modal</h1>
  <Portal from="modal" />
 </div>
</template>

這是一項改進,因為現在我們實際上是在編寫HTML,而不僅僅是傳遞物件。 它更具宣告性,更容易檢視應用程式中發生的事情。

由於 portal 在背後執行一些操作以在不同位置渲染元素,因此它完全打破了DOM渲染在Vue中工作方式的模型。 看起來您正在正常渲染元素,但根本無法正常工作,這可能會引起很多混亂和沮喪。

還有一個很大的問題,稍後我們會講到。

提升狀態

“提升狀態”是指將狀態從子元件移動到父元件或祖父元件,將它向上移動到元件樹中。

這可能對應用程式的體系結構產生較大的影響。對於我們的目的,這會是更簡單的解決方案。

這裡的“狀態”是我們試圖傳遞到ActionBar元件插槽中的內容。但是該狀態包含在Page元件中,我們不能真正將 page 特定的邏輯移到layout元件中。 我們的狀態必須保留在我們正在動態渲染的Page元件內。

因此,我們必須提升整個Page元件才能提升狀態。當前,我們的Page元件是Layout元件的子元件:

<template>
 <div>
  <FullPageError />
  <ActionBar />
  <Page />
 </div>
</template>

解除它需要我們將其翻轉,並使Layout元件成為Page元件的子元件。 我們的Page元件看起來像這樣:

<template>
 <Layout>
  <!-- Page-specific content -->
 </Layout>
</template>

現在,我們的Layout元件將看起來像這樣,我們可以在其中使用插槽插入頁面內容:

<template>
 <div>
  <FullPageError />
  <ActionBar />
  <slot />
 </div>
</template>

但這還不能讓我們自定義任何內容。 我們必須在Layout元件中新增一些命名的插槽,以便我們可以傳遞應放置在ActionBar中的內容。

最簡單的方法是使用一個插槽來完全替代ActionBar元件:

<template>
 <div>
  <FullPageError />
  <slot name="actionbar">
   <ActionBar />
  </slot>
  <slot />
 </div>
</template>

這樣,如果你不指定“actionbar”插槽,預設使用ActionBar元件。 但我們可以使用自己的自定義ActionBar配置覆蓋此插槽:

<template>
 <Layout>
  <template #actionbar>
   <ActionBar>
    <!-- Custom content that goes into the action bar -->
   </ActionBar>
  </template>
  <!-- Page-specific content -->
 </Layout>
</template>

對我來說,這是一種理想的處理方式,但是它確實需要我們重構頁面的佈局方式。 對於介面複雜點的,這可能是一項艱鉅的任務。

簡化一下

當我們第一次定義問題時:

我們可以讓子元件填充父元件的插槽嗎?

但實際上,這個問題與props沒有任何關係。 更簡單地說,它是關於使子元件控制在其自己的子樹之外渲染的內容。

我們可以這樣表述問題

元件控制在其子元件之外渲染的內容的最佳方法是什麼?

通過這個鏡頭檢查我們提出的每個解決方案,都會為我們提供一個有趣的新視角。

向父元件發出事件

資料流經元件樹的唯一途徑是使用 props。 而向上通訊的方法是使用事件。這意味著,如果要讓子元件與父元件進行通訊,我們需要使用事件來實現。

靜態配置

只是將必要的資訊提供給其他元件,而不是主動地要求另一個元件做事情。

傳送門

元件無法控制其子樹之外的內容。這裡的每個方法都是讓另一個元件執行我們的命令並控制我們真正感興趣的元素不同的方式。

在這方面,使用 portal 更好的原因是它們允許我們將所有這些通訊邏輯封裝到單獨的元件中。

提升狀態

提升狀態是一種比我們前面看到的3種更簡單、更強大的技術,這裡我們的主要限制是我們想要控制的內容在子元件之外。

最簡單的解決方法是:

提升狀態以及操縱該狀態的邏輯,使我們可以擁有更大範圍的元件,並將目標元素包含在該元件中。如果可以這樣做,這是解決此特定問題以及所有相關問題的最簡單方法。

請記住,這並不一定意味著要提升整個元件。 你也可以重構你的應用程式,以將邏輯移到元件樹中更高的元件中。

依賴注入

如果熟悉軟體工程設計模式的人可能已經注意到,我們在這裡所做的是依賴注入,這是我們在軟體工程中已經使用了幾十年的技術。

它的用途之一是編寫易於配置的程式碼。在我們的例子中,,我們在使用的每個Page中以不同的方式配置Layout元件。

當調換PageLayout元件時,我們正在執行所謂的控制元件反轉。

在基於元件的框架中,父元件控制子元件的操作,因此我們選擇讓Page來控制Layout元件,而不是由Layout元件控制Page。

為了做到這一點,我們使用插槽為Layout元件提供完成任務所需的內容。

正如我們所看到的,使用依賴注入可以使我們的程式碼更加模組化和易於配置。

總結

我們討論瞭解決這個問題的4種不同方法,展示了每種方法的優缺點。然後我們更進一步,將問題轉化為一個更一般的問題,即控制組件子樹之外的內容。

、提升狀態和依賴項注入是兩個非常有用的模式。它們是我們武器庫中最好的工具,因為它們可以應用於無數的軟體開發問題。

但最重要的是,希望你還能學會:

通過使用一些常見的軟體模式,將一個醜陋解決方案的問題轉變成一個非常優雅的問題。許多其他的問題都可以用這種方法解決,即把一個醜陋的、複雜的問題轉化成一個更簡單、更容易解決的問題。

程式碼部署後可能存在的BUG沒法實時知道,事後為了解決這些BUG,花了大量的時間進行log 除錯,這邊順便給大家推薦一個好用的BUG監控工具 Fundebug。

原文:https://dev.to/michaelthiesse...

以上就是Vue 技巧之控制父類的 slot的詳細內容,更多關於Vue控制父類的 slot的資料請關注我們其它相關文章!