面向初學者的高階組件教程
前言
本期早讀文章由@冉余翻譯分享。
正文從這開始~
一篇面向初學者的 HOC 介紹。高階組件聽起來挺唬人的,只看名字恐怕不是那麼容易理解,而且高階組件不是組件,而是接受組件作為參數,並且返回組件的函數。早期利用 ES5 的 mixin 語法來做的事,基本都可以使用高階組件代替,而且能做的還有更多。
寫這篇文章的起因是其他關於高階組件(Higher-Order Components)的文章,包含官方文檔,都令初學者感到相當困惑。我知道有高階組件這樣一個東西,但不知道它到底有什麼用。所以,想通過一篇文章來對高階組件有一個更好的理解。
在此之前,我們需要先來講一下 JavaScript 中的函數。
ES6 箭頭函數簡介
接下來將提供一些箭頭函數的簡單示例,如果之前沒有使用過,可以認為它們與普通函數基本一致。下面的代碼會展示箭頭函數與普通函數的區別。
function(){
return42
}
// same as:
()=>42
// same as:
()=>{
return42
}
functionperson(name){
return{name:name}
}
// same as:
(name)=>{
return{name:name}
}
閱讀 MDN 的箭頭函數文檔 了解更多信息。
作為值得函數與部分調用
就像是數字、字元串、布爾值 一樣,函數也是值,意味著可以像傳遞其他數據一樣傳遞函數,可以將函數作為參數傳遞給另外一個函數。
constexecute=(someFunction)=>someFunction()
execute(()=>alert( Executed ))
也可以在在函數中返回一個函數:
constgetOne=()=>()=>1
getOne()()
之所以在 getOne 後面有兩個 () ,是因為第一個返回的返回值是一個函數。如下:
constgetOne=()=>()=>1
getOne
//=> () => () => 1
getOne()
//=> () => 1
getOne()()
//=> 1
從函數返回函數可以幫助我們追蹤初始輸入函數。例如,下面的函數接受一個數字作為參數,並返回一個將該參數乘以新參數的函數:
constmultiply=(x)=>(y)=>x*y
multiply(5)(20)
這個示例跟上述 getOne 一樣,在下面這個例子,讓 x = 5,y = 20。
constmultiply=(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 的函數。依此類推。通過部分調用可以創建一個預定義功能的新函數:
constmultiply=(x)=>(y)=>x*y
constmultiplyByFive=multiply(5)
constmultiplyBy100=multiply(100)
multiplyByFive(20)
//=> 100
multiply(5)(20)
//=> 100
multiplyBy100(5)
//=> 500
multiply(100)(5)
//=> 500
一開始看起來似乎沒什麼用,但是,通過部分調用這種方式可以編寫可讀性更高,更易於理解的代碼。舉個例子,可以用一種更清晰的方式來代替 style-components 的函數插入語法。
// before
constButton=styled.button`
background-color:${({theme}) => theme.bgColor}
color:${({theme}) => theme.textColor}
`
Submit
// after
constfromTheme=(prop)=>({theme})=>theme[prop]
constButton=styled.button`
background-color:${fromTheme("bgColor")}
color:${fromTheme("textColor")}
`
Submit
我們創建一個接受一個字元串作為參數的函數 fromTheme("textColor"):它返回一個接受具有 theme 屬性的對象的函數:({ theme }) => theme[prop],然後再通過初始傳入的字元串 "textColor" 進行查找。我們可以做得更多,寫類似的 backgroundColor 和 textColor 這種部分調用 fromTheme 的函數:
constfromTheme=(prop)=>({theme})=>theme[prop]
constbackgroundColor=fromTheme("bgColor")
consttextColor=fromTheme("textColor")
constButton=styled.button`
background-color:${backgroundColor}
color:${textColor}
`
Submit
高階函數
高階函數的定義是,接受函數作為參數的函數。如果曾經使用過類似 map 這樣的函數,可能已經很熟悉高階函數。如果不熟悉 map,它是一個數組遍歷的方法,接受一個函數作為參數應用到數組中的每個元素。例如,可以像這樣對一個數組作平方:
constsquare=(x)=>x*x
[1,2,3].map(square)
//=> [ 1, 4, 9 ]
可以實現一個我們自己的 map 版本來說明這個概念:
constmap=(fn,array)=>{
constmappedArray=[]
for(leti=;i
mappedArray.push(
// apply fn with the current element of the array
fn(array[i])
)
}
returnmappedArray
}
然後再使用我們的 map 版本來對一個數組作平方:
constsquare=(x)=>x*x
console.log(map(square,[1,2,3,4,5]))
//=> [ 1, 4, 9, 16, 25 ]
這樣也可以像上述例子一樣調用。或者更函數式的做法,再來點柯里化:
或者是返回一個 的 React 元素數組:
constHeroList=({heroes})=>(
{map((hero)=>(
{hero}
),heroes)}
)
"Wonder Woman",
"Black Widow",
"Spider Man",
"Storm",
"Deadpool"
]/>
/*=> (
Wonder Woman
Black Widow
Spider Man
Storm
Deadpool
)*/
高階組件
我們知道,高階函數是接受函數作為參數的函數。在 React 中,任何返回 JSX 的函數都被稱為無狀態函數組件,簡稱為函數組件。基本的函數組件如下所示:
constTitle=(props)=>{props.children}
Higher-OrderComponents(HOCs)forReact Newbies
//=> Higher-Order Components(HOCs) for React Newbies
高階組件則是接受組件作為參數並返回組件的函數。如何使用傳入組件完全取決於你,甚至可以完全忽視它:
// Technically an HOC
constignore=(anything)=>(props)=>:)
constIgnoreHeroList=ignore(HeroList)
//=>
可以編寫一個將輸入轉換成大寫的 HOC:
constyell=(PassedComponent)=>
({children,...props})=>
{children.toUpperCase()}!
constTitle=(props)=>{props.children}
constAngryTitle=yell(Title)
Whatever
//=> WHATEVER!
你也可以返回一個有狀態組件,因為 JavaScript 中的類不過是函數的語法糖。這樣就可以使用到 React 生命周期的方法,比如 componentDidMount。這是 HOCs 真正有用的地方。我們現在可以做一些稍微有趣點的事,比如將 HTTP 請求的結果傳遞給函數組件。
constwithGists=(PassedComponent)=>
classWithGistsextendsReact.Component{
state={
gists:[]
}
componentDidMount(){
fetch("https://api.github.com/gists/public")
.then((r)=>r.json())
.then((gists)=>this.setState({
gists:gists
}))
}
render(){
return(
{...this.props}
gists={this.state.gists}
/>
)
}
}
constGists=({gists})=>(
{JSON.stringify(gists,null,2)}
)
constGistsList=withGists(Gists)
//=> Before api request finishes:
//
//
//=> After api request finishes:
//
// {/* … */},
// {/* … */},
// {/* … */}
// ]} />
withGists 會傳遞 gist api 調用的結果,並且你可以在任何組件上使用。點擊這裡 可以看到一個更加完整的例子。
結論:高階組件是
react-redux 也是使用 HOC, connect 將應用 store 的值傳遞到「已連接」 的組件。它還會執行一些錯誤檢查和組件生命周期優化,如果手動完成將導致編寫大量重複代碼。
如果你發現自己在不同地方編寫了大量的代碼,那麼也可以將代碼重構成可重用的 HOC。
HOCs 非常具有表現力,可以使用它們創造很多很酷的東西。
儘可能地保持你的 HOC 簡單,不要編寫需要閱讀長篇大論才能理解的代碼。
附加練習
下面有一些練習,來鞏固對 HOC 的理解:
寫一個反轉其輸入的 HOC
編寫一個HOC,將 API 中的數據提供給組件
寫一個HOC來實現 shouldComponentUpdate,以避免更新。
編寫一個 HOC,使用 React.Children.toArray 對傳入組件子元素進行排序。
關於本文
譯者:@冉余
作者:@Brandon Newton
原文:https://btnwtn.com/articles/higher-order-components-for-beginners
點擊展開全文
※構建高性能展開&收縮動畫
※BATJ 前端面試的 5 大關鍵點,你 Get 到了嗎?
※坦然面對:應對前端疲勞
※JSON schema與表單驗證
TAG:前端早讀課 |