---
# System prepended metadata

title: Vue2第四章
tags: [Vue, 第四章]

---

---
tags: Vue,第四章
---
# 腳手架介紹
![](https://hackmd.io/_uploads/rJqVSny6h.png)
![](https://hackmd.io/_uploads/S1UJ82163.png)
![](https://hackmd.io/_uploads/rypfSEDTh.png)
![](https://hackmd.io/_uploads/S1vtCLvTn.png)
![](https://hackmd.io/_uploads/ryES0uwTh.png)
![](https://hackmd.io/_uploads/H1nl1Fwa2.png)
# 裝less
npm i less-loader less -D
![](https://hackmd.io/_uploads/B1Zw6FkCh.png)
# main.js
```javascript=
// 引入vue構造函數
import Vue from 'vue'
// 引入app.vue文件
// .vue結尾的文件都可稱為組件
import App from './App.vue'
// 設置生產環境時的提示，不顯示
Vue.config.productionTip = false
// 創建vue實例對象
new Vue({
  // render作用:根據app組件來創建dom元素並掛載到指定的容器中
  // render(createElement) {
  //   return createElement(App)
  // }
  render: h => h(App),
  // $mount()完全等同於設置el
}).$mount('#app')
```
# .vue規則
```javascript=
<template>
  <!-- 一定要放template標籤 -->
  <!-- 一定要放一個一級子元素(不可放兩個以上) -->
  <div>
    HelloWorld
  </div>
</template>

<script>

</script>

<style>

</style>
```
# App.vue範例
```javascript=
<template>
  <!-- 一定要放template標籤 -->
  <!-- 一定要放一個一級子元素(不可放兩個以上) -->
  <div @click="open" class="father">
    大盒子
    <div class="son">小盒子</div>
  </div>
</template>

<script>
// 組件中的js默認導出一個對象，這個對象就是組件對象
export default {
  // vue核心成員
  // 不要寫el掛載點了
  methods: {
    open() {
      alert('你好哇')
    }
  }
}
</script>
<!-- 記得要裝less包 -->
<style lang="less">
.father {
  width: 400px;
  height: 400px;
  background-color: orange;

  .son {
    width: 200px;
    height: 200px;
    background-color: green;
  }
}
</style> 
```
# 局部註冊組件
![](https://hackmd.io/_uploads/Sy7aq9xA2.png)
## 快捷鍵
```
<vue>
```
直接在組件打出vue模板
```
<!-- 在根組件裡 -->
<template>
    <子組件名>
</template>
```
直接在script標籤裡引好子組件
## 組件命名風格
在vue裡面兩種命名風格是可以混用的
### 大駝峰命名法 Camel-Case
HmHeader
HmMain
HmFooter
### 烤串命名法 Kebab-Case
hm-header
hm-main
hm-footer
# 全局註冊組件
![](https://hackmd.io/_uploads/rJT95seA3.png)
main.js
```javascript=
// 引入vue構造函數
import Vue from 'vue'
// 引入app.vue文件
// .vue結尾的文件都可稱為組件
import App from './App.vue'
// 引入組件
import HmButton from './components/HmButton.vue'
// 註冊組件
Vue.component('HmButton',HmButton)
// 設置生產環境時的提示，不顯示
Vue.config.productionTip = false
// 創建vue實例對象
new Vue({
  // render作用:根據app組件來創建dom元素並掛載到指定的容器中
  // render(createElement) {
  //   return createElement(App)
  // }
  render: h => h(App),
  // $mount()完全等同於設置el
}).$mount('#app')
```
App.vue
```javascript=
<template>
  <div class="app">
    <hm-header></hm-header>
    <hm-main></hm-main>
    <hm-footer></hm-footer>
  </div>
</template>

<script>
import HmHeader from './components/HmHeader.vue';
import HmMain from './components/HmMain.vue';
import HmFooter from './components/HmFooter.vue';
export default {
  components: {
    // 傳統完整寫法
    HmHeader: HmHeader,
    // ES6簡寫
    HmMain,
    HmFooter
  }
}
</script>

<style lang="less">
.app{
  width: 400px;
  height: 600px;
  background-color: #87ceeb;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}
</style>
```
HmButton.vue
```javascript=
<template>
  <button class="hm-button">通用按鈕</button>
</template>

<script>
export default {

}
</script>

<style>
.hm-button{
  height: 35px;
  line-height: 35px;
  padding: 0 20px;
  background-color: green;
  border-radius: 5px;
  color: white;
  border: none;
  cursor: pointer;
}</style>
```
HmHeader.vue
```javascript=
<template>
  <div class="hm-header">
    我是頭部組件
    <hm-button></hm-button>
  </div>

</template>

<script>
import HmButton from './HmButton.vue'
export default {
    components: { HmButton },

}
</script>

<style>
.hm-header{
  height: 80px;
  line-height: 80px;
  color: white;
  font-size: 30px;
  text-align: center;
  background-color: #8064a2;
}
</style>
```
HmMain.vue
```javascript=
<template>
  <div class="hm-main">
    我是身體組件
    <hm-button></hm-button>
  </div>
</template>
    
<script>
export default {

}
</script>
    
<style>
.hm-main {
  height: 320px;
  line-height: 320px;
  color: white;
  font-size: 30px;
  text-align: center;
  background-color: #f79646;
}
</style>
```
HmFooter.vue
```javascript=
<template>
  <div class="hm-footer">
    我是底部組件
    <hm-button></hm-button>
  </div>
</template>
  
<script>
export default {

}
</script>
  
<style>
.hm-footer {
  height: 80px;
  line-height: 80px;
  color: white;
  font-size: 30px;
  text-align: center;
  background-color: #4f8ebd;
}
</style>
```
# scoped
![](https://hackmd.io/_uploads/H1uFI8Q0n.png)
![](https://hackmd.io/_uploads/r1RfsOX0n.png)
![](https://hackmd.io/_uploads/rydBjd7R3.png)
![](https://hackmd.io/_uploads/SJNDiOXR2.png)
# data是函數
如果用對象的話:對象就像是一個地址(引用數據類型)，所有人都讀取同一個地址，無法讓各組件數據獨立不受干擾
如果用函數的話:函數每執行一次就會產生一個新獨立的對象
![](https://hackmd.io/_uploads/H1tfKcEC3.png)
# 組件通信
![](https://hackmd.io/_uploads/H1HvyoNA3.png)
## 父子通信
![](https://hackmd.io/_uploads/SyJWxiNRh.png)
![](https://hackmd.io/_uploads/BJadrlIA2.jpg)
```javascript=
<template>
  <div class="app">
    我是App組件{{ myTitle }}
    <son @changeTitle="change" :title="myTitle"></son>
  </div>
</template>

<script>
// 父向子傳數據
// 在App.vue中，給子組件添加自定義數據
// 在Son.vue中，使用props接收自定義屬性傳來的數據
// 分隔線
// 子向父傳數據
// 在Son.vue中，使用$emit傳遞數據
// 在App.vue中，使用自定義事件接收數據
import Son from './components/Son.vue'
export default {
  name: 'App',
  data() {
    return {
      myTitle: '學前端'
    }
  },
  methods: {
    // val就是子組件this.$emit裡的數據1
    change(val) {
      this.myTitle = val
    }
  },
  components: {
    Son
  }
}
</script>

<style scoped lang="less">
.app {
  border: 3px solid black;
  margin: 10px;
}
</style>
```
```javascript=
<template>
  <div class="son">
    <button @click="change">修改title</button>
    我是Son組件{{ title }}
  </div>
</template>

<script>
export default {
  name: 'Son-Child',
  props: ['title'],
  methods: {
    change() {
      // 父組件通過props傳過來的數據，子組件無法直接修改
      // (錯誤寫法)this.title='不想學習了'
      // this.$emit('自定義事件名','數據1','數據2')
      this.$emit('changeTitle', '不想學習了')
    }
  }
}
</script>

<style scoped lang="less">
.son {
  border: 3px solid black;
  margin: 10px;
}
</style>
```
## props作用
![](https://hackmd.io/_uploads/rkDWhxU02.png)
![](https://hackmd.io/_uploads/HJglX3g8A3.png)
## props校驗
![](https://hackmd.io/_uploads/SybtNHF0n.png)
```javascript=
<script>
export default {
  // 初學者用數組，進階要用對象
  // 數據類型首字母一定大寫
  props: {
    w: Number
  }
}
</script>
```
```javascript=
<script>
export default {
  props: {
    w: {
      type: Number, // 數據類型
      default: 50, // 默認值，與非空校驗互斥
      require: true, // 非空校驗，與默認值互斥，true代表一定要傳東西
      // 自定義校驗
      validator(val){
        if (val >= 0 && val <= 100) {
          return true
        } else {
          console.error('取值範圍是0~100')
          return false
        }
      }
    }
  }
}
</script>
```
## 單向數據流
父組件
```javascript=
<template>
  <div>
    <son @change="hChange" :count="count"></son>
  </div>
</template>

<script>
import Son from './components/Son.vue'
export default {
  data() {
    return {
      count: 100
    }
  },
  methods: {
    hChange(count) {
      this.count = count
    }
  },
  components: {
    Son
  }
}
</script>

<style></style>
```
子組件
```javascript=
<template>
  <div> 
    <span>{{ count }}</span>
    <button @click="handleSub">-</button>
  </div>
</template>

<script>
export default {
  props: {
    count: {
      type:Number
    }
  },
  methods: {
    handleSub() {
      // 千萬不能寫this.count --
      // 因為this.count -- 等同於 this.count = this.count - 1
      this.$emit('change',this.count - 1)
    }
  }
}
</script>

<style></style>
```
# 小黑記事本組件拆分
App.vue
```javascript=
<template>
  <div>
    <todo-header @add="hAdd"></todo-header>
    <todo-main @del="hDel" :list="list"></todo-main>
    <todo-footer @clear="hClear" :list="list"></todo-footer>
  </div>
</template>

<script>
import TodoHeader from './components/TodoHeader.vue'
import TodoMain from './components/TodoMain.vue'
import TodoFooter from './components/TodoFooter.vue'
export default {
  components: { TodoHeader, TodoMain, TodoFooter },
  data() {
    return {
      list:JSON.parse(localStorage.getItem('list')) || []
      // list: [
      //   { id: 1, name: '跑步十公里' },
      //   { id: 2, name: '打球一整天' },
      //   { id: 3, name: '吃飯一公斤' },
      // ]
    }
  },
  methods: {
    hAdd(taskName) {
      this.list.unshift({
        id: +new Date(),
        name: taskName
      })
    },
    hDel(id) {
      this.list = this.list.filter(item => item.id !== id)
    },
    hClear() {
      this.list = []
    }
  },
  watch: {
    list: {
      deep: true,
      handler() {
        localStorage.setItem('list',JSON.stringify(this.list))
      }
    }
  }

}
</script>

<style></style>
```
頭部
```javascript=
<template>
  <div>
    <header>
      <h1>小黑記事本</h1>
      <input @keydown.enter="hAdd" v-model.trim="taskName" placeholder="請輸入任務" type="text">
      <button @click="hAdd">添加任務</button>
    </header>
  </div>
</template>

<script>
export default {
  data() {
    return {
      taskName: ''
    }
  },
  methods: {
    hAdd() {
      // 非空校驗
      if (!this.taskName) { return alert('請輸入任務名稱') }
      // 將數據傳給父組件
      this.$emit('add', this.taskName)
      // 清空輸入框
      this.taskName = '';
    }
  }
}
</script>

<style></style>
```
身體
```javascript=
<template>
  <div>
    <section>
      <ul>
        <li v-for="(item, index) in list" :key="item.id">
          <div>
            <span>{{ index + 1 }}</span><label>{{ item.name }}</label>
            <button @click="hDel(item.id)">按鈕</button>
          </div>
        </li>
      </ul>
    </section>
  </div>
</template>

<script>
export default {
  props: {
    list: {
      required: true,
      type: Array
    }
  },
  methods: {
    hDel(id) {
      this.$emit('del',id)
    }
  }
}
</script>

<style></style>
```
腳部
```javascript=
<template>
  <div>
    <footer>
      <!-- 統計 -->
      <span>合計:<strong>{{ list.length }}</strong></span>
      <!-- 清空 -->
      <!-- 在模板中寫代碼可以省略this -->
      <button @click="$emit('clear')">清空任務</button>
    </footer>
  </div>
</template>

<script>
export default {
  props: {
    list: {
      type: Array,
      required: true
    }
  },
  // methods: {
  //   hClear() {
  //     this.$emit('clear')
  //   }
  // }
}
</script>

<style></style>
```