---
# System prepended metadata

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

---

---
tags: Vue,第三章
---
# 同步異步
computed只能做同步任務
watch可以做異步任務
# watch
```javascript=
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <div>
        翻譯成的語言:
        <select v-model="obj.lang">
          <option value="italy">義大利</option>
          <option value="english">英語</option>
          <option value="german">德語</option>
        </select>
      </div>
      <!-- 翻譯框 -->
      <div class="box">
        <div class="input-wrap">
          <textarea v-model="obj.keyword"></textarea>
        </div>
        <div class="output-wrap">
          <textarea class="transbox">{{result}}</textarea>
        </div>
      </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script>
      const app = new Vue({
        el: "#app",
        data: {
          words: "",
          result: "",
          obj: {
            keyword: "紫陽道長",
            lang: "english",
          },
        },
        // watch默認情況下只能監視簡單數據類型
        watch: {
          // 完整寫法
          obj: {
            immediate: true, // 網頁運行立即執行
            deep: true, // 開啟深度監聽
            async handler(newVal) {
              // 對象數據發生變化時自動執行
              console.log(newVal);
              // 發請求
              const res = await axios({
                url: "https://applet-base-api-t.itheima.net/api/translate",
                params: {
                  words: newVal.keyword,
                  lang: newVal.lang,
                },
              });
              console.log(res.data.data);
              this.result = res.data.data;
            },
          },
          // 高級用法:監視對象的屬性
          // 'obj.keyword'(newVal){
          //   console.log(this.obj.keyword);
          // }
          // 基本用法:監視data中的數據
          // words(newVal, oldVal) {
          //   console.log(newVal, oldVal);
          //   console.log(newVal);
          //   console.log(this.words);
          // }
        },
      });
    </script>
  </body>
</html>
```
# 綜合案例-購物車
```javascript=
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      #app{
        width: 1000px;
        height: 1000px;
        margin: 0 auto;
      }
      .main {
        width: 1000px;
        height: 500px;
      }
      .thead .tr {
        display: flex;
      }
      .thead .tr .th {
        width: 200px;
        height: 20px;
        border: 1px solid black;
        text-align: center;
      }
      .tbody {
        border: 1px solid red;
      }
      .tbody .tr {
        display: flex;
      }
      .tbody .tr .td {
        width: 200px;
        height: 100px;
        text-align: center;
        line-height: 100px;
      }
      .active {
        background-color: #f6f7fa;
      }
      .bottom {
        display: flex;
        justify-content: space-between;
        border: 1px solid yellow;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <!-- 頂部 -->
      <div><img src="./fruit.png" alt="" /></div>
      <!-- 麵包屑 -->
      <div>
        <span>房子</span>
        <span>購物車</span>
      </div>
      <!-- 購物車主體 -->
      <div class="main" v-if="fruitList.length>0">
        <div class="table">
          <!-- 頭部 -->
          <div class="thead">
            <div class="tr">
              <div class="th">選中</div>
              <div class="th">圖片</div>
              <div class="th">單價</div>
              <div class="th">個數</div>
              <div class="th">小計</div>
              <div class="th">操作</div>
            </div>
          </div>
          <!-- 身體 -->
          <div class="tbody">
            <div
              class="tr"
              v-for="item in fruitList"
              :key="item.id"
              :class="{active:item.isChecked}"
            >
              <div class="td">
                <input type="checkbox" v-model="item.isChecked" />
              </div>
              <div class="td"><img :src="item.icon" /></div>
              <div class="td">{{item.price}}</div>
              <div class="td">
                <div class="my-input-number">
                  <button :disabled="item.num<=1" @click="item.num--">-</button>
                  <span>{{item.num}}</span>
                  <button @click="item.num++">+</button>
                </div>
              </div>
              <div class="td">{{item.price * item.num}}</div>
              <div class="td"><button @click="del(item.id)">刪除</button></div>
            </div>
          </div>
          <!-- 底部 -->
          <div class="bottom">
            <!-- 全選 -->
            <label>
              <input type="checkbox" v-model="isAll" />
              全選
            </label>
            <div>
              <!-- 所有總價 -->
              <span>總價<span>{{totalPrice}}</span></span>
              <!-- 結算按鈕 -->
              <button>結算{{totalCount}}</button>
            </div>
          </div>
        </div>
      </div>
      <!-- 空車 -->
      <div class="empty" v-else>空空如也</div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
      const app = new Vue({
        el: "#app",
        data: {
          fruitList:JSON.parse(localStorage.getItem('list')) || [],
          // 這段注釋要留著
          // fruitList: [
          //   {
          //     id: 1,
          //     icon: "./apple.png",
          //     isChecked: true,
          //     num: 2,
          //     price: 6,
          //   },
          //   {
          //     id: 2,
          //     icon: "./banana.png",
          //     isChecked: true,
          //     num: 2,
          //     price: 6,
          //   },
          //   {
          //     id: 3,
          //     icon: "./watermelon.png",
          //     isChecked: true,
          //     num: 2,
          //     price: 6,
          //   },
          // ],
        },
        methods: {
          del(id) {
            this.fruitList = this.fruitList.filter((item) => {
              return item.id !== id;
            });
          },
        },
        computed: {
          isAll: {
            get() {
              // 統計小選框的狀態
              return this.fruitList.every((item) => item.isChecked);
            },
            set(val) {
              // isAll被修改時自動執行
              // 讓小選框狀態跟著全選框改變
              this.fruitList.forEach((item) => (item.isChecked = val));
            },
          },
          // 簡單寫法無法被賦值導致報錯
          // isAll() {
          //   return this.fruitList.every((item) => item.isChecked);
          // },
          totalPrice() {
            return this.fruitList.reduce((sum, item) => {
              // 有勾選的東西才要累加
              if (item.isChecked) {
                return sum + item.price * item.num;
              }
              // 累加不能斷
              return sum;
            }, 0);
          },
          totalCount() {
            return this.fruitList.reduce((sum, item) => {
              // 有勾選的東西才要累加
              if (item.isChecked) {
                return sum + item.num;
              }
              // 累加不能斷
              return sum;
            }, 0);
          },
        },
        watch:{
          // 深度偵聽
          fruitList:{
            deep:true,
            handler(){
              console.log('數組變了');
              localStorage.setItem('list',JSON.stringify(this.fruitList))
            }
          }
        }
      });
    </script>
  </body>
</html>
```
# 生命週期
可在chrome輸入以下指令模擬銷毀
`app.$destroy()`
![](https://hackmd.io/_uploads/HJnI3w5h2.png)
![](https://hackmd.io/_uploads/HkJAAho3h.png)
```javascript=
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <div>{{msg}}</div>
      <button @click="count++">+</button>
      <div>{{count}}</div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
      const app = new Vue({
        el: "#app",
        data: {
          msg: "你好",
          count: 100,
        },
        beforeCreate() {
          // 還不能訪問數據
          console.log("創建前", this.count);
        },
        created() {
          // 可以訪問數據
          // 建議這時發ajax請求
          console.log("創建後", this.count);
          // 只能拿到模板代碼
          console.log(document.querySelector("button"));
        },
        beforeMount() {
          console.log("掛載前");
        },
        mounted() {
          console.log("掛載後");
          // 此時才拿到真實DOM
          console.log(document.querySelector("button"));
        },
        beforeUpdate() {
          console.log("更新前");
        },
        updated() {
          console.log("更新後");
        },
        beforeDestroy() {
          console.log("銷毀前");
          // 通常會在此清除定時器和卸載事件，因為這些東西屬於全局
        },
        destroyed() {
          console.log("銷毀後");
        },
      });
    </script>
  </body>
</html>
```
## 新聞渲染
```javascript=
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <ul>
        <li v-for="item in list" :key="item.id" class="news">
          <div class="left">
            <div class="title">{{item.title}}</div>
            <div class="info">
              <span>{{item.source}}</span>
              <span>{{item.time}}</span>
            </div>
          </div>
          <div class="right">
            <img :src="item.img" alt="" />
          </div>
        </li>
      </ul>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script>
      const app = new Vue({
        el: "#app",
        data: {
          list: [],
        },
        async created() {
          const res = await axios.get("http://hmajax.itheima.net/api/news");
          console.log(res.data.data);
          this.list = res.data.data;
        },
      });
    </script>
  </body>
</html>
```
## 搜索框聚焦
```javascript=
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <div class="search-box">
        <input type="text" v-model="words" id="inp" />
        <!-- 原生HTML用autofocus直接解決 -->
        <!-- Vue因為重新渲染的關係會害聚焦丟失 -->
        <!-- <input autofocus type="text" v-model="words" id="inp"> -->
        <button>搜索一下</button>
      </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
      const app = new Vue({
        el: "#app",
        data: {
          words: "",
        },
        mounted() {
          document.querySelector("#inp").focus();
        },
      });
    </script>
  </body>
</html>
```
# 綜合案例-記帳清單
```javascript=
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      .red {
        color: red;
      }
      .echarts-box {
        width: 1000px;
        height: 1000px;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <div class="contain">
        <!-- 左側列表 -->
        <div class="list-box">
          <!-- 添加資產 -->
          <form>
            <input v-model.trim="name" type="text" placeholder="消費名稱" />
            <input v-model.number="price" type="text" placeholder="消費價格" />
            <button @click="add" type="button">添加帳單</button>
          </form>
          <table>
            <thead>
              <tr>
                <th>編號</th>
                <th>消費名稱</th>
                <th>消費價格</th>
                <th>操作</th>
              </tr>
            </thead>
            <tbody>
              <tr v-for="(item,index) in list" :key="item.id">
                <td>{{index + 1}}</td>
                <td>{{item.name}}</td>
                <td :class="{red:item.price>100}">{{item.price}}</td>
                <td><a @click="del(item.id)" href="javascript:;">刪除</a></td>
              </tr>
            </tbody>
            <tfoot>
              <tr>
                <td colspan="4">消費總計{{totalPrice.toFixed(2)}}</td>
              </tr>
            </tfoot>
          </table>
        </div>
        <!-- 右側圖表 -->
        <div class="echarts-box" id="main"></div>
      </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.0/dist/echarts.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script>
      const app = new Vue({
        el: "#app",
        data: {
          list: [],
          name: "",
          price: "",
        },
        async created() {
          // const res = await axios.get(
          //   "https://applet-base-api-t.itheima.net/bill",
          //   {
          //     params: {
          //       creator: "紫陽",
          //     },
          //   }
          // );
          // console.log(res.data.data);
          // this.list = res.data.data;
          this.getList();
        },
        mounted() {
          // 基于准备好的dom，初始化echarts实例
          this.myChart = echarts.init(document.getElementById("main"));
          // 使用刚指定的配置项和数据显示图表。
          this.myChart.setOption({
            title: {
              text: "Referer of a Website",
              subtext: "Fake Data",
              left: "center",
            },
            tooltip: {
              trigger: "item",
            },
            legend: {
              orient: "vertical",
              left: "left",
            },
            series: [
              {
                name: "消費帳單",
                type: "pie",
                radius: "50%",
                data: [
                  { value: 1048, name: "表" },
                  { value: 735, name: "帽子" },
                ],
                emphasis: {
                  itemStyle: {
                    shadowBlur: 10,
                    shadowOffsetX: 0,
                    shadowColor: "rgba(0, 0, 0, 0.5)",
                  },
                },
              },
            ],
          });
        },
        computed: {
          totalPrice() {
            return this.list.reduce((sum, item) => sum + item.price, 0);
          },
        },
        methods: {
          async getList() {
            const res = await axios.get(
              "https://applet-base-api-t.itheima.net/bill",
              {
                params: {
                  creator: "紫陽",
                },
              }
            );
            console.log(res.data.data);
            this.list = res.data.data;
            // 圖表渲染
            this.myChart.setOption({
              series: [
                {
                  data: this.list.map((item) => ({
                    value: item.price,
                    name: item.name,
                  })),
                },
              ],
            });
          },
          async add() {
            if (!this.name) {
              return alert("請輸入消費名稱");
            }
            if (typeof this.price !== "number") {
              return alert("請輸入正確的金額");
            }
            const res = await axios.post(
              "https://applet-base-api-t.itheima.net/bill",
              {
                creator: "紫陽",
                name: this.name,
                price: this.price,
              }
            );
            this.getList();
            this.name = "";
            this.price = "";
          },
          async del(id) {
            await axios.delete(
              `https://applet-base-api-t.itheima.net/bill/${id}`
            );
            this.getList();
          },
        },
      });
    </script>
  </body>
</html>
```