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 章節作業
作業流程
- 使用生命週期取得遠端資料
- 點擊時,呈現右邊的區塊
- 搭配 Google Map 的 iframe 直接呈現位置
- 使用 computed 的技巧,製作過濾功能
- 使用 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;
}