Vue.js-組件化前端開發(fā)新思路

本文章是我最近在公司的一場(chǎng)內(nèi)部分享的內(nèi)容。我有個(gè)習(xí)慣就是每次分享都會(huì)先將要分享的內(nèi)容寫成文章。所以這個(gè)文集也是用來放這些文章的,順便也當(dāng)圖床用。

1. 認(rèn)識(shí)Vue.js

Vue.js(讀音 /vju?/,類似于view)是一套構(gòu)建用戶界面的漸進(jìn)式框架。

如果你有react或者Angular開發(fā)經(jīng)驗(yàn),你肯定不會(huì)對(duì)Vue.js感到太過陌生。Vue.js是踩在Angular和React肩膀上的后來者,它充分吸收了二者的優(yōu)點(diǎn),是MVVM框架的集大成者。我們只需要花10分鐘寫一點(diǎn)代碼,就能大概窺見Vue的本質(zhì)。

1.1 數(shù)據(jù)綁定

所有的MVVM框架要解決的第一件事都是數(shù)據(jù)綁定。首先要將Model的變化渲染到View中,當(dāng)有用戶輸入還需要把用戶的修改反映到Model中。所謂的MVVM就是這么來的。

<!DOCTYPE html>
<html>
  <head>
    <title>Hello Vue</title>
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      {{ message }}
    </div>
  </body>
  <script>
    var app = new Vue({
      el: '#app',
      data: {
        message: 'Hello Vue'
      }
    })
  </script>
</html>

在瀏覽器打開這個(gè)HTML文件后,可以看到頁面上顯示了“Hello Vue”字樣。我們?cè)诳刂婆_(tái)輸入app.message = 'hello world'并回車后,發(fā)現(xiàn)頁面上的消息也變成了“Hello World”。你會(huì)發(fā)現(xiàn)這一切都是響應(yīng)式的!Vue在背后為我們搞定了數(shù)據(jù)到視圖的綁定,然而這一切并沒有什么黑魔法,這背后的原理是Object.defineProperty和對(duì)象的存取器屬性。

vue的數(shù)據(jù)綁定
vue的數(shù)據(jù)綁定

這是Vue官網(wǎng)的一張圖,高度概括了響應(yīng)式數(shù)據(jù)綁定的原理。使用Object.definePropertydata中的所有屬性都轉(zhuǎn)為存取器屬性,然后在首次渲染過程中把屬性的依賴關(guān)系記錄下來并為這個(gè)Vue實(shí)例添加觀察者。當(dāng)數(shù)據(jù)變化時(shí),setter會(huì)通知觀察者數(shù)據(jù)變動(dòng),最后由觀察者觸發(fā)render函數(shù)進(jìn)行再次渲染。

理解了這個(gè),就不難理解Vue中視圖到數(shù)據(jù)的綁定了:

<!DOCTYPE html>
<html>
  <head>
    <title>Hello Vue</title>
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <p>Welcom {{ name }}!</p>
      <input type="text" placeholder="Enter your name" v-model="name">
    </div>
  </body>
  <script>
    var app = new Vue({
      el: '#app',
      data: {
        name: ''
      }
    })
  </script>
</html>

1.2 條件、循環(huán)與事件

Vue中可以很方便地進(jìn)行條件渲染、循環(huán)渲染和事件綁定。我們將通過一個(gè)列表的例子來體驗(yàn):

<!DOCTYPE html>
<html>
  <head>
    <title>Hello Vue</title>
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <style>
      body, html {
        margin: 0;
        padding: 0;
      }
      body {
        padding: 20px;
      }
      .students {
        margin: 0;
        padding: 0 0 20px 0;
        list-style: none;
      }
      .students > li {
        padding: 20px;
        border-bottom: 1px solid #ddd;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <ul class="students">
        <li v-for="student in students">
          <h3 class="name">
            {{student.name}}
            <span>{{student.age}}</span>
          </h3>
          <p v-if="Number(student.age) > 18">{{student.profile}}</p>
          <button v-on:click="sayHi(student.name)">Say hi</button>
        </li>
      </ul>
    </div>
  </body>
  <script>
    var students = [
      {
        name: 'Susan',
        age: 17,
        profile: 'Hi there I\'m a dog! Wang Wang!'
      },
      {
        name: 'Amanda',
        age: 21,
        profile: 'Kneel Down, Human! Miao~'
      },
      {
        name: 'Lench',
        age: 25,
        profile: 'боевой народ!!'
      }
    ]
    new Vue({
      el: '#app',
      data: {
        students
      },
      methods: {
        sayHi (name) {
          window.alert('Hi '+ name)
        }
      }
    })
  </script>
</html>

1.3 組件系統(tǒng)

我們今天的重點(diǎn)是Vue的組件系統(tǒng)。在Vue中定義和使用一個(gè)組件非常簡單:

<!DOCTYPE html>
<html>
  <head>
    <title>Hello Vue</title>
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <my-component-a></my-component-a>
      <my-component-b></my-component-b>
    </div>
  </body>
  <script>
    // register a global component
    Vue.component('my-component-a', {
      template: `<div>custom component A!</div>`
    })
    var app = new Vue({
      el: '#app',
      data: {},
      components: {
        'my-component-b': { // register a local component
          template: '<div>custom component B!</div>'
        }
      }
    })
    console.log(myComponentA, app)
  </script>
</html>

我們?cè)谶@里分別注冊(cè)了一個(gè)全局組件和一個(gè)局部組件。所謂全局組件就是一旦注冊(cè),所有的Vue實(shí)例中都可任意使用而不需要再單獨(dú)聲明;局部組件則是只有當(dāng)前Vue實(shí)例可以使用該組件。

另外,既然是組件系統(tǒng),肯定會(huì)有生命周期。在Vue中組件實(shí)質(zhì)上就是Vue實(shí)例,Vue實(shí)例的生命周期就是組件的生命周期:

Vue實(shí)例生命周期
Vue實(shí)例生命周期

在進(jìn)一步了解Vue的組件系統(tǒng)之前,有一個(gè)概念我們需要先來統(tǒng)一,就是組件化。

2. 組件化

2.1 組件化的定義

將實(shí)現(xiàn)頁面某一部分功能的結(jié)構(gòu)、樣式和邏輯封裝成為一個(gè)整體,使其高內(nèi)聚,低耦合,達(dá)到分治與復(fù)用的目的。

在前端范疇,我們可以用下面的這張圖來簡單地理解組件化:

頁面組件結(jié)構(gòu)

這樣看起來,組件化前端開發(fā)就像造一輛車,我們將輪子、發(fā)動(dòng)機(jī)、懸掛、車身車門等等各部分組裝成一輛車,輪子、發(fā)動(dòng)機(jī)就是組件,車就是最終產(chǎn)品。我們將頁頭、側(cè)邊欄、頁腳、內(nèi)容區(qū)等等組件拼裝起來組成了我們的頁面。

2.2 組件化的意義

分而治之

在談到組件化的意義時(shí),很多人的看法都是組件化的目的是復(fù)用,但我并不贊同這一看法。

良好地組件化以后的組件,會(huì)表現(xiàn)出高內(nèi)聚低耦合的特征,這會(huì)給我們帶來好處:

  1. 組件之間不會(huì)相互影響,能有效減少出現(xiàn)問題時(shí)定位和解決問題的時(shí)間
  2. 組件化程度高的頁面,具有清晰的頁面組織和高可讀性的HTML結(jié)構(gòu)代碼,組件之間的關(guān)系一目了然
  3. 組件化會(huì)強(qiáng)迫開發(fā)人員劃清各個(gè)組件的功能邊界,使得開發(fā)出的功能更加健壯

所以分而治之才是組件化的意義所在,復(fù)用只是它的副作用。同時(shí)我們還有很多其他方式都可以做到復(fù)用,這并不是組件化的專利。

2.3 組件化與模塊化

有時(shí)候我們可能會(huì)分不清組件化和模塊化的區(qū)別。

模塊化是一種處理復(fù)雜系統(tǒng)分解成為更好的可管理模塊的方式。它可以通過在不同組件設(shè)定不同的功能,把一個(gè)問題分解成多個(gè)小的獨(dú)立、互相作用的組件,來處理復(fù)雜、大型的軟件。[^2]

這段話出《Java應(yīng)用架構(gòu)設(shè)計(jì)》,似乎在后端領(lǐng)域,組件化和模塊化說的是同一件事。但在我的理解中,前端領(lǐng)域的組件化和模塊化是兩個(gè)概念。先說結(jié)論

組件化是從產(chǎn)品功能角度進(jìn)行分割,模塊化是從代碼實(shí)現(xiàn)角度進(jìn)行分割,模塊化是組件化的前提和基礎(chǔ)。

當(dāng)我們將一段代碼寫成一個(gè)模塊的時(shí)候,它有可能是一個(gè)函數(shù)、一個(gè)對(duì)象或者其他什么做了一件單一事情的東西,我們將它做成模塊是因?yàn)樗瓿闪艘粋€(gè)單一的功能,并且這個(gè)功能很多地方都可能用得到。

而當(dāng)一個(gè)組件被從產(chǎn)品中抽象出來,它有時(shí)候就只是一個(gè)模塊,但有時(shí)候卻有相對(duì)復(fù)雜的實(shí)現(xiàn),它就可能會(huì)有多個(gè)模塊。

我們說一個(gè)日期選擇器是一個(gè)組件,但實(shí)現(xiàn)它的時(shí)候,我們分成了計(jì)算模塊、渲染模塊、用戶輸入響應(yīng)模塊等等模塊來實(shí)現(xiàn)。一個(gè)單一產(chǎn)品功能的實(shí)現(xiàn),可能是由多個(gè)模塊來實(shí)現(xiàn)的。這樣理解起來,其實(shí)可以說組件化是更粗粒度的模塊化,它是在產(chǎn)品功能上的模塊化。說到這里,其實(shí)不難理解為什么后端領(lǐng)域可以認(rèn)為組件化與模塊化是一件事了,這一點(diǎn)交給大家思考。

2.4 組件化在前端工程中的位置

現(xiàn)在市面上的前端團(tuán)隊(duì)的武功等級(jí)大概可以用下面的這張圖概括:

組件化在前端工程中過的位置

今天我們前端領(lǐng)域最先進(jìn)的工程化水平,在傳統(tǒng)的桌面軟件開發(fā)領(lǐng)域中早就被用爛了,所以這都不是什么新概念。但這也是我今天要分享的原因,既然組件化早就大行其道了,那我們是不是可以探討一下在組件化過程中要面對(duì)的常見問題,以及如何優(yōu)雅地運(yùn)用Vue提供的組件系統(tǒng)進(jìn)行組件化開發(fā)?

2.5 前端組件化開發(fā)的常見問題

  • 組件隔離(模塊化):既然要組件化,那么第一件事就是實(shí)現(xiàn)組件之間的隔離,否則內(nèi)聚和低耦合就無從談起。組件隔離其實(shí)就是模塊化,這里我們需要實(shí)現(xiàn)CSS模塊化和JS模塊化。
  • 組件間通信:高內(nèi)聚低耦合必然會(huì)帶來數(shù)據(jù)流動(dòng)上的壁壘,所以隔離后的組件就要解決組件之間的通信處理。組件通信分為父子組件通信和非父子組件通信,這就涉及到接口設(shè)計(jì)、事件處理和狀態(tài)管理三塊內(nèi)容。
  • 內(nèi)容分發(fā):有時(shí)候我們希望抽象的是組件的某種行為模式或交互方式,而組件中包含的內(nèi)容卻是需要使用組件時(shí)才能確定,這雖然本質(zhì)上也是組件間通信,但它的方式更為直觀和方便。內(nèi)容分發(fā)涉及到具名/非具名內(nèi)容分發(fā),子組件向分發(fā)內(nèi)容傳遞數(shù)據(jù)等。
  • 遞歸和循環(huán)引用:組件本質(zhì)上也是模塊,那么肯定也需要面對(duì)模塊會(huì)面對(duì)的問題,遞歸和循環(huán)引用。
  • 按需加載:既然已經(jīng)組件化了,那么更進(jìn)一步應(yīng)該實(shí)現(xiàn)組件的按需加載,從而提高產(chǎn)品體驗(yàn)

3. Vue中的組件化

Vue在組件化上針對(duì)上述問題給出了很完整的解決方案。

3.1 單文件組件系統(tǒng)與CSS局部作用域

之前我們已經(jīng)看到了Vue中是如何注冊(cè)和使用一個(gè)組件的,然而很多時(shí)候一個(gè)組件本身的結(jié)構(gòu)和邏輯都遠(yuǎn)遠(yuǎn)比這要多和復(fù)雜,在這種時(shí)候僅僅依靠對(duì)象實(shí)例這種形式,就會(huì)出現(xiàn)諸多不便,同時(shí)基本沒有什么好的辦法來實(shí)現(xiàn)CSS隔離。

<style lang="scss" scoped>
  .my-component {
    color: red;
  }
</style>
<template>
  <div class="my-component">
    {{ message }}
  </div>
</template>
<script>
  export default {
    data () {
      return {
        message: 'This is my component!'
      }
    }
  }
</script>

Vue給我們提供了單文件組件系統(tǒng),在這套系統(tǒng)中,我們可以使用一個(gè).vue后綴的文件來組織組件,這個(gè)文件內(nèi)的結(jié)構(gòu)像極了普通的html文件:一個(gè)表示結(jié)構(gòu)的template標(biāo)簽,一個(gè)編寫樣式的style標(biāo)簽,和一個(gè)表示邏輯的script標(biāo)簽。

在script中我們將組件輸出為一個(gè)模塊,利用ES6的Module系統(tǒng)來作為隔離組件的基礎(chǔ)。同時(shí)我想你已經(jīng)注意到了style標(biāo)簽中的這個(gè)scoped屬性,它意味著當(dāng)前組件的樣式是局部的,不會(huì)影響其他組件。至于如何實(shí)現(xiàn)的,非常簡單:

image

Webpack的vue-style-load會(huì)在組件的每個(gè)元素上添加一個(gè)data-v-hash屬性,然后在其對(duì)應(yīng)的CSS選擇器上添加這個(gè)屬性作為選擇器:

image

這樣就將組件的樣式與其他組件隔離開來。

3.2 Vue組件通信

可以用一張圖來表示Vue組件系統(tǒng)中父子組件的數(shù)據(jù)流動(dòng):

父子組件的數(shù)據(jù)流動(dòng)

使用props向子組件傳遞數(shù)據(jù),首先要在子組件中定義子組件能接受的props,然后在父組件中子組件的自定義元素上將數(shù)據(jù)傳遞給它:

雖然官方并沒有這樣的說法,但我仍舊習(xí)慣將子組件的props叫做它的接口,通過組件的接口,我們可以從外部向組件傳遞數(shù)據(jù)。但是如果組件需要向外部傳遞數(shù)據(jù),則不能通過props,這是Vue 2與前一代Vue的區(qū)別。Vue 2中強(qiáng)調(diào)“單項(xiàng)數(shù)據(jù)流”。跟React中提倡的“單項(xiàng)數(shù)據(jù)流”一樣,所謂“單向數(shù)據(jù)流”,即是數(shù)據(jù)的變動(dòng)只能由外向內(nèi)傳遞,而不能由內(nèi)向外傳遞。組件只能將從接口傳遞進(jìn)來的數(shù)據(jù)進(jìn)行使用,不能對(duì)其進(jìn)行修改:

export default {
  props: ['message'],
  mounted () {
    this.message = 'local message' // Vue will warn you if you try to modify props
  }
}

我們唯一能做的,就是在子組件中將props中傳遞進(jìn)來的數(shù)據(jù)賦值給子組件的本地data變量,然后在修改了這個(gè)本地變量的時(shí)候,發(fā)送事件通知外部。父組件通過監(jiān)聽子組件發(fā)送的這個(gè)事件,來決定需要做什么:

<template>
  <div>
    <input type="text" v-model="localMessage" v-on:change="localMessageChange">
  </div>
</template>
<script>
  export default {
    props: ['message'],
    data () {
      return {
        localMessage: this.message
      }
    }
    methods: {
      localMessageChange () {
        this.$emit('message-change', localMessage) // notify parent component the change of message
      }
    }
  }
</script>

另外,事件系統(tǒng)也能夠解決非父子組件的通信問題,我們使用一個(gè)空的Vue實(shí)例來作為中央事件總線,就像這樣:

let bus = new Vue()

bus.$on('a-custom-event', function () {
    // handle the event
})

bus.$emit('a-custom-event', 'some custom event data')

講到這里就不得不提Vuex。和Redux一樣,Vuex是Vue官方提供的狀態(tài)管理方案。在很多情況下,通過props和事件系統(tǒng)就基本能滿足我們的需求,但當(dāng)情況復(fù)雜到一定階段(比如咱們的Cube),上述簡單的手段就會(huì)讓狀態(tài)管理變得不可控,這時(shí)應(yīng)該考慮使用Vuex。

3.3 向子組件分發(fā)內(nèi)容

有時(shí)候我們希望將某種“容器”功能抽象出來成為組件,這時(shí)它內(nèi)部的“容納物”就不確定了。我們當(dāng)然可以完全通過props向組件傳遞大量的HTML字符串來解決問題,但那樣的寫法相信沒幾個(gè)人會(huì)喜歡。HTML是用于表示“結(jié)構(gòu)”的,我們自然希望他們出現(xiàn)在他們?cè)摮霈F(xiàn)的位置上。

Vue提供了slot(插槽)來解決這個(gè)問題。父組件可以通過子組件的slot向子組件中注入HTML:

<template>
  <div class="modal">
    <slot></slot>
    <slot name="operations"></slot>
  </div>
</template>
<modal>
  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>
  <div slot="operations">
    <button>cancel</button>
    <button>confirm</button>
  </div>
</modal>

在Vue 2.1以前,子組件對(duì)于通過slot傳遞進(jìn)來的HTML是沒有太多手段去控制的,但在2.1版本后,Vue甚至還提供了一個(gè)叫做“作用域插槽”的特性,子組件現(xiàn)在可以向被注入的HTML中傳遞數(shù)據(jù)了!這意味著子組件得到了被注入HTML的數(shù)據(jù)控制權(quán),它可以自定義每一項(xiàng)的展示行為,更可以將列表項(xiàng)中那些特殊項(xiàng)的共同行為和特征也抽象到子組件內(nèi)部去,不需要額外在子組件外部進(jìn)行處理了,舉個(gè)不是很恰當(dāng)?shù)睦樱?/p>

<!--with out scope slot-->
<my-list>
  <li v-for="item in listItem">{{ item.url || item.text }}</li>
</my-list>

<!--with scope slot-->
<my-list :items="listItem">
  <template slot="item" scope="props">
    <li v-for="item in listItem">{{ props.text }}</li>
  </template>
</my-list>

列表組件可以將“優(yōu)先顯示url”這個(gè)特性,通過作用于插槽封裝到組件內(nèi)部進(jìn)行處理,不再需要外部去處理了:

<!--my-list component-->
<ul>
  <slot
    name="item"
    v-for="item in items"
    :text="item.url || item.text"></slot>
</ul>

這個(gè)時(shí)候每一項(xiàng)展示的數(shù)據(jù)來源就不是父組件而是子組件了。到這里我們回過頭來看一看這三個(gè)特性:props、slotscope slot

`props`、`slot`和`scope slot`各自對(duì)結(jié)構(gòu)和數(shù)據(jù)二者控制權(quán)的劃分

使用props來傳遞數(shù)據(jù),是將子組件中的結(jié)構(gòu)和數(shù)據(jù)的控制權(quán)完全封閉到子組件中,父組件只管向其提供數(shù)據(jù);如果使用了slot來分發(fā)內(nèi)容,則是將子組件中的某些結(jié)構(gòu)和數(shù)據(jù)的控制權(quán)完全交給父組件,父組件要將這部分結(jié)構(gòu)和數(shù)據(jù)渲染好了放到子組件指定的位置中;而scope slot則是二者的中和,它將數(shù)據(jù)控制權(quán)交給了子組件,將結(jié)構(gòu)控制權(quán)交給了父組件。

3.4 Vue組件的遞歸與循環(huán)引用

大部分模塊系統(tǒng)都會(huì)需要處理遞歸和循環(huán)引用這兩個(gè)問題。Vue組件系統(tǒng)中對(duì)這兩個(gè)問題的處理非常優(yōu)雅,首先是遞歸:

<template>
  <ul class="admin-menu" :class="isTopLevel ? 'top-level' : ''">
    <li v-for="item in localItems">
      {{ item.text }}
      <admin-menu v-if="item.children && item.children.length" :menu-items="item.children"></admin-menu>
    </li>
  </ul>
</template>
export default {
  name: 'admin-menu',
  data () {
    return {
      localItems: this.menuItems
    }
  },
  props: ['meneItems']
}

這是來自于Admin-UI中的組件admin-menu中的實(shí)現(xiàn),Vue中的組件只要給定了name屬性,就能夠很自然地進(jìn)行遞歸調(diào)用,只要確保遞歸有停止條件即可。所以通常遞歸會(huì)與v-if、v-for等配合使用。

組件引用自身為遞歸引用,AB組件互相引用則為循環(huán)引用。Vue.component()方法內(nèi)部自動(dòng)處理了這種循環(huán)引用,你不僅不需要擔(dān)心這是個(gè)循環(huán)引用,你甚至可以將這個(gè)特性作為優(yōu)勢(shì)進(jìn)行充分利用。但當(dāng)使用的是ES2015的模塊系統(tǒng)來引入的組件,Webpack就會(huì)報(bào)循環(huán)引用錯(cuò)誤了。

為了解釋為什么會(huì)報(bào)錯(cuò),簡單的將上面兩個(gè)組件稱為 A 和 B ,模塊系統(tǒng)看到它需要 A ,但是首先 A 需要 B ,但是 B 需要 A, 而 A 需要 B,陷入了一個(gè)無限循環(huán),因此不知道到底應(yīng)該先解決哪個(gè)。要解決這個(gè)問題,我們需要在其中一個(gè)組件中(比如 A )告訴模塊化管理系統(tǒng),“A 雖然需要 B ,但是不需要優(yōu)先導(dǎo)入 B”

Vue的官方教程上說的非常清楚,只要讓兩個(gè)組件的導(dǎo)入不同時(shí)發(fā)生,就可以規(guī)避這個(gè)問題。那么事情就簡單了,我們?cè)谄渲幸粋€(gè)組件中注冊(cè)另一個(gè)組件的時(shí)候再去引入它就錯(cuò)開了它們的引入時(shí)間:

// a.vue
export default {
  beforeCreate: function () {
    this.$options.components.TreeFolderContents = require('./b.vue')
  }
}

3.5 配合Webpack實(shí)現(xiàn)組件按需加載

在大型應(yīng)用中,我們可能需要將應(yīng)用拆分為多個(gè)小模塊,按需從服務(wù)器下載。為了讓事情更簡單, Vue.js 允許將組件定義為一個(gè)工廠函數(shù),動(dòng)態(tài)地解析組件的定義。Vue.js 只在組件需要渲染時(shí)觸發(fā)工廠函數(shù),并且把結(jié)果緩存起來,用于后面的再次渲染。

Vue.component('async-webpack-example', function (resolve) {
  // 這個(gè)特殊的 require 語法告訴 webpack
  // 自動(dòng)將編譯后的代碼分割成不同的塊,
  // 這些塊將通過 Ajax 請(qǐng)求自動(dòng)下載。
  require(['./my-async-component'], resolve)
})

3.6 vue-cli實(shí)例演示(待定)

使用Node作服務(wù)器,制作一個(gè)TODO List頁面,實(shí)現(xiàn)增刪改查

4. 其他

4.1 組件層級(jí)劃分

組件的三個(gè)層級(jí)

依據(jù)與業(yè)務(wù)的耦合程度,由低到高,我們可以將組件分為三個(gè)層次:UI組件,應(yīng)用組件和業(yè)務(wù)組件。

UI組件主要是大部分由UI庫提供的業(yè)務(wù)無關(guān)的純UI渲染組件,三者中它的粒度最細(xì),每個(gè)組件就完成一個(gè)UI功能;同時(shí)因?yàn)闊o關(guān)業(yè)務(wù)它可以在項(xiàng)目間具有通用性。

應(yīng)用組件則是與業(yè)務(wù)有一定耦合的組件,它是基于UI組件進(jìn)行的封裝或組合,粒度與UI組件類似,但帶上了一定的業(yè)務(wù)屬性,僅在本項(xiàng)目通用。

業(yè)務(wù)組件則是完成某個(gè)具體業(yè)務(wù)的組件,它是基于UI組件和應(yīng)用組件進(jìn)行的封裝或組合,粒度最粗,具有針對(duì)性的業(yè)務(wù)屬性,它不需要也不具備通用性。

反映到實(shí)現(xiàn)中,可以用一個(gè)例子來理解:列表組件 -> 用戶列表組件 -> 用戶管理組件?;谶@種分層,從文件組織,到組件劃分,都會(huì)有一些最佳實(shí)踐。

  • 適度的組件嵌套:a->b->c->d->e->f...當(dāng)嵌套層級(jí)過多時(shí)會(huì)帶來另一個(gè)極端,復(fù)雜度不降反升。合適的嵌套規(guī)則應(yīng)該是UI組件盡可能相互獨(dú)立,不進(jìn)行嵌套;應(yīng)用組件是最容易發(fā)生過度嵌套的地方,所以它們之間也應(yīng)該盡可能互相獨(dú)立,即使嵌套也請(qǐng)不要超過1層,它們應(yīng)當(dāng)純粹由UI組件和業(yè)務(wù)規(guī)則組成;業(yè)務(wù)組件則僅僅應(yīng)當(dāng)由UI組件和應(yīng)用組件組成,不應(yīng)該在一個(gè)業(yè)務(wù)組件中嵌套另一個(gè)業(yè)務(wù)組件,這會(huì)讓業(yè)務(wù)邏輯顯得很奇怪
  • 良好的組件命名:UI組件的名稱應(yīng)當(dāng)反映組件功能,應(yīng)用組件的名稱應(yīng)當(dāng)反映業(yè)務(wù)屬性和組件功能,業(yè)務(wù)組件名稱則應(yīng)當(dāng)完全體現(xiàn)業(yè)務(wù)屬性,至于英文還是拼音...我只能說隨緣吧...
  • 統(tǒng)一的組件接口:組件的接口命名應(yīng)當(dāng)表達(dá)一致的語義,類似message、text、items這樣常用的接口名稱代表的語義和功能盡可能要在項(xiàng)目中得到統(tǒng)一
  • 清晰的文件組織:UI組件應(yīng)當(dāng)來自項(xiàng)目中引入的UI庫,或者項(xiàng)目中單獨(dú)的UI組件文件夾,應(yīng)用組件應(yīng)當(dāng)來自單獨(dú)的應(yīng)用組件文件夾,而業(yè)務(wù)組件則應(yīng)當(dāng)每個(gè)業(yè)務(wù)組件一個(gè)文件夾,在其中存放該業(yè)務(wù)組件相關(guān)的一切文件
文件層級(jí)與業(yè)務(wù)組件代碼

最后,當(dāng)我們按照上面的劃分來組織組件的時(shí)候,還會(huì)面臨一個(gè)問題,一個(gè)業(yè)務(wù)組件中,并不完全是由UI組件和應(yīng)用組件組成的,很多部分其實(shí)并不具有任何通用性,那這部分應(yīng)該如何處理?通常情況下我們會(huì)直接將它們寫在業(yè)務(wù)組件中,所以我們一般見到的業(yè)務(wù)組件多是自定義組件和原生HTML代碼混雜在一起的。但更優(yōu)雅的解決方案,是將這部分內(nèi)容也拿出來做成組件,它們就放置在業(yè)務(wù)組件自己的目錄中,一旦你這樣做,你會(huì)發(fā)現(xiàn)你的業(yè)務(wù)組件中不再出現(xiàn)大塊的原生HTML代碼,取而代之的是語義清晰結(jié)構(gòu)簡明的自定義組件。組件化的首要目的是分治而不是復(fù)用,所以即使沒有復(fù)用的需求,你也應(yīng)該有動(dòng)力去進(jìn)行組件化。

4.2 ajax是否需要置于組件內(nèi)

大量的剛剛開始進(jìn)行組件化的團(tuán)隊(duì)成員們都會(huì)對(duì)一個(gè)問題進(jìn)行爭(zhēng)論:ajax是否需要封裝到組件內(nèi)部?

先說結(jié)論:不需要也不應(yīng)該。原因很簡單:解耦。

僅考慮兩種情況:

  • 一個(gè)應(yīng)用組件在某個(gè)業(yè)務(wù)組件中引用了兩次:當(dāng)這個(gè)應(yīng)用組件內(nèi)部在created鉤子中封裝了加載數(shù)據(jù)請(qǐng)求的ajax時(shí),如果參數(shù)相同,那么該組件的請(qǐng)求會(huì)在同一個(gè)業(yè)務(wù)組件中被發(fā)送兩次
  • 項(xiàng)目需要進(jìn)行統(tǒng)一的ajax管理和優(yōu)化:當(dāng)組件內(nèi)部存在ajax邏輯的時(shí)候,統(tǒng)一的ajax管理和優(yōu)化會(huì)變得麻煩

還有更多的坑我沒有列出來,所以出于解耦的目的,盡可能不要將ajax邏輯封裝到組件中,組件僅關(guān)心渲染邏輯即可。

4.3 為什么選擇Vue

安利一波Vue給大家:

  • 快速上手,事實(shí)上Vue沒有改變傳統(tǒng)的開發(fā)模式,我們?cè)?code>style中寫樣式,我們?cè)?code>template中寫模板,我們?cè)?code>script中寫邏輯,同時(shí)文檔極其完善,各種語言都有,所以不關(guān)你是老鳥還是新手,都能非??焖俚厣鲜諺ue進(jìn)行開發(fā)
  • 全姿勢(shì)解鎖,數(shù)據(jù)驅(qū)動(dòng)、HTML模板與JSX三者兼得,不喜歡Vue的姿勢(shì)?沒關(guān)系,什么姿勢(shì)都可以,你可以像寫React一樣去寫Vue,也可以像寫Angula一樣去寫Vue
  • 強(qiáng)大的項(xiàng)目模板,超好用的項(xiàng)目模板——vue-cli,比create-react-app不知道高到哪里去了
  • 性能強(qiáng)悍,基本上Vue的渲染性能是React的差不多兩倍,至于Angular...我不說了
  • 可愛的開發(fā)者,接地氣的開發(fā)者:尤雨溪活躍在知乎、github、stackoverflow等國內(nèi)外各大平臺(tái),而React和Angular則是facebook和Google團(tuán)隊(duì)在維護(hù),你很難接觸到他們
  • 腦殘粉,我喜歡我喜歡我喜歡

4.4 Admin-UI:

最后,再安利一波我們出的Admin-UI庫給大家(暫未開源)。

Admin-UI是一套基于Vue,用于PC端的UI庫。就像名字那樣,這套UI庫主要用于PC端的后臺(tái)管理系統(tǒng)。這一類系統(tǒng)對(duì)樣式的定制要求比較低,相應(yīng)地我們希望用于其中的UI庫能夠帶來更快速的開發(fā)體驗(yàn)。與BootStrapde的大而全不一樣的是,我們對(duì)Admin-UI的預(yù)期是小而美,借此盡可能降低使用者的學(xué)習(xí)成本,加速開發(fā)。

admin-ui
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容