一個(gè)高階組件就是一個(gè)函數(shù),這個(gè)函數(shù)接受一個(gè)組件作為輸入,然后返回一個(gè)新的組件作為結(jié)果,而且,返回的新組件擁有了輸入組件所不具有的功能。我們可以這么打比方,每個(gè)組件最后都返回了一個(gè)jsx,而jsx實(shí)質(zhì)上一個(gè)對(duì)象,相當(dāng)于我們傳入一個(gè)對(duì)象,最后返回了一個(gè)新對(duì)象,它具有參數(shù)對(duì)象不具有的功能
// 刪除user這個(gè)props
function removeUserProp(WrapperComponent){
return class WrappingComponent extends React.Component{
render(){
const {user,...otherProps} = this.props
return <WrapperComponent {...otherProps}>
}
}
}
定義高階組件的意義何在呢?
首先,重用代碼 有時(shí)候很多 React 組件都需要公用同樣一個(gè)邏輯,比如說 react-redux中容器組件的部分,沒有必要讓每個(gè)組件都實(shí)現(xiàn)一遍 shouldComponentUpdate 這些生命周期函數(shù),把這部分邏輯提取出來,利用高階組件的方式應(yīng)用出去,就可以減少很多組件的重復(fù)代碼
其次,修改現(xiàn)有 React 組件的行為 有些現(xiàn)成 React 組件并不是開者自己開發(fā)的,來自于第3方,或者,即使是我們自己開發(fā)的,但是我們不想去觸碰這些組件的內(nèi)部邏輯,這時(shí)候高階組件有了用武之地 通過一個(gè)獨(dú)立于原有組件的函數(shù),可以產(chǎn)生新的組件,對(duì)原有組件沒有任何侵害。
根據(jù)返回的新組件和傳人組件參數(shù)的關(guān)系,高階組件的實(shí)現(xiàn)方式可以分為兩大類:
代理方式的高階組件
繼承方式的高階組件
代理方式的高階組件
上面的 removeUserProp 例子就是一個(gè)代理方式的高階組件,特點(diǎn)是返回的新組件類直接繼承自 React. Component 新組件扮演的角色是傳入?yún)?shù)組件的一個(gè)“代理”,在新組建的 render 函數(shù)中,把被包裹組件渲染出來,除了高階組件自己要做的工作,其余功能全都轉(zhuǎn)手給了被包裹的組件。
代理方式的高階組件,可以應(yīng)用在下列場(chǎng)景中:
操縱 prop
訪問 ref
抽取狀態(tài)
包裝組件
- 操縱 prop
// 添加新props
const addNewProp = (WrapperComponent,newProps) => {
return class WrappingComponent extends React.Component{
render(){
return <WrapperComponent {...this.props} {...newProps}>
}
}
}
- 訪問 ref
// 獲取refs
const refsHOC = (WrapperComponent) => {
return class HOCComponent extends React.Component{
constructor(){
super(...arguments)
this.linkRef = this.linkRef.bind(this)
}
linkRef(wrappedInstance){
this._root = wrappedInstance
}
render(){
const props = {...this.props,ref:this.linkRef}
return <WrapperComponent {...props}>
}
}
}
- 抽取狀態(tài)
const doNothing = () => ({})
function connect(mapStateToProps=doNothing,mapDispatchToProps=doNothing){
return function(WrapperComponent){
class HOCComponent extends React.Component{
//定義聲明周期函數(shù)
constructor(){
super(...arguments)
this.onChange = this.onChange.bind(this)
this.store = {}
}
componentDidMount(){
this.context.store.subscribe(this.onChange)
}
componentWillUnMount(){
this.context.store.unsubscribe(this.onChange)
}
onChange(){
this.setState({})
}
render(){
const store = this.context.store
const newProps = {
...this.props,
...mapStateToProps(store.getState()),
...mapDispatchToProps(store.dispatch())
}
return <WrapperComponent {...newProps}>
}
}
HOCComponent.contextTypes = {
store:React.PropTypes.object
}
return HOCComponent
}
}
function getDisplayName(WrappedComponent){
return WrappedComponent.displayName || WrappedComponent.name || Component
}
- 包裝組件
const styleHOC = (WrappedComponent,style) => {
return class HOCComponent extends React.Component{
render(){
return(){
<div style={style}>
<WrappedComponent {...this.props} />
</div>
}
}
}
}
繼承方式的高階組件
繼承方式的高階組件采用繼承關(guān)系關(guān)聯(lián)作為參數(shù)的組件和返回的組件,假如傳入的組件參數(shù)是 WrComponeappednt ,那么返回的組件就直接繼承自 WrappedComponent
function removeUserProp(WrapperComponent){
//繼承于參數(shù)組件
return class NewComponent extends WrapperComponent{
render(){
const {user,...otherProps} = this.props
this.props = otherProps
//調(diào)用WrapperComponent的render方法
// 只是一個(gè)render函數(shù),不是一整個(gè)生命周期
return super.render()
}
}
}
繼承方式的高階組件可以應(yīng)用于下列場(chǎng)景:
操縱 prop
操縱生命周期函數(shù)
- 操縱 prop
const modifyPropsHOC = (WrappedComponent) => {
return class NewComponent extends WrappedComponent{
render(){
const elements = super.render()
const newStyle = {
color:(elements && elements.type === 'div') ? 'red' : 'green'
}
const newProps = {...this.props,style:newStyle}
return React.cloneElement(elements,newProps,elements.props.children)
}
}
}
- 操縱生命周期函數(shù):修改參數(shù)組件的生命周期
const onlyForLoggedHOC = (WrappedComponent) => {
return class NewComponent extends WrappedComponent{
render(){
if(this.props.loggedIn){
return super.render()
}else{
return null
}
}
}
}
const cacheHOC = (WrappedComponent) => {
return class NewComponent extends WrappedComponent{
shouldComponentUpdate(nextProps,nextState){
return !nextProps.userCache
}
}
}
以函數(shù)為子組件
高階組件并不是唯一可用于提高 React 組件代碼重用的方法 在上 節(jié)的介紹中可以體會(huì)到,高階組件擴(kuò)展現(xiàn)有組件功能的方式主要是通過 props ,增加 props 或者減少props ,或者修改原有的 props 以代理方式的高階組件為例,新產(chǎn)生的組件和原有的組件說到底是兩個(gè)組件,是父子關(guān)系,而兩個(gè) React 組件之間通信的方式自然是 props 因?yàn)槊總€(gè)組件都應(yīng)該通過 propTypes 聲明自己所支持的 props 高階組件利用原組件的 props擴(kuò)充功能,在靜態(tài)代碼檢查上也占優(yōu)勢(shì)>
但是,高階組件也有缺點(diǎn),那就是對(duì)原組件的 props 有了固化的要求 也就是說,能不能把一個(gè)高階組件作用于某個(gè)組件 ,要先看一下這個(gè)組件 是不是能夠接受高階組件傳過來的 props ,如果組件 并不支持這些 props ,或者對(duì)這些 props 的命名有不同,或者使用方式不是預(yù)期的方式,那也就沒有辦法應(yīng)用這個(gè)高階組件。
“以函數(shù)為子組件”的模式就是為了克服高階組件的這種局限而生的 在這種模式下,實(shí)現(xiàn)代碼重用的不是一個(gè)函數(shù),而是一個(gè)真正的 React 組件,這樣的 React 組件有個(gè)特點(diǎn),要求必須有子組件的存在,而且這個(gè)子組件必須是一個(gè)函數(shù) 在組件實(shí)例的生命周期函數(shù)中, this props children 引用的就是子組件, render 函數(shù)會(huì)直接this.props.children當(dāng)做函數(shù)來調(diào)用,得到的結(jié)果就可以作為 render 返回結(jié)果的一部分
class CountDown extends React.Component{
constructor(){
super(...arguments)
this.state = {count:this.props.startCount}
}
componentDidMount(){
this.intervalHandle = setInterval(() => {
const newCount = this.state.count - 1
if(newCount >= 0){
this.setState({count:newCount})
}else{
window.clearInterval(this.intervalHandle)
}
},1000)
}
componentWillUnMount(){
if(this.intervalHandle){
window.clearInterval(this.intervalHandle)
}
}
render(){
return this.props.children(this.state.count)
}
}
CountDown.propTypes = {
children:PropTypes.func.isRequired,
startCount:PropTypes.number.isRequired
}
<CountDown>
{
(count) => <div>count</div>
}
</CountDown>
<CountDown>
{
(count) => <div>{count > 0 ? count : 'happy new year'}</div>
}
</CountDown>
<CountDown>
{
(count) => <Bomb countdown={count}>
}
</CountDown>
以函數(shù)為子組件這種方法非常合適做動(dòng)畫,作為子組件的函數(shù)主要專注于參數(shù)來渲染就可以了;但是它難以做性能優(yōu)化,因?yàn)樽咏M件是函數(shù),沒有生命周期,無法利用shouldComponentUpdate