還在寫最簡單的vue功能組件麼?

在平常項目開發過程當中,每每離不開封裝組件的過程。好比a頁面有一個功能恰巧b頁面和c頁面也使用。這時候咱們考慮到代碼複用,會考慮將這個功能作成組件。這篇文章咱們就來說一講開發vue組件時候的一些小技巧和一些黑()科()技()。javascript

咱們會經過大量例子來一個個講解
全部本文例子代碼已經上傳到 github

目錄

  • 常規功能組件小技巧
  • 函數式組件
  • 內聯模板
  • 遞歸組件
  • v-once低開銷靜態組件
  • 全局函數調用組件

常規功能組件小技巧

首先咱們用腳手架搭建一個項目,用來寫咱們的組件,咱們用到的UI框架是Iview。功能組件的編寫,離不開組件之間的通訊,這個相關的文章不少,本文就不詳細闡述了,這裏推薦一篇掘金的關於組件通訊的文章,寫的十分全面感興趣的小夥伴能夠去看看。html

slot插槽vue

slot插槽在咱們日常寫組件的時候出場率應該很高。將 <slot> 元素做爲承載分發內容的出口,咱們能夠在插槽內包含任何模板代碼,這使得咱們的組件更加的靈活。咱們來說一個比較經典的例子對話框組件,先看代碼。java

<template>
  <div>
    <div>頭部</div>
    <div>
      <slot>默認內容</slot>
    </div>
    <div>
      <slot name="footer"> <Button>確認</Button> </slot>
    </div>
  </div>
</template>
<script>
export default { name: "model" };
</script>
複製代碼

model標籤中除slot爲footer的代碼,其餘代碼都將做爲對話框的主體內容顯示,這就是做用域插槽的用法。而slot爲footer的代碼將在底部操做按鈕部分顯示,對應<slot name="footer">這就是具名插槽的用法。ios

屬性透傳git

屬性透傳的出現的場景很少,咱們以UI組件的二次封裝爲例。假如你的項目中頻繁使用一個UI組件,並且這個UI組件每次都要設置不少相似的屬性,那麼咱們就能夠進行二次封裝,調用起來更加的方便,甚至能夠在原有組件上擴展功能。咱們來看一段二次封裝Iview的Table組件的代碼。github

<script>
import { Table } from "iview";
export default {
  name: "customizeTable",
  props: { ...Table.props, test: { type: [String], default: "擴展屬性" } },
  data() {
    return { defaultProps: { border: true, stripe: true } };
  },
  render(h) {
    var props = Object.assign({ ...this._props }, this.defaultProps);
    return h(Table, { props: props });
  }
};
</script>複製代碼

//方法二web

<template>
  <div>
    <Table v-bind="{...mergeProps}"></Table>
  </div>
</template>
<script>
import { Table } from "iview";
export default {
  name: "customizeTable",
  props: { 
      ...Table.props,
      test: { 
          type: [String],
          default: "擴展屬性" 
      } 
  },
  computed: {
    mergeProps: function() {
      return Object.assign({ ...this._props }, this.defaultProps);
    }
  },
  data() {
    return { 
        defaultProps: { 
            border: true, 
            stripe: true 
        } 
    };
  }
};
</script>複製代碼

首先咱們經過Table組件的props屬性獲取的Table全部默認參數,而後經過this._props實現屬性透傳,同時咱們還能夠在props中擴展你須要的屬性如test。方法一中綁定屬性咱們使用了render方法,這個咱們在函數式組件中再細說。方法二中咱們經過v-bind實現綁定對象中全部的屬性。api


函數式組件

咱們先引用一下官網對於函數式組件的介紹數組

函數式組件,沒有管理任何狀態,也沒有監放任何傳遞給它的狀態,也沒有生命週期方法。實際上,它只是一個接受一些 prop 的函數。在這樣的場景下,咱們能夠將組件標記爲 functional,這意味它無狀態 (沒有響應式數據),也沒有實例 (沒有 this 上下文)。

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

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

在添加 functional: true 以後,須要更新咱們的錨點標題組件的渲染函數,爲其增長 context 參數,並將 this.$slots.default 更新爲 context.children,而後將 this.level 更新爲 context.props.level

爲何要用函數式組件?

因爲函數式組件沒有生命週期、無狀態、沒有實例,那就意味着它渲染速度更快,開銷更低,同時由於函數式組件只是函數,咱們能夠在函數中實現一些咱們須要的邏輯。

何時使用函數式組件?

在我看來函數式組件更像是一箇中間件的角色,因此它比較常見的應用場景就是做爲包裝組件。舉例來講咱們有一個組件可能渲染多個組件中的一個,須要根據傳遞的參數來決定渲染那個組件,咱們來看一段代碼。

export default { 
  functional: true, 
  render: function (createElement, context) { 
    return createElement(context.props.domType, context.data, context.children); 
  } 
}複製代碼

<template>
  <div>
    <functionnalScript domType="model" style="width:100%"></functionnalScript>
  </div>
</template>

<script>
import customizeTable from "../components/customizeTable.vue";
import model from "../components/model.vue";
import functionnalScript from "../components/functionnal.js";
export default {
  name: "pageA",
  components: { 
      customizeTable, 
      model, 
      functionnalScript: functionnalScript 
  }
};
</script>複製代碼
//模板的函數式組件 functional.vue

<template functional>
  <div>
    <button v-bind="data.attrs" v-on="listeners">
      <slot />
    </button>
  </div>
</template>複製代碼

正常調用的時候父頁面咱們有customizeTable組件和model組件咱們能夠經過在一個屬性domType指定functionnalScript顯示對應的組件,並且能夠在多處複用。

還記得咱們前面是否是講了屬性透傳,其實經過函數式組件也能夠實現屬性透傳,咱們來看模板的函數式組件functional組件就實現了徹底透傳任何 attribute事件監聽器子節點等。

內聯模板

組件的模板通常都是在template選項內定義,而內聯模板在使用組件時,給組件標籤使用inline-template特性,組件就會把它的內容看成模板,而不是把它當內容分發,這讓模板更靈活。可是這也致使了一些問題,咱們先看代碼。

<script>
export default {
  name: "inlineChildren",
  data() {
    return { tip: "我是子組件的數據" };
  }
};
</script>
複製代碼

<template>
  <div>
    <inlineChildren inline-template>
      <div>
        <h3>{{tip}}</h3>
      </div>
    </inlineChildren>
  </div>
</template>
<script>
import inlineChildren from "../components/inlineChildren.vue";
export default {
  name: "pageA",
  components: { inlineChildren },
  data() {
    return { tip: "我是父組件的數據" };
  }
};
</script>複製代碼

inline-template的存在讓inlineChildren標籤內部內容再也不做爲slot(分發內容),而是做爲inlineChildren組件的template,而且這部份內容所在的上下文,是子組件的,並非父組件的。因此除非特殊場景(我是沒遇到過~~),其它適合正如官網所說的,inline-template 會讓模板的做用域變得更加難以理解。因此做爲最佳實踐,請在組件內優先選擇 template 選項或 .vue 文件裏的一個 <template> 元素來定義模板。

遞歸組件

什麼是遞歸組件?

在組件本身的模板中調用自身就是咱們所謂的遞歸組件。(注意須要經過組件的name調用)

使用場景有哪些?

遞歸組件在咱們平常開發中的發揮空間仍是很大的,好比咱們的菜單,評論等有層級的功能均可以使用到它。咱們來看一個菜單的例子:

<template>
  <div>
    <div v-for="item in menuList">
      <div class="hasChildren">{{item.name}}
        <Icon v-if="item.children" type="ios-arrow-down" />
      </div>
      <side-menu v-if="item.children" :menuList="item.children"></side-menu>
    </div>
  </div>
</template>

<script>
export default {
  name: "SideMenu",
  props: {
    menuList: {
      type: Array,
      default() {
        return [];
      }
    }
  }
};
</script>

<style>
.hasChildren {
  background: #999;
  color: #fff;
  height: 40px;
  line-height: 40px;
}
</style>複製代碼

<template>
  <div>
    <sideMenu :menuList="menuList"></sideMenu>
  </div>
</template>
<script>
import sideMenu from "../components//side-menu.vue";
export default {
  name: "pageA",
  components: { sideMenu },
  data() {
    return {
      menuList: [
        { name: "side1", children: [{ name: "side1-1" }, { name: "side1-2" }] },
        {
          name: "side2",
          children: [
            { name: "side2-1" },
            { name: "side2-2", children: [{ name: "side2-2-1" }] }
          ]
        }
      ]
    };
  }
};
</script>複製代碼

咱們能夠看到在sideMenu組件內部調用了它自己,這就是遞歸組件的基本使用方式。在更加複雜一點的狀況就是有2個組件A和B,他們之間互有調用關係。這時會出現一個問題,究竟是先有雞仍是先有蛋。


模塊系統發現它須要 A,可是首先 A 依賴 B,可是 B 又依賴 A,可是 A 又依賴 B,如此往復。這變成了一個循環,不知道如何不通過其中一個組件而徹底解析出另外一個組件,解決方法有2個。

//方法一 在A組件
beforeCreate: function () {
  this.$options.components.TreeFolderContents = require('./B.vue').default
}
//方法二 異步加載B組件
components: {
  B: () => import('./B.vue')
}複製代碼

v-once低開銷靜態組件

若是你的組件中出現了大量靜態內容,那麼它們可能致使你的組件渲染速度變慢,這時候你能夠在根元素上添加 v-once attribute 以確保這些內容只計算一次而後緩存起來。

<template>
  <div v-once> {{hugeData}} </div>
</template>

<script>
export default {
  name: "vOnce",
  data() {
    return { hugeData: "huge Data..." };
  },
  mounted() {
    setTimeout(() => {
      this.hugeData = "change Data...";
    }, 100);
  }
};
</script>複製代碼

你會發現hugeData一直是huge Data...沒有變,由於v-once致使這個模板渲染後數據發生變化沒法正常更新,因此考慮到其餘開發者對於這個屬性並不必定了解,若是不是特別須要儘可能不要使用。

全局函數調用組件

正常的組件調用的過程:

<testModule></testModule>
import TestModule from "./TestModule "

export default {  components: { testModule  }};複製代碼

若是你須要在多處使用這個組件,那麼意味着每次調用你都須要重複上面的步驟,那麼有沒有更加方便的調用方式呢?console.logalert不陌生吧,有時候咱們也但願能經過這種方式調用一個組件,不在須要把組件寫入到節點中。例如一個提示框組件,可能須要在各類時候喚醒它,而且提示對應內容,咱們經過代碼來實現這個tip組件。

//extend.js
import Vue from 'vue'import Tip from './tip.vue';
//使用基礎 Vue 構造器,建立一個包含tip組件的「子類」    
Tip.newInstance = (props) => { 
  const tip = Vue.extend(Tip); 
  const messageInstance = new tip({ propsData: props }); 
  const div = document.createElement('div'); 
  document.body.appendChild(div); messageInstance.$mount(div) 
}
export default Tip.newInstance複製代碼

//main.js
import tip from 'extend.js'Vue.prototype.$tip=tip;
//調用
this.$tip({});複製代碼

這樣咱們就能實現經過調用一個函數this.$tip({})來調用Tip組件了。你能夠看到咱們的核心思想就是建立一個包含咱們須要組件的vue實例,而後將這個實例掛載到一個新建立的div上。若是咱們可能屢次調用這個函數,那麼頁面中將會被添加入多個div,這並非咱們所但願,因此咱們還能夠再優化一下。

<template>
  <div>
    <div v-for="item in tips">
      <tip v-bind="item"></tip>
    </div>
  </div>
</template>
<script>
let i = 0;
const now = new Date().getTime();
function getUid() {
  return "tip" + now + "-" + i++;
}
import tip from "./tip.vue";
export default {
  name: "tipList",
  data() {
    return { tips: [] };
  },
  components: { tip },
  methods: {
    add(options) {
      let name = getUid;
      let _options = Object.assign({ name: name }, options);
      this.tips.push(_options);
    }
  }
};
</script>複製代碼

import Vue from 'vue'import Tip from './tipList.vue';
let tip, initialization;
//使用基礎 Vue 構造器,建立一個包含tip組件的「子類」
Tip.newInstance = () => {  
  tip = Vue.extend(Tip);  
  const messageInstance = new tip();  
  const div = document.createElement('div'); 
  document.body.appendChild(div); 
  messageInstance.$mount(div)      
  return {          
    prompt(props) {              
      messageInstance.show(props);          
    }      
  }
}
initialization=function(){   
  if(!tip){        
    tip=Tip.newInstance()    
  }
  return tip
}
export default initialization
//main.js
import tip from 'extend.js'
Vue.prototype.$tip=tip;
//調用
this.$tip.prompt({});複製代碼

咱們經過一個組件tipList.vueprompt方法實現屢次調用的tip組件的需求,每次調用tip組件等於向tips數組中push一個對象這樣就會多渲染出一個tip組件。

總結

但願看完以後,你也能在你的組件中使用上它們,讓你的組件能夠減小一些沒必要要的邏輯。做者可能有一些理解不對的地方但願大佬能夠再評論裏提出來,咱們一塊兒學習一塊兒進步。

其餘文章傳送門:

  1. 基於vue實現web端超大數據量表格
  2. 百度地圖-大數據量點實時更新
相關文章
相關標籤/搜索