做者:Brandon Newtonjavascript
原文:Higher-Order Components (HOCs) for Beginnershtml
談點:一篇面向初學者的 HOC 介紹。高階組件聽起來挺唬人的,只看名字恐怕不是那麼容易明白到底是何物,並且一般來說高階組件並非組件,而是接受組件做爲參數,而且返回組件的函數。早期利用 ES5 的 mixin 語法來作的事,基本均可以使用高階組件代替,並且能作的還有更多。java
寫這篇文章的原由是其餘關於高階組件(Higher-Order Components)的文章,包含官方文檔,都令初學者感到至關困惑。我知道有高階組件這樣一個東西,但不知道它到底有什麼用。因此,想經過一篇文章來對高階組件有一個更好的理解。react
在此以前,咱們須要先來說一下 JavaScript 中的函數。git
接下來將提供一些箭頭函數的簡單示例,若是以前沒有使用過,能夠認爲它們與普通函數基本一致。下面的代碼會展現箭頭函數與普通函數的區別。github
function () { return 42 } // same as: () => 42 // same as: () => { return 42 } function person(name) { return { name: name } } // same as: (name) => { return { name: name } }
閱讀 MDN 的箭頭函數文檔 瞭解更多信息。json
就像是數字、字符串、布爾值 同樣,函數也是值,意味着能夠像傳遞其餘數據同樣傳遞函數,能夠將函數做爲參數傳遞給另一個函數。redux
const execute = (someFunction) => someFunction() execute(() => alert('Executed'))
也能夠在在函數中返回一個函數:api
const getOne = () => () => 1 getOne()()
之因此在 getOne
後面有兩個 ()
,是由於第一個返回的返回值是一個函數。以下:數組
const getOne = () => () => 1 getOne //=> () => () => 1 getOne() //=> () => 1 getOne()() //=> 1
從函數返回函數能夠幫助咱們追蹤初始輸入函數。例如,下面的函數接受一個數字做爲參數,並返回一個將該參數乘以新參數的函數:
const multiply = (x) => (y) => x * y multiply(5)(20)
這個示例跟上述 getOne
同樣,在下面這個例子,讓 x = 5,y = 20。
const multiply = (x) => (y) => x * y multiply //=> (x) => (y) => x * y multiply(5) //=> (y) => 5 * y multiply(5)(20) //=> 5 * 20
在只傳入一個參數調用 multiply
函數時,即部分調用該函數。好比,multiply(5)
講獲得一個將其輸入值乘以 5 的函數,multiply(7)
將獲得一個將其輸入值乘以 7 的函數。依此類推。經過部分調用能夠建立一個預約義功能的新函數:
const multiply = (x) => (y) => x * y const multiplyByFive = multiply(5) const multiplyBy100 = multiply(100) multiplyByFive(20) //=> 100 multiply(5)(20) //=> 100 multiplyBy100(5) //=> 500 multiply(100)(5) //=> 500
一開始看起來彷佛沒什麼用,可是,經過部分調用這種方式能夠編寫可讀性更高,更易於理解的代碼。舉個例子,能夠用一種更清晰的方式來代替 style-components 的函數插入語法。
// before const Button = styled.button` background-color: ${({ theme }) => theme.bgColor} color: ${({ theme }) => theme.textColor} ` <Button theme={themes.primary}>Submit</Button> // after const fromTheme = (prop) => ({ theme }) => theme[prop] const Button = styled.button` background-color: ${fromTheme("bgColor")} color: ${fromTheme("textColor")} ` <Button theme={themes.primary}>Submit</Button>
咱們建立一個接受一個字符串做爲參數的函數 fromTheme("textColor")
:它返回一個接受具備 theme
屬性的對象的函數:({ theme }) => theme[prop]
,而後再經過初始傳入的字符串 "textColor"
進行查找。咱們能夠作得更多,寫相似的 backgroundColor
和 textColor
這種部分調用 fromTheme
的函數:
const fromTheme = (prop) => ({ theme }) => theme[prop] const backgroundColor = fromTheme("bgColor") const textColor = fromTheme("textColor") const Button = styled.button` background-color: ${backgroundColor} color: ${textColor} ` <Button theme={themes.primary}>Submit</Button>
高階函數的定義是,接受函數做爲參數的函數。若是曾經使用過相似 map 這樣的函數,可能已經很熟悉高階函數。若是不熟悉 map,它是一個數組遍歷的方法,接受一個函數做爲參數應用到數組中的每一個元素。例如,能夠像這樣對一個數組做平方:
const square = (x) => x * x [1, 2, 3].map(square) //=> [ 1, 4, 9 ]
能夠實現一個咱們本身的 map
版原本說明這個概念:
const map = (fn, array) => { const mappedArray = [] for (let i = 0; i < array.length; i++) { mappedArray.push( // apply fn with the current element of the array fn(array[i]) ) } return mappedArray }
而後再使用咱們的 map 版原本對一個數組做平方:
const square = (x) => x * x console.log(map(square, [1, 2, 3, 4, 5])) //=> [ 1, 4, 9, 16, 25 ]
譯者注:咱們也能夠將 map 方法從對象中解耦出來:
const map = (fn, array) => Array.prototype.map.call(array, fn)這樣也能夠像上述例子同樣調用。
或者更函數式的作法,再來點柯里化:const map = array => fn => Array.prototype.map.call(array, fn)
或者是返回一個 <li>
的 React 元素數組:
const HeroList = ({ heroes }) => ( <ul> {map((hero) => ( <li key={hero}>{hero}</li> ), heroes)} </ul> ) <HeroList heroes=[ "Wonder Woman", "Black Widow", "Spider Man", "Storm", "Deadpool" ]/> /*=> ( <ul> <li>Wonder Woman</li> <li>Black Widow</li> <li>Spider Man</li> <li>Storm</li> <li>Deadpool</li> </ul> )*/
咱們知道,高階函數是接受函數做爲參數的函數。在 React 中,任何返回 JSX 的函數都被稱爲無狀態函數組件,簡稱爲函數組件。基本的函數組件以下所示:
const Title = (props) => <h1>{props.children}</h1> <Title>Higher-Order Components(HOCs) for React Newbies</Title> //=> <h1>Higher-Order Components(HOCs) for React Newbies</h1>
高階組件則是接受組件做爲參數並返回組件的函數。如何使用傳入組件徹底取決於你,甚至能夠徹底忽視它:
// Technically an HOC const ignore = (anything) => (props) => <h1>:)</h1> const IgnoreHeroList = ignore(HeroList) <IgnoreHeroList /> //=> <h1>:)</h1>
能夠編寫一個將輸入轉換成大寫的 HOC:
const yell = (PassedComponent) => ({ children, ...props }) => <PassedComponent {...props}> {children.toUpperCase()}! </PassedComponent> const Title = (props) => <h1>{props.children}</h1> const AngryTitle = yell(Title) <AngryTitle>Whatever</AngryTitle> //=> <h1>WHATEVER!</h1>
你也能夠返回一個有狀態組件,由於 JavaScript 中的類不過是函數的語法糖。這樣就可使用到 React 生命週期的方法,好比 componentDidMount
。這是 HOCs 真正有用的地方。咱們如今能夠作一些稍微有趣點的事,好比將 HTTP 請求的結果傳遞給函數組件。
const withGists = (PassedComponent) => class WithGists extends React.Component { state = { gists: [] } componentDidMount() { fetch("https://api.github.com/gists/public") .then((r) => r.json()) .then((gists) => this.setState({ gists: gists })) } render() { return ( <PassedComponent {...this.props} gists={this.state.gists} /> ) } } const Gists = ({ gists }) => ( <pre>{JSON.stringify(gists, null, 2)}</pre> ) const GistsList = withGists(Gists) <GistsList /> //=> Before api request finishes: // <Gists gists={[]} /> // //=> After api request finishes: // <Gists gists={[ // { /* … */ }, // { /* … */ }, // { /* … */ } // ]} />
withGists
會傳遞 gist api 調用的結果,而且你能夠在任何組件上使用。點擊這裏 能夠看到一個更加完整的例子。
react-redux 也是使用 HOC, connect 將應用 store 的值傳遞到「已鏈接」 的組件。它還會執行一些錯誤檢查和組件生命週期優化,若是手動完成將致使編寫大量重複代碼。
若是你發現本身在不一樣地方編寫了大量的代碼,那麼也能夠將代碼重構成可重用的 HOC。
HOCs 很是具備表現力,可使用它們創造不少很酷的東西。
儘量地保持你的 HOC 簡單,不要編寫須要閱讀長篇大論才能理解的代碼。
下面有一些練習,來鞏固對 HOC 的理解:
shouldComponentUpdate
,以免更新。React.Children.toArray
對傳入組件子元素進行排序。