# 16. 單檔模組化
TIP
模組化的目的,在於將不同的功能、不同的流程分開,以便未來好管理。
# 摘要
關於 Component:
- 也有人稱為組件、元件、模組。
- 可以是按鈕、標題、輸入框...等網頁中所使用的物件。
- 目的在於將不同的功能、不同的流程分開,以便未來好管理。
- 包含了 HTML、CSS 和 JS。
使用 component 的方法:
- 建立檔案,寫一支 component 檔案。
- 檔名首字要大寫。
- 避免和 HTML 原有標籤相同。
- 副檔名為 ".vue"。
- 撰寫內容,建立三大區塊。
<script>
- 撰寫 JavaScript 程式碼。
- 包含這個 componemt 名稱、管理要從父組件接收的資料,以及
computed
、methods
等資料的處理。
<template>
- 撰寫 HTML 程式碼。
<style>
- 撰寫 CSS 程式碼。
- 可使用
scoped
屬性限制這些 CSS 只能用於此 componemt。 - 選擇器建議使用
id
或class
而非 HTML 標籤,以免效能變差。
- 到 App.vue 載入並掛載組件
- 將這個 component 用
import
匯入進來。 - 把 component 掛載到 Vue 的
components
屬性。
- 將這個 component 用
- 在 HTML 加入組件的自定義標籤
- 把 component 的自定義組件標籤放到 App.vue 的
<template>
中適當的位置。
- 把 component 的自定義組件標籤放到 App.vue 的
- 透過
props
可將資料從外部的父組件 (例如 App.vue) 傳遞到內部的子組件。- 方式一:資料放在 component 的自訂標籤。
- 方式二:資料放在
data
,綁定到 component 的自訂標籤。
- 透過
computed
屬性,使用set
和$emit
,可將子組件的值更新到父組件。- 方式一:子組件用
$emit("事件", 值)
,父組件在標籤上綁定事件,再用methods
進行處理。 - 方式二:子組件用
$emit("update:變數", 值)
,父組件在標籤上綁定的變數後方加上.sync
來讓更新同步。
- 方式一:子組件用
# Component 檔案內容
使用 Vue CLI 建立一個新的專案資料夾,例如: component-test。
vue create component-test
打開裡面的 "App.vue" 或 "HelloWorld.vue" 等副檔名為 "vue" 的檔案,這些都是用於這個專案的 component,裡面包含三大部分:
- template → HTML
- script → JavaScript
- style → CSS
以 "HelloWorld.vue" 為例:
# template
用於撰寫這個 component 所需要的 HTML 程式碼。
//- HelloWorld.vue
<template>
<div class="hello">
<h1>{{ msg }}</h1>
// 略...
</div>
</template>
# script
負責 JavaScript 程式碼,包含:
- 使用 ES6 的
export
語法 - 給予這個 componemt 名稱 (
name: 'HelloWorld'
) - 管理要從父組件接收的資料 (
props
) - 以及
computed
、methods
的處理
//- HelloWorld.vue
<script>
export default {
name: 'HelloWorld', // 這個 componemt 的名稱
props: { // 管理資料從父組件傳遞到這個子組件的屬性
msg: String // 資料的名稱為 msg,型別為字串 (String)
}
}
</script>
# style
<style>
則為 CSS 程式碼撰寫的區域。可使用 scoped
屬性限制這些 CSS 只能用於此 componemt。
如果 CSS 選擇器使用像下方這些 HTML 的標籤 (h3, ul, ...),效能較差。因此建議配合 HTML 標籤的 id
或 class
來使用。
//- HelloWorld.vue
/* 使用 scope 屬性,限制樣式影響的範圍 */
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
# Component 與 App.vue 的關係
"App.vue" 是主要 component ,也是其他 component 最後匯集的地方。
當我們寫好 component 時,需要到 "App.vue" 進行這些動作:
- 載入 component:用
import
載入進來。 - 掛載 component:將載入的 component 掛載到 Vue 的
components
屬性。
//- App.vue
<script>
// 將 component 用 import 載入進來
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'App',
components: {
HelloWorld // 掛載 component
}
}
</script>
然後,就可以用 <component></component>
或者 <component />
格式的自定義組件標籤,來將組件放到 HTML 主頁中。(裡面的 component
字樣可替換成自定義的組件標籤名稱。)
要傳到子組件的資料 (例如 msg
) 也會被放在子組件所屬的自定義標籤裡。
//- App.vue
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/> // 自定義組件標籤
</div>
</template>
以下則是用於 #app
的 CSS 樣式。
//- App.vue
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
# 建立單檔 component
像 "HelloWorld.vue" 這樣把 HTML、CSS、JavaScript 這三大部分都寫在同一個檔案裡,就是單檔 component。
以下新增一個標題模組 (TitleComponent.vue):
# 1. 建立新檔
檔名為 "TitleComponent.vue",需注意:
- 檔名首字要大寫。
- 避免和 HTML 原有標籤相同。
- 副檔名為 ".vue"。
# 2. 撰寫內容
先在新檔中建立好 <script>
、<template>
、<style>
三大區塊。然後分別在這些區塊內撰寫 "TitleComponent" 這個標題組件的內容。
//- TitleComponent.vue
<template>
<h1 class="title">{{ title }}</h1>
</template>
<script>
export default {
name: "titleComponent",
// 使用函式回傳 data 中的資料
data() {
return {
title: "This is title in TitleComponent"
};
}
};
</script>
<style scoped>
h1.title {
color: deeppink;
}
</style>
:::info
使用 data 函式回傳資料
需要注意的是,如果在 component 本體會用到 data
,要用函式 return
資料,而非用以往物件的形式來放資料。
這是因為 component 可以重複被利用、分別使用不同的資料。使用物件會讓所有的 component 都使用同樣的資料。
但以函式來回傳 data
,則每次註冊 component,就會回傳一個新的物件。
:::
# 3. 到 App.vue 載入並掛載組件
將 "TitleComponent" 載入到 "App.vue",並掛載到 Vue ,以使用這個組件。
//- App.vue
<script>
import HelloWorld from "./components/HelloWorld.vue";
import TitleComponent from "./components/TitleComponent.vue";
export default {
name: "App",
components: {
HelloWorld,
TitleComponent
}
};
</script>
# 4. 在 HTML 加入組件的自定義標籤
把 component 的自定義標籤,像一般的 HTML 標籤一樣,放到 App.vue 的 <template>
中適當的位置。
//- App.vue
<template>
<div id="app">
<TitleComponent />
<img alt="Vue logo" src="./assets/logo.png" />
<HelloWorld msg="Welcome to Your Vue.js App" />
</div>
</template>
# 5. 執行以觀看成果
終端機進入到專案資料夾,輸入 npm run serve
取得執行位置。將位置貼到瀏覽器開啟,就能看到執行結果。
如果沒有結束終端機的執行,則在檔案進行的任何變更,存檔後都會即時更新到瀏覽器畫面上。
# props 接收外部傳入資料
# 子組件 (內組件)
如果在網頁中有多個地方都會用到這個 component,但這些 component 的內容各不相同,顯示的資料希望由外部提供,則不會在 component 本身使用 data
屬性。
為了接收外部傳進來的資料,我們會使用 props
屬性來管理外部資料。
//- TitleComponent.vue
<script>
export default {
name: "titleComponent",
// data() {
// return {
// title: "This is title in TitleComponent"
// };
// }
props: { // 管理外部傳入的資料
title: { // 資料的名稱
type: String, // 傳入的資料類型
required: true // 有資料才會顯示此組件
}
}
};
</script>
required 屬性 在 "HelloWorld.vue" 裡面,資料為
msg: String
,只指定了資料的型別。 在這個例子,則為資料title
,多用了required
屬性,表示有資料時,才會顯示這個 component。
# 父組件 (外組件)
至於外部的父組件,則要給予資料。資料的呈現方式有兩種:
- 直接放在自定義標籤。例如:
<HelloWorld msg="Welcome to Your Vue.js App" />
。 - 將資料放在
data
函式回傳,並綁定到所需的自定義標籤中的屬性。
HTML
//- App.vue
<template>
<div id="app">
<TitleComponent :title="titleText" /> // 資料綁定到 title 屬性
<img alt="Vue logo" src="./assets/logo.png" />
<HelloWorld msg="Welcome to Your Vue.js App" /> // 資料直接放在自定義標籤中
</div>
</template>
JS
//- App.vue
<script>
import HelloWorld from "./components/HelloWorld.vue";
import TitleComponent from "./components/TitleComponent.vue";
export default {
name: "App",
data() { // 用來回傳資料
return {
titleText: "This is title from App.vue" // 要放到 title 的內容
};
},
components: {
HelloWorld,
TitleComponent
}
};
</script>
# 建立模組化 component
除了建立單檔 component 之外,副檔名為 ".vue" 的組件檔案,也可以像平常撰寫時,將 HTML、CSS,以及 JavaScript 這三個部分拆成獨立的檔案,建立模組化 component。
# 1. 建立組件資料夾與檔案
在 "components" 資料夾底下建立一個新資料夾,名稱為新的組件名稱,規則與單檔建立名稱相同,例如:InputComponent。
然後在此資料夾裡面建立以下三個檔案:
- index.vue
- style.css
- template.html
src
├─assets
├─components
│ ├─InputComponent // 模組化 component
│ │ ├─index.vue // JavaScript,主要檔案
│ │ ├─style.css // CSS
│ │ └─template.html // HTML
│ ├─HelloWorld.vue // 單檔 component
│ └─TitleComponent.vue // 單檔 component
└─App.vue
# 2. 將 HTML 和 CSS 匯入到此組件的 .vue 檔案
以往撰寫 HTML、CSS 和 JavaScript 檔案時,會將 CSS 和 JavaScript 檔案分別以 <link>
和 <script>
標籤匯入到主要的 HTML 檔案裡面。
而在 Vue CLI ,則是將作用於同一個 component 的 "template.html" 和 "style.css" 的檔案路徑,分別用 src
屬性透過 <template>
和 <style>
標籤匯入到 "index.vue" 裡面。
//- index.vue
<script>
export default {
name: "inputComponent"
};
</script>
// 使用 src 匯入此組件的 template 和 style
<template src="./template.html"></template>
<style src="./style.css" scoped></style>
如此一來,就能專心在個別檔案寫程式。
# 3. 撰寫程式
假如我們要在 input
欄位裡面預設顯示 "TitleComponent.vue" 組件用到的資料 titleText
。
HTML 部分,在 input
標籤使用 v-model
並綁定新的變數 text
來連接資料。
//- InputComponent/template.html
<input type="text" v-model="text" />
由於要使用 text
接收 titleText
,而 titleText
又是從 "App.vue" 傳入的資料,所以需要用 props
來管理 text
。
//- InputComponent/index.vue
<script>
export default {
name: "inputComponent",
props: {
text: {
type: String,
required: true
}
}
};
</script>
# 4. 到 App.vue 載入並掛載組件
同樣地,我們要將這個 component 載入到 "App.vue" ,才能夠顯示到畫面上。
之前的 component 只有單檔,所以匯入檔案就等於匯入組件。但現在 "InputComponent" 被模組化,所以我們要傳入 "InputComponent" 資料夾底下主要的 "index.vue" 來匯入這個 component。
//- App.vue
<script>
import HelloWorld from "./components/HelloWorld.vue";
import TitleComponent from "./components/TitleComponent.vue";
import InputComponent from "./components/InputComponent/index.vue";
export default {
name: "App",
data() {
return {
titleText: "This is title from App.vue"
};
},
components: {
HelloWorld,
TitleComponent,
InputComponent
}
};
</script>
匯入之後,將這個 component 放到畫面,並且綁定資料。
//- App.vue
<template>
<div id="app">
<TitleComponent :title="titleText" />
<img alt="Vue logo" src="./assets/logo.png" />
<InputComponent :text="titleText" /> // 放上畫面並綁定資料
<HelloWorld msg="Welcome to Your Vue.js App" />
</div>
</template>
如此一來,就能在畫面上看到多了一個輸入框,並且裡面已填好 titleText
的內容。
# $emit 將資料傳送到外部
v-model
可以讓資料與畫面雙向互動,如果我們希望修改 input
的內容時,上面的 titleText
標題的內容也能跟著改變,則需要透過 $emit
來進行資料的傳遞。
這是因為 titleText
是屬於外組件 "App.vue" 的資料,而不是 "InputComponent" 本身的資料,因此不被允許跨組件任意改動。
這裡我們建立一個新的變數 inputText
以和原有變數 text
區別,並透過 computed
去跟原本的 text
進行連動。也因此,HTML 這裡,input
改成跟資料 inputText
連結。
//- InputComponent/template.html
<input type="text" v-model="inputText" />
由於 inputText
要「取值」 (從 "App.vue" 取得 titleText
的內容) 以及「給值」 (當 input
的內容被改變時,新的值要被賦予 "App.vue" 的 titleText
)。所以 inputText
用物件而非函式的形式,使用 get(){}
和 set(){}
來實現「取值」以及「給值」這兩件事。
# 方法一:傳入事件、新值作為參數
# InputComponent
使用 $emit
有兩種方法。第一個方法是傳入事件、新值作為參數。
//- InputComponent/index.vue
<script>
export default {
name: "inputComponent",
props: {
text: {
type: String,
required: true
}
},
computed: {
inputText: {
// 取值:回傳 text 的值 (來自 App.vue 的 titleText 變數)
get() {
return this.text;
},
// 賦值:當 textChange 事件發生時,將新值 val 給父組件
set(val) {
this.$emit("textChange", val);
}
}
}
};
</script>
# App.vue
textChange
這個事件會發生在 "InputComponent" 這個組件的內容改變時,並且觸發 "App.vue" 的事件處理器 changeHandler
。
//- App.vue
<template>
<div id="app">
<TitleComponent :title="titleText" />
<img alt="Vue logo" src="./assets/logo.png" />
<InputComponent :text="titleText" @textChange="changeHandler" /> // 綁定事件
<HelloWorld msg="Welcome to Your Vue.js App" />
</div>
</template>
要處理事件,一樣用 methods
來將改變後的新值,賦予 titleText
,使得讀取到 titleText
的 TitleComponent
標題內容也跟著改變。
//- App.vue
<script>
import HelloWorld from "./components/HelloWorld.vue";
import TitleComponent from "./components/TitleComponent.vue";
import InputComponent from "./components/InputComponent/index.vue";
export default {
name: "App",
data() {
return {
titleText: "This is title from App.vueText
};
},
components: {
HelloWorld,
TitleComponent,
InputComponent
},
// 使用 methods 來處理事件,改變 titleText 的值
methods: {
changeHandler(val) {
this.titleText = val;
}
}
};
</script>
# 方法二:使用 update 和 sync
# InputComponent
使用 $emit
還有另一個更簡潔的寫法來傳值。第一個參數使用 update:變數名稱
。
<script>
export default {
name: "inputComponent",
props: {
text: {
type: String,
required: true
}
},
computed: {
inputText: {
get() {
return this.text;
},
set(val) {
this.$emit("update:text", val);
}
}
}
};
</script>
# App.vue
然後在 "App.vue" 的 template
裡面,將那個要被改值的變數後面加上 .sync
。
<template>
<div id="app">
<TitleComponent :title="titleText" />
<img alt="Vue logo" src="./assets/logo.png" />
<InputComponent :text.sync="titleText" /> // 變數 text 後面加上 .sync
<HelloWorld msg="Welcome to Your Vue.js App" />
</div>
</template>
這樣一來,事件跟處理器都不用寫,當內部的值更新時,就能將值從內部傳到外部同步改寫。只不過,除了改值以外,這個方法無法對變數進行其他動作。