[譯] Hooks 對 Vue 而言意味著什麼

清秋發表於2019-02-28

不要把 Hooks 和 Vue 的生命週期鉤子(Lifecycle Hooks) 弄混了,Hooks 是 React 在 V16.7.0-alpha 版本中引入的,而且幾天後 Vue 釋出了其概念驗證版本。雖然 Hooks 是由 React 提出的,它是一個對各 JavaScript 框架生態系統都有價值的、重要的組合機制,因此我們今天會花一點時間討論 Hooks 意味著什麼。

Hooks 主要是對模式的複用提供了一種更明確的思路 —— 避免重寫元件本身,並允許有狀態邏輯的不同部分能無縫地進行協同工作。

最初的問題

就 React 而言,問題在於:在表達狀態的概念時,類是最常見的元件形式。無狀態函式式元件也非常受歡迎,但由於它們只能單純地渲染,所以它們的用途僅限於展示任務。

類本身存在一些問題。例如,隨著 React 變得越來越流行,類的問題也普遍成為新手的阻礙。開發者為了理解 React,也必須理解類。繫結使得程式碼冗長且可讀性差,並且需要理解 JavaScript 中的 this這裡還討論了使用類所帶來的一些優化障礙。

在邏輯複用方面,我們通常使用 render props 和高階元件等模式。但使用這些模式後會發現自己處於類似的“厄運金字塔”中 —— 樣式實現地獄,即過度使用巢狀可能會導致元件難以維護。這導致我想對 Dan Abramov 像喝醉了一樣大吼大叫,沒有人想要那樣。

Hooks 允許我們使用函式呼叫來定義元件的有狀態邏輯,從而解決這些問題。這些函式呼叫變得更具有組合性、可複用性,並且允許我們在使用函式式元件的同時能夠訪問和維護狀態。React 釋出 Hooks 時,人們很興奮 —— 下面你可以看到 Hooks 展示的一些優勢,關於它們如何減少程式碼和重複:

@dan_abramov 的程式碼(來自 #ReactConf2018)視覺化,你能看到 React Hooks 為我們帶來的好處。pic.twitter.com/dKyOQsG0Gd

— Pavel Prichodko (@prchdk) 2018 年 10 月 29 日

在維護方面,簡單性是關鍵,Hooks 提供了一種單一的、函式式的方式來實現邏輯共享,並且可能程式碼量更小。

為什麼 Vue 中需要 Hooks?

讀到這裡你肯定想知道 Hooks 在 Vue 中必須提供什麼。這似乎是一個不需要解決的問題。畢竟,類並不是 Vue 主要使用的模式。Vue 提供無狀態函式式元件(如果需要它們),但為什麼我們需要在函式式元件中攜帶狀態呢?我們有 mixins 用於組合可以在多個元件複用的相同邏輯。問題解決了。

我想到了同樣的事情,但在與 Evan You 交談後,他指出了我忽略的一個主要用例:mixins 不能相互消費和使用狀態,但 Hooks 可以。這意味著如果我們需要鏈式封裝邏輯,可以使用 Hooks。

Hooks 實現了 mixins 的功能,但避免了 mixins 帶來的兩個主要問題:

  • 允許相互傳遞狀態。
  • 明確指出邏輯來自哪裡。

如果使用多個 mixins,我們不清楚哪個屬性是由哪個 mixins 提供的。使用 Hooks,函式的返回值會記錄消費的值。

那麼,這在 Vue 中如何執行呢?我們之前提到過,在使用 Hooks 時,邏輯在函式呼叫時表達從而可複用。在 Vue 中,這意味著我們可以將資料呼叫、方法呼叫或計算屬性呼叫封裝到另一個自定義函式中,並使它們可以自由組合。資料、方法和計算屬性現在可用於函式式元件了。

例子

讓我們來看一個非常簡單的 hook,以便我們在繼續學習 Hooks 中的組合例子之前理解構建塊。

useWat?

好的,Vue Hooks 和 React Hooks 之間存在交叉部分。使用 use 作為字首是 React 的約定,所以如果你在 React 中查詢 Hooks,你會發現 Hooks 的名稱都會像 useStateuseEffect 等。更多資訊可以檢視這裡。

Evan 的線上 demo 裡,你可以看到他在何處訪問 useStateuseEffect 並用於 render 函式。

如果你不熟悉 Vue 中的 render 函式,那麼看一看官網文件可能會有所幫助。

但是當我們使用 Vue 風格的 Hooks 時,我們會如何命名呢 —— 你猜對了 —— 比如:useDatauseComputed等。

因此,為了讓我們看看如何在 Vue 中使用 Hooks,我建立了一個示例應用程式供我們探索。

詳見視訊演示:css-tricks.com/wp-content/…

演示網站

GitHub 倉庫

在 src/hooks 資料夾中,我建立了一個 hook,它在 useMounted hook 上阻止了滾動,並在 useDestroyed 上重新啟用滾動。這有助於我在開啟檢視內容的對話方塊時暫停頁面滾動,並在檢視對話方塊結束時再次允許滾動。這是一個好的抽象功能,因為它在整個應用程式中可能會多次使用。

import { useDestroyed, useMounted } from "vue-hooks";

export function preventscroll() {
  const preventDefault = (e) => {
    e = e || window.event;
    if (e.preventDefault)
      e.preventDefault();
    e.returnValue = false;
  }

  // keycodes for left, up, right, down
  const keys = { 37: 1, 38: 1, 39: 1, 40: 1 };

  const preventDefaultForScrollKeys = (e) => {
    if (keys[e.keyCode]) {
      preventDefault(e);
      return false;
    }
  }

  useMounted(() => {
    if (window.addEventListener) // older FF
      window.addEventListener('DOMMouseScroll', preventDefault, false);
    window.onwheel = preventDefault; // modern standard
    window.onmousewheel = document.onmousewheel = preventDefault; // older browsers, IE
    window.touchmove = preventDefault; // mobile
    window.touchstart = preventDefault; // mobile
    document.onkeydown = preventDefaultForScrollKeys;
  });

  useDestroyed(() => {
    if (window.removeEventListener)
      window.removeEventListener('DOMMouseScroll', preventDefault, false);

    //firefox
    window.addEventListener('DOMMouseScroll', (e) => {
      e.stopPropagation();
    }, true);

    window.onmousewheel = document.onmousewheel = null;
    window.onwheel = null;
    window.touchmove = null;
    window.touchstart = null;
    document.onkeydown = null;
  });
} 
複製程式碼

然後我們可以在像 AppDetails.vue 一樣的 Vue 元件中呼叫它:

<script>
import { preventscroll } from "./../hooks/preventscroll.js";
...

export default {
  ...
  hooks() {
    preventscroll();
  }
}
</script>
複製程式碼

我們不僅可以在該元件中使用它,還可以在整個應用程式中使用相同的功能!

能夠相互理解的兩個 Hooks

我們之前提到過,Hooks 和 mixins 之間的主要區別之一是 Hooks 實際上可以互相傳值。讓我們看一下這個簡單但有點不自然的例子。

在我們的應用程式中,我們需要在一個可複用的 hook 中進行計算,還有一些需要使用該計算結果的東西。在我們的例子中,我們有一個 hook,它獲取視窗寬度並將其傳遞給動畫,讓它知道只有當我們在更大的螢幕上時才會觸發。

詳見視訊演示:css-tricks.com/wp-content/…

第一個 hook:

import { useData, useMounted } from 'vue-hooks';

export function windowwidth() {
  const data = useData({
    width: 0
  })

  useMounted(() => {
    data.width = window.innerWidth
  })

  // this is something we can consume with the other hook
  return {
    data
  }
}
複製程式碼

然後,在第二個 hook 中,我們使用它來建立一個觸發動畫邏輯的條件:

// the data comes from the other hook
export function logolettering(data) {
  useMounted(function () {
    // this is the width that we stored in data from the previous hook
    if (data.data.width > 1200) {
      // we can use refs if they are called in the useMounted hook
      const logoname = this.$refs.logoname;
      Splitting({ target: logoname, by: "chars" });

      TweenMax.staggerFromTo(".char", 5,
        {
          opacity: 0,
          transformOrigin: "50% 50% -30px",
          cycle: {
            color: ["red", "purple", "teal"],
            rotationY(i) {
              return i * 50
            }
          }
        },
        ...
複製程式碼

然後,在元件內部,我們將一個 hook 作為引數傳遞給另一個 hook:

<script>
import { logolettering } from "./../hooks/logolettering.js";
import { windowwidth } from "./../hooks/windowwidth.js";

export default {
  hooks() {
    logolettering(windowwidth());
  }
};
</script>
複製程式碼

現在我們可以在整個應用程式中使用 Hooks 來編寫邏輯!再提一下,這是一個用於演示目的不太自然的例子,但你可以看到這對於大型應用程式,將邏輯儲存在較小的、可複用的函式中是有效的。

未來的計劃

Vue Hooks 現在已經可以與 Vue 2.x 一起使用了,但仍然是實驗性的。我們計劃將 Hooks 整合到 Vue 3 中,但在我們自己的實現中可能會偏離 React 的 API。我們發現 React Hooks 非常鼓舞人心,正在考慮如何向 Vue 開發人員介紹其優勢。我們想以一種符合 Vue 習慣用法的方式來做,所以還有很多實驗要做。

你可以檢視這個倉庫作為起步。Hooks 可能會成為 mixins 的替代品,所以雖然這個功能還處於早期階段,但是一個在此期間探索其概念是有好處的。

(真誠地感謝 Evan You 和 Dan Abramov 為本文審閱。)

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章