[譯] 理解 React Hooks

鄭昊川發表於2018-11-12

本週,Sophie Alpert 和我在 React Conf 上提出了 “Hooks” 提案,緊接著是 Ryan Florence 對 Hooks 的深入介紹:

我強烈推薦大家觀看這個開場演講,在這個演講裡,大家可以瞭解到我們嘗試使用 Hooks 提案去解決的問題。不過,花費一個小時看視訊也是時間上的巨大投入,所以我決定在下面分享一些關於 Hooks 的想法。

注意:Hooks 是 React 的實驗性提案。你無需現在就去學習它們。另請注意,這篇文章包含我的個人意見,並不一定代表 React 團隊的立場

為什麼需要 Hooks?

我們知道元件和自上而下的資料流可以幫助我們將龐大的 UI 組織成小型、獨立、可複用的塊。但是,我們經常無法進一步拆分複雜的元件,因為邏輯是有狀態的,而且無法抽象為函式或其他元件。這也就是人們有時說 React 不允許他們“分離關注點”的意思。

這些情況很常見,包括動畫、表單處理、連線到外部資料來源以及其他很多我們希望在元件中做的事情。當我們嘗試單獨使用元件來解決這些問題時,通常我們會這樣收場:

  • 巨大的元件 難以重構和測試。
  • 重複的邏輯 在不同的元件和生命週期函式之間。
  • 複雜的模式 像 render props 和高階元件。

我們認為 Hooks 是解決所有這些問題的最佳實踐。Hooks 讓我們將元件內部的邏輯組織成可複用的隔離單元

[譯] 理解 React Hooks

Hooks 在元件內部應用 React 的哲學(顯式資料流和組合),而不僅僅是元件之間。這就是為什麼我覺得 Hooks 天生就適用於 React 的元件模型。

不同於 render props 或高階元件等的模式,Hooks 不會在元件樹中引入不必要的巢狀。它們也沒有受到 mixins 的負面影響

即使你內心一開始是牴觸的(就像我剛開始一樣!),我還是強烈建議你直接對這個提案進行一次嘗試和探索。我想你會喜歡它的。

Hooks 會使 React 變得臃腫嗎?

在我們詳細介紹 Hooks 之前,你可能會擔心我們通過 Hooks 只是向 React 新增了更多概念。這是一個公正的批評。我認為雖然學習它們肯定會有短期的認知成本,不過最終的結果卻恰恰相反。

如果 React 社群接受 Hooks 的提案,這將減少編寫 React 應用時需要考慮的概念數量。Hooks 可以使得你始終使用函式,而不必在函式、類、高階元件和 reader 屬性之間不斷切換。

就部署大小而言,對 Hooks 的支援僅僅增加了 React 約 1.5kB(min + gzip)的大小。雖然不多,但由於使用 Hooks 的程式碼通常可以比使用類的等效程式碼壓縮得更小,所以使用 Hooks 也可能會減少你的包大小。下面這個例子有點極端,但它有效地展示了我這麼說的原因(點選檢視整個帖子):

[譯] 理解 React Hooks

Hooks 提案不包括任何重大變化。即使你在新編寫的元件中採用了 Hooks,你現有的程式碼仍將照常執行。事實上,這正是我們推薦的 —— 不做大的重寫!在任何關鍵程式碼中採用 Hooks 都是一個好主意。與此同時,如果你能夠嘗試 16.7 alpha 版並在 Hooks proposalreport any bugs 向在我們提供反饋,我們將不勝感激。

究竟什麼是 Hooks?

要了解 Hooks,我們需要退一步來思考程式碼複用。

今天,有很多方式可以在 React 應用中複用邏輯。我們可以編寫一個簡單的函式並呼叫它們來進行某些計算。我們也可以編寫元件(它們本身可以是函式或類)。元件更強大,但它們必須渲染一些 UI。這使得它們不便於共享非可視邏輯。這使得我們最終不得不用到 render props 和高階元件等複雜模式。如果只用一種簡單的方式來複用程式碼而不是那麼多,那麼React會不會簡單點

函式似乎是程式碼複用的一種完美機制。在函式之間組織邏輯僅需要最少的精力。但是,函式內無法包含 React 的本地狀態。在不重構程式碼或不抽象出 Observables 的情況下,你也無法從類元件中抽象出“監視視窗大小並更新狀態”或“隨時間變化改變動畫值”的行為。這兩種方法都破壞了我們喜歡的 React 的簡單性。

Hooks 正好解決了這個問題。 Hooks 允許你通過呼叫單個函式以在函式中使用 React 的特性(如狀態)。React 提供了一些內建的 Hooks,它們暴露了 React 的“構建塊”:狀態、生命週期和上下文。

由於 Hooks 是普通的 JavaScript 函式,因此你可以將 React 提供的內建 Hooks 組合到你自己的“自定義 Hooks”中。這使你可以將複雜問題轉換為一行程式碼,並在整個應用或 React 社群中分享它們:

[譯] 理解 React Hooks

注意,自定義 Hooks 從技術上講並不是 React 的特性。編寫自定義 Hooks 的可行性源自於 Hooks 的設計方式。

來點程式碼!

假設我們想要將訂閱一個自適應當前視窗寬度的元件(例如,在有限的檢視上顯示不同的內容)。

現在你有幾種方法可以編寫這種程式碼。這些方法包括編寫類,設定一些生命週期函式,如果要在元件之間複用,甚至可以需要提取 render props 或更高一層的元件。但我認為沒有比這更好的了:

[譯] 理解 React Hooks

如果你看這段程式碼,它恰恰就是我所表達的。我們在我們的元件中使用視窗的寬度,而 React 將會在它變化是重新渲染。這就是 Hooks 的目的 —— 使元件做到真正的宣告式,即使它們包含狀態和副作用。

讓我們來看看如何實現這個自定義 Hooks。我們使用 React 的本地狀態來儲存當前視窗寬度,並在視窗調整大小時使用一個副作用來設定該狀態:

[譯] 理解 React Hooks

就像你從上面看到的那樣,像 useStateuseEffect 這樣作為基本構建塊的 React 內建 Hooks。我們可以直接在元件中使用它們,或者我們可以將它們整合到自定義 Hooks 中,就像 useWindowWidth 那樣。使用自定義 Hooks 感覺就像使用 React 的內建 API 一樣得心應手。

你可以從此概述中瞭解有關內建 Hooks 的更多資訊。

Hooks 是完全封裝的 —— 你每次呼叫 Hooks 函式, 它都會從當前執行元件中獲取到獨立的本地狀態。對這個特殊的例子來說並不重要(所有元件的視窗寬度是相同的!),但這正是 Hooks 如此強大的原因。它們不僅是一種共享狀態的方式,更是共享狀態化邏輯的方式。我們不想破壞自上而下的資料流!

每個 Hooks 都可以包含一些本地狀態和副作用。你可以在不同 Hooks 之間傳值,就像在通常在函式之間做的那樣。Hooks 可以接受引數並返回值,因為它們就是JavaScript 函式。

這是一個實驗 Hooks 的 React 動畫庫的例子:

[譯] 理解 React Hooks

在 CodeSandbox 上執行這個例子

注意,在演示程式碼中,這個驚人的動畫是通過幾個自定義 Hooks 的傳值實現的。

[譯] 理解 React Hooks

(如果你想了解更多關於這個例子的資訊, 檢視此介紹。)

在 Hooks 之間傳遞資料的能力使得它們非常適合實現動畫、資料訂閱、表單管理和其他狀態化的抽象。不同於 render props 和高階元件,Hooks 不會在渲染樹中建立“錯誤層次結構”。它們更像是一個連線到元件的“儲存單元”的平面列表。沒有額外的層。

類又該何去何從?

在我們看來,自定義 Hooks 是 Hooks 提案中最吸引人的部分。但是為了使自定義 Hooks 工作,React 需要為函式提供一種宣告狀態和副作用的辦法。而這也正是像 useStateuseEffect 這樣的內建 Hooks 允許我們做的事情。你可以在文件中瞭解它們。

事實證明,這些內建 Hooks 不僅可用於建立自定義 Hooks。它們足以用來定義元件,因為它們像 state 一樣為我們提供了所有必要的特性。這就是為什麼我們希望 Hooks 成為未來定義 React 元件的主要原因。

我們沒有打算棄用類。在 Facebook,我們有成千上萬的類元件,而且和你一樣,我們無意重寫它們。但是如果 React 社群接受了 Hooks,那麼同時推薦兩種不同的方式來編寫元件是沒有意義的。Hooks 可以涵蓋類的所有應用場景,同時在抽象,測試和複用程式碼方面提供更大的靈活性。這就是為什麼 Hooks 代表了我們對 React 未來的願景。

不過 Hooks 是不是有點“魔術化”?

你可能會對Hooks 的規則感到驚訝。

雖然必須在頂層呼叫 Hooks 是不尋常的,但即使可以,你可能也不希望在某種條件判斷中定義狀態。例如,你也無法對類中定義的狀態進行條件判斷,而在過去四年和 React 使用者的交流中,我也沒有聽到過對此的抱怨。

這種設計在不引入額外的語法噪音或其他坑的情況下,對自定義 Hooks 至關重要。我們知道使用者一開始可能不熟悉,但我們認為這種取捨對未來是值得的。如果你不同意,我鼓勵你動手去實踐一下,看看這是否會改變你的感受。

我們已經在生產環境下使用 Hooks 一個月了,以觀察工程師們是否對這些規則感到困惑。我們發現實際情況是人們會在幾個小時內習慣它們。就個人而言,我承認這些規則起初讓我“感覺不對”,但我很快就克服了它。這次經歷像極了我對 React 的第一印象。(你一開始就喜歡 React 嗎?至少我不是一開始就喜歡,直到更多次嘗試後才改變看法。)

記住,在 Hooks 的實現中也沒有什麼“魔術”。就像 Jamie 指出的那樣,它像極了這個:

[譯] 理解 React Hooks

我們為每個元件保留了一個 Hooks 列表,並在每次 Hooks 被呼叫時移動到列表中的下一項。得意於 Hooks 的規則,它們的順序在每次渲染中都是相同的,因此我們可以為每次呼叫提供正確的元件狀態。要知道 React 不需要做任何特殊處理就能知道哪個元件正在渲染 —— 呼叫你的元件的正是 React。

也許你在想 React 在哪裡儲存了 Hooks 的狀態。答案就是,它儲存在和 React 為類保持狀態相同位置。無論你如何定義元件,React 都有一個內部的更新佇列,它是任何狀態的真實來源。

Hooks 不依賴於現代 JavaScript 庫中常見的代理或 getter。按理說,Hooks 比一些解決類似問題的流行方法平常。我想說 Hooks 就像呼叫 array.push 和 array.pop 一樣普通(一樣的取決於呼叫順序!)。

Hooks 的設計與 React 無關。事實上,在提案發布後的前幾天,不同的人提出了針對 Vue,Web Components 甚至原生 JavaScript 函式的相同 Hooks API 的實驗性實現。

最後,如果你是一個純函式程式設計主義者並且對 React 依賴可突變狀態的實現細節感到不安,你會欣喜的發現完成 Hooks 可以以函數語言程式設計的方式實現(如果 JavaScript 支援它們)。當然,React 一直依賴於內部的可突變狀態 —— 正因如此不必那樣做。

無論你是從一個更務實還是教條的角度來考慮(或者你兩者兼有),我希望至少有一個立場是有意義的。最重要的是,我認為 Hooks 讓我們用更少的精力去構建元件,並提供更好的使用者體驗。這就正是我個人對 Hooks 感到興奮的地方。

傳播正能量,而不是炒作

如果 Hooks 對你還沒有什麼吸引力,我完全可以理解。我仍然希望你能在一個很小的專案上嘗試一下,看看是否會改變你的看法。無論你是遇到需要 Hooks 來解決的問題,還是說你有不同的解決方案,歡迎通過 RFC 告訴我們!

如果我你感到興奮,或者說有那麼點好奇,那就太好了!我只有一個問題要問。現在有很多人正在學習 React,如果我們匆匆忙忙的編寫教程,並把僅僅才出現幾天的功能宣稱為最佳實踐,他們會感到困惑。即使對我們在 React 團隊的人來說,關於 Hooks 的一些事情還不是很清楚。

如果你在 Hooks 不穩定期間開發了任何有關 Hooks 的內容,請突出提示它們是一個實驗性提案,幷包含指向官方文件的連結。我們會在提案發生任何改變時及時更新它。我們也花了相當多的精力來完善它,所以很多問題已在那裡得到了解決。

當你和其他不像你那麼興奮的人交流時,請保持禮貌。如果你發現別人對此有誤解,如果對方樂意的話你可以和他分享更多資訊。但任何改變都是可怕的,作為一個社群,我們應該盡力幫助人們,而不是疏遠他們。如果我(或 React 團隊中的任何其他人)未遵循此建議,請致電我們!

更進一步

檢視 Hooks 提案的文件以瞭解更多資訊:

Hooks 仍然處於早期階段,但我們很樂意能聽到你們的反饋。你可以直接去 RFC,與此同時,我們也會盡量及時回覆 Twitter 上的對話。

如果有不清楚的地方,請告訴我,我很樂意為你答疑解惑。感謝你的閱讀!

[譯] 理解 React Hooks

Vitra — Portemanteau Hang it all

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


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

相關文章