vue成長之路+實戰+Vue2+VueRouter2+webpack(二)vue組件入門第一節

推薦我的vue教程:VUE系列教程目錄

上篇講解了vue-router路由入門,現在講講關于vue組件的內容。
如果你們有使用過element組件的話,他就是以vue組件的形式進行封裝的,在講解組件之前我們需要知道vue是數據驅動的,它的一切依賴于數據,我們應該根據數據的不同來進行相關的處理,在這一前提下才能形成vue框架的思考模式。
在了解這一模式的前提下我們來看看vue組件是個什么東西。

什么是VUE組件?

在github上,各位請使用git拉一下項目:vuetemplate。不會使用git拉文件的請去GitHub上下載壓縮包。

/src/page/components下是有關組件的代碼

我們打開vue官網的組件API,可以簡單瀏覽,對于新手來說這個API的閱讀有時很晦澀,或者跟實際應用有些許差別。于是我的講解是建立在對這個API的補充說明與簡單化的,所以你們最好還是看看這個。

VUE組件可以擴展 HTML 元素,封裝可重用的代碼。在較高層面上,VUE組件是自定義元素, Vue.js 的編譯器為它添加特殊功能。在有些情況下,組件也可以是原生 HTML 元素的形式,以 is 特性擴展。

注冊組件

在注冊組件時我們有兩種方式,第一種注冊全局組件,第二種是局部使用組件,對于那些應用多的組件來說全局無疑是最好的選擇,對于變數太大,應用不多且在統一目錄下的局部使用是我們想要的。

全局注冊:

要注冊一個全局組件,你可以使用 Vue.component(tagName, options)。

// 模板
Vue.component('my-component', {
  // 選項
})

一個模板并不能說明什么,實例才能讓人看的更明白:

// html
<my-component></my-component>

// 注冊
Vue.component('my-component', {
  template: '<div>A custom component!</div>'
})

組件的簡單使用就是這個樣子,即<my-component></my-component>最后變成了<div>A custom component!</div>。可是,實際操作中我們并不會這么弱智的使用,組件的復雜度遠遠不是這個樣子的。

局部注冊

要注冊一個局部組件,你可以使用 Vue的components屬性。

<script>
export default {
  components: {},
  data () {
    return {
    }
  }
}
</script>

例子才是真理:

// html
<my-component></my-component>

<script>
var vuecomponent = {
  template: '<div>A custom component!</div>'
}
export default {
  components: {
    'my-component': vuecomponent
  },
  data () {
    return {
    }
  }
}
</script>

DOM渲染的局限

在html中我們知道有些標簽的孩子是固定的比如<ul> ,<ol>,<table> ,<select>限制了能被它包裹的元素,例如ul里面只能包裹li。同時,一些像 <option> 這樣的元素只能出現在某些其它元素內部。

在自定義組件中使用這些受限制的元素時會導致一些問題,例如:

<table>
  <my-row>...</my-row>
</table>

自定義組件 <my-row> 被認為是無效的內容,因此在渲染的時候會導致錯誤。那我們怎么解決呢?變通的方案是使用特殊的 is 屬性:

<table>
  <tr is="my-row"></tr>
</table>

data 必須是函數

組件中必須是函數,通過Vue構造器傳入的各種選項大多數都可以在組件里用。 data 是一個例外,它必須是函數。

// html
<simple-counter></simple-counter>
<script>
export default {
  components: {
    'simple-counter': {
      template: '<el-button size="small" v-on:click="counter += 1">{{ counter }}</el-button>',
      // 技術上 data 的確是一個函數了,因此 Vue 不會警告,
      // 但是我們返回給每個組件的實例的卻引用了同一個data對象
      data: function () {
        return {
            counter: 0
        }
      }
    }
  },
  data () {
    return {
    }
  }
}
</script>

這里有很多人看蒙了,問起初不是這樣寫嗎???

data: {
    counter: 0
}

這里別問,我起初也沒看懂,你可以這樣想:把所有關于data的數據引入變為data () {}就可以了。
為何我會這樣說?我曾經在上一篇文章里說:vue路由的本質是根據url的不同來進行組件的各種切換罷了。而組件的data必須是數據,所以你看我寫的代碼里關于.vue文件的都使用的是這種結構:

<template>
  <div>
  </div>
</template>
<script>
export default {
  data () {
    return {
    }
  }
}
</script>

如果不明白你可以看上面的<simple-counter>組件的例子,其最終的本質變成了:

<template>
  <div>
        <el-button size="small" v-on:click="counter += 1">{{ counter }}</el-button>
  </div>
</template>
<script>
export default {
  data () {
    return {
        counter: 0
    }
  }
}
</script>

其實你只要使用vue-router路由你的.vue文件的結構只能變成這樣:

<template>
  <div>
  </div>
</template>
<script>
export default {
  data () {
    return {
        //  這里寫基礎數據
    }
  }
}
</script>

構成組件

組件意味著協同工作,引用組件的地方叫父組件,如<simple-counter></simple-counter>,組件的內容則被成為子組件。

通常父子組件會是這樣的關系:組件 A 在它的模版中使用了組件 B 。它們之間必然需要相互通信:父組件要給子組件傳遞數據,子組件需要將它內部發生的事情告知給父組件。然而,在一個良好定義的接口中盡可能將父子組件解耦是很重要的。這保證了每個組件可以在相對隔離的環境中書寫和理解,也大幅提高了組件的可維護性和可重用性。

在 Vue.js 中,父子組件的關系可以總結為 props down, events up 。父組件通過 props 向下傳遞數據給子組件,子組件通過 events 給父組件發送消息。看看它們是怎么工作的?如圖:

父子組件數據交互

父子組件的交互的原理這個具體呢?

父子組件的交互

props的單向流

組件實例的作用域是孤立的。這意味著不能(也不應該)在子組件的模板內直接引用父組件的數據。要讓子組件使用父組件的數據,我們需要通過子組件的props選項。

我們在開發中,一個單文件組件的.vue文件的基本構成是這樣的:

<template>
  <div>
  </div>
</template>

<script>
export default {
  // el: '',
  props: {}, // 父到子傳參
  components: {}, // 組件接收
  mixins: [], // 混合
  data () { // 基礎數據
    return {
      //
    }
  },
  created () {}, // 創建周期
  watch: {}, // 狀態過渡
  methods: {}, // 方法存放的地方
  computed: {}, // 計算屬性存放的地方
  filters: {}, // 過濾
  directives: {} // 指令
}
</script>

可是常用的很少,父組件給子組件傳值使用的就是props選項

props選項可以接受兩種模式的參數:

第一種固定的屬性:如這樣message="hello!傳一個普通的字符;
第二種動態屬性:如這樣v-bind:myMessage="this.message"傳一個變量,其可以簡化為:myMessage="this.message"

例子如下:(父子組件在同一目錄下,子組件-child.vue)

// 父組件
// HTML
<child message="HELLO!" :my-message="this.message"></child>

// script

<script>
import child from './child.vue'
export default {
  components: {
    child: child
    }
  },
  data () {
    return {
      message: '你猜'
    }
  }
}
</script>

// 子組件
<template>
  <div>
    <div>{{message}}</div>
    <div v-text="myMessage"></div>
  </div>
</template>

<script>
export default {
  props: {
    message: null,
    myMessage: null
  }, // 父到子傳參
  data () { // 基礎數據
    return {
      //
    }
  },
  created () {}, // 創建周期
  watch: {}, // 狀態過渡
  methods: {} // 方法存放的地方
}
</script>

prop 是單向綁定的:當父組件的屬性變化時,將傳導給子組件,但是不會反過來。這是為了防止子組件無意修改了父組件的狀態。

另外,每次父組件更新時,子組件的所有 prop 都會更新為最新值。這意味著你不應該在子組件內部改變 prop 。如果你這么做了,Vue 會在控制臺給出警告。

但是有時我們就是需要修改,怎么辦?(老子需求)

一般情況下我們修改通常是這兩種原因:

  1. prop 作為初始值傳入后,子組件想把它當作局部數據來用;
  2. prop 作為初始值傳入,由子組件處理成其它數據輸出。

對于第一種情況我們可以李代桃僵,即用另外一個變量替代它,把它的值賦給那個變量:

data () { // 基礎數據
    return {
      //
      counter: this.message
    }
  }

第二種情況可以定義一個計算屬性,處理 prop 的值并返回:

computed: {
    messagetoLowerCase: function () {
      return this.message.trim().toLowerCase()
    }
}
注意:使用字面量語法傳遞數值時,必須使用動態props,即如這樣`v-bind:number="1"`

props驗證

我們可以為組件的 props 指定驗證規格。如果傳入的數據不符合規格,Vue 會發出警告。當組件給其他人使用時,這很有用。

要指定驗證規格,需要用對象的形式,而不能用字符串數組:(修改上面的例子)

// html
<child message="HELLO!" :my-message="this.message" :number="11"></child>

// 子組件props
props: {
    message: String,
    myMessage: {
      type: String,
      required: true
    },
    number: {
      validator: function (value) {
        return value > 10
      }
    }
  }

驗證規格模板:

props: {
    // 基礎類型檢測 (`null` 意思是任何類型都可以)
    propA: Number,
    // 多種類型
    propB: [String, Number],
    // 必傳且是字符串
    propC: {
      type: String,
      required: true
    },
    // 數字,有默認值,如果你沒有傳則以默認為準
    propD: {
      type: Number,
      default: 100
    },
    // 數組或對象的默認值應當由一個工廠函數返回
    propE: {
      type: Object,
      default: function () {
        return { message: 'hello' }
      }
    },
    // 自定義驗證函數
    propF: {
      validator: function (value) {
        return value > 10
      }
    }
  }

自定義事件向父組件傳值

事件$on與$emit

我們知道,父組件是使用 props 傳遞數據給子組件,但如果子組件要把數據傳遞回去,應該怎樣做?那就是自定義事件!

每個 Vue 實例都實現了事件接口(Events interface),即:

使用 $on(eventName) 監聽事件
使用 $emit(eventName) 觸發事件

注意:Vue的事件系統分離自瀏覽器的EventTarget API。盡管它們的運行類似,但是$on 和 $emit 不是addEventListener 和 dispatchEvent 的別名。

你們一定很奇怪怎么用事件監聽來向父元素傳遞?

其實原理很簡單就是我們在父組件上通過v-on監聽子組件的事件,而子組件通過$emit(eventName) 觸發事件。例子如下:

// 父組件
<template>
  <div>
<child  v-on:onchild="inparent"></child>
</div>
</template>
<script>
import child from './child.vue'
export default {
  components: {
    child: child
  },
  data () {
    return {
    }
  },
  methods: {
    inparent () {
      alert('父組件響應了')
    }
  }
}
</script>
// 子組件
<template>
  <div>
    <el-button size="small" v-on:click="onparent">父組件響應吧!!!</el-button>
  </div>
</template>

<script>
export default {
  props: {},
  data () { // 基礎數據
    return {
    }
  },
  methods: {
    onparent () {
      this.$emit('onchild')
    }
  }
}
</script>

這個例子中,子組件給父組件傳值通過$emit('onchild'),觸發父組件的v-on:onchildv-on:onchild響應后執行inparent函數。但是就達到我們的目的了???

傳值,傳值,傳值,值呢?這個API里可沒說,那怎么辦呢?

很簡單按照程序工程師的思路來想,值肯定是這種模式:

this.$emit('onchild', 需要的值)
// 多個呢?
this.$emit('onchild', 需要的值1,需要的值2)

那接值呢?

onparent (需要的值1, 需要的值2) {
}

所以完整的模式應該是這樣的:

// 父組件
<template>
  <div>
<child  v-on:onchild="inparent"></child>
</div>
</template>
<script>
import child from './child.vue'
export default {
  components: {
    child: child
  },
  data () {
    return {
    }
  },
  methods: {
   inparent (childs, childrens) {
      alert('父組件響應了')
      console.log(childs)
      console.log(childrens)
    }
  }
}
</script>
// 子組件
<template>
  <div>
    <el-button size="small" v-on:click="onparent">父組件響應吧!!!</el-button>
  </div>
</template>

<script>
export default {
  props: {},
  data () { // 基礎數據
    return {
        childs: '我是孩子的值',
        childrens: '我是孩子的另一個值'
    }
  },
  methods: {
    onparent () {
      this.$emit('onchild', this.childs, this.childrens)
    }
  }
}
</script>
sync-修飾符

在一些情況下,我們可能會需要對一個 prop 進行『雙向綁定』。當一個子組件改變了一個 prop 的值時,這個變化也會同步到父組件中所綁定的值。這很方便,但也會導致問題,因為它破壞了『單向數據流』的假設。由于子組件改變 prop 的代碼和普通的狀態改動代碼毫無區別,當光看子組件的代碼時,你完全不知道它何時悄悄地改變了父組件的狀態。這在 debug 復雜結構的應用時會帶來很高的維護成本。

事實上,這正是 Vue 1.x 中的 .sync修飾符所提供的功能。但是VUE在 2.0 中移除了 .sync。后來在 2.3 VUE又重新引入了 .sync 修飾符。

// 父組件
<template>
  <div>
<child  :foo.sync="bar"></child>
</div>
</template>
<script>
import child from './child.vue'
export default {
  components: {
    child: child
  },
  data () {
    return {
          bar: 1
    }
  },
  watch: {
    bar: function () {
      console.log(this.bar)
    }
  }
}
</script>
// 子組件
<template>
  <div>
    <div>{{foo}}</div>
    <el-button size="small" v-on:click="onsync">改變foo</el-button>
  </div>
</template>

<script>
export default {
  props: {
      foo: null
  },
  data () { // 基礎數據
    return {}
  },
  methods: {
      onsync () {
         this.$emit('update:foo', this.foo + 1)
    }
  }
}
</script>

其實本質上VUE做到的只是:需要做的只是讓子組件改變父組件狀態的代碼更容易被區分。

即把<comp :foo="bar" @update:foo="val => bar = val"></comp>簡寫為<child :foo.sync="bar"></child>,不讓使用者在父元素上進行事件監聽了而已其他都是一樣的,它通過子組件傳值改變父組件,依賴props傳值把修改的父組件元素再傳回子組件而已。

小結:

父組件向子組件傳值通過props;子組件向父組件傳值,我們在父組件上通過v-on監聽子組件的事件,而子組件通過$emit(eventName) 觸發事件。

至此組件的基本知識就結束了,高深的組件有關的,下一節再說。

提示:在最近幾天我會慢慢附上VUE系列教程的其他后續篇幅,后面還有精彩敬請期待,請大家關注我的專題:web前端。如有意見可以進行評論,每一條評論我都會認真對待。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 這篇筆記主要包含 Vue 2 不同于 Vue 1 或者特有的內容,還有我對于 Vue 1.0 印象不深的內容。關于...
    云之外閱讀 5,079評論 0 29
  • 組件是vue最強大的功能之一,組件可以擴展 HTML 元素,封裝可重用的代碼。在較高層面上,組件是自定義元素, V...
    __越過山丘__閱讀 525評論 0 2
  • 此文基于官方文檔,里面部分例子有改動,加上了一些自己的理解 什么是組件? 組件(Component)是 Vue.j...
    陸志均閱讀 3,864評論 5 14
  • 下載安裝搭建環境 可以選npm安裝,或者簡單下載一個開發版的vue.js文件 瀏覽器打開加載有vue的文檔時,控制...
    冥冥2017閱讀 6,081評論 0 42
  • 三月暢想 三月,是一個幸福的季節。 從今天起,我決定做一個幸福...
    甘草子的簡書閱讀 321評論 0 0