[Vue] #常見開發場景 #條件渲染#手動掛載 #組件方法的值驅動執行

引言

本篇文章總結了日常開發中,時長會處理的兩類問題, 一個是,條件渲染, 這裏的所說的條件渲染不僅僅包含了v-if , 而是泛指,一個組件的渲染,需要具備一定的觸發時機, 那麼處理這種場景時, 通常有 v-if 、 手動掛載 、 以及 動態組件。

此外,第二種場景,是 某個組件中的某些方法的執行, 需要依賴另一個組件中函數處理返回的數據。也就是需要值監聽,然後執行某些方法。

這裏常用的是:通過ref訪問實例直接調用、 vm.$watcher 和 遞歸判斷。 因需求的變化,其應用場景也有不同。

1. 條件渲染

開發工作時, 要求 A 組件掛載完成之後,再去掛載 B 組件, 是極爲常見的開發需求。通常是由於B 組件的渲染,依賴 A 組件處理返回的數據。
這裏討論幾種最常用的處理方案, 在不同的情況下度量應用。

爲了演示方便, 這裏通過一個簡單的示例來進行說明:

image-20211221165542000

有這樣一個頁面, 期望,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>

image-20211222143943391

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 ,在定義一個監聽器後返回的是其取消偵聽函數

https://cn.vuejs.org/v2/api/index.html#vm-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() 返回的取消偵聽函數,在目標事件執行完後去調用以取消偵聽。

image-20211222132718573

可以看到,實現的效果,就是即便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>

image-20211222135045941

可以看到也可以達到監聽 props 值的效果。

總結

本文總結了日常開發中,經常遇到的兩種場景處理, 分別是 組件的條件渲染問題, 以及組件方法執行由值驅動時的問題。

根據不同開發需求,又列舉了多種不同場景時解決方案。 它們有的可能並不是專用於解決該類問題, 而是能夠解決。

此外,組件方法由值驅動執行這種場景下, 本文按照可能需要重複執行和單次執行 進行了總結, 如 以常用的 ref 實例引用的方式直接調用子組件內的方法,用於調用一次執行一次。 所以當其在被一個變化值所驅動時,子組件內對應的方法也就會重複被執行。

而有的時候,我們需要的其實是一次性執行,例如我們需要在子組件目標方法中去初始化一個事件監聽器,等類似的場景。

這時候我們可以利用 vm.$watch() vue 的實例方法, 返回的是其取消監聽的函數這一特點,實現一次性監聽。

也可以用循環延時判斷,props 值, 然後在該值滿足所需要求的時候,銷燬定時器, 執行下面的流程。

實際上,這只是一部分方法, 例如, 第二種場景的 如果要求能重複執行,除了 ref ,我們還可以通過 props 傳值,然後通過 定義watch 對象,去達成目的。 再比如,無論第二種場景下的哪種情況,都是可以通過自定義事件(eventBus)去達成目的的, 要求重複執行?, $on, 要救僅執行一次? , $once

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章