這個標題可能不太好,但此文章確實不是一篇使用教程,並且也不會覆蓋太多點,建議時間充裕的仍是應該完整地看下 官網文檔。javascript
React Hooks 對於部分人來講可能仍是陌生的,但仍是阻止不了它成爲了當前 React 社區裏「最」熱門的一個詞彙。html
一開始瞭解到這個仍是 Dan Abramov 在十月底的時候發了一個推,是一篇文章 Making Sense of React Hooks,建議沒看過的先看下。看完第一感覺就是:React 本就應該是這樣的啊!java
看完這篇文章,但願你能夠從總體上對 Hooks 有個認識,並對其設計哲學有一些理解,但願看的過程不要急,跟着個人思路走。react
若是你想本身跟着文章一塊兒練手,須要把react
和react-dom
更新到16.7.0-alpha
及以上,若是配置了 ESLint,記得添加對應的 Plugin。
長期以來不少人會把 Stateless Component
和 Functional Component
混爲一談,我會試着跟他們解釋這不是一回事(不是一個維度),但當時 Functional Component
裏確實沒法使用 state,我不管怎麼解釋都會顯得很無力。難道這冥冥之中都預示着會有相似 React Hooks 的東西出現?npm
稍微複雜點的項目確定是充斥着大量的 React 生命週期函數(注意,即便你使用了狀態管理庫也避免不了這個),每一個生命週期裏幾乎都承擔着某個業務邏輯的一部分,或者說某個業務邏輯是分散在各個生命週期裏的。編程
而 Hooks 的出現本質是把這種面向生命週期編程變成了面向業務邏輯編程,你不用再去關心本不應關心的生命週期。json
咱們先假想一個常見的需求,一個 Modal 裏須要展現一些信息,這些信息須要經過 API 獲取且跟 Modal 強業務相關,要求咱們:redux
咱們可能的實現以下:api
代碼完整演示地址app
class RandomUserModal extends React.Component { constructor(props) { super(props); this.state = { user: {}, loading: false, }; this.fetchData = this.fetchData.bind(this); } componentDidMount() { if (this.props.visible) { this.fetchData(); } } componentDidUpdate(prevProps) { if (!prevProps.visible && this.props.visible) { this.fetchData(); } } fetchData() { this.setState({ loading: true }); fetch('https://randomuser.me/api/') .then(res => res.json()) .then(json => this.setState({ user: json.results[0], loading: false, })); } render() { const user = this.state.user; return ( <ReactModal isOpen={this.props.visible} > <button onClick={this.props.handleCloseModal}>Close Modal</button> {this.state.loading ? <div>loading...</div> : <ul> <li>Name: {`${(user.name || {}).first} ${(user.name || {}).last}`}</li> <li>Gender: {user.gender}</li> <li>Phone: {user.phone}</li> </ul> } </ReactModal> ) } }
咱們抽象了一個包含業務邏輯的 RandomUserModal
,該 Modal 的展現與否由父組件控制,所以會傳入參數 visible
和 handleCloseModal
(用於 Modal 關閉本身)。
爲了實如今 Modal 打開的時候才進行數據獲取,咱們須要同時在 componentDidMount
和 componentDidUpdate
兩個生命週期裏實現數據獲取的邏輯,並且 constructor
裏的一些初始化操做也少不了。
其實咱們的要求很簡單:在合適的時候經過 API 獲取新的信息,這就是咱們抽象出來的一個業務邏輯,爲了這個業務邏輯能在 React 里正確工做,咱們須要將其按照 React 組件生命週期進行拆解。這種拆解除了代碼冗餘,還很難複用。
下面咱們看看採用 Hooks 改造後會是什麼樣:
function RandomUserModal(props) { const [user, setUser] = React.useState({}); const [loading, setLoading] = React.useState(false); React.useEffect(() => { if (!props.visible) return; setLoading(true); fetch('https://randomuser.me/api/').then(res => res.json()).then(json => { setUser(json.results[0]); setLoading(false); }); }, [props.visible]); return ( // View 部分幾乎與上面相同 ); }
很明顯地能夠看到咱們把 Class 形式變成了 Function 形式,使用了兩個 State Hook 進行數據管理(類比 constructor
),以前 cDM
和 cDU
兩個生命週期裏乾的事咱們直接在一個 Effect Hook 裏作了(若是有讀取或修改 DOM 的需求能夠看 這裏)。作了這些,最大的優點是代碼精簡,業務邏輯變的緊湊,代碼行數也從 50+ 行減小到 30+ 行。
Hooks 的強大之處還不只僅是這個,最重要的是這些業務邏輯能夠隨意地的的抽離出去,跟普通的函數沒什麼區別(僅僅是看起來沒區別),因而就變成了能夠複用的自定義 Hook。具體能夠看下面的進一步改造:
// 自定義 Hook function useFetchUser(visible) { const [user, setUser] = React.useState({}); const [loading, setLoading] = React.useState(false); React.useEffect(() => { if (!visible) return; setLoading(true); fetch('https://randomuser.me/api/').then(res => res.json()).then(json => { setUser(json.results[0]); setLoading(false); }); }, [visible]); return { user, loading }; } function RandomUserModal(props) { const { user, loading } = useFetchUser(props.visible); return ( // 與上面相同 ); }
這裏的 useFetchUser
爲自定義 Hook,它的地位跟自帶的 useState
等比也沒什麼區別,你能夠在其它組件裏使用,甚至在這個組件裏使用兩次,它們會自然地隔離開。
這裏說的業務邏輯複用主要是須要跨生命週期的業務邏輯。單單按照組件堆積的形式組織代碼雖然也能夠達到各類複用的目的,可是會致使組件很是複雜,數據流也會很亂。組件堆積適合 UI 佈局,可是不適合邏輯組織。爲了解決這些問題,在 React 發展過程當中,產生了不少解決方案,我認知裏常見的有如下幾種:
壞處遠遠大於帶來的好處,由於如今已經再也不支持,很少說,能夠看看這篇文章:Mixins Considered Harmful。
官方 很不推薦此作法,實際上我也沒真的看到有人這麼作。
React 高階組件 在封裝業務組件上簡直是屢試不爽,它的實現是把本身做爲一個函數,接受一個組件,再返回一個組件,這樣它能夠統一處理掉一些業務邏輯並達到複用目的。
比較常見的一個就是 react-redux
裏的 connect
函數:
(圖片來自 這裏)
可是它也被不少人吐槽嵌套問題:
(圖片來自 這裏)
Render Props 其實很常見,好比 React Context API:
class App extends React.Component { render() { return ( <ThemeProvider> <ThemeContext.Consumer> {val => <div>{val}</div>} </ThemeContext.Consumer> </ThemeProvider> ) } }
它的實現思路很簡單,把原來該放「組件」的地方,換成了回調,這樣當前組件裏就能夠拿到子組件的狀態並使用。
可是,一樣這會產生 Wrapper Hell 問題:
(圖片來自 這裏)
Hooks 本質上面說了,是把面向生命週期編程變成了面向業務邏輯編程,寫法上帶來的優化只是順帶的。
這裏,作一個類比,await/async
本質是把 JS 裏異步編程思惟變成了同步思惟,寫法上表現出來的特色就是原來的 Callback Hell 被打平了。
總結對比:
await/async
把 Callback Hell 幹掉了,異步編程思惟變成了同步編程思惟這裏不得不客觀地說,HOC 和 Render Props 仍是有存在的必要,一方面是支持 React Class,另外一方面,它們不光適用於純邏輯封裝,不少時候也適合邏輯 + 組件的封裝場景,雖然此時使用 Hooks 也能夠,可是會顯得囉嗦點。另外,上面詬病的最大的問題 Wrapper Hell,我我的以爲使用 Fragment 也能夠基本解決。
首先,React Hooks 的設計是反直覺的,爲何這樣說呢?能夠先試着問本身:爲何 Hooks 只能在其它 Hooks 的函數或者 React Function 組件裏?
在咱們的認知裏,React 社區一直推崇函數式、純函數等思想,引入 Hooks 概念後的 Functional Component
變的再也不純了,useXxx
與其說是一條執行語句,不如說是一個聲明。聲明這裏放了一個「狀態盒子」,盒子有輸入和輸出,剩下的內部實現就一無所知,重要的是,盒子是有記憶的,下次執行到此位置時,它有以前上下文信息。
類比「代碼」和「程序」的區別,前者是死的,後者是活的。表達式 c = a + b
表示把 a
和 b
累加後的值賦值給 c
,可是若是寫成 c := a + b
就表示 c
的值由 a
和 b
相加獲得。看起來表述差很少,但實際上,後者隱藏着一個時間的維度,它表示的是一種聯繫,而不僅僅是個運算。這在 RxJS 等庫中被大量使用。
這種聲明目前是經過很弱的 use
前綴標識的(可是設計上會簡潔不少),爲了避免弄錯每一個盒子和狀態的對應關係,書寫的時候 Hooks 須要 use
開頭且放在頂層做用域,即不能夠包裹 if/switch/when/try
等。若是你按文章開頭引入了那個 ESLint Plugin 就不用擔憂會弄錯了。
這篇文章可能並無一個很條理的目錄結構,大可能是一些我的理解和相關思考。所以,這不能替代你去看真正的文檔瞭解更多。若是你看完後仍是以爲廢話太多,不知所云,那我但願你至少能夠在下面幾點上跟做者達成共鳴:
文章可隨意轉載,但請保留此 原文連接。
很是歡迎有激情的你加入 ES2049 Studio,簡歷請發送至 caijun.hcj(at)alibaba-inc.com 。