vue components 組件封裝

原文鏈接:https://blog.csdn.net/qq_33988065/article/details/85124428

什么叫做組件化

vue.js 有兩大法寶固额,一個是數(shù)據(jù)驅(qū)動费就,另一個就是組件化祝辣,那么問題來了嘉竟,什么叫做組件化二蓝,為什么要組件化?接下來我就針對這兩個問題一一解答惩猫,所謂組件化霉颠,就是把頁面拆分成多個組件,每個組件依賴的 CSS事期、JS滥壕、模板、圖片等資源放在一起開發(fā)和維護兽泣。 因為組件是資源獨立的绎橘,所以組件在系統(tǒng)內(nèi)部可復(fù)用,組件和組件之間可以嵌套唠倦,如果項目比較復(fù)雜金踪,可以極大簡化代碼量,并且對后期的需求變更和維護也更加友好牵敷。

如何進行組件化開發(fā)
先看下圖:

圖片.png

這是 vue.js 中的一個報錯胡岔,原因是使用了一個未經(jīng)注冊的組件 lx-xxx ,這個報錯告訴我們一個道理:使用自定義組件之前必須注冊枷餐。
那么如何注冊一個組件呢靶瘸? Vue.js 提供了 2 種組件的注冊方式,全局注冊局部注冊毛肋。

  1. 全局注冊

在 vue.js 中我們可以使用 Vue.component(tagName, options) 進行全局注冊怨咪,例如

Vue.component('my-component', {
  // 選項
})
  1. 局部注冊

Vue.js 也同樣支持局部注冊,我們可以在一個組件內(nèi)部使用 components 選項做組件的局部注冊润匙,例如:

import HelloWorld from './components/HelloWorld'

export default {
  components: {
    HelloWorld
  }
}

區(qū)別:全局組件是掛載在 Vue.options.components 下诗眨,而局部組件是掛載在 vm.$options.components 下,這也是全局注冊的組件能被任意使用的原因孕讳。
組件化開發(fā)必備知識

所謂工欲善其事匠楚,必先利其器,在正式開發(fā)一個組件之前厂财,我們先要掌握一些必備的知識芋簿,這里我只會簡單介紹一下,詳情參考官網(wǎng)璃饱。

name

組件的名稱与斤,必填

<lx-niu/>
<lx-niu></lx-niu/>

name: 'lxNiu'

js 中使用駝峰式命令,HTML 使用kebab-case命名。

props

組件屬性撩穿,用于父子組件通信磷支,可通過this.msg訪問

<div>{{msg}}</div>

props: {
  msg: {
    type: String,
    default: ''
  }
}

show: Boolean // 默認false

msg: [String, Boolean]  // 多種類型

computed

處理data或者props中的屬性,并返回一個新屬性

<div>{{newMsg}}</div>

computed: {
  newMsg() {
    return 'hello ' + this.msg
  }
},

注:因為props食寡,data和computed在編譯階段都會作為vm的屬性合并齐唆,所以不可重名

render

用render函數(shù)描述template

<lx-niu tag='button'>hello world</lx-niu>

<script type="text/javascript">
  export default {
    name: 'lxNiu',
    props: {
      tag: {
        type: String,
        default: 'div'
      },
    },
    // h: createElement
    render(h) {
      return h(this.tag,
        {class: 'demo'}, 
        this.$slots.default)
    }
  }
</script>

render 中的 h 其實就是 createElement,它接受三個參數(shù)冻河,返回一個 vnode
h 參數(shù)解釋:
args1: {string | Function | Object} 用于提供DOM的html內(nèi)容
args2: {Object} 設(shè)置DOM樣式箍邮、屬性、綁定事件之類
args3: {array} 用于設(shè)置分發(fā)的內(nèi)容

注:vue編譯順序: template–> compile --> render --> vnode --> patch --> DOM

slot

分發(fā)內(nèi)容叨叙,有傳入時顯示锭弊,無傳入 DOM 時顯示默認,分為無名和具名兩種擂错,this. solts.default默 認 指 向 無 名 插 槽 味滞, 多 個 s l o t 時 用 法 this. solts.default默認指向無名插槽,多個slot時用法this.slots.name

<lx-niu>
  <div slot='header'>header</div>
  <div class="body" slot='body'>
    <input type="text">
  </div>
  <div slot='footer'>footer</div>

  <button class='btn'>button</button>
</lx-niu>

<template>
  <div>
    <slot name='header'></slot>
    <slot name='body'></slot>
    <slot name='footer'></slot>
    <slot></slot>
  </div>
</template>

<script>
  export default {
    name: 'lxNiu',
    mounted() {
      this.$slots.header // 包含了slot="foo"的內(nèi)容
      this.$slots.default // 得到一個vnode钮呀,沒有被包含在具名插槽中的節(jié)點剑鞍,這里是button
    }
  }
</script>

class

定義子組件的類名

// 父組件
<lx-niu round type='big'/>

// 子組件
<div :class="[
  type ? 'lx-niu__' + type : '',
  {'is-round': round},
]">控制</div>

//真實DOM
<div class='lx-niu__big is-round'>hello</div>

其他屬性

$attrs

v-bind="$attrs" 將除class和style外的屬性添加到父組件上,如定義input:

<input v-bind="$attrs">

v-once

組件只渲染一次爽醋,后面即使數(shù)據(jù)發(fā)生變化也不會重新渲染蚁署,比如例子中val不會變成456

<template>
  <div>
    <button @click="show = !show">button</button>
    <button @click="val = '456'">button</button>
    <div v-once v-if="show">
      <span>{{val}}</span>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return{
      show: false,
      val: '123'
    }
  },
};
</script>

mixins

// mixin.js
export default {
  data() {
    return{
       msg: 'hello world'
    }
  },
  methods: {
    clickBtn() {
      console.log(this.msg)
    }
  },
}

// index.vue
<button @click="clickBtn">button</button>

import actionMixin from "./mixin.js";
export default {
  methods: {},
  mixins: [actionMixin]
}

實例演示

比如我們要注冊一個 lx-button 這樣一個組件,那么目錄和偽代碼如下:


圖片.png

index.vue

<template>
  <button>lxButton</button>
</template>

<script>
export default {
  name: 'lxButton'
}
</script>

index.js

import lxButton from './src/index'

lxButton.install = (Vue) => {
  Vue.component(lxButton.name, lxButton)
}

export default lxButton

其中 install 是 Vue.js 提供了一個公開方法蚂四,這個方法的第一個參數(shù)是 Vue 構(gòu)造器光戈,第二個參數(shù)是一個可選的選項對象筐带。
MyPlugin.install = function (Vue, options){}

watch-彈窗實現(xiàn)原理

<button @click="dialogVisible = true">顯示</button>
<lx-niu :visible.sync="dialogVisible"></lx-niu>

<script>
  export default {
    data() {
      return {
        dialogVisible: false
      }
    },
    watch: {
      dialogVisible(val) {
        console.log('father change', val)
      }
    }
  }
</script>

定義組件

<template>
  <div v-show="visible">
    <button @click="hide">關(guān)閉</button>
  </div>
</template>

<script>
  export default {
    name: 'lxNiu',
    props: {
      visible: Boolean
    },
    watch: {
      visible(val) {
        console.log('child change:', val)
      }
    },
    methods: {
      hide() {
        this.$emit('update:visible', false);
      }
    },
  }
</script>

點擊父組件中的顯示按鈕暇仲,改變傳入子組件中的值,點擊子組件中的關(guān)閉桂敛,改變父組件中值跷睦。

注:@click=“dialogVisible = true” 點擊時將dialogVisible的值改為true
注::visible.sync: 雙向數(shù)據(jù)綁定筷弦,配合update:visible使用,實現(xiàn)子組件修改父組件中的值

col組件實例

export default {
  name: 'ElCol',

  props: {
    span: {
      type: Number,
      default: 24
    },
    tag: {
      type: String,
      default: 'div'
    },
    offset: Number,
    pull: Number,
    push: Number,
    xs: [Number, Object],
    sm: [Number, Object],
    md: [Number, Object],
    lg: [Number, Object],
    xl: [Number, Object]
  },

  computed: {
    gutter() {
      let parent = this.$parent;
      while (parent && parent.$options.componentName !== 'ElRow') {
        parent = parent.$parent;
      }
      return parent ? parent.gutter : 0;
    }
  },
  render(h) {
    let classList = [];
    let style = {};

    if (this.gutter) {
      style.paddingLeft = this.gutter / 2 + 'px';
      style.paddingRight = style.paddingLeft;
    }

    ['span', 'offset', 'pull', 'push'].forEach(prop => {
      if (this[prop] || this[prop] === 0) {
        classList.push(
          prop !== 'span'
            ? `el-col-${prop}-${this[prop]}`
            : `el-col-${this[prop]}`
        );
      }
    });

    ['xs', 'sm', 'md', 'lg', 'xl'].forEach(size => {
      if (typeof this[size] === 'number') {
        classList.push(`el-col-${size}-${this[size]}`);
      } else if (typeof this[size] === 'object') {
        let props = this[size];
        Object.keys(props).forEach(prop => {
          classList.push(
            prop !== 'span'
              ? `el-col-${size}-${prop}-${props[prop]}`
              : `el-col-${size}-${props[prop]}`
          );
        });
      }
    });

    return h(this.tag, {
      class: ['el-col', classList],
      style
    }, this.$slots.default);
  }
};

col組件使用render函數(shù)抑诸,而不是template來實現(xiàn)組件烂琴,原因有兩個:

1.該組件有大量的類判斷,如果采用template代碼比較冗余哼鬓,使用js代碼更加簡潔
2.直接render描述性能更好

button組件實例

<template>
  <button
    class="el-button"
    @click="handleClick"
    :disabled="buttonDisabled || loading"
    :autofocus="autofocus"
    :type="nativeType"
    :class="[
      type ? 'el-button--' + type : '',
      buttonSize ? 'el-button--' + buttonSize : '',
      {
        'is-disabled': buttonDisabled,
        'is-loading': loading,
        'is-plain': plain,
        'is-round': round,
        'is-circle': circle
      }
    ]"
  >
    <i class="el-icon-loading" v-if="loading"></i>
    <i :class="icon" v-if="icon && !loading"></i>
    <span v-if="$slots.default"><slot></slot></span>
  </button>
</template>
<script>
  export default {
    name: 'ElButton',

    inject: {
      elForm: {
        default: ''
      },
      elFormItem: {
        default: ''
      }
    },

    props: {
      type: {
        type: String,
        default: 'default'
      },
      size: String,
      icon: {
        type: String,
        default: ''
      },
      nativeType: {
        type: String,
        default: 'button'
      },
      loading: Boolean,
      disabled: Boolean,
      plain: Boolean,
      autofocus: Boolean,
      round: Boolean,
      circle: Boolean
    },

    computed: {
      _elFormItemSize() {
        return (this.elFormItem || {}).elFormItemSize;
      },
      buttonSize() {
        return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
      },
      buttonDisabled() {
        return this.disabled || (this.elForm || {}).disabled;
      }
    },

    methods: {
      handleClick(evt) {
        this.$emit('click', evt);
      }
    }
  };
</script>

icon組件

1.組件封裝


圖片.png
<template>
  <svg
    :class="getClassName"
    :width="width"
    :height="height"
    aria-hidden="true">
    <use :xlink:href="getName"></use>
  </svg>
</template>

<script>
  export default {
    name: 'icon-svg',
    props: {
      name: {
        type: String,
        required: true
      },
      className: {
        type: String
      },
      width: {
        type: String
      },
      height: {
        type: String
      }
    },
    computed: {
      getName () {
        return `#icon-${this.name}`
      },
      getClassName () {
        return [
          'icon-svg',
          `icon-svg__${this.name}`,
          this.className && /\S/.test(this.className) ? `${this.className}` : ''
        ]
      }
    }
  }
</script>

<style>
  .icon-svg {
    width: 1em;
    height: 1em;
    fill: currentColor;
    overflow: hidden;
  }
</style>

2.全局注冊使用


圖片.png

svg文件夾下是靜態(tài)svg圖片


圖片.png
/**
 * 字體圖標, 統(tǒng)一使用SVG Sprite矢量圖標(http://www.iconfont.cn/)
 *
 * 使用:
 *  1. 在阿里矢量圖標站創(chuàng)建一個項目, 并添加圖標(這一步非必須, 創(chuàng)建方便項目圖標管理)
 *  2-1. 添加icon, 選中新增的icon圖標, 復(fù)制代碼 -> 下載 -> SVG下載 -> 粘貼代碼(重命名)
 *  2-2. 添加icons, 下載圖標庫對應(yīng)[iconfont.js]文件, 替換項目[./iconfont.js]文件
 *  3. 組件模版中使用 [<icon-svg name="canyin"></icon-svg>]
 *
 * 注意:
 *  1. 通過2-2 添加icons, getNameList方法無法返回對應(yīng)數(shù)據(jù)
 */
import Vue from 'vue'
import IconSvg from '@/components/icon-svg'
import './iconfont.js'
//全局注入
Vue.component('IconSvg', IconSvg)

const svgFiles = require.context('./svg', true, /\.svg$/)
const iconList = svgFiles.keys().map(item => svgFiles(item))

export default {
  // 獲取圖標icon-(*).svg名稱列表, 例如[shouye, xitong, zhedie, ...]
  getNameList () {
    return iconList.map(item => item.default.id.replace('icon-', ''))
  }
}

在main.js 中引入

圖片.png

3.在頁面中使用
name的值监右,為svg下面的靜態(tài)svg圖片名稱

   <icon-svg name="zhedie"></icon-svg>
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市异希,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖称簿,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扣癣,死亡現(xiàn)場離奇詭異,居然都是意外死亡憨降,警方通過查閱死者的電腦和手機父虑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來授药,“玉大人士嚎,你說我怎么就攤上這事』谶矗” “怎么了莱衩?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長娇澎。 經(jīng)常有香客問我笨蚁,道長,這世上最難降的妖魔是什么趟庄? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任括细,我火速辦了婚禮,結(jié)果婚禮上戚啥,老公的妹妹穿的比我還像新娘奋单。我一直安慰自己,他們只是感情好猫十,可當我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布辱匿。 她就那樣靜靜地躺著,像睡著了一般炫彩。 火紅的嫁衣襯著肌膚如雪匾七。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天江兢,我揣著相機與錄音昨忆,去河邊找鬼。 笑死杉允,一個胖子當著我的面吹牛邑贴,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播叔磷,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼拢驾,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了改基?” 一聲冷哼從身側(cè)響起繁疤,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后稠腊,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體躁染,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年架忌,在試婚紗的時候發(fā)現(xiàn)自己被綠了吞彤。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡叹放,死狀恐怖饰恕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情井仰,我是刑警寧澤埋嵌,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站糕档,受9級特大地震影響莉恼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜速那,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一俐银、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧端仰,春花似錦捶惜、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至鹤竭,卻和暖如春踊餐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背臀稚。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工吝岭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人吧寺。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓窜管,卻偏偏與公主長得像,于是被迫代替她去往敵國和親稚机。 傳聞我的和親對象是個殘疾皇子幕帆,可洞房花燭夜當晚...
    茶點故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容