# Vue JS 2 Tutorial part 2 ###### tags: `Javascript, Vue.js` # The Vue CLI * 創造一個可以使用webpack的開發環境 * 使用ES6 * 編譯並且讓檔案容量縮小成一個JS檔 * 使用single file template給網頁的不同app使用 * 在渲染到畫面上前,編譯所有的內容在本地端而不是在瀏覽器上 * 使用在即時更新的伺服器上 使用方式: 1. [安裝node.js](https://nodejs.org/en/) 2. 確認是否安裝成功輸入 node -v 出版本就是成功摟 3. `npm install -g vue-cli` 4. `vue create <project-name>` 接下來會出現一些問題需要回答: 這邊作者選擇Maunally select features ![](https://i.imgur.com/jW0ghVg.png) 可以視專案需求做選擇 ![](https://i.imgur.com/vVCw2Mk.png) 下一步選擇vue.js的版本 ![](https://i.imgur.com/YFRho9t.png) 下一步選擇 In dedicated config files ![](https://i.imgur.com/xb3SfHX.png) 下一步會詢問是否儲存設定建議選擇N 畢竟每次專案設定不同看需求而定 接下來我們進入資料夾 cd vue-crash-2021會發現需要的資料都已經載入 ![](https://i.imgur.com/hyCcMPd.png) 下一步使用 ``` npm run serve ``` ![](https://i.imgur.com/eXlTVHv.png) 就可以成功叫出來瞜! ![](https://i.imgur.com/fnsycfI.png) # Vue Files & The Root Component 針對剛剛產生出來的vue-cli檔案做一些解析 ## Vue Files ### assets * 放置圖片Logo的區域 * 需要使用到的img都可以放置在這區 ### main.js 控制所有的components的檔案 ```javascript= import Vue from 'vue' import App from './App.vue' new Vue({ el: '#app', render: h => h(App) }) ``` 從最基礎的檔案中可以看出它引入了: 1. Vue(就是整個框架內容) 1. App App就是指這個部分的檔案,也就是The Root Component ![](https://i.imgur.com/ypz1R8a.png) 並創造一個新的Vue實體: 抓取id=app這個元素並且把App.vue的內容渲染到元素上面位於index.html內 ![](https://i.imgur.com/hy0EJSM.png) html內部抓取的div ![](https://i.imgur.com/fP0zRQ7.png) ## The Root Component 也就是剛剛提到的App的部分 可以發現這樣的vue檔案其實就是vue components的延伸,但是拆分到不一樣的檔案 * script 這邊處理把內容輸出到main.js裡面 * style 處理畫面 ### 特別注意 template內部只能用一個div包裹住全部的html如果有兩個會報錯 ![](https://i.imgur.com/sPJtlZO.png) ```htmlembedded= <template> <div id="app"> <img src="./assets/logo.png"> <h1>{{ msg }}</h1> <h2>Essential Links</h2> <ul> <li><a href="https://vuejs.org" target="_blank">Core Docs</a></li> <li><a href="https://forum.vuejs.org" target="_blank">Forum</a></li> <li><a href="https://chat.vuejs.org" target="_blank">Community Chat</a></li> <li><a href="https://twitter.com/vuejs" target="_blank">Twitter</a></li> </ul> <h2>Ecosystem</h2> <ul> <li><a href="http://router.vuejs.org/" target="_blank">vue-router</a></li> <li><a href="http://vuex.vuejs.org/" target="_blank">vuex</a></li> <li><a href="http://vue-loader.vuejs.org/" target="_blank">vue-loader</a></li> <li><a href="https://github.com/vuejs/awesome-vue" target="_blank">awesome-vue</a></li> </ul> </div> </template> <script> export default { name: 'app', data () { return { msg: 'Welcome to Your Vue.js App' } } } </script> <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; } h1, h2 { font-weight: normal; } ul { list-style-type: none; padding: 0; } li { display: inline-block; margin: 0 10px; } a { color: #42b983; } </style> ``` ## 範例 使用App.vue的內容來渲染index.html內部的id=app的tag App.vue ```javascript= <template> <div> <h1>{{title}}</h1> <p>{{greeting()}}</p> </div> </template> <script> export default { data () { return { title:'yout first vue file' } },methods:{ greeting() { return 'heeeeeeee cowboy!' } }, } </script> <style></style> ``` 印出內容: ![](https://i.imgur.com/mmMmAZt.png) # Nesting Components * 創造components以及import它們、nest它們到其他的components ![](https://i.imgur.com/oFKUY5F.png) ## Nesting Globally 主要把import寫在main.js內部,並且所有的components都可以使用 1. import 資料夾名稱 from 資料夾位置 1. 並且components建立在這裡`Vue.component('ninjas', Ninjas);` ```javascript= import Vue from 'vue' import App from './App.vue' import Ninjas from './Ninjas.vue' Vue.component('ninjas', Ninjas); new Vue({ el: '#app', render: h => h(App) }) ``` 在App.vue檔案內則要使用ninjas tag來使用這個components App.vue ```javascript= <template> <div> <h1>{{title}}</h1> <ninjas></ninjas> </div> </template> <script> export default { data () { return { title:'New title for nesting components' } } } </script> <style></style> ``` 印出結果: 把Ninja.vue這個components的內容成功渲染到頁面上 ![](https://i.imgur.com/U1zBixE.png) ## Nesting locally 不會操作在main.js上面,而是在想要渲染的components上面操作 1. 把import的部分寫在App.vue內 1. 把components的內容寫在App.vue內部script內 ```javascript= <template> <div> <h1>{{title}}</h1> <ninjas></ninjas> </div> </template> <script> import Ninjas from './Ninjas.vue' export default { components:{ 'ninjas': Ninjas }, data () { return { title:'New title for nesting components' } } } </script> <style></style> ``` # Component CSS (scoped) 在style tag加上 scoped讓其CSS只會影響到檔案本身 * 有socped的CSS會針對每個components的CSS新增一個屬性 * 避免複寫其他的components的CSS效果 ![](https://i.imgur.com/a6nrbG6.png) 印出結果: 在App.vue寫的是h1 color:purple 在Ninjas.vue寫的是 h1 color:blue ![](https://i.imgur.com/EaH9rPq.png) # Nesting Components Examples ## 成品: ![](https://i.imgur.com/snxMPqK.png) ## 成品功能: 1. 點擊人物名稱會顯示使用技能 ## HTML 畫面呈顯處index.html ### html程式碼: ```htmlembedded= <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>vuejs-playist</title> </head> <body> <div id="app"></div> <script src="/dist/build.js"></script> </body> </html> ``` ## vue-app * 引入其他components的地點 * 使用components屬性添進進去templates內 ```javascript= <template> <div> <app-header> </app-header> <app-content></app-content> <app-footer> </app-footer> </div> </template> <script> import Header from'./components/Header.vue' import Footer from'./components/Footer.vue' import Content from'./components/Content.vue' export default{ components:{ 'app-header':Header, 'app-footer':Footer, 'app-content':Content, }, data() { return { } } } </script> <style></style> ``` ## Header.vue ```javascript= <template> <header> <h1>{{title}}</h1> </header> </template> <script> export default{ data() { return { title:'nesting example' } } } </script> <style scoped> header{ background:lightgreen; padding:10px; } h1{ color:#222; text-align:center; } </style> ``` ## Footer.vue ```javascript= <template> <footer> <p>{{copyright}}</p> </footer> </template> <script> export default{ data() { return { copyright:"Copyright 2021 Vue" } } } </script> <style scoped> footer{ background:#222; padding:6px; } p{ color:lightgreen; text-align:center; } </style> ``` ## Content.vue 使用: * v-for 依序印出物件 * v-on:click 讓其內容可以被toggle * v-show 當v-show為true時speciality會出現,不是則否 ```javascript= <template> <div id="ninjas"> <ul> <li v-for="ninja in ninjas" v-on:click="ninja.show = !ninja.show"> <h2>{{ninja.name}}</h2> <h3 v-show="ninja.show">{{ninja.speciality}}</h3> </li> </ul></div> </template> <script> export default{ data() { return { ninjas:[ {name:'Ryu',speciality:'Vue Components', show:false}, {name:'Crystal',speciality:'HTML Wizardry', show:false}, {name:'Hitoshi',speciality:'Click Events', show:false}, {name:'Tango',speciality:'Conditionals', show:false}, {name:'Kami',speciality:'Webpack', show:false}, {name:'Yoshi',speciality:'Data Diggin', show:false}, ] } } } </script> <style scoped> #ninjas{ Width:100%; max-width:1200px; margin:40px auto; padding:0 20px; box-sizing:border-box; } ul{ display:flex; flex-wrap:wrap; list-style-type:none; padding:0; } li{ flex-grow:1; flex-basis:300px; text-align:center; padding:30px; border:1px solid #222; margin:10px } </style> ``` # Props * 從Root component 傳送資料給nesting components * 使用情境是當需要不同的components需要相同的資訊時可以使用Props ![](https://i.imgur.com/kQZCG8H.png) ## 範例 把上面實作的範例做改寫: 1. 把ninjas的資料都搬回Root 1. 在Content.vue寫接收 `props:['ninjas']` 1. 在App.vue 寫 輸出prop `<app-content v-bind:ninjas="ninjas"></app-content>` * App.vue ```javascript= <template> <div> <app-header> </app-header> <app-content v-bind:ninjas="ninjas"></app-content> <app-footer> </app-footer> </div> </template> <script> import Header from'./components/Header.vue' import Footer from'./components/Footer.vue' import Content from'./components/Content.vue' export default{ components:{ 'app-header':Header, 'app-footer':Footer, 'app-content':Content, }, data() { return { ninjas:[ {name:'Ryu',speciality:'Vue Components', show:false}, {name:'Crystal',speciality:'HTML Wizardry', show:false}, {name:'Hitoshi',speciality:'Click Events', show:false}, {name:'Tango',speciality:'Conditionals', show:false}, {name:'Kami',speciality:'Webpack', show:false}, {name:'Yoshi',speciality:'Data Diggin', show:false}, ] } } } </script> <style></style> ``` * Content.vue 1. 可以從template內部發現不需要修改,prop像是components的方式一樣使用 1. 使用在scirpt 內部的methods的部分也是一樣可以直接調用 ```javascript= methods:{ test:function(){ this.ninjas } } ``` ```javascript= <template> <div id="ninjas"> <ul> <li v-for="ninja in ninjas" v-on:click="ninja.show = !ninja.show"> <h2>{{ninja.name}}</h2> <h3 v-show="ninja.show">{{ninja.speciality}}</h3> </li> </ul></div> </template> <script> export default{ props:['ninjas'], data() { return { } } } </script> ``` ## Validation * 當收到props時我們必須確認是我們需要的資料型別(String, Array...),這邊就可以使用Validation * required則用來確認是否Root components成功傳送props過來 * 使用在接收props的檔案 ```javascript= export default { props: { ninjas: { type: Array, required: true } }, data(){ return{ } } } ``` # Primitive vs Reference Types ## Primitive Number, String, Boolean, 如果修改了Primivtive的資料則只會影響當下檔案的內容而不會影響到Root component,這是因為 by Primitive的性質 ## Reference Object, Array 如果修改 Reference的資料Root跟使用的component都會一起改變,這是因為 by Reference的性質 ## 範例說明兩者區別 ### Reference 會全部一起改變 1. 在Content.vue設置一個method刪除Content的內容 1. 在App.vue多設置一對app-content讓內容呈現兩個 1. 按下刪除按鈕會發現兩邊都會一起刪除內容 Content.vue ```javascript= methods: { deleteContent:function(){ this.ninjas.pop() } } ``` App.vue ```javascript= <template> <div> <app-header> </app-header> <app-content v-bind:ninjas="ninjas"></app-content> <hr/> <app-content v-bind:ninjas="ninjas"></app-content> <app-footer> </app-footer> </div> </template> ``` 印出結果: 因為by Reference操作的是同一個物件,任何改動都是同步的 ![](https://i.imgur.com/XIAf4fw.png) ### Primitive 只會改變當前檔案 * 設置App.vue的內容 title:"Vue contnet" prop到header, footer內 * 改變App.vue的內容會讓header, footer內容跟著改變 重點來了: 1. 當我們在接受prop的檔案修改Primitive時,其他檔案不會像Reference一樣改變,而是保持原樣 1. 在Header.vue使用methods changeTitle 當點擊header時修改其title為Vue Wizards 2. header修改了,此時的footer保持原樣 ```javascript= <template> <header> <h1 v-on:click="changeTitle">{{title}}</h1></header> </template> ``` ```javascript= methods: { changeTitle:function(){ this.title ="Vue Wizards" } } ``` 印出結果: 因為這邊props傳送的是Primitive,當修改本地檔案時,不會影響其他檔案內容 ![](https://i.imgur.com/0Mo9sw9.png) # Events (child to parent) * props是把資料從Root 傳到其他 components * Events則是把資料從其他 components傳回 Root ![](https://i.imgur.com/5DTgLjZ.png) ## 範例-使用Event回傳修改Root進而透過prop修改其他components * 首先在Header.vue的部分做修改並送出資料: 1. 使用 v-on:click觸發changeTitle來修改title內容 1. function changeTitle的內容使用`this.$emit('changeTitle', "Vue Wizards")`來把資料回傳回去Root ```javascript= <template> <header> <h1 v-on:click="changeTitle">{{title}}</h1></header> </template> ``` ```javascript= methods: { changeTitle:function(){ this.$emit('changeTitle', "Vue Wizards") } } ``` * 在App.vue做接收Event的動作: 1. 使用`v-on:changeTitle="updateTitle($event)"`接受Header.vue的資料 1. 並使用其內容"Vue Wizards"修改Title 1. methods的部分使用參數就是`$event`也就是"Vue Wizards"(名稱可以自訂) ```javascript= <template> <div> <app-header v-bind:title="title" v-on:changeTitle="updateTitle($event)"></app-header> <app-content v-bind:ninjas="ninjas"></app-content> <app-footer v-bind:title="title"></app-footer> </div> </template> ``` ```javascript= methods: { updateTitle:function(updatedTitle){ this.title = updatedTitle; } } ``` # The Event Bus * 在Event的部分是我們操作過從其他components傳送資料回去Root再由Root改變其他components * 而Event Bus做的事情則簡化這個過程: 1. 創造一個新的Vue實體 1. 並且import到想要操作的componets內 1. 這樣就可以彼此溝通而不需要傳回Root再傳出來 ## 範例說明Events Bus * 通常使用在想要改變 siblings或是任何其他的componets但不想經過Root來更動時 1. 在main.js創造其Vue實體並且輸出 `export const bus = new Vue();` 2. 在Header.vue(想要改變的檔案): 引入bus也就是Bus Event的實體 `import {bus} from '../main';` 這邊的methods處則不使用$emit把資料傳回Root 而是使用`this.title = 'Vue Wizards'` 直接修改Header.vue的title 並且使用引入的bus做$emit資料出去給想要修改內容的其他components,並設定名稱titlechanged以及內容"Vue Wizards" ```javascript= methods: { changeTitle:function(){ this.title = 'Vue Wizards'; bus.$emit('titleChanged',"Vue Wizards"); } } ``` 3. Footer.vue做接收 引入bus也就是Bus Event的實體 `import {bus} from '../main';` 使用life cycle hook: created 當實體被創建時會馬上觸發此函式 接收來自Header.vue的資料也就是titleChanged使用`$on()` 並使用callback函式處理修改title成Header.vue傳送來的資料也就是"Vue Wizards" ```javascript= created(){ bus.$on('titleChanged',(data)=>{ this.title = data; }) } ``` 4. 通過Event Bus 的傳遞就不須經過Root而是透過Vue實體Bus來傳遞兩者的資料 5. 需要注意的點是 你可以在Header.vue檔案中發現 * 除了傳送資料的函式$emit之外,他還有使用`this.title = 'Vue Wizards';`修改當前自己目前的資料 * 這個部份如果沒有處理則只會改變Footer.vue的title * 原因在於我們只有監聽Footer.vue的event而Header.vue的部分就必須這樣自己做修改 ```javascript= methods: { changeTitle:function(){ // this.$emit('changeTitle', "Vue Wizards") this.title = 'Vue Wizards'; bus.$emit('titleChanged',"Vue Wizards"); } } ``` # Life-cycle Hooks **beforeCreate** Vue實體初始化後立刻呼叫此函式,不過此時Vue實體還未創建所以其中的設定都還未能使用(如data observation, event, watcher setup) **created** Vue實例創建完成後立刻呼叫此函式,已設置 data, computed properties, methods, watch/event callbacks,但尚未開始mounting階段,且 $el 還不能在此階段使用。 **beforeMount** 在mounting階段開始前被調用:render function首次被調用。 **mounted** 選項物件中的el被新創建的vm.$el替換,並掛載到到 vm 上,並調用mounted這個鉤子。 **beforeUpdate** 數據被更新時會調用,發生在 Virtual DOM re-render 和 patch 之前(連結:Day4: Virtual DOM),可以在此時更改狀態數據,並不會增加重新渲染的成本。 **updated** 由於數據更新導致 Virtual DOM re-render 和 patch 之後會調用updated這個鉤子。 不精確白話文為:由於updated被調用時,DOM 已經更新。所以在此時更新數據很可能會導致updated無限循環的被調用。 **beforeDestroy** 在 Vue Instance 被銷毀前被調用,因此 Vue Instance 在beforeDestroy中仍可運作。 不精確白話文為:Vue Instance 可以在此時做垂死前的掙扎。 **destroyed** 在 Vue Instance 被銷毀後被調用,此時 Vue Instance 所有東西會解除綁定,事件監聽也都會被移除,子實例也會被銷毀。 ## 範例 - life cycle hook 出現的時機 ```javascript= <template> <div id="ninjas"> <ul> <li v-for="ninja in ninjas" v-on:click="ninja.show = !ninja.show"> <h2>{{ninja.name}}</h2> <h3 v-show="ninja.show">{{ninja.speciality}}</h3> </li> </ul> <button v-on:click="deleteContent">Delete Content</button> </div> </template> <script> export default { props: { ninjas: { type: Array, required: true } }, data(){ return{ } }, methods: { deleteContent:function(){ this.ninjas.pop() } }, // lifecycle hooks beforeCreate(){ alert('beforeCreate'); }, created(){ alert('created'); }, beforeMount(){ alert('beforeMount'); }, mounted(){ alert('mounted'); }, beforeUpdate(){ alert('beforeUpdate'); }, updated(){ alert('updated'); } } </script> <style scoped></style> ``` 重整畫面第一個出現的就是beforeCreate,畢竟是在實體出現之前所以不會有畫面呈現 ![](https://i.imgur.com/jBDv9Hb.png) 第二個出現的是created實體已經被創造了,但是還沒被mounted上去,故也沒有畫面 ![](https://i.imgur.com/DCR9cer.png) 第三個出現的是beforeMounted,在mounted之前的可以做一些處理依舊沒有畫面 ![](https://i.imgur.com/tWJuOUR.png) 第四個出現的是mounted點擊OK之後畫面產生! ![](https://i.imgur.com/O8iFfJw.png) 畫面產生 ![](https://i.imgur.com/F3i8VOo.png) 第五按下delete Content後會出現beforeUpdate按下OK後出現update ![](https://i.imgur.com/ZLlUIz1.png) 第六後會出現updated按下OK後即更新畫面 ![](https://i.imgur.com/1UDf4rm.png) 畫面更新 ![](https://i.imgur.com/Kxmc2Vt.png) # Slots * 在子元件上面開個洞, 由外層元件將內容置放在至子層元件指定的位置中 * 可以傳送HTML tag藉由slot ## 範例 slot的使用 1. 要使用slot必須先引入子層到App.vue `import formHelper from'./components/formHelper.vue'` 2. 輸出components處輸出到formHelper位置 ```javascript= export default{ components:{ 'form-helper':formHelper } ``` 3. template處放入創好的components - form-helper 4. 輸入內容 App.vue ```javascript= <template> <div> <form-helper> <h2>I am the slot title</h2> <p>I am the paragraph text for the slot</p> </form-helper> </div> </template> <script> import formHelper from'./components/formHelper.vue' export default{ components:{ 'form-helper':formHelper }, data() { return { } }, methods: { } } </script> <style></style> ``` 1. 在formHelper.vue則在template使用slot tag來接收來自Root的資料 formHelper.vue ```javascript= <template> <div> <h1> I am the form helper</h1> <slot></slot> </div> </template> <script> export default{ components:{ }, data() { return { } }, methods: { } } </script> <style scoped></style> ``` 印出結果: 就可以使用這樣的方式傳遞HTML ![](https://i.imgur.com/WSfqrJ9.png) ## 範例二 使用name抓取Root的slot * 這時候如果我想要h1, p 各別在不同位置該怎麼處理 * 藉由命名的方式子層的slot就知道該抓取哪個部分: 1. 在App.vue命名後,在子層 使用name去抓取 App.vue ```javascript= <template> <div> <form-helper> <h2 slot="title">I am the slot title</h2> <p slot="text">I am the paragraph text for the slot</p> </form-helper> </div> </template> ``` formHelper.vue ```javascript= <template> <div> <slot name="title"></slot> <h1> I am the form helper</h1> <slot name="text"></slot> </div> </template> ``` 印出結果: 就可以分開呈現內容 ![](https://i.imgur.com/1S58ekw.png) ### 修飾子層的內容 * 要處理子層的style則必須在子層的style處理 formHelper.vue ```javascript= <style scoped> h1{ color:red; } </style> ``` 處出結果: ![](https://i.imgur.com/tEWiLgj.png) ### 動態顯示文字則在Root處理 * 動態文字要修改的話主要處理在Root內 App.vue ```javascript= <template> <div> <form-helper> <h2 slot="title">{{title}}</h2> <p slot="text">I am the paragraph text for the slot</p> </form-helper> </div> </template> <script> import formHelper from'./components/formHelper.vue' export default{ components:{ 'form-helper':formHelper }, data() { return { title:'I am a dynamic slot title' } }, methods: { } } </script> <style></style> ``` 印出結果: ![](https://i.imgur.com/JnOI6PA.png) ## 範例三 slot真正的用法 上面的偏向展示slot怎麼用,接下來會使用真實的範例說明如何使用slot: 製作一個網站,需要數個不同的form表單,並且他們的結構要相似但是內容必須要可以修改這時候就可以使用slots這個概念 在Root這邊處理內容: 1. 使用form-helper tag包住要傳送到子層的內容 1. 使用slot="form-相關插槽"的方式抓取子層的架構並且填上內容 ```javascript= <form-helper> <div slot="form-header"> <h3>This is the title of the form</h3> <p>information about the form</p> </div> <div slot="form-fields"> <input type="text" placeholder="name" required> ![](https://i.imgur.com/LiN6jD8.png) <input type="password"placeholder="password" required> </div> <div slot="form-controls"> <button v-on:click="handleSubmit">Submit</button> </div> </form-helper> ``` App.vue ```javascript= <template> <div> <form-helper> <div slot="form-header"> <h3>This is the title of the form</h3> <p>information about the form</p> </div> <div slot="form-fields"> <input type="text" placeholder="name" required> <input type="password"placeholder="password" required> </div> <div slot="form-controls"> <button v-on:click="handleSubmit">Submit</button> </div> </form-helper> </div> </template> <script> import formHelper from'./components/formHelper.vue' export default{ components:{ 'form-helper':formHelper }, data() { return { title:'I am a dynamic slot title' } }, methods: { } } </script> <style></style> ``` 子層的formHelper這邊可以處理好form的架構,使用slot name的方式接受來自Root的資料 * form-header * form-fields * form-controls * useful-links formHelper.vue ```javascript= <template> <div> <h1>Please fill out our form...</h1> <form > <div id="form-header"> <slot name="form-header"></slot> </div> <div id="form-fields"> <slot name="form-fields"></slot> </div> <div id="form-controls"> <slot name="form-controls"></slot> </div> <div id="uesful-links"> <ul> <li><a href="#">link1</a></li> <li><a href="#">link2</a></li> <li><a href="#">link3</a></li> <li><a href="#">link4</a></li> </ul> </div> </form> </div> </template> <script> export default{ components:{ }, data() { return { } }, methods: { } } </script> <style scoped>~這邊我就省略~</style> ``` 印出結果: 下次想要打造form表格就是直接在App.vue內 使用template form-helper並且抓取想要修改的slot修改好內容後就可以在做出另一個form表個瞜 ![](https://i.imgur.com/o3DM1ho.png) # Dynamic components 在上一篇課程中我們製作了格式固定但是內容可以更改的表格,這次我們針對這個表格想要動態的切換表格內容(新增了兩個表格檔案),比方 點擊按鈕切換表格一變成二 App.vue 1. 首先在引入檔案進App.vue 2. 註冊components 3. 使用component template 並且使用 is 抓取要使用的component 4. 使用v-bind讓 is的部分做動態並在data處理component 5. 針對按鈕使用click事件 動態顯示當點哪個按鈕呈現哪個內容 6. 最後為了保存表格input內的資料輸入不會因為切換表格被刪除使用 keep-alive template ```html= <template> <div> <keep-alive> <component v-bind:is='component'></component> </keep-alive> <button v-on:click="component = 'form-one'">Show form one</button> <button v-on:click="component = 'form-two'">Show form two</button> </div> </template> ``` ```javascript= <script> // Imports import formOne from './components/formOne.vue'; import formTwo from './components/formTwo.vue'; export default { components: { 'form-one': formOne, // 註冊component 'form-two': formTwo }, data () { return { component: 'form-one' // v-bind 處理的動態部分 } }, methods: { handleSubmit: function(){ alert('thanks for submitting'); } } } </script> ``` 在下兩個頁面中切換並且儲存input輸入的內容不會因為切換頁面而不見 Form One ![](https://i.imgur.com/erbTJLe.png) Form Two ![](https://i.imgur.com/GWqU9ls.png) [formOne, formTwo 程式碼](https://github.com/iamshaunjp/vuejs-playlist/tree/lesson-28/src/components)