引言
本篇文章總結了日常開發中,時長會處理的兩類問題, 一個是,條件渲染, 這裏的所說的條件渲染不僅僅包含了v-if
, 而是泛指,一個組件的渲染,需要具備一定的觸發時機, 那麼處理這種場景時, 通常有 v-if
、 手動掛載 、 以及 動態組件。
此外,第二種場景,是 某個組件中的某些方法的執行, 需要依賴另一個組件中函數處理返回的數據。也就是需要值監聽,然後執行某些方法。
這裏常用的是:通過ref
訪問實例直接調用、 vm.$watcher
和 遞歸判斷。 因需求的變化,其應用場景也有不同。
1. 條件渲染
開發工作時, 要求 A 組件掛載完成之後,再去掛載 B 組件, 是極爲常見的開發需求。通常是由於B 組件的渲染,依賴 A 組件處理返回的數據。
這裏討論幾種最常用的處理方案, 在不同的情況下度量應用。
爲了演示方便, 這裏通過一個簡單的示例來進行說明:
有這樣一個頁面, 期望,MainContent 要先於 SideArea 掛載。
組件定義內容如下:
// MainContent
<template>
<div class="flex justify-center items-center">
MainContent
</div>
</template>
<script>
export default {
created() {
setTimeout(() => this.$emit("isMounted", { msg: "依賴的假數據" }), 2000);
},
};
</script>
<style scoped>
div {
height: 100%;
width: 800px;
background-color: lightcoral;
color: white;
font-size: 24px;
}
</style>
爲了模擬MainContent組件需要較長時間的渲染, 這裏通過延時定時器觸發 自定義事件 來標識組件掛載完畢
// SideArea
<template>
<div class="flex justify-center items-center">
SideArea
<br />
{{ mockMsg }}
</div>
</template>
<script>
export default {
props: ["mockMsg"],
};
</script>
<style scoped>
div {
height: 100%;
width: 200px;
background-color: lightseagreen;
color: white;
font-size: 24px;
}
</style>
方案一 : 通過 v-if
指令,條件渲染
index.vue
組件
<template>
<div class="wrapper">
<main-content @isMounted="isMountedHandler"></main-content>
<side-area v-if="ifRender" :mock="mock"></side-area>
</div>
</template>
<script>
import MainContent from "./comps/MainContent.vue";
import SideArea from "./comps/SideArea.vue";
export default {
components: {
MainContent,
SideArea,
},
data() {
return {
ifRender: false,
mock: "",
};
},
methods: {
isMountedHandler({ mock }) {
this.ifRender = true;
this.mock = mock;
},
},
};
</script>
<style scoped>
.wrapper {
height: 500px;
width: 1000px;
border: 1px solid red;
display: flex;
justify-content: flex-start;
}
</style>
當 MainContent 組件渲染完成, 通過$emit
觸發自定義事件 isMounted
,並傳入返回的mock 數據, 上層組件監聽函數isMountedHandler
執行, 將 SideArea v-if
值設定爲 true
並props傳入 mock 數據。
這種方式是最方便,也是最常見的方式。
方案二 :vm.$mount()
手動掛載
不再通過v-if
去控制是否渲染SideArea, 利用Vue 的手動掛載機制:
index.vue
組件
<template>
<div class="wrapper">
<main-content @isMounted="isMountedHandler"></main-content>
<div id="side-area-box"></div>
</div>
</template>
<script>
import MainContent from "./comps/MainContent.vue";
import SideArea from "./comps/SideArea.vue";
import Vue from "vue";
export default {
components: {
MainContent,
},
methods: {
isMountedHandler({ msg }) {
let sideAreaInstance = Vue.extend(SideArea);
new sideAreaInstance({
propsData: {
mockMsg: msg,
},
}).$mount("#side-area-box");
},
},
};
</script>
<style scoped>
.wrapper {
height: 500px;
width: 1000px;
border: 1px solid red;
display: flex;
justify-content: flex-start;
}
</style>
這種方式要注意的是, 你需要有一個佔位的容器, 需要考慮樣式佈局,被掛載的組件樣式應該獨立。 因爲手動掛載後, 並不是將組件插入到佔位容器中去, 而是直接替換,這意味着,如果你的樣式作用在佔位容器上,將全部失效, 所以應該將樣式寫在組件內部。
此外如果必須要傳入
props
可以直接這樣寫:isMountedHandler() { new Vue(SideArea).$mount("#side-area-box"); },
方案三 :動態組件
index.vue
組件
<template>
<div class="wrapper">
<main-content @isMounted="isMountedHandler"></main-content>
<component :is="componentName" :mockMsg="mock" />
</div>
</template>
<script>
import MainContent from "./comps/MainContent.vue";
import SideArea from "./comps/SideArea.vue";
export default {
components: {
MainContent,
SideArea,
},
data() {
return {
mock: null,
componentName: undefined,
};
},
methods: {
isMountedHandler({ msg }) {
this.componentName = "SideArea";
this.mock = msg;
},
},
};
</script>
<style scoped>
.wrapper {
height: 500px;
width: 1000px;
border: 1px solid red;
display: flex;
justify-content: flex-start;
}
</style>
實現效果和v-if
是比較類似的。
2. 組件方法的值驅動執行⭐
還有一種常見的場景,B 組件中的某個方法的執行, 依賴於 A 組件某個方法執行的返回值。 聽起來,常用eventbus 解決這個問題, 但是這裏,不是利用事件機制, 而是通過觀察值的變化去執行B組件內的方法。 即我們監測A 組件的方法執行返回值, 有值之後再去執行B 組件方法。
方案一:通過 ref 直接調用
這種方法是最常見的方式, 但是其重心就不在 SideArea
組件中了,而是在其上層組件, 示例中,即 index.vue
文件。
//mainContent.vue
<template>
<div class="flex justify-center items-center">
MainContent
</div>
</template>
<script>
export default {
created() {
let count = 0;
setInterval(
() => this.$emit("isMounted", { msg: "依賴的假數據" + count++ }),
2000,
);
},
};
</script>
<style scoped>
div {
height: 100%;
width: 800px;
background-color: lightcoral;
color: white;
font-size: 24px;
}
</style>
讓props 值每兩秒變化一次
// SideArea.vue
<template>
<div class="flex justify-center items-center">
SideArea
</div>
</template>
<script>
export default {
methods: {
doSomething(msg) {
console.log("觸發一次執行一次", msg, "--line10");
},
},
};
</script>
<style scoped>
div {
height: 100%;
width: 200px;
background-color: lightseagreen;
color: white;
font-size: 24px;
}
</style>
index.vue
文件:
<template>
<div class="wrapper">
<main-content @isMounted="isMountedHandler" />
<SideArea ref="sideArea" />
</div>
</template>
<script>
import MainContent from "./comps/MainContent.vue";
import SideArea from "./comps/SideArea.vue";
export default {
components: {
MainContent,
SideArea,
},
data() {
return {
mock: null,
};
},
methods: {
isMountedHandler({ msg }) {
this.$refs.sideArea.doSomething(msg);
},
},
};
</script>
<style scoped>
.wrapper {
height: 500px;
width: 1000px;
border: 1px solid red;
display: flex;
justify-content: flex-start;
}
</style>
mainContent 中值發生變化,然後通過$emit
觸發其上層組件index.vue
中的 handler
—— isMountedHandler , 然後直接在handler 中通過 ref
引用訪問到 SideArea 組件實例,直接調用預定義的目標方法,並通過函數參數傳參達到傳值的目的。
這種方式的特點就是 簡單, 還有就是觸發一次, 執行一次, 這意味着,值一旦變化, 這個方法就會重新被觸發;大部分時候是符合我們的用意的,但是有些場景下是不適用的, 例如要求僅觸發一次。
下面的兩種方案,則是常用於解決要求僅觸發一次的場景。
接下來的兩種方案, 重心都在 SideArea.vue 組件, 另外兩個文件也稍有變化, 先寫在前面:
index.vue
文件:
// index.vue
<template>
<div class="wrapper">
<main-content @isMounted="isMountedHandler" />
<SideArea :mockMsg="mock" />
</div>
</template>
<script>
import MainContent from "./comps/MainContent.vue";
import SideArea from "./comps/SideArea.vue";
export default {
components: {
MainContent,
SideArea,
},
data() {
return {
mock: null,
};
},
methods: {
isMountedHandler({ msg }) {
this.mock = msg;
},
},
};
</script>
<style scoped>
.wrapper {
height: 500px;
width: 1000px;
border: 1px solid red;
display: flex;
justify-content: flex-start;
}
</style>
直接簡單的把 mainContent.vue 中變化的值通過 props 傳給 SideArea
mainContent.vue
文件:
<template>
<div class="flex justify-center items-center">
MainContent
</div>
</template>
<script>
export default {
created() {
let count = 0;
setInterval(
() => this.$emit("isMounted", { msg: "依賴的假數據" + count++ }),
2000,
);
},
};
</script>
<style scoped>
div {
height: 100%;
width: 800px;
background-color: lightcoral;
color: white;
font-size: 24px;
}
</style>
讓props 值每兩秒變化一次
接下來的說明,主要是在SideArea.vue
中演示的, 和上面這兩個文件的關聯不大。
方案二: vw.$watch
利用vue實例方法 $watch()
, 之所以不用普通的監聽器,寫在 watch 對象中, 是因爲 vue 實例方法 $watch
,在定義一個監聽器後返回的是其取消偵聽函數
SideArea.vue
文件 :
<template>
<div class="flex justify-center items-center">
SideArea
<br />
{{ mockMsg }}
</div>
</template>
<script>
export default {
props: ["mockMsg"],
created() {
var unwatch = this.$watch("mockMsg", function() {
//do something here
console.log("只觸發一次", "--line14");
if (unwatch) {
unwatch();
}
});
},
};
</script>
<style scoped>
div {
height: 100%;
width: 200px;
background-color: lightseagreen;
color: white;
font-size: 24px;
}
</style>
簡單的描述,就是通過vue 的實例方法
$watch
去監聽mainContent 組件傳入的props 值, 且爲了 SideArea 組件中的目標事件僅觸發一次, 我們通過調用vm.$watch()
返回的取消偵聽函數,在目標事件執行完後去調用以取消偵聽。
可以看到,實現的效果,就是即便props在不斷變化, 但是目標函數僅會執行一次。
方案三:遞歸判斷
這種方法,也是類似監聽,不過方法更加的原始。
SideArea.vue
<template>
<div class="flex justify-center items-center">
SideArea
<br />
{{ mockMsg }}
</div>
</template>
<script>
export default {
props: ["mockMsg"],
created() {
this.doSomething();
},
methods: {
doSomething() {
if (!this.mockMsg) {// init prop is null
this.timer = setTimeout(() => this.doSomething(), 10);
return;
}
if (this.timer) clearTimeout(this.timer);
// do something here
console.log("僅執行一次", "--line17");
},
},
};
</script>
<style scoped>
div {
height: 100%;
width: 200px;
background-color: lightseagreen;
color: white;
font-size: 24px;
}
</style>
定義了一個定時器,用以不斷的去問有沒有props值過來, 一旦有值,就立即銷燬定時器, 然後執行之後的代碼邏輯。
爲了觀察更加方便可以加一個定時器:
<template>
<div class="flex justify-center items-center">
SideArea
<br />
{{ mockMsg }}
</div>
</template>
<script>
export default {
props: ["mockMsg"],
data() {
return {
count: 0,
};
},
created() {
this.doSomething();
},
methods: {
doSomething() {
console.log(this.count, "--line17");
if (!this.mockMsg) {
this.count++;
// init prop is null
this.timer = setTimeout(() => this.doSomething(), 100);
return;
}
if (this.timer) clearTimeout(this.timer);
// do something here
console.log("僅執行一次", "--line17");
},
},
};
</script>
<style scoped>
div {
height: 100%;
width: 200px;
background-color: lightseagreen;
color: white;
font-size: 24px;
}
</style>
可以看到也可以達到監聽 props 值的效果。
總結
本文總結了日常開發中,經常遇到的兩種場景處理, 分別是 組件的條件渲染問題, 以及組件方法執行由值驅動時的問題。
根據不同開發需求,又列舉了多種不同場景時解決方案。 它們有的可能並不是專用於解決該類問題, 而是能夠解決。
此外,組件方法由值驅動執行這種場景下, 本文按照可能需要重複執行和單次執行 進行了總結, 如 以常用的 ref 實例引用的方式直接調用子組件內的方法,用於調用一次執行一次。 所以當其在被一個變化值所驅動時,子組件內對應的方法也就會重複被執行。
而有的時候,我們需要的其實是一次性執行,例如我們需要在子組件目標方法中去初始化一個事件監聽器,等類似的場景。
這時候我們可以利用 vm.$watch()
vue 的實例方法, 返回的是其取消監聽的函數這一特點,實現一次性監聽。
也可以用循環延時判斷,props 值, 然後在該值滿足所需要求的時候,銷燬定時器, 執行下面的流程。
實際上,這只是一部分方法, 例如, 第二種場景的 如果要求能重複執行,除了 ref ,我們還可以通過 props 傳值,然後通過 定義watch 對象,去達成目的。 再比如,無論第二種場景下的哪種情況,都是可以通過自定義事件(eventBus)去達成目的的, 要求重複執行?, $on
, 要救僅執行一次? , $once
。