高級 Vue 技巧:控制父類的 slot

做者:Michael Thiessen
譯者:前端小智
來源:dev
點贊再看,養成習慣

本文 GitHub https://github.com/qq44924588... 上已經收錄,更多往期高贊文章的分類,也整理了不少個人文檔,和教程資料。歡迎Star和完善,你們面試能夠參照考點複習,但願咱們一塊兒有點東西。前端

首先來思考一個問題:是否有一種方法能夠從子組件填充父組件的插槽?vue

最近一位同事問我這個問題,答案很簡單:能夠的。但個人解決方案可能和你想的徹底不同,這是涉及一個棘手的Vue架構問題,但也是一個很是有趣的問題。node

爲何會有這個問題

clipboard.png

在咱們的應用程序中,咱們有一個頂部欄,其中包含不一樣的按鈕、搜索欄和其餘一些控件。根據每一個人所在的頁面,它可能略有不一樣,所以咱們須要一種基於每一個頁面配置它的方法。git

clipboard.png

爲此,咱們但願每一個頁面都可以配置操做欄。看起來很簡單,但這裏有個問題github

這個頂部欄(咱們稱之爲ActionBar)其實是咱們的主佈局的一部分,結構以下:面試

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

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

咱們可使用ActionBar上的一些插槽來配置它。 可是,咱們如何從App組件中控制這些插槽?微信

定義問題

首先,最好是儘量清楚地知道咱們要解決的問題。架構

咱們來看一個具備一個子組件和一個插槽的組件: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...


交流

文章每週持續更新,能夠微信搜索「 大遷世界 」第一時間閱讀和催更(比博客早一到兩篇喲),本文 GitHub https://github.com/qq449245884/xiaozhi 已經收錄,整理了不少個人文檔,歡迎Star和完善,你們面試能夠參照考點複習,另外關注公衆號,後臺回覆福利,便可看到福利,你懂的。

相關文章
相關標籤/搜索