[譯] Vue.js 的注意事項與技巧

原文連接:Vue.js — Considerations and Tricksjavascript

Vue.js 是一個很棒的框架。然而,當你開始構建一個大型 JavaScript 項目的時候,你將對 Vue.js 感到一些困惑。這些困惑並非來自框架自己,相反 Vue.js 團隊會常常調整一些重要設計策略。css

相對於 React 和 Angular,Vue.js 面向一些不一樣水平的開發者。它更加的友好,不論是對初學者仍是經驗豐富的老手。它並不隱藏一些 DOM 操做,相反它與 DOM 配合的很好。html

這篇文章更像是一個目錄,列舉了我在 Vue.js 的初學路上遇到一些問題和技巧。理解這些關鍵性的設計技巧,有助於咱們構建大型的 Web 應用。vue

寫這篇文章的時候是 2018 年 5 月 18 日,下面這些技巧依然是有效的。可是框架升級,或者瀏覽器底層或者 JS API 發生改變時,他們可能會變得不是那麼有用。java

譯者注:儘管 Vue.js 3 即將到來,可是下面的技巧大部分是有用的,由於 3 的版本並不會改變一些上層 API ,最大的特性多是底層數據 Observer 改有 proxy 實現,以及源碼使用 typescript 構建。git


一、爲何 Vue.js 不使用 ES Classes 的方式編寫組件

若是你使用過相似於 Angular 的框架或者某些後端 OOP 語言後,那麼你的第一個問題多是:爲何不使用 Class 形式的組件?es6

Vue.js 的做者在 GitHub issues 中很好的回答了這個問題: Use standard JS classes instead of custom syntax?github

爲何不使用 Class 這裏有三個很重要的緣由:web

  1. ES Classes 不可以知足當前 Vue.js 的需求,ES Classes 標準尚未徹底規範化,而且老是朝着錯誤的方向發展。若是 Classes 的私有屬性和裝飾器(當前已進入 Stage 3)穩定後,可能會有必定幫助。
  2. ES Classes 只適合於那些熟悉面嚮對象語言的人,它對哪些不使用複雜構建工具和編譯器的人不夠友好。
  3. 優秀的 UI 組件層次結構通常都是組件的橫向組合,它並非基於繼承的層次結構。而 Classes 形式顯然更擅長的是後者。

譯者注:But,Vue.js 3.0 將支持基於 Class 的組件寫法,真香。typescript

二、如何構建本身的抽象組件?

若是你想構建本身的抽象組件(好比 transition、keep-alive),這是一個比構建大型 web 應用更加瘋狂地想法,這裏有一些關於這個問題的討論,可是並無什麼進展。

Any plan for docs of abstract components?

譯者注:在 Vue.js 內部組件(transition、keep-alive)中,使用了一個 abstract 屬性,用於聲明抽象組件,這個屬性做者並不打算開放給你們使用,因此文檔也沒有說起。可是若是你要使用也是能夠的,那麼你必須深刻源碼探索該屬性有何做用。

可是不要懼怕,若是你能夠很好地理解 slots ,你就能夠構建本身的抽象組件了。這裏有一篇很好的博客介紹了要如何作到這一點。

Writing Abstract Components with Vue.js

譯者注:下面是《在 Vue.js 中構建抽象組件》的簡單翻譯

抽象組件與普通組件同樣,只是它不會在界面上顯示任何 DOM 元素。它們只是爲現有組件添加額外的行爲。
就像不少你已經熟悉的 Vue.js 的內置組件,好比:`<transition>`、`<keep-alive>`、`<slot>`。

如今展現一個案例,如何跟蹤一個 DOM 已經進入了可視區域 ,讓咱們使用 IntersectionObserver API 來實現一個解決這個問題的抽象組件。
(完整代碼在這裏:[vue-intersect](https://github.com/heavyy/vue-intersect))
複製代碼
// IntersectionObserver.vue
export default {
  // 在 Vue 中啓用抽象組件
  // 此屬性不在官方文檔中, 可能隨時發生更改,可是咱們的組件必須使用它
  abstract: true,
  // 從新實現一個 render 函數
  render() {
    // 咱們不須要任何包裹的元素,只須要返回子組件便可
    try {
      return this.$slots.default[0];
    } catch (e) {
      throw new Error('IntersectionObserver.vue can only render one, and exactly one child component.');
    }

    return null;
  },
  mounted () {
    // 建立一個 IntersectionObserver 實例
    this.observer = new IntersectionObserver((entries) => {
      this.$emit(entries[0].isIntersecting ? 'intersect-enter' : 'intersect-leave', [entries[0]]);
    });

    // 須要等待下一個事件隊列,保證子元素已經渲染
    this.$nextTick(() => {
      this.observer.observe(this.$slots.default[0].elm);
    });
  },
  destroyed() {
    // 確保組件移除時,IntersectionObserver 實例也會中止監聽
    this.observer.disconnect();
  }
}
複製代碼
讓咱們看看如何使用它?
複製代碼
<intersection-observer @intersect-enter="handleEnter" @intersect-leave="handleLeave">
  <my-honest-to-goodness-component></my-honest-to-goodness-component>
</intersection-observer>
複製代碼

可是在這樣作以前,請你三思。咱們通常依賴 mixins 和一些純函數來解決一些特殊場景的問題,你能夠將 mixins 直接看作一個抽象組件。

How do I extend another VueJS component in a single-file component? (ES6 vue-loader)

三、我不太喜歡 Vue.js 的單文件組件,我更但願 HTML、CSS 和 JavaScript 分離。

沒有人阻止你這樣作,若是你是個注重分離的哲學家,喜歡把不一樣的東西放在不一樣文件,或者討厭編輯器對 .vue 文件的不穩定行爲,那麼你這麼作也是能夠的。你要作的很簡單:

<!--https://vuejs.org/v2/guide/single-file-components.html -->
<!-- my-component.vue -->
<template src="./my-component.html"></template>
<script src="./my-component.js"></script>
<style src="./my-component.css"></style>
複製代碼

這麼作,就會出現下一個問題:個人組件老是須要 4 個文件(vue + html + css + js)嗎?我能不能擺脫 .vue 文件? 答案是確定的,你可使用 vue-template-loader

個人同事還爲此寫了一篇很棒的教程:

Using vue-template-loader with Vue.js to Compile HTML Templates

四、 函數式組件

感謝 React.js 讓函數式組件很流行,這是由於他們無狀態、易於測試。然而它們也存在一些問題。

譯者注:不瞭解 Vue.js 函數式組件的能夠先在官方文檔查看:官方文檔

4.1 爲何我不能對功能組件使用基於 Class 的 @Component 裝飾器?

再次回到 Classes,它只是一種用於保存本地狀態的數據結構。若是函數式組件是無狀態的,那麼使用 @Component 裝飾器就是無心義的。

這裏有關於這個的討論:

How to create functional component in @Component?

4.2 外部類和樣式不該用於函數式組件

函數式組件不能像普通組件那樣,綁定具體的類和樣式,必須在 render 函數中手動應用這些綁定。

DOM class attribute not rendered properly with functional components

class attribute ignored on functional components

4.3 函數式組件老是會重複渲染?

TLDR:在函數式組件中使用有狀態組件時務必要當心

Functional components are re-rendered when props are unchanged.

函數式組件至關於直接調用組件的 Render 函數,這意味着你應該:

避免在 render 函數中直接使用有狀態組件,由於這會在每次調用 render 函數時建立不一樣的組件實例。

若是函數式組件是葉子組件,會更好地利用它們。 須要注意的是,一樣的行爲也適用於 React.js。

4.4 如何在Vue.js 函數式組件中觸發一個事件?

在從函數式組件中觸發一個事件並不簡單。不幸的是,文檔中也沒有提到這一點。函數式組件中不可用 $emit 方法。stack overflow 上有人討論過這個問題:

How to emit an event from Vue.js Functional component?

五、Vue.js 的透明包裹組件

組件包裹一些DOM元素,而且公開了這些DOM元素的事件,而不是根DOM的節點實例。

例如:

<!-- Wrapper component for input -->
<template>
    <div class="wrapper-comp">
        <label>My Label</label>
        <input @focus="$emit('focus')" type="text"/>
    </div>
</template>
複製代碼

這裏咱們真正感興趣的是 input 節點,而不是 div 根節點,由於它主要是爲了樣式和修飾而添加的。用戶可能對這個組件的幾個輸入事件感興趣,好比 blurfocusclickhover等等。這意味着咱們必須從新綁定每一個事件。咱們的組件以下所示。

<!-- Wrapper component for input -->
<template>
    <div class="wrapper-comp">
        <label>My Label</label>
        <input type="text" @focus="$emit('focus')" @click="$emit('click')" @blur="$emit('blur')" @hover="$emit('hover')" />
    </div>
</template>
複製代碼

實際上這是徹底不必的。簡單的解決方案是使用 Vue 實例上的屬性 vm.$listeners 將事件從新綁定到所需DOM 元素上:

<!-- Notice the use of $listeners -->
<template>
    <div class="wrapper-comp">
        <label>My Label</label>
        <input v-on="$listeners" type="text"/>
    </div>
</template>
<!-- Uses: @focus event will bind to internal input element -->
<custom-input @focus="onFocus"></custom-input>
複製代碼

六、爲何你不能在 slot 上綁定和觸發事件

我常常看到有些開發人員,在 slot 上進行事件的監聽和分發,這是不可能的。

組件的 slot 由調用它的父組件提供,這意味着全部事件都應該與父組件相關聯。嘗試去傾聽這些變化意味着你的父子組件是緊密耦合的,不過有另外一種方法能夠作到這一點,Evan You解釋得很好:

Is it possible to emit event from component inside slot #4332

Suggestion: v-on on slots

七、slot 中的 slot(訪問孫輩slot)

在某些時候,可能會遇到這種狀況。假設有一個組件,好比 A ,它接受一些 slot 。遵循組合的原則,使用組件 A 構建另外一個組件 B 。而後你把 B 用在 C 中。

那麼如今問題來了: 如何將 slot 從 C 組件傳遞到 A 組件?

要回答這個問題,首先取決你使用何種方式構建組件? 若是你是用 render 函數,那就很簡單。你只須要在組件 B 的 render 函數中進行以下操做:

// Render function for component B
function render(h) {
    return h('component-a', {
        // Passing slots as they are to component A
        scopedSlot: this.$scopedSlots
    }
}
複製代碼

可是,若是你使用的是基於模板的方式,那麼就有些糟糕了。幸運的是,在這個問題上有了進展:

feat(core): support passing down scopedSlots with v-bind


但願這篇文章讓你對 Vue.js 的設計思路有了更深刻的瞭解,併爲你提供了一些在高級場景中的技巧。

相關文章
相關標籤/搜索