---
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()`


```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>
```