面向初学者的高阶组件介绍
作者:Brandon Newton
原文:Higher-Order Components (HOCs) for Beginners
談點:一篇面向初學者的 HOC 介紹。高階組件聽起來挺唬人的,只看名字恐怕不是那么容易明白究竟是何物,而且通常來講高階組件并不是組件,而是接受組件作為參數,并且返回組件的函數。早期利用 ES5 的 mixin 語法來做的事,基本都可以使用高階組件代替,而且能做的還有更多。
前言
寫這篇文章的起因是其他關于高階組件(Higher-Order Components)的文章,包含官方文檔,都令初學者感到相當困惑。我知道有高階組件這樣一個東西,但不知道它到底有什么用。所以,想通過一篇文章來對高階組件有一個更好的理解。
在此之前,我們需要先來講一下 JavaScript 中的函數。
ES6 箭頭函數簡介
接下來將提供一些箭頭函數的簡單示例,如果之前沒有使用過,可以認為它們與普通函數基本一致。下面的代碼會展示箭頭函數與普通函數的區別。
function () {return 42 }// same as: () => 42// same as: () => {return 42 }function person(name) {return { name: name } }// same as: (name) => {return { name: name } }閱讀 MDN 的箭頭函數文檔 了解更多信息。
作為值的函數與部分調用
就像是數字、字符串、布爾值 一樣,函數也是值,意味著可以像傳遞其他數據一樣傳遞函數,可以將函數作為參數傳遞給另外一個函數。
const execute = (someFunction) => someFunction()execute(() => alert('Executed'))也可以在在函數中返回一個函數:
const getOne = () => () => 1getOne()()之所以在 getOne 后面有兩個 () ,是因為第一個返回的返回值是一個函數。如下:
const getOne = () => () => 1getOne //=> () => () => 1getOne() //=> () => 1getOne()() //=> 1從函數返回函數可以幫助我們追蹤初始輸入函數。例如,下面的函數接受一個數字作為參數,并返回一個將該參數乘以新參數的函數:
const multiply = (x) => (y) => x * ymultiply(5)(20)這個示例跟上述 getOne 一樣,在下面這個例子,讓 x = 5,y = 20。
const multiply = (x) => (y) => x * ymultiply //=> (x) => (y) => x * ymultiply(5) //=> (y) => 5 * ymultiply(5)(20) //=> 5 * 20在只傳入一個參數調用 multiply 函數時,即部分調用該函數。比如,multiply(5) 講得到一個將其輸入值乘以 5 的函數,multiply(7) 將得到一個將其輸入值乘以 7 的函數。依此類推。通過部分調用可以創建一個預定義功能的新函數:
const multiply = (x) => (y) => x * yconst multiplyByFive = multiply(5) const multiplyBy100 = multiply(100)multiplyByFive(20) //=> 100 multiply(5)(20) //=> 100multiplyBy100(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 arrayfn(array[i]))}return mappedArray }然后再使用我們的 map 版本來對一個數組作平方:
const square = (x) => x * xconsole.log(map(square, [1, 2, 3, 4, 5])) //=> [ 1, 4, 9, 16, 25 ]譯者注:我們也可以將 map 方法從對象中解耦出來:
const map = (fn, array) => 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 的理解:
- 寫一個反轉其輸入的 HOC
- 編寫一個HOC,將 API 中的數據提供給組件
- 寫一個HOC來實現 shouldComponentUpdate,以避免更新。
- 編寫一個 HOC,使用 React.Children.toArray 對傳入組件子元素進行排序。
總結
以上是生活随笔為你收集整理的面向初学者的高阶组件介绍的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: GopherChina第一天小结
- 下一篇: VTL语法参考