React Hook
是React16.8.0
版本以後提出的新增特性,因爲以前的項目都不怎麼用到React
,所以也就匆匆瞭解一下,最近由於換工做,主要技術棧變爲React
了,因此須要着重研究一下React
的一些特性以更好地應用到項目開發中和更好地進行知識沉澱。javascript
在解釋這個問題以前,能夠先看一段代碼:css
import React, { useState } from 'react' function Example() { // 聲明一個叫 "count" 的 state 變量 const [count, setCount] = useState(0) // 與 componentDidMount and componentDidUpdate效果相似 useEffect(() => { // Update the document title using the browser API document.title = `You clicked ${count} times` }) return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
Hook
是一個特殊的函數,它可讓你「鉤入」React
的特性。例如,useState
是容許你在React
函數組件中添加state
的Hook
。若是你在編寫函數組件並意識到須要向其添加一些state
,之前的作法是必須將其它轉化爲class
。如今你能夠在現有的函數組件中使用Hook
;又例如useEffect Hook
能夠告訴React
組件須要在渲染後執行某些操做,React
會保存你傳遞的函數(咱們將它稱之爲 「effect」),而且在執行 DOM 更新以後調用它,能夠把它看作componentDidMount
,componentDidUpdate
和componentWillUnmount
這三個函數的組合。html
官方解釋:Hook 是 React 16.8 的新增特性。它可讓你在不編寫 class 的狀況下使用 state 以及其餘的 React 特性。
React
沒有提供將可複用性行爲「附加」到組件的途徑(例如,把組件鏈接到store
)。若是你使用過React
一段時間,你也許會熟悉一些解決此類問題的方案,好比render props
和高階組件
。可是這類方案須要從新組織你的組件結構,這可能會很麻煩,使你的代碼難以理解。若是你在React DevTools
中觀察過React
應用,你會發現由providers,consumers,高階組件,render props
等其餘抽象層組成的組件會造成「嵌套地獄」。儘管咱們能夠在DevTools
過濾掉它們,但這說明了一個更深層次的問題:React
須要爲共享狀態邏輯提供更好的原生途徑。
Hook
能夠在無需修改組件結構的狀況下複用狀態邏輯,這使得在組件間或社區內共享Hook
變得更便捷java
咱們常常維護一些組件,組件起初很簡單,但隨着業務複雜度的提高,組件逐漸會變得比較複雜,使得每一個生命週期經常包含一些不相關的邏輯。例如,組件經常在componentDidMount
和componentDidUpdate
中獲取數據。可是,同一個componentDidMount
中可能也包含不少其它的邏輯,如設置事件監聽,而以後需在componentWillUnmount
中清除。相互關聯且須要對照修改的代碼被進行了拆分,而徹底不相關的代碼卻在同一個方法中組合在一塊兒,如此很容易產生bug
,而且致使邏輯不一致,維護起來也會顯得比較吃力。react
爲了解決這個問題,Hook
將組件中相互關聯的部分拆分紅更小的函數(好比設置訂閱或請求數據),而並不是強制按照生命週期劃分。你還可使用reducer
來管理組件的內部狀態,使其更加可預測。npm
引用官方的話:編程
除了代碼複用和代碼管理會遇到困難外,咱們還發現class
是學習React
的一大屏障。你必須去理解JavaScript
中this
的工做方式,這與其餘語言存在巨大差別。還不能忘記綁定事件處理器。沒有穩定的語法提案,這些代碼很是冗餘。你們能夠很好地理解props
,state
和自頂向下的數據流,但對class
卻束手無策。即使在有經驗的React
開發者之間,對於函數組件與class
組件的差別也存在分歧,甚至還要區分兩種組件的使用場景。另外,
React
已經發布五年了,咱們但願它能在下一個五年也與時俱進。就像Svelte
,Angular
,Glimmer
等其它的庫展現的那樣,組件預編譯會帶來巨大的潛力。尤爲是在它不侷限於模板的時候。最近,咱們一直在使用Prepack
來試驗component folding
,也取得了初步成效。可是咱們發現使用class
組件會無心中鼓勵開發者使用一些讓優化措施無效的方案。class
也給目前的工具帶來了一些問題。例如,class
不能很好的壓縮,而且會使熱重載出現不穩定的狀況。所以,咱們想提供一個使代碼更易於優化的API
。segmentfault
Hook
可以在非class
的狀況下使用更多的React
特性。 其實, React
組件一直更像是函數。而Hook
則擁抱了函數,同時也沒有犧牲React
的精神原則。Hook
提供了問題的解決方案,無需學習複雜的函數式或響應式編程技術。數組
最重要的是,Hook
是向下兼容的,它和現有代碼能夠同時工做,你能夠漸進式地使用他們,不用急着遷移到Hook
。瀏覽器
HOOK
概覽基礎Hook
額外的Hook
能夠看下面的代碼:
import React, { useState } from "react"; import "./styles.css"; export default function App() { const [count, setCount] = useState(0); return ( <div className="App"> <h1>這是一個示例</h1> <div>點擊了{count}次</div> <button onClick={() => { setCount(count + 1); }} > 點擊 </button> <button onClick={() => { setCount(0); }} > 清除 </button> </div> ); }
上述代碼中,useState
就是一個Hook
。經過在函數組件裏調用它來給組件添加一些內部state
。React
會在重複渲染時保留這個state
。useState
會返回一對值:當前狀態和一個讓你更新它的函數,你能夠在事件處理函數中或其餘一些地方調用這個函數。它相似class
組件的this.setState
,可是它不會把新的state
和舊的state
進行合併。
useState
惟一的參數就是初始state
。在上面的例子中,咱們的計數器是從零開始的,因此初始state
就是0
。值得注意的是,不一樣於this.state
,這裏的state
不必定要是一個對象,但若是你有須要,它也能夠是。這個初始state
參數只有在第一次渲染時會被用到。
你也能夠在函數組件中屢次使用state Hook
。
它定義一個 「state 變量」。在上面的示例中該變量叫count
, 但它能夠是任意的變量名,好比banana
。這是一種在函數調用時保存變量的方式,useState
是一種新方法,它與class
裏面的this.state
提供的功能徹底相同。通常來講,在函數退出後變量就會」消失」,而state
中的變量會被React
保留。
useState()
方法裏面惟一的參數就是初始state
。不一樣於class
的是,咱們能夠按照須要使用數字或字符串對其進行賦值,而不必定是對象。在示例中,只需使用數字來記錄用戶點擊次數,因此咱們傳了0
做爲變量的初始state
。(若是咱們想要在state
中存儲兩個不一樣的變量,只需調用useState()
兩次便可。)
返回值爲:當前state
以及更新state
的函數。這就是咱們寫 const [count, setCount] = useState()
的緣由。這與class
裏面this.state.count
和this.setState
相似,惟一區別就是你須要成對的獲取它們。
咱們直接看代碼來方便理解:
function Counter() { const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
<p>You clicked {count} times</p>
看到這段代碼,有必定經驗的人可能會想,這其中的原理是否是經過watcher
,或者是data binding
或者是proxy
來實現的呢?都不是,count僅僅只是一個數字類型的變量而已,不是上述中的任何一個,就像下面的普通的變量賦值同樣:
const count = 42; // ... <p>You clicked {count} times</p>
組件在第一次渲染的時候,從useState()
拿到count
的初始值0
。當咱們調用setCount(1)
,React
會再次渲染組件,這一次count
是1
。就如同下面示例的同樣:
// During first render function Counter() { const count = 0; // Returned by useState() // ... <p>You clicked {count} times</p> // ... } // After a click, our function is called again function Counter() { const count = 1; // Returned by useState() // ... <p>You clicked {count} times</p> // ... } // After another click, our function is called again function Counter() { const count = 2; // Returned by useState() // ... <p>You clicked {count} times</p> // ... }
當咱們更新狀態的時候,React
會從新渲染組件。每一次渲染都能拿到獨立的count
狀態,這個狀態值是函數中的一個常量
因此下面的這行代碼沒有作任何特殊的數據綁定:
<p>You clicked {count} times</p>
它僅僅只是在渲染輸出中插入了count
這個數字。這個數字由React
提供。當setCount
的時候,React
會帶着一個不一樣的count
值再次調用組件。而後,React
會更新DOM
以保持和渲染輸出一致。
這裏關鍵的點在於任意一次渲染中的count
常量都不會隨着時間改變。渲染輸出會變是由於咱們的組件被一次次調用,而每一次調用引發的渲染中,它包含的count
值獨立於其餘渲染。
什麼是反作用?React
官網是這麼定義的:
你以前可能已經在React
組件中執行過數據獲取、訂閱或者手動修改過DOM
。咱們統一把這些操做稱爲「反作用」,或者簡稱爲「做用」。
useEffect
就是一個Effect Hook
,給函數組件增長了操做反作用的能力。它跟class
組件中的 componentDidMount
、componentDidUpdate
和componentWillUnmount
具備相同的用途,只不過被合併成了一個API
。
例如,下面這個組件在React
更新DOM
後會設置一個頁面標題:
import React, { useState, useEffect } from 'react'; function Example() { const [count, setCount] = useState(0); // 至關於 componentDidMount 和 componentDidUpdate: useEffect(() => { // 使用瀏覽器的 API 更新頁面標題 document.title = `You clicked ${count} times`; }); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
當你調用useEffect
時,就是在告訴React
在完成對DOM
的更改後運行你的「反作用」函數。因爲反作用函數是在組件內聲明的,因此它們能夠訪問到組件的props
和state
。默認狀況下,React
會在每次渲染後調用反作用函數,包括第一次渲染的時候。
反作用函數還能夠經過返回一個函數來指定如何「清除」反作用。例如,在下面的組件中使用反作用函數來訂閱好友的在線狀態,並經過取消訂閱來進行清除操做:
import React, { useState, useEffect } from 'react'; function FriendStatus(props) { const [isOnline, setIsOnline] = useState(null); function handleStatusChange(status) { setIsOnline(status.isOnline); } useEffect(() => { ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); if (isOnline === null) { return 'Loading...'; } return isOnline ? 'Online' : 'Offline'; }
在這個示例中,React
會在組件銷燬時取消對ChatAPI
的訂閱,而後在後續渲染時從新執行反作用函數。
跟useState
同樣,你能夠在組件中屢次使用useEffect
。經過使用Hook
,你能夠把組件內相關的反作用組織在一塊兒(例如建立訂閱及取消訂閱),而不要把它們拆分到不一樣的生命週期函數裏。這樣就有利於你對代碼的維護。也再一次說明了React
官方爲何會使用Hook
。
再次看到官網文檔中的例子:
function Counter() { const [count, setCount] = useState(0); useEffect(() => { document.title = `You clicked ${count} times`; }); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
那麼:effect是如何讀取到最新的count
狀態值的呢?
也許,是某種data binding
或watching
機制使得count
可以在effect
函數內更新?也或許count
是一個可變的值,React
會在咱們組件內部修改它以使咱們的effect
函數總能拿到最新的值?
都不是。
咱們已經知道count是某個特定渲染中的常量。事件處理函數「看到」的是屬於它那次特定渲染中的count
狀態值。對於effects
也一樣如此:
並非count的值在「不變」的effect
中發生了改變,而是effect
函數自己在每一次渲染中都不相同。
每個effect
版本「看到」的count
值都來自於它屬於的那次渲染:
// During first render function Counter() { // ... useEffect( // Effect function from first render () => { document.title = `You clicked ${0} times`; } ); // ... } // After a click, our function is called again function Counter() { // ... useEffect( // Effect function from second render () => { document.title = `You clicked ${1} times`; } ); // ... } // After another click, our function is called again function Counter() { // ... useEffect( // Effect function from third render () => { document.title = `You clicked ${2} times`; } ); // .. }
React
會記住你提供的effect
函數,而且會在每次更改做用於DOM
並讓瀏覽器繪製屏幕後去調用它。
因此雖然咱們說的是一個effect
(這裏指更新document
的title
),但其實每次渲染都是一個不一樣的函數 — 而且每一個effect
函數「看到」的props
和state
都來自於它屬於的那次特定渲染。
Hook 就是 JavaScript 函數,可是使用它們會有兩個額外的規則:
Hook
。不要在循環、條件判斷或者子函數中調用。只能在React
的函數中調用調用Hook
。不要在普通的JavaScript
函數中調用Hook
,你能夠:
React
的函數組件中調用Hook
Hook
中調用其餘Hook
爲了更好地執行這個規則,react提供了eslint插件幫助你去檢測和強制執行上述規則:eslint-plugin-react-hooks。
這要從React
內部執行Hook
的機制提及:
React
函數組件中,可使用多個useState
或者useEffect
,那麼React
怎麼知道哪一個state
對應哪一個useState
?答案是React
靠的是Hook
調用的順序。只要Hook
的調用順序在屢次渲染之間保持一致,React
就能正確地將內部state
和對應的Hook
進行關聯。若是咱們將一個Hook
調用放在了條件語句中,就有可能會擾亂Hook
的調用的順序,致使內部錯誤的對應state和useState,進而致使bug的產生。
自定義Hook
是一個函數,其名稱以 「use」 開頭,函數內部能夠調用其餘的Hook
。
當咱們想在兩個函數之間共享邏輯時,咱們會把它提取到第三個函數中。而組件和Hook
都是函數,因此也一樣適用這種方式。
能夠直接看下面的例子:
import { useState, useEffect } from 'react'; function useFriendStatus(friendID) { const [isOnline, setIsOnline] = useState(null); useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange); }; }); return isOnline; }
與React
組件不一樣的是,自定義Hook
不須要具備特殊的標識。咱們能夠自由的決定它的參數是什麼,以及它應該返回什麼。換句話說,它就像一個正常的函數,可是它的名字應該始終以use
開頭,這樣能夠一眼看出其符合Hook
的規則。
此處useFriendStatus
的Hook
目的是訂閱某個好友的在線狀態。這就是咱們須要將friendID
做爲參數,並返回這位好友的在線狀態的緣由。
function FriendStatus(props) { const isOnline = useFriendStatus(props.friend.id); if (isOnline === null) { return 'Loading...'; } return isOnline ? 'Online' : 'Offline'; } function FriendListItem(props) { const isOnline = useFriendStatus(props.friend.id); return ( <li style={{ color: isOnline ? 'green' : 'black' }}> {props.friend.name} </li> ); }
自定義Hook
是一種天然遵循Hook
設計的約定,而並非React
的特性。
自定義Hook
必須以 「use」 開頭嗎?必須如此。這個約定很是重要。不遵循的話,因爲沒法判斷某個函數是否包含對其內部Hook
的調用,React
將沒法自動檢查你的Hook
是否違反了Hook
的規則。
在兩個組件中使用相同的Hook
會共享state
嗎?不會。自定義Hook
是一種重用狀態邏輯的機制(例如設置爲訂閱並存儲當前值),因此每次使用自定義Hook
時,其中的全部state
和反作用都是徹底隔離的。
自定義Hook
如何獲取獨立的state
?每次調用Hook
,它都會獲取獨立的state
。因爲咱們直接調用了useFriendStatus
,從React
的角度來看,咱們的組件只是調用了useState
和useEffect
。正如咱們在以前章節中瞭解到的同樣,咱們能夠在一個組件中屢次調用useState
和useEffect
,它們是徹底獨立的。
零零碎碎寫了這麼多,做爲一個入門參考,看了這篇文章,應該會對React Hook有了大體的瞭解,文章中也有深刻其內部機制剖析的地方,可是僅僅對state和effect部分作了簡要的深刻,而實際上React Hook中間還有不少的點值得去深刻推敲,因爲實際項目工做中用到的很少,所以也無法抓住某個坑作深刻的研究,準備後續認真研讀一下react的源碼,對其內部機制作深刻的研究。好好靜下心來沉澱。