搞懂並學會運用 Vue 中的無狀態組件

做者:Milos Protic
譯者:前端小智
來源:medium

阿里雲最近在作活動,低至2折,真心以爲很划算了,能夠點擊本條內容或者連接進行參與
https://promotion.aliyun.com/...css

騰訊雲最近在作活動,百款雲產品低至 1 折,能夠點擊本條內容或者連接進行參與html


啥是應用程序狀態,爲何我們須要它?

狀態管理一般在較小的項目並不須要,可是當涉及到更大的範圍時,如企業級的應用大部分須要它了。簡單的說,狀態是一個包含應用程序使用的最新值的對象。可是,若是我們從結構的、更抽象的角度來看待它,就會清楚地看到,狀態是複雜應該中重要一塊,它使可以構建乾淨的體系結構,並將關注點強有力地分離開來。前端

一般,缺少經驗的開發人員沒法預測對狀態管理的需求,以及如何實現狀態管理,所以很難了解狀態管理的重要性。若是基於狀態的組件堆積起來,它們之間的數據管理和共享將成爲一場噩夢。從長遠來看,擁有的基於狀態的組件越多,出現的問題就越多。vue

若是沒有使用外部包進行狀態管理,那麼最好儘量少地使用基於狀態的組件,而展現組件則使用圍繞它們構建的狀態。node

Vue 和無狀態(函數)組件

Vue 中的無狀態組件其實就是函數組件。但函數組件又是啥呢? 要回答這個問題,我們首先必須理解什麼是函數式編程。git

與將程序分解爲對象的面向對象方法不一樣,函數式編程鼓勵將程序分解爲小函數,這些小函數用於造成更高級的程序。咱們建立的函數不依賴於或能夠改變任何外部狀態,這致使另外一個觀察結果,對於給定的輸入,它們老是返回相同的輸出。github

所以,函數組件是沒有狀態的組件,而且能夠更改它。函數組件輸出老是基於給定的輸入。在 Vue 方面,這類組件會根據給定的props給出不一樣的輸出。編程

語法

Vue 提供了一種定義函數組件的簡單方法。我們只須要給個 functional 關鍵字就能夠。在 2.5.0 及以上版本中,若是使用了單文件組件,那麼基於模板的函數式組件能夠這樣聲明::segmentfault

<template functional>
  <div> 函數/無狀態組件 </div>
</template>

或者數組

export default {
  functional: true,
  props: {
    // ...
  },
  render(createElement, context) {
    return createElement(
      'div', '函數/無狀態組件'
    )
  }
}
注意:在 2.3.0 以前的版本中,若是一個函數式組件想要接收 prop,則 props 選項是必須的。在 2.3.0 或以上的版本中,你能夠省略 props 選項,全部組件上的特性都會被自動隱式解析爲 prop

當使用函數式組件時,該引用將會是 HTMLElement,由於他們是無狀態的也是無實例的。

須要注意的是,傳遞給函數組件的唯一數據是props。這些組件是徹底無狀態的(沒有響應數據),它們忽略傳遞給它們的任何狀態,而且不觸發任何生命週期方法(createdmounted等等)。

並且,我們也不能經過使用 this 關鍵字來訪問實例,由於這些組件也是不實例化的。相反,組件須要的全部東西都是經過context提供的。在render函數中,它做爲createElement方法的第二個參數傳遞。

組件須要的一切都是經過 context 參數傳遞,它是一個包括以下字段的對象:

  • props:提供全部 prop 的對象
  • children: VNode 子節點的數組
  • slots: 一個函數,返回了包含全部插槽的對象
  • scopedSlots: (2.6.0+) 一個暴露傳入的做用域插槽的對象。也以函數形式暴露普通插槽。
  • data:傳遞給組件的整個數據對象,做爲 createElement 的第二個參數傳入組件
  • parent:對父組件的引用
  • listeners: (2.3.0+) 一個包含了全部父組件爲當前組件註冊的事件監聽器的對象。這是 data.on 的一個別名。
  • injections: (2.3.0+) 若是使用了 inject 選項,則該對象包含了應當被注入的屬性。

爲何我們須要無狀態組件

到目前爲止,我們已經瞭解到函數組件是無狀態的,在它們的核心中,它們只是可執行的函數,接受一些輸入並根據其提供輸出。

就它們的用法而言,由於函數式組件只是函數,因此渲染開銷也低不少,這也意味着它們是很是高效的,不須要花太多時間渲染。同時,考慮高階組件,它們不須要任何狀態,它們所要作的就是用額外的邏輯或樣式包裝給定的子組件。

接下來,通例事例展現同樣啥時使用函數組件,函數組件很是適合此類任務。

實例

在這個示例中,我們建立一個panel組件,它充當一個包裝器,並提供所需的樣式。子組件將在panel 主體中渲染:

export default {
  name: 'panel',
  functional: true,
  props: {
    title: String
  },
  render(createElement, context) {
    const slots = context.slots();

    const header = createElement('header', {
      attrs: { class: 'panel-header'}
    }, context.props.title);
    
    const body = createElement('main', {
      attrs: { class: 'panel-body'}
    }, slots.default);

    return createElement('section', {
      attrs: { class: 'panel' }
    }, [header, body]);
  }
}

如上所述,此組件的惟一目的是提供相似於面板(卡片)的樣式,它有headermain元素,分別保存面板標題和HTML內容。整個過程是經過使用render函數中的createElement參數在中完成。createElementVue 核心中實現的虛擬 Dom 系統的一部分。

虛擬 DOM

Vue 經過創建一個虛擬 DOM 來追蹤本身要如何改變真實 DOM。請仔細看這行代碼:

return createElement('h1', this.blogTitle)

createElement 到底會返回什麼呢?其實不是一個實際的 DOM 元素。它更準確的名字多是 createNodeDescription,由於它所包含的信息會告訴 Vue 頁面上須要渲染什麼樣的節點,包括及其子節點的描述信息。咱們把這樣的節點描述爲「虛擬節點 (virtual node)」,也常簡寫它爲「VNode」。「虛擬 DOM」是咱們對由 Vue 組件樹創建起來的整個 VNode 樹的稱呼。

createElement 參數

接下來你須要熟悉的是如何在 createElement 函數中使用模板中的那些功能。這裏是 createElement 接受的參數:

// @returns {VNode}
createElement(
  // {String | Object | Function}
  // 一個 HTML 標籤名、組件選項對象,或者
  // resolve 了上述任何一種的一個 async 函數。必填項。
  'div',

  // {Object}
  // 一個與模板中屬性對應的數據對象。可選。
  {
    // (詳情見下一節)
  },

  // {String | Array}
  // 子級虛擬節點 (VNodes),由 `createElement()` 構建而成,
  // 也可使用字符串來生成「文本虛擬節點」。可選。
  [
    '先寫一些文字',
    createElement('h1', '一則頭條'),
    createElement(MyComponent, {
      props: {
        someProp: 'foobar'
      }
    })
  ]
)

面板 CSS 樣式以下:

.panel {
  margin-bottom: .5rem
}

.panel, .panel-header {
    border: 1px solid #d3d3d3;
    border-radius: 4px;
}

.panel-header, .panel-body, .panel {
  padding: .5rem;
}

.panel-header {
  background-color:#efefef;
  color: #eeeee
}

這是一個簡單直接的 CSS,提供了一些paddingcolor

子組件

如今,爲了讓例子更加生動爲此,我們再建立兩個附加組件,一個顯示汽車列表,另外一個只是一個簡單lorem-ipsum的文本組件,要求它們具備相同的面板樣式和外觀。

列表組件:

export default {
  name: 'cars',
  props: {
    data: Array
  }
}

template:

<template>
  <ul>
    <li v-for="car in data" :key="car">{{car}}</li>
  </ul>
</template>

文本組件:

export default {
  name: 'lorem-ipsum'
}

template:

<template>
  <p>
   終身學習者,終身學習者,終身學習者,終身學習者,終身學習者
  </p>
</template>

如今,有了可用的子組件,我們所須要作的就是用panel組件將它們封裝到應用程序中,以下所示:

<div class="vue-app">
  <panel :title="'Car Manufacturers'">
    <cars :data="['Mazda', 'Ford', 'Mercedes']"></cars>
  </panel>
  <panel :title="'Lorem Ipsum'">
    <lorem-ipsum></lorem-ipsum>
  </panel>
</div>
請注意,使用這些組件是由於示例比較簡單。在實際應用中,它能夠是任何類型的組件。

完整代碼

hmtl

<div class="vue-app">
  <panel :title="'Car Manufacturers'">
    <cars :data="['Mazda', 'Ford', 'Mercedes']"></cars>
  </panel>
  <panel :title="'Lorem Ipsum'">
    <lorem-ipsum></lorem-ipsum>
  </panel>
</div>

<script type="text/x-template" id="cars">
  <template>
    <ul>
      <li v-for="car in data" :key="car">{{car}}</li>
    </ul>
  </template>
</script>

<script type="text/x-template" id="lorem-ipsum">
  <template>
    <p>前端小智, 終身學習者,終身學習者,終身學習者,終身學習者,終身學習者</p>
  </template>
</script>

css

body {
  padding: .5rem
}

* {
  padding: 0;
  margin:0;
  box-sizing: border-box;
}

.panel {
  margin-bottom: .5rem
}

.panel, .panel-header {
    border: 1px solid #d3d3d3;
    border-radius: 4px;
}

.panel-header, .panel-body, .panel {
  padding: .5rem;
}

.panel-header {
  background-color:#efefef;
  color: #eeeee
}

ul {
  list-style: none;
}

ul > li {
  padding: .5rem .2rem
}

js

// the wrapper panel
const panel = {
  functional: true,
  name: "panel",
  props: {
    title: String
  },
  render(createElement, context) {
    const slots = context.slots();

    const header = createElement('header', {
      attrs: { class: 'panel-header'}
    }, context.props.title);
    
    const body = createElement('main', {
      attrs: { class: 'panel-body'}
    }, slots.default);

    return createElement('section', {
      attrs: { class: 'panel' }
    }, [header, body]);
  }
}

// sample components

const cars = {
  name: 'cars',
  template: '#cars',
  props: {
    data: Array
  }
}

const loremIpsum = {
  name: 'lorem-ipsum',
  template: '#lorem-ipsum'
}

new Vue({
  el: '.vue-app',
  components: {
    panel,
    cars,
    'lorem-ipsum': loremIpsum
  }
});

運行效果:

clipboard.png


代碼部署後可能存在的BUG無法實時知道,過後爲了解決這些BUG,花了大量的時間進行log 調試,這邊順便給你們推薦一個好用的BUG監控工具 Fundebug

原文:https://itnext.io/whats-the-d...


交流

乾貨系列文章彙總以下,以爲不錯點個Star,歡迎 加羣 互相學習。

https://github.com/qq449245884/xiaozhi

由於篇幅的限制,今天的分享只到這裏。若是你們想了解更多的內容的話,能夠去掃一掃每篇文章最下面的二維碼,而後關注我們的微信公衆號,瞭解更多的資訊和有價值的內容。

clipboard.png

每次整理文章,通常都到2點才睡覺,一週4次左右,挺苦的,還望支持,給點鼓勵

相關文章
相關標籤/搜索