關於Hook的定義官方文檔是這麼說的:html
Hook 是 React 16.8 的新增特性。它可讓你在不編寫 class 的狀況下使用 state 以及其餘的 React 特性。
簡單來講,就是在使用函數式組件時能用上state,還有一些生命週期函數等其餘的特性。react
若是想了解Hook怎麼用,官方文檔和阮一峯的React Hooks 入門教程都講得很清楚了,我建議直接看官方文檔和阮大神的文章便可。編程
本篇博客只講爲何要用React的Hook新特性,以及它解決了什麼問題。redux
讓咱們先看看別人怎麼說。數組
阮大神的文章中給了一個示例代碼:函數
import React, { Component } from "react"; export default class Button extends Component { constructor() { super(); this.state = { buttonText: "Click me, please" }; this.handleClick = this.handleClick.bind(this); } handleClick() { this.setState(() => { return { buttonText: "Thanks, been clicked!" }; }); } render() { const { buttonText } = this.state; return <button onClick={this.handleClick}>{buttonText}</button>; } }
而且提出:測試
這個組件類僅僅是一個按鈕,但能夠看到,它的代碼已經很"重"了。 真實的 React App 由多個類按照層級,一層層構成,複雜度成倍增加。 再加入 Redux,就變得更復雜。
實際上,上面這個代碼的「重」有部分來源於寫法問題,他可能並無「重」,讓咱們看看下面這種class寫法:this
import React, { Component } from "react"; export default class Button extends Component { state = { buttonText: "Click me, please" } handleClick = () => { this.setState(() => { return { buttonText: "Thanks, been clicked!" }; }); } render() { const { buttonText } = this.state; return <button onClick={this.handleClick}>{buttonText}</button>; } }
而後再對比下使用了Hook的函數式組件:spa
import React, { useState } from "react"; export default function Button() { const [buttonText, setButtonText] = useState("Click me, please"); function handleClick() { return setButtonText("Thanks, been clicked!"); } return <button onClick={handleClick}>{buttonText}</button>; }
即便是咱們簡化過的class寫法,比起Hook的看起來好像也確實「重」了點。code
Hook的語法確實簡練了一些,可是這個理由並非那麼充分。
阮大神同時列舉了Redux 的做者 Dan Abramov 總結了組件類的幾個缺點:
這三點都是事實,因而有了函數化的組件,但以前的函數化組件沒有state和生命週期,有了Hook那麼就能夠解決這個痛點。
並且Hook並不僅是這麼簡單,經過自定義Hook,咱們能夠將原有組件的邏輯提取出來實現複用。
上面舉的幾個缺點,第一點和第三點你可能很容易理解,第二點就不容易理解了,因此咱們須要深刻到具體的代碼中去理解這句話。
咱們看看下面這段代碼:
import React, { Component } from "react"; export default class Match extends Component { state={ matchInfo:'' } componentDidMount() { this.getMatchInfo(this.props.matchId) } componentDidUpdate(prevProps) { if (prevProps.matchId !== this.props.matchId) { this.getMatchInfo(this.props.matchId) } } getMatchInfo = (matchId) => { // 請求後臺接口獲取賽事信息 // ... this.setState({ matchInfo:serverResult // serverResult是後臺接口的返回值 }) } render() { const { matchInfo } = this.state return <div>{matchInfo}</div>; } }
這樣的代碼在咱們的業務中常常會出現,經過修改傳入賽事組件的ID,去改變這個賽事組件的信息。
在上面的代碼中,受生命週期影響,咱們須要在加載完畢和Id更新時都寫上重複的邏輯和關聯邏輯。
因此如今你應該比較好理解這句話:業務邏輯分散在組件的各個生命週期方法之中,致使重複邏輯或關聯邏輯。
爲了解決這一點,React提供了useEffect這個鉤子。
可是在講這個以前,咱們須要先了解到React帶來的一個新的思想:同步。
咱們在上面的代碼中所作的實際上就是在把組件內的狀態和組件外的狀態進行同步。
因此在使用Hook以前,咱們須要先摒棄生命週期的思想,而用同步的思想去思考這個問題。
如今再讓咱們看看改造後的代碼:
import React, { Component } from "react"; export default function Match({matchId}) { const [ matchInfo, setMatchInfo ] = React.useState('') React.useEffect(() => { // 請求後臺接口獲取賽事信息 // ... setMatchInfo(serverResult) // serverResult是後臺接口的返回值 }, [matchId]) return <div>{matchInfo}</div>; }
看到這個代碼,再對比上面的代碼,你心中第一反應應該就是:簡單。
React.useEffect接受兩個參數,第一個參數是Effect函數,第二個參數是一個數組。
組件加載的時候,執行Effect函數。
組件更新會去判斷數組中的各個值是否變更,若是不變,那麼不會執行Effect函數。
而若是不傳第二個參數,那麼不管加載仍是更新,都會執行Effect函數。
順便提一句,這裏有組件加載和更新的生命週期的概念了,那麼也應該是有組件卸載的概念的:
import React, { Component } from "react"; export default function Match({matchId}) { const [ matchInfo, setMatchInfo ] = React.useState('') React.useEffect(() => { // 請求後臺接口獲取賽事信息 // ... setMatchInfo(serverResult) // serverResult是後臺接口的返回值 return ()=>{ // 組件卸載後的執行代碼 } }, [matchId]) return <div>{matchInfo}</div>; } }
這個經常使用於事件綁定解綁之類的。
React的高階組件是用來提煉重複邏輯的組件工廠,簡單一點來講就是個函數,輸入參數爲組件A,輸出的是帶有某邏輯的組件A+。
回想一下上面的Match組件,假如這個組件是頁面A的首頁頭部用來展現賽事信息,而後如今頁面B的側邊欄也須要展現賽事信息。
問題就在於頁面A的這塊UI須要用div,而頁面B側邊欄的這塊UI須要用到span。
保證今天早點下班的作法是複製A頁面的代碼到頁面B,而後改下render的UI便可。
保證之後早點下班的作法是使用高階組件,請看下面的代碼:
import React from "react"; function hocMatch(Component) { return class Match React.Component { componentDidMount() { this.getMatchInfo(this.props.matchId) } componentDidUpdate(prevProps) { if (prevProps.matchId !== this.props.matchId) { this.getMatchInfo(this.props.matchId) } } getMatchInfo = (matchId) => { // 請求後臺接口獲取賽事信息 } render () { return ( <Component {...this.props} /> ) } } } const MatchDiv=hocMatch(DivUIComponent) const MatchSpan=hocMatch(SpanUIComponent) <MatchDiv matchId={1} matchInfo={matchInfo} /> <MatchSpan matchId={1} matchInfo={matchInfo} />
可是實際上有的時候咱們的高階組件可能會更復雜,好比react-redux的connect,這就是高階組件的複雜化使用方式。
又好比:
hocPage( hocMatch( hocDiv(DivComponent) ) )
毫無疑問高階組件能讓咱們複用不少邏輯,可是過於複雜的高階組件會讓以後的維護者望而卻步。
而Hook的玩法是使用自定義Hook去提煉這些邏輯,首先看看咱們以前使用了Hook的函數式組件:
import React, { Component } from "react"; export default function Match({matchId}) { const [ matchInfo, setMatchInfo ] = React.useState('') React.useEffect(() => { // 請求後臺接口獲取賽事信息 // ... setMatchInfo(serverResult) // serverResult是後臺接口的返回值 }, [matchId]) return <div>{matchInfo}</div>; }
而後,自定義Hook:
function useMatch(matchId){ const [ matchInfo, setMatchInfo ] = React.useState('') React.useEffect(() => { // 請求後臺接口獲取賽事信息 // ... setMatchInfo(serverResult) // serverResult是後臺接口的返回值 }, [matchId]) return [matchInfo] }
接下來,修改原來的Match組件
export default function Match({matchId}) { const [matchInfo]=useMatch(matchId) return <div>{matchInfo}</div>; }
相比高階組件,自定義Hook更加簡單,也更加容易理解。
如今咱們再來處理如下這種狀況:
hocPage( hocMatch( hocDiv(DivComponent) ) )
咱們的代碼將不會出現這種不斷嵌套狀況,而是會變成下面這種:
export default function PageA({matchId}) { const [pageInfo]=usePage(pageId) const [matchInfo]=useMatch(matchId) const [divInfo]=useDiv(divId) return <ul> <li>{pageInfo}</li> <li>{matchInfo}</li> <li>{divInfo}</li> </ul> }
如今咱們瞭解到了Hook的好,因此就須要去改造舊的class組件。
官方推薦不須要專門爲了hook去改造class組件,而且保證將繼續更新class相關功能。
實際上咱們也沒有必要專門去改造舊項目中的class組件,由於工做量並不小。
可是咱們徹底能夠在新的項目或者新的組件中去使用它。
Hook是對函數式組件的一次加強,使得函數式組件能夠作到class組件的state和生命週期。
Hook的語法更加簡練易懂,消除了class的生命週期方法致使的重複邏輯代碼,解決了高階組件難以理解和使用困難的問題。
然而Hook並無讓函數式組件能作到class組件作不到的事情,它只是讓不少事情變得更加簡單而已。
class組件並不會消失,但hook化的函數式組件將是趨勢。