wordpress_blog

This is a dynamic to static website.

Vue3 複習04

Options API: 方法、運算、監聽、生命週期

Options API 概述

// Options API - HTML
<div id="app">
  <h3>什麼是 options API 與 composition API</h3>
  <h5>Option API Sample</h5>
  <button type="button" @click="trigger">{{ num }}</button>

  <h3>常見的 Option API</h3>
  <p>
    官網:<a href="https://vue3js.cn/docs/zh/api/options-api.html"
      >https://vue3js.cn/docs/zh/api/options-api.html</a
    >
  </p>
  <ul>
    <li>常用功能(本章節介紹、部分在元件章節介紹):data</li>
    <li>元件知識:template(元件章節)、生命週期(本章節介紹)、資源</li>
    <li>進階、外部套件:資源、組合、雜項(進階章節、套件運用)</li>
  </ul>

  <h3>提醒:option API 與 this</h3>
</div>
// Options API - JS
<!-- https://vue3js.cn/docs/zh/api/options-api.html -->
const App = {
  name: "漂亮的元件",
  data() {
    return {
      num: 0,
    };
  },
  methods: {
    trigger: function () {
      this.num++;
    },
  },
  created() {
    this.num = 1;
    console.log(this);
  },
};
Vue.createApp(App).mount("#app");
// Composition API - HTML
<div id="compositionApp">
  <h5>Composition API Sample</h5>
  <button type="button" @click="trigger">{{ num }}</button>
</div>
// Composition API - JS
<!-- https://vue3js.cn/docs/zh/api/composition-api.html#setup -->
<script type="module">
  import {
    ref,
    onMounted,
    createApp,
  } from "https://cdnjs.cloudflare.com/ajax/libs/vue/3.0.5/vue.esm-browser.js";

  const CompositionApp = {
    setup() {
      // 定義資料
      const num = ref(0);
      // 生命週期
      onMounted(() => {
        num.value = 1;
      });
      // Methods
      function trigger() {
        console.log("trigger");
        num.value++;
      }
      // 匯出方法、資料
      return {
        trigger,
        num,
      };
    },
  };
  createApp(CompositionApp).mount("#compositionApp");
</script>

Methods 方法

// HTML
<div id="app">
  <h3>methods 的結構</h3>
  <ul>
    <li>由 methods 定義的物件</li>
    <li>內層均是函式</li>
  </ul>

  <h3>methods 的觸發方法(點擊、其它 options api、生命週期...)</h3>
  <button type="button" @click="trigger('Click Methods')">點擊觸發</button>

  <button type="button" @click="callOtherMethod">呼叫另一個 methods</button>

  <h3>參數傳入</h3>
  <button type="button" @click="methodParameter(1, 2, 3, $event)">
    參數傳入
  </button>

  <h3>使用 methods 處理複雜資料</h3>
  <ul>
    <li v-for="product in products">
      {{ product.name }} / {{ product.price }}
      <button type="button" @click="addToCart(product)">加入購物車</button>
    </li>
  </ul>
  ...
  <h6>購物車項目</h6>
  <ul>
    <li v-for="item in carts">{{ item.name }}</li>
  </ul>
  總金額 {{ sum }}

  <h3>作為 $filter 使用(取代複雜表達式)</h3>
  {{ convertToAmount(sum) }}
</div>
// JS
const App = {
  data() {
    return {
      num: 10,
      products: [
        {
          name: "蛋餅",
          price: 30,
          vegan: false,
        },
        {
          name: "飯糰",
          price: 35,
          vegan: false,
        },
        {
          name: "小籠包",
          price: 60,
          vegan: false,
        },
        {
          name: "蘿蔔糕",
          price: 30,
          vegan: true,
        },
      ],
      carts: [],
      sum: 0,
    };
  },
  methods: {
    trigger(name) {
      console.log(name, "此事件被觸發了");
    },
    callOtherMethod() {
      this.trigger("由 callOtherMethod 觸發");
    },
    methodParameter(a, b, c, d) {
      console.log(a, b, c, d);
    },
    addToCart(product) {
      // console.log(product);
      this.carts.push(product);
      this.calculate();
    },
    calculate() {
      // console.log("calculate");
      let total = 0;
      this.carts.forEach((item) => {
        total += item.price;
      });
      // console.log(total);
      this.sum = total;
    },
    convertToAmount(price) {
      return `NT$ ${price}`;
    },
  },
  created() {
    this.trigger("由生命週期觸發");
  },
};
Vue.createApp(App).mount("#app");

Computed 運算基礎運用

// HTML
<div id="app">
  <h3>Computed 將目前的數值運算呈現至畫面上</h3>
  <ul>
    <li v-for="product in products">
      {{ product.name }} / {{ product.price }}
      <button type="button" @click="addToCart(product)">加入購物車</button>
    </li>
  </ul>
  ...
  <h6>購物車項目</h6>
  <ul>
    <li v-for="item in carts">{{ item.name }}</li>
  </ul>
  total 的值: {{ total }}<br />

  <h3>Computed 常見技巧 - 搜尋</h3>
  <input type="search" v-model="search" />
  <ul>
    <li v-for="item in filterProducts">{{ item.name }} / {{ item.price }}</li>
  </ul>
</div>
// JS
const App = {
  data() {
    return {
      num: 10,
      search: "",
      products: [
        {
          name: "蛋餅",
          price: 30,
          vegan: false,
        },
        {
          name: "飯糰",
          price: 35,
          vegan: false,
        },
        {
          name: "小籠包",
          price: 60,
          vegan: false,
        },
        {
          name: "蘿蔔糕",
          price: 30,
          vegan: true,
        },
      ],
      carts: [],
      sum: 0,
    };
  },
  // 運算
  computed: {
    total() {
      // return `100`;
      let total = 0;
      this.carts.forEach((item) => {
        total += item.price;
      });
      return total;
    },
    filterProducts() {
      // return [];
      return this.products.filter((item) => {
        // console.log(item);
        return item.name.match(this.search);
      });
    },
  },
  methods: {
    addToCart(product) {
      this.carts.push(product);
    },
  },
  created() {
    console.log(this);
  },
};
Vue.createApp(App).mount("#app");

Computed 運算之 Getter, Setter

// HTML
<div id="app">
  <h3>Computed 將目前的數值運算呈現至畫面上</h3>
  <ul>
    <li v-for="product in products">
      {{ product.name }} / {{ product.price }}
      <button type="button" @click="addToCart(product)">加入購物車</button>
    </li>
  </ul>
  ...
  <h6>購物車項目</h6>
  <ul>
    <li v-for="item in carts">{{ item.name }}</li>
  </ul>
  total 的值: {{ total }}<br />

  <h3>Computed 常見技巧 - 搜尋</h3>
  <input type="search" v-model="search" />
  <ul>
    <li v-for="item in filterProducts">{{ item.name }} / {{ item.price }}</li>
  </ul>

  <h3>Computed Getter, Setter</h3>
  sum 的值:
  <input type="number" v-model.number="num" />
  <button type="button" @click="total = num">更新</button>
  total 的值:{{ total }}<br />
  sum 的值:{{ sum }}
</div>
// JS
const App = {
  data() {
    return {
      num: 10,
      search: "",
      products: [
        {
          name: "蛋餅",
          price: 30,
          vegan: false,
        },
        {
          name: "飯糰",
          price: 35,
          vegan: false,
        },
        {
          name: "小籠包",
          price: 60,
          vegan: false,
        },
        {
          name: "蘿蔔糕",
          price: 30,
          vegan: true,
        },
      ],
      carts: [],
      sum: 0,
    };
  },
  // 運算
  computed: {
    total: {
      get() {
        let total = 0;
        this.carts.forEach((item) => {
          total += item.price;
        });
        // 講解1
        // return total;

        // 講解2
        return this.sum || total;

        // 練習
        // return (this.num = total);
      },
      set(val) {
        // 講解1
        // console.log(val);
        // this.sum = val * 0.8;

        // 講解2
        this.sum = val;

        // 練習
        // this.sum = val *0.8;
      },
    },
    filterProducts() {
      // return [];
      return this.products.filter((item) => {
        // console.log(item);
        return item.name.match(this.search);
      });
    },
  },
  methods: {
    addToCart(product) {
      this.carts.push(product);
    },
  },
  created() {
    console.log(this);
  },
};
Vue.createApp(App).mount("#app");

Watch 監聽

// HTML
<div id="app">
  <h3>watch 監聽單一變數</h3>
  <label for="name">名字須超過十個字</label>
  <input type="text" id="name" v-model="tempName" />
  <p>result: {{ result }}</p>
  <p>name: {{ name }}</p>

  <h3>watch vs computed</h3>
  <h5>Watch</h5>
  <ul>
    <li>監聽單一 “變數” 觸發事件</li>
    <li>該函式可同時操作多個變數</li>
  </ul>

  <h5>Computed</h5>
  <ul>
    <li>監聽多個變數觸發事件</li>
    <li>會產生一個值</li>
  </ul>

  <label for="productName">商品名稱</label
  ><input type="text" v-model="productName" /><br />
  <label for="productPrice">商品價格</label
  ><input type="number" v-model.number="productPrice" /><br />
  <label><input type="checkbox" v-model="productVegan" /> 素食</label>
  <p>Computed result2: {{ result2 }}</p>
  <p>Watch result3: {{ result3 }}</p>

  <h3>watch 深層監聽</h3>
  <label for="productName">商品名稱</label
  ><input type="text" v-model="product.name" /><br />
  <label for="productPrice">商品價格</label
  ><input type="number" v-model.number="product.price" /><br />
  <label><input type="checkbox" v-model="product.vegan" /> 素食</label>
  <p>result4: {{ result4 }}</p>
</div>
// JS
const App = {
  data() {
    return {
      name: "",
      tempName: "",
      result: "",
      result3: "",
      result4: "",
      // 單一產品
      productName: "蛋餅",
      productPrice: 30,
      productVegan: false,
      // 單一產品
      product: {
        name: "蛋餅",
        price: 30,
        vegan: false,
      },
      products: [
        {
          name: "蛋餅",
          price: 30,
          vegan: false,
        },
        {
          name: "飯糰",
          price: 35,
          vegan: false,
        },
        {
          name: "小籠包",
          price: 60,
          vegan: false,
        },
        {
          name: "蘿蔔糕",
          price: 30,
          vegan: true,
        },
      ],
      carts: [],
      sum: 0,
    };
  },
  computed: {
    result2() {
      return `媽媽買了 ${this.productName},總共花費 ${
        this.productPrice
      } 元,另外這 ${this.productVegan ? "是" : "不是"} 素食的`;
    },
  },
  // 監聽
  watch: {
    // 新的值 = n,舊的值 = o
    tempName(n, o) {
      console.log(n, o);
      if (n.length >= 10) {
        this.result = `文字長度為 ${n.length} 個字,將儲存至變數中`;
        this.name = n;
      } else {
        this.result = `輸入的文字僅有 ${n.length} 個字,上一次有 ${o.length} 個字`;
      }
    },
    productName() {
      this.result3 = `媽媽買了 ${this.productName},總共花費 ${
        this.productPrice
      } 元,另外這 ${this.productVegan ? "是" : "不是"} 素食的`;
    },
    productPrice() {
      this.result3 = `媽媽買了 ${this.productName},總共花費 ${
        this.productPrice
      } 元,另外這 ${this.productVegan ? "是" : "不是"} 素食的`;
    },
    productVegan() {
      this.result3 = `媽媽買了 ${this.productName},總共花費 ${
        this.productPrice
      } 元,另外這 ${this.productVegan ? "是" : "不是"} 素食的`;
    },
    product: {
      handler(n, o) {
        console.log(n, o);
        this.result4 = `媽媽買了 ${this.product.name},總共花費 ${
          this.product.price
        } 元,另外這 ${this.product.vegan ? "是" : "不是"} 素食的`;
      },
      deep: true,
    },
  },
  // 保留文字:
  // `文字長度為 ${n.length} 個字,將儲存至變數中`
  // `輸入的文字僅有 ${n.length} 個字,上一次有 ${o.length} 個字`
  // `媽媽買了 ${this.productName},總共花費 ${this.productPrice} 元,另外這 ${this.productVegan? '是' : '不是'} 素食的`
};
Vue.createApp(App).mount("#app");

生命週期詳解

// HTML
<div id="app">
  <h3>Vue 元件生命週期展示</h3>
  <p>
    生命週期介紹:<a
      href="https://vue3js.cn/docs/zh/guide/instance.html#%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E5%9B%BE%E7%A4%BA"
      >生命週期</a
    >
  </p>
  <button @click="isShowing = !isShowing" class="btn btn-primary">
    <span v-if="isShowing">隱藏元件</span>
    <span v-else>顯示元件</span>
  </button>
  <hr />

  <!-- <child v-if="isShowing"></child> -->
  <!-- <child v-show="isShowing"></child> -->

  <keep-alive>
    <child v-if="isShowing"></child>
  </keep-alive>

  <p>講解事項:</p>
  <ul>
    <li>Vue 都是元件,元件的生命週期</li>
    <li>生命週期流程</li>
    <li>v-if 與 v-show 的差異</li>
    <li>使用 Keep Alive 維持生命週期</li>
  </ul>
</div>
// JS
<script type="text/x-template" id="childarea">
  <div>
    <h4>{{ text }}</h4>
    <input type="text" class="form-control" v-model="text">
  </div>
</script>

<script>
  const child = {
    template: "#childarea",
    data: function () {
      return {
        text: "Vue data 資料狀態",
      };
    },
    beforeCreate() {
      console.log(`beforeCreate! ${this.text}`);
    },
    created() {
      console.log(`created! ${this.text}`);
      alert(`created! ${this.text}`);
    },
    beforeMount() {
      alert(`beforeMount! ${this.text}`);
    },
    mounted() {
      alert(`mounted! ${this.text}`);
    },
    updated() {
      console.log(`updated! ${this.text}`);
    },
    activated() {
      alert(`activated! ${this.text}`);
    },
    deactivated() {
      alert(`deactivated! ${this.text}`);
    },
    beforeUnmount() {
      console.log(`beforeUnmount! ${this.text}`);
    },
    unmounted() {
      console.log(`unmounted! ${this.text}`);
    },
  };
  const App = {
    data() {
      return {
        isShowing: false,
      };
    },
  };
  Vue.createApp(App).component("child", child).mount("#app");
</script>

Options API 章節作業

作業流程

  1. 使用生命週期取得遠端資料
  2. 點擊時,呈現右邊的區塊
  3. 搭配 Google Map 的 iframe 直接呈現位置
  4. 使用 computed 的技巧,製作過濾功能
  5. 使用 watch,製作瀏覽紀錄 (數量不超過十筆)
// HTML
<!-- 
  作業流程
  1. 使用生命週期取得遠端資料
  2. 點擊時,呈現右邊的區塊
  3. 搭配 Google Map 的 iframe 直接呈現位置
  4. 使用 computed 的技巧,製作過濾功能
  5. 使用 watch,製作瀏覽紀錄 (數量不超過十筆)
-->

<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.1/axios.min.js"></script>
<div id="app" class="mt-2">
  <div class="row h-100">
    <div class="col-md-3 h-100 d-flex flex-column">
      <div class="form-floating mb-2">
        <input type="search" class="form-control" id="search" placeholder="search" v-model="cacheSearch">
        <label for="search">search</label>
      </div>
      <div class="list-group option">
        <!-- datastore 改成 filterSearch -->
        <!-- 'data' 改成 filter -->
        <label class="list-group-item" v-for="(item, key) in filterSearch" :key="'filter' + key">
          <input class="form-check-input me-1" type="radio" :value="item" name="area" @click="getArea(item)" :checked="cacheArea.Name === item.Name">
          {{ item.Name }}
        </label>
      </div>
    </div>
    <div class="col-md-8 h-100 d-flex flex-column">
      <div class="form-floating">
        <select id="cacheArea" class="form-select w-50 mb-2" aria-label="select example" v-model="cacheArea">
          <option selected value="" disabled>瀏覽紀錄</option>
          <option v-for="(item, key) in browseLog" :value="item">{{ key + 1 }}. {{ item.Name }}</option>
        </select>
        <label for="cacheArea">瀏覽紀錄</label>
      </div>
      <div class="card overflow-auto">
        <!-- v-if、v-else -->
        <div v-if="cacheArea.Name">
          <img :src="cacheArea.Picture1" class="card-img-top" :alt="cacheArea.Name">
          <iframe width="100%" height="300" frameborder="0" scrolling="no" marginheight="0" marginwidth="0"
            :src=`https://maps.google.com.tw/maps?f=q&hl=zh-TW&geocode=&q=${cacheArea.Py},${cacheArea.Px}(${cacheArea.Name})&z=16&output=embed`>
          </iframe>
          <div class="card-body">
            <h5 class="card-title">{{ cacheArea.Name }}</h5>
            <p>{{ cacheArea.Description }}</p>
          </div>
        </div>
        <div v-else class="card-body">
          請選擇地方
        </div>
      </div>
    </div>
  </div>
</div>
// JS
const apiUrl = 'https://raw.githubusercontent.com/hexschool/KCGTravel/master/datastore_search.json';

axios.get(apiUrl).then((res) => {
  // 取得遠端資料
})

const App = {
  data() {
    return {
      // datastore 物件改成陣列
      datastore: [],
      cacheArea: '',
      cacheSearch: '',
      browseLog: [],
    };
  },
  methods: {
    getData() {
      const apiUrl = 'https://raw.githubusercontent.com/hexschool/KCGTravel/master/datastore_search.json';

      axios.get(apiUrl).then((res) => {
        console.log(res, '抓取資料成功');
        this.datastore = res.data.result.records;
        // console.log(this.datastore);
      }).catch((err) => {
        console.log(err, '抓取資料失敗');
      })
    },
    getArea(area) {
      console.log(area, '取得地點');
      this.cacheArea = area;
    }
  },
  created() {
    this.getData();
  },
  computed: {
    filterSearch() {
      return this.datastore.filter((item) => item.Name.match(this.cacheSearch));
    }
  },
  watch: {
    cacheArea() {
      if (this.browseLog.length < 10) {
        this.browseLog.push(this.cacheArea);
      } else {
        this.browseLog.shift();
        this.browseLog.push(this.cacheArea);
      }
    }
  }
};

Vue.createApp(App).mount('#app');
// CSS
#app {
  height: 600px;
}

.option {
  overflow-y: auto;
}