組件化的思想
- 模塊化是一種思想,一種構建方式,把一種很復雜的事物拆分成一個一個的小模塊,然后通過某種特定的方式把這些小模塊組織到一起相互協作完成這個復雜的應用功能。
- 在 Vue 中,組件就是用來封裝視圖(HTML)的。組件思想就是把一個很大的復雜的 Web 頁面視圖拆分成一塊一塊的組件視圖,然后利用某種特定的方式把他們組織到一起完成完整的 Web 應用構建。
- HTML 結構
- CSS 樣式
- JavaScript 行為
- 為什么要把視圖組件化,優勢?
- 開發效率
- 可維護性
- 可重用性
- 便于分工
通過 Element 體會組件的威力
Element 是基于 Vue 開發的一個知名的第三方組件庫,它能幫助我們更加快速的構建應用。
Element 官網
使用:
- 安裝:`npm i element-ui -S``
- 引入 element-ui 組件庫的 CSS 樣式
- 導入 Vue
- 引入 element-ui 組件庫的 JavaScript 腳本
- 調用 element-ui 組件
組件是什么
組件在代碼中的直觀體現就是封裝了一個自定義的 HTML 標簽
使用組件
組件的定義方式分為兩種,全局定義和局部定義:
- 全局組件定義在全局,在任意組件中都能直接使用
- 局部組件定義在局部,只能在當前組件使用
- 建議把通用組件定義在全局,把不通用組件定義在局部
全局注冊
注冊:
基本形式:Vue.component('組件名字', 組件模板對象)
方式 一:
Vue.component('my-component',
Vue.extend({
template: '<div>A custom component!</div>'
})
)
new Vue({
el: '#app'
})
方式二:
Vue.component('my-component', {
template: '<div>A custom component!</div>'
})
new Vue({
el: '#app'
})
方式三:
<template id="tmpl">
<div>A custom component!</div>
</template>
<script>
Vue.component('my-component', {
template: '#tmpl'
})
new Vue({
el: '#app'
})
</script>
方式四:
<script type="text/x-template" id="tmpl">
<div>A custom component!</div>
</script>
<script>
Vue.component('my-component', {
template: '#tmpl'
})
var vm = new Vue({
el: '#app'
})
</script>
在模板中使用組件:
<div id="app">
<my-component></my-component>
</div>
渲染結果:
<div id="app">
<div>A custom component!</div>
</div>
局部注冊
不必把每個組件都注冊到全局。可以通過某個 Vue 實例/組件的實例選項 components
注冊,僅作用在其作用域中可用的組件:
注冊:
new Vue({
el: '#app'
components: {
// <my-component> 將只能在父組件模板中可用
// 鍵名就是組件名稱,值是一個對象,對象中配置組件的選項
'my-component': {
template: '<div>A custom component!</div>'
}
}
})
使用:
<div id="app">
<my-component></my-component>
</div>
- 組件一般分為兩種
- 通用的組件
- 不通用的業務組件
所以設計具體業務盡量定義成局部。不要污染全局
組件的模板
組件的模板 template 只能有有一個根元素,否則警告報錯:
Component template should contain exactly one root element.
組件定義 template 可以是字面量字符串或是一個定義了字面量字符串的變量,缺點是沒有高亮,內置在 JavaScript 中,寫起來麻煩。
X-Templates:template 可以寫在 script 標簽中,解決了高亮的問題,但是當組件數量多的時候,也麻煩。
-
以上方式都不好,我們最終的解決方案是使用 Vue 的
.vue
單文件組件來寫。但是要想使用這種方式必須結合 webpack 構建工具。
image.png
組件的復用
- 組件是可復用的 Vue 實例,可以有自己的 data、methods、computed、watch 等等選項
- 組件是獨立的作用域,不能訪問父組件的數據
- 組件的 data 必須是函數,函數中返回一個對象作為組件的 data。因此每個實例可以維護一份被返回對象的獨立的拷貝。
data: function () {
return {
count: 0
}
}
組件的組織
-
采用組件化構建方式,一個應用被一個根組件管理起來,根組件中嵌套了子組件,子組件還可以嵌套自己的子子組件。
組件樹
放到匿名自執行函數中,將某些代碼包裹起來可以實現塊級作用域的效果,減少全局變量的數量,減少命名沖突,在匿名自執行函數執行結束后變量就會被內存釋放掉,從而也會節省了內存。
圖解 Vue 組件化構建方式
從程序角度實現組件化應用構建的架構方式如下圖,這種方式的缺點是:
- 組件的模板沒有高亮
- 沒有模塊化之前,首頁一堆 script 標簽,比較麻煩
使用 vue 實例的 render 方法渲染組件
官網 render 選項:https://cn.vuejs.org/v2/api/#render
我們可以用 render 選項進行組件渲染。該渲染函數接收一個 createElement 方法作為第一個參數用來創建 VNode,與前面介紹的的組件渲染不同的是,使用 render 渲染會把 app 的 div 給覆蓋。所以也就只能進行一次模板替代。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="node_modules/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
</div>
<script>
var login = {
template: '<h1>這是登錄組件</h1>'
}
var vm=new Vue({
el:'#app',
render: function (createElement, context) {
return createElement(login)
}
});
</script>
</body>
</html>
組件通信
在 Vue 中,父子組件的關系可以總結為 prop 向下傳遞,事件向上傳遞。父組件通過 prop 給子組件下發數據,子組件通過事件給父組件發送消息。
父子組件通信:Props Down
- 在父組件中通過子組件標簽聲明屬性的方式傳遞數據。注意:只有
v-bind
才可以傳遞動態數據。
<child message="hello!"></child>
- 在子組件中聲明 props 接收父組件傳遞的數據,組件接收到 props 就可以像訪問 data 中的數據一樣,來訪問 props 中的數據并使用。
Vue.component('child', {
// 聲明 props
props: ['message'],
// 就像 data 一樣,prop 也可以再模板中使用
// 同樣也可以再 vm 實例中通過 this.message 來使用
template: '<span>{{ message }}</span>'
})
- Vue 組件通信原則:單向數據流
父組件數據的改變可以影響到子組件,但是子組件不要去修改父組件的數據。因為當你的組件嵌套過深的時候,在子組件中修改某個父組件的數據可能會讓你的應用數據流變得非常復雜而難以理解。
注意:在 JavaScript 中對象和數組是通過引用傳入的,所以對于一個數組或對象類型的 prop 來說,在子組件中改變這個對象或數組本身將會影響到父組件的狀態。引用類型數據雖然可以修改,但是不建議使用,因為這樣就違背了 Vue 組件的通信原則
思考:那子組件要改數據呢?子組件能不能把數據給父親呢,讓父組件自己去修改自己的數據呢?
父子組件通信:Event Up
父組件使用 prop 傳遞數據給子組件。但子組件怎么跟父組件通信呢?這個時候 Vue 的自定義事件系統就派的上用場了。
- 在子組件中調用
$emit()
方法發布一個事件
Vue.component('button-counter', {
template: `<button v-on:click="incrementCounter">{{ counter }}</button>`
data: function() {
return {
counter: 0
}
}
methods: {
incrementCounter: function () {
this.counter += 1
// 發布一個名字叫 increment 的事件
this.$emit('increment')
}
}
})
- 在父組件中提供一個子組件內部發布的事件處理函數
new Vue({
el: '#counter-event-example',
data: {
total: 0
},
methods: {
incrementTotal : function () {
this.total += 1
}
}
})
- 在子組件的模板的標簽上訂閱子組件內部發布的事件
<div id="counter-event-example">
<p>{{ total }}</p>
<!--訂閱子組件內部發布的 increment 事件,當子組件內部 $emit('increment')
發布的時候,就會調用到父組件中的 incrementTotal 方法-->
<button-counter @increment="incrementTotal"></button-counter>
</div>
非父子組件通信:Event Bus
專業組件通信:Vuex
Vuex 是 Vue 配套的公共數據管理工具,它可以把一些共享的數據,保存到 Vuex 中,方便整個程序的任何組件直接獲取或修改我們的公共數據。如果組件之間有共享的數據,可以直接掛載到 Vuex 中,而不必通過父子組件之間傳值。
Vuex 是一個全局的共享數據存儲區域,就相當于是一個數據的倉庫
vuex 的使用
配置vuex的步驟
- 運行 cnpm i vuex -S
- 導入包:
import Vuex from 'vuex'
- 注冊 vuex 到 vue 中:
Vue.use(Vuex)
- new Vuex.Store() 實例,得到一個 數據倉儲對象。
var store = new Vuex.Store({
state: {
// 專門用來存儲數據的
},
mutations: {
// 專門用來定義方法的
}
- 將 vuex 創建的 store 掛載到 VM 實例上, 只要掛載到了 vm 上,任何組件都能使用 store 來存取數據
})
import App from './App.vue'
const vm = new Vue({
el: '#app',
render: c => c(App),
store
- 操作共享的數據與方法。
- 如果在組件中想要訪問store 中的數據,只能通過
this.$store.state
來訪問。 - 如果組件想要調用 mutations 中的方法,只能使用
this.$store.commit('方法名')
。 - 如果要操作 store 中的 state 值,只能通過 調用 mutations 提供的方法,才能操作對應的數據,不推薦直接操作 state 中的數據,因為萬一導致了數據的紊亂,不能快速定位到錯誤的原因,因為,每個組件都可能有操作數據的方法。
- mutations 的 函數參數列表中,最多支持兩個參數:
- param1: 是 state 狀態
+param2: 通過 commit 提交過來的參數
要想傳遞多個參數,可以通過對象或者數組去傳遞。
- param1: 是 state 狀態
- Vuex 中除了 state 和 mutations 選項參數,還有 getters 選項參數,只負責對外提供數據,不修改數據(想要修改 state 中的數據用 mutations) 。
- getters 中的方法,和過濾器比較類似,因為過濾器和 getter 都沒有修改原數據,只是把原數據做了一層包裝,提供給調用者
- getters 正好也引用了這個數據,那么就會立即觸發 getters 重新求值。
- 組件訪問 getters 要用
this.$store.getters
來訪問
路由
什么是路由?
- 后端路由:對于普通的網站,所有的超鏈接都是URL地址,所有的URL地址都對應服務器上對應的資源。
- 前端路由:對于單頁面應用程序來說,主要通過URL中的hash(#號)來實現不同頁面之間的切換,同時,hash有一個特點:HTTP請求中不會包含hash相關的內容;所以,單頁面程序中的頁面跳轉主要用hash實現。
vue-router
vue-router官方文檔:https://router.vuejs.org/zh/
安裝:
npm install vue-router
引包:
<script src="node_modules/vue-router/dist/vue-router.js"></script>
箭頭函數綁定父級上下的 this
路由、和服務端交互、webpack