Making Sense of React Hooks
(原文發佈於 2018 年 10 月 31 日)
這周, Sophie Alpert 和我在 React Conf 上介紹了 "Hooks" 提案,以後 Ryan Florence 又進行了深刻的介紹:
視頻地址html
我強烈建議你們去看看這個開場主題演講,瞭解咱們試圖用 Hooks 提案解決的問題。不過一個小時也是巨大的時間投入,因此我決定在下面分享一些關於 Hooks 的想法。react
注意:Hooks 是 React 的實驗性提案。你不須要如今瞭解他們。另請注意,這篇文章包含了我本身的觀點,並不必定反映 React 團隊的立場。
咱們知道組件和自上而下的數據流幫助咱們將大型的 UI 分解成小型,獨立,可重用的部分。可是咱們常常沒法進一步分解複雜的組件,由於邏輯是有狀態的,沒法提取到函數或者其餘組件。這就是人們說 React 不讓他們 「分開關注」 的意思。
這種狀況很是常見,包括動畫,表單處理,鏈接到外部數據源以及咱們但願的許多在組件中執行的其餘操做。當咱們嘗試只使用組件解決這鞋問題時,咱們一般會這麼解決:git
咱們認爲 Hooks 是解決全部這些問題的最好機會。Hooks 讓咱們將組件內部的邏輯組織成可重用的隔絕單元:
github
在一個組件中 Hooks 符合 React 的理念(明確的數據流和組成),而不只僅是組件之間。這就是爲何我以爲 Hooks 很適合 React 的組件模型。spring
和 render props 或者高階組件的模式不一樣,Hooks 不會在組件樹中引入沒必要要的嵌套。它們也沒有 mixins 的弊端。編程
即便你的第一反應是發自肺腑的(像我第一次那樣),我也鼓勵你不帶偏見地去嘗試這個提案,我想你會喜歡它的。ide
在咱們仔細瞭解 Hooks 以前,你可能會擔憂咱們只是用 Hooks 在 React 中添加了更多的概念。這是一種正常的顧慮。我認爲學習它們確定會有短時間的認知成本,可是最終的結果將是相反的。
若是 React 社區擁抱了 Hooks 提案,它將減小編寫 React 應用程序時須要處理的概念數量。 Hooks 讓你老是使用函數而沒必要在函數(function),類(classes),高階組件(higher-order components)和 render props 之間切換。函數式編程
就實現大小而言,Hooks 支持只增長了 React ~1.5kB(min + gzip)。雖然這很少,可是使用 Hooks 可能會減小你的包的大小,由於使用 Hooks 的代碼比使用類(classes)的代碼更容易縮小。下面這個例子比較極端可是它有效地演示了爲何(點擊查看整個例子):
(原文這裏掛掉了)函數
Hooks 提案不包含任何破壞性升級。當你在新寫的組件中使用 Hooks 的時候你現有的代碼將繼續工做。實際上這正是咱們所推崇的——不作任何重大的改寫!在任何關鍵代碼中採用 Hooks 都是一個好主意。儘管如此,若是你嘗試使用 16.7 alpha 版本以後向咱們提供關於 Hooks 的反饋並報告一些錯誤,咱們會很是感激。工具
要理解 Hooks,咱們須要退一步而後思考代碼重用。
今天,有不少方法能夠在 React 應用中重用邏輯。咱們能夠寫簡單的方法並調用它們來計算某些東西。咱們也能夠編寫組件(它們自己能夠是函數或是類)。組件功能更強大,可是他們須要渲染一些 UI。這使得它們不便於共享非可視邏輯。這就是爲何最終咱們會獲得像渲染 props 和高階組件的複雜的模式。若是有一種經常使用的方法替代這些實現代碼重用,React 會不會更簡單?
函數看上去是代碼重用的完美機制。函數之間邏輯轉移花費最少的消耗。可是函數不能在它裏面包含本地 React 狀態(local React state)。若是你不重構代碼或者引入像 Observables 這樣的抽象,你沒法從類組件中抽取出「監視窗口大小且更新狀態」或「隨時間變化值」這樣的事件。這兩種事件會傷害咱們所喜歡的 React 的簡潔性。
Hooks 正好解決了這個問題。 Hooks 容許你在函數中使用 React 的功能(像 state)——經過執行單個函數調用。React 提供了一些內置的 Hooks,它們暴露了 React 的「構建快」:狀態,生命週期和上下文。
因爲 Hooks 是常規的 Javascript 函數,所以你能夠將 React 提供的內置 Hooks 組合到你本身的「自定義 Hooks」中。這使你能夠將複雜的問題轉換成單行,並在整個應用程序或者 React 社區中分享它們:
請注意,自定義 Hooks 在技術上不是 React 的功能。你本身的 Hooks 的實現得益於 Hooks 設計的方式。
假設咱們想給組件訂閱當前窗口的寬度(例如在窄視窗上顯示不一樣的內容)。
如今你有好幾種方法去編寫這種代碼。它們包括編寫類,設置一些生命週期方法,若是要在組件之間重用它,甚至能夠提取出 render props 或者高階組件。但我認爲沒有什麼比這更好:
// MyResponsiveComponent.js function MyResponsiveComponent() { const width = useWindowWidth(); // Our custom Hook return ( <p>Window width is {width}</p> ); } // useWindowWidth.js function useWindowWidth() { const [width, setWidth] = useState(window.innerWidth); useEffect(() => { const handleResize = () => setWidth(window.innerWidth); window.addEventListener('resize', handleResize); return () => { window.removeEventListener('resize', handleResize); }; }); return width; }
Examples from "Making Sense of React Hooks"
若是你讀了這段代碼,它徹底照它說的執行。咱們在組件中使用窗口高度,當它變化時 React 從新渲染組件。這就是 Hooks 的目標——使組件真正聲明,即便它們包含狀態和反作用。
讓咱們看看如何實現這個自定義 Hook。咱們使用 React 的本地狀態 來保存當前窗口寬度,窗口大小改變時用一個反作用去設置這個狀態。
如你所見, 像 useState 和 useEffet 這些內置的 React Hooks 充當基本構建塊。咱們能夠直接在咱們的組件中使用它們,或者咱們能夠將它們組合成像useWindowWidth那樣的自定義 Hooks。使用自定義 Hooks 感受就像使用 React 內置 API 同樣順手。
你能夠經過這個介紹瞭解更多的內置 Hooks 相關內容。
Hooks 是徹底封裝的——每一次你調用 Hook,它在當前正在執行的組件中得到隔離的本地狀態。這對於這個特定的例子可有可無(窗口寬度在全部組件中都相同),但這是 Hooks 如此強大的緣由。它們不是一種分享狀態的方式 — 而是一種分享狀態邏輯的方式。咱們不想破壞自上而下的數據流!
每一個 Hook 可能包含一些本地狀態和反作用。你能夠像平時在函數之間那樣在多個 Hooks 之間傳遞數據。它們能夠接受參數並返回值,由於它們是 Javascript 函數。
這是一個實驗 Hooks 的 React 動畫庫的例子:
https://codesandbox.io/embed/...
請注意,演示代碼中經過一個渲染函數中使用多個自定義 Hooks 來傳遞值實現交錯動畫。
https://codesandbox.io/s/ppxnl191zx
(若是你想了解更多關於這個例子,看看這個教程。)
雖然這不是 Hooks 的主要目標,它們還爲強大的交互調試工具打開了大門:
Hooks 之間傳遞數據的能力使它們很是適合展現動畫,描述數據,表單管理,以及其餘有狀態的抽象。不像 render props 或者 高階組件, Hooks 不會在你的渲染樹上建立一個「錯誤的層級」。它們更像是一個鏈接到組件的「存儲單元」的平面列表。沒有額外的層。
在咱們看來,自定義 Hooks 是 Hooks 提案中最吸引人的部分。可是爲了使自定義 Hooks 工做,React 得提供函數去聲明狀態和反作用。這就是 useState 和 useEffect 這樣的內置 Hooks 讓咱們作的。你能夠在文檔中瞭解它們。
事實證實,這些內置 Hooks 不只在建立自定義 Hooks 的時候有用。它們也足以定義通常的組件,由於它們爲咱們提供了像 state 這樣的全部必要的功能。這就是爲何咱們但願 Hooks 在將來能夠成爲定義 React 組件的主要方式。
咱們沒有打算棄用類。在 Facebook,咱們有成千上萬的類組件,和你同樣,咱們無心重寫它們。可是若是 React 社區擁抱 Hooks,用兩種不一樣的推薦方法來編寫組件是沒有意義的。Hooks 能夠覆蓋類的全部用例,同時在提取,測試和重用代碼方面提供更大的靈活性。這就是爲何 Hooks 表明了咱們對 React 將來的願景。
你可能會對 Hooks 的規則 感到驚訝。
雖然必須在頂層調用 Hooks 是不常規的,不過即便能夠你估計也不但願在某個條件下定義狀態。舉個例子,你不能在類中有條件地定義狀態,在與 React 用戶交流的四年中我歷來沒有聽到過他們對此有任何怨言。
這種設計對於啓用自定義 Hooks 而不引入額外的語法雜質或其餘陷阱相當重要。咱們意識到最初的陌生感,可是咱們認爲這種代價是值得的。若是你不一樣意,我建議你在練習中使用它,看看它是否會改變你的感覺。
爲了瞭解開發者是否會對這些規則困惑,咱們已經在生產中使用了一個月 Hooks。咱們發如今實踐中人們會在幾個小時內習慣它們。就我我的而言,我認可起初我對這些規則也「感受不舒服」,可是我很快就克服了它。此次經歷很像了我對 React 的第一印象。(你立刻就愛上 React 了嗎?第二次使用時我纔開始喜歡它。)
請注意,Hooks 的實現中也沒有「魔法」,如 Jamie 指出 的那樣,它看起來與下面很是類似:
咱們保留了每一個組件的 Hooks 列表,並在每次使用 Hooks 的時候移動到列表中的下一個。因爲 Hooks 的規則,在每一個 render 中它們的順序都是相同的,所以咱們能夠爲組件的每一個調用提供正確的狀態。不要忘記 React 不須要作任何特殊的事情來知道哪一個組件正在渲染 — React 只調用你的組件。
(Rudi Yardley 的這篇文章 包含了一個很好的可視化解釋!)
或許你還想知道 React 把 Hooks 的狀態放在哪裏。答案是它們保存在 React 爲類保存狀態的地方。不論你怎麼定義你的組件,React 有一個內部更新隊列,這是全部狀態的來源。
Hooks 不依賴於現代 Javascript 庫中常見的代理(Proxies)或者 getters。因此講道理 Hooks 沒有那些流行的解決相似問題的途徑神奇。我會說 Hooks 和調用 array.push
, array.pop
同樣神奇(調用順序也很重要!)
Hooks 的設計與 React 無關。事實上,在提案發布後的前幾天,針對 Vue,Web 組件甚至純 Javascript 函數不一樣的人都想出了相同的 Hooks API 的實驗性實現方式。
最後,若是你是一個純粹的函數式編程主義者而且對 React 的依賴可變狀態做爲實現細節感到不自在,你可能會對使用代數效果純粹地處理 Hooks 的方式感到滿意(若是 Javascript 支持它們)。固然 React 一直在內部依賴可變狀態 — 目的就是你沒必要這麼作。
不管你是從務實的角度仍是教條的角度來考慮(若是你有的話),我但願這些理由中至少有一個能說服你。若是你很好奇,Sebastian(Hooks 的做者)在這篇關於 RFC 的 回覆中 也迴應了這些和其餘問題。最重要的是,我認爲 Hooks 幫助咱們用更少的精力構建組件,創造了更好的用戶體驗。這就是我我的對 Hooks 感到興奮的緣由。
若是你還沒被 Hooks 吸引,我徹底能夠理解。但我仍是但願你能嘗試一個小的項目,看看是否會改變你的觀點。不管你是遇到了 Hooks 解決不了的問題,或是你有其餘的解決方案,請經過 RFC 告訴咱們!
若是個人介紹讓你興奮了,或者至少有一點好奇,那就太好了!我只有一個請求。如今有不少人學習 React,若是咱們忙於編寫教程併爲幾乎剛出來沒幾天的功能發佈最佳實踐,他們會感到困惑。Hooks 中有一些東西甚至對 React 團隊中的咱們來講都不是很清楚。
若是你在 Hooks 不穩定時建立任何有關 Hooks 的內容,請特別說起它們是實驗性提案,並帶上包含 官方文檔 的連接。咱們會及時更新全部提案的更改。咱們也花了很多精力使它更全面,在那裏不少問題都已經獲得解答了。
當你和其餘不像你那麼興奮的人交流時,請保持理性。若是你發現別人誤解了它,能夠在對方開放的時候分享一些額外的信息。不過任何的改變都是可怕的,做爲一個社區,咱們應該盡力幫助人們,而不是疏遠他們。若是我(或者是 React 團隊中的任何其餘人)未能遵循這個建議,請聯繫咱們!
查看 Hooks 提案的文檔以瞭解更多信息:
Hooks 仍然處於早期階段,可是咱們很高興聽到你麼全部人的反饋,你能夠前往 RFC ,但咱們也會盡力跟上 Twitter 上的對話。
若是有不清楚的地方,請告訴我,我很樂意與你聊聊你的疑慮。謝謝你的閱讀!