React高階組件

 

概況:

什麼是高階組件?

高階部件是一種用於複用組件邏輯的高級技術,它並非 React API的一部分,而是從React 演化而來的一種模式。 具體地說,高階組件就是一個接收一個組件並返回另一個新組件的函數!javascript

這是官方文檔說的,我沒有截全,由於後面的解釋會形成誤解,但簡單講高階組件(函數)就比如一個加工廠,一樣的,屏幕、cpu、揚聲器、鍵盤按鍵、外殼、電池,小米手機工廠組裝完就是小米手機,魅族手機組裝完就是魅族手機,基本材料都是相同的,不一樣工廠(高階組件)有不一樣的實現及產出,固然這個工廠(高階組件)也多是針對某個基本材料的處理。
總之產出的結果擁有了輸入組件不具有的功能,輸入的組件能夠是一個組件的實例,也能夠是一個組件類,還能夠是一個無狀態組件的函數。java

解決什麼問題?

隨着項目愈來愈複雜,開發過程當中,多個組件須要某個功能,並且這個功能和頁面並無關係,因此也不能簡單的抽取成一個新的組件,可是若是讓一樣的邏輯在各個組件裏各自實現,無疑會致使重複的代碼。好比頁面有三種彈窗一個有title,一個沒有,一個又有右上角關閉按鈕,除此以外別無它樣,你總不能整好幾個彈窗組件吧,這裏除了tilte,關閉按鈕其餘的就能夠作爲上面說的基本材料。react


高階組件總共分爲兩大類

  • 代理方式
    1. 操縱prop
    2. 訪問ref(不推薦)
    3. 抽取狀態
    4. 包裝組件
  • 繼承方式
    1. 操縱生命週期
    2. 操縱prop

代理方式之 操縱prop

刪除prop
import React from 'react'
function HocRemoveProp(WrappedComponent) {
  return class WrappingComPonent extends React.Component {
    render() {
      const { user, ...otherProps } = this.props;
      return <WrappedComponent {...otherProps} />
    }
  }
}
export default HocRemoveProp;
增長prop

接下來我把簡化了寫法,把匿名函數去掉,同時換成箭頭函數es6

import React from 'react'

const HocAddProp = (WrappedComponent,uid) =>
  class extends React.Component {
    render() {
      const newProps = {
        uid,
      };
      return <WrappedComponent {...this.props}  {...newProps}  />
    }
  }

export default HocAddProp;

上面HocRemoveProp高階組件中,所作的事情和輸入組件WrappedComponent功能同樣,只是忽略了名爲user的prop。也就是說,若是WrappedComponent能處理名爲user的prop,這個高階組件返回的組件則徹底無視這個prop。數組

const { user, ...otherProps } = this.props; 

這是一個利用es6語法技巧,通過上面的語句,otherProps裏面就有this.props中全部的字段除了user.
假如咱們如今不但願某個組件接收user的prop,那麼咱們就不要直接使用這個組件,而是把這個組件做爲參數傳遞給HocRemoveProp,而後咱們把這個函數的返回結果看成組件來使用
兩個高階組件的使用方法:app

const  newComponent = HocRemoveProp(SampleComponent);
const  newComponent = HocAddProp(SampleComponent,'1111111');

也能夠利用decorator語法糖這樣使用函數

import React, { Component } from 'React';

@HocRemoveProp 
class SampleComponent extends Component {
render() {}
}
export default SampleComponent;

代理方式之 抽取狀態

將全部的狀態的管理交給外面的容器組件,這個模式就是 抽取狀態
外面的容器就是這個高階組件工具

const HocContainer = (WrappedComponent) =>
  class extends React.Component {
    constructor(props) {
      super(props)
      this.state = {
        name: ''
      }
    }
    onNameChange = (event) => {
      this.setState({
        name: event.target.value
      })
    }
    render() {
      const newProps = {
        name: {
          value: this.state.name,
          onChange: this.onNameChange
        }
      }
      return <WrappedComponent {...this.props} {...newProps} />
    }
  }
@HocContainer
class SampleComponent extends React.Component {
  render() {
    return <input name="name" {...this.props.name}/>
  }
}

這樣當咱們在使用這個已經被包裹的input組件(SampleComponent)時候
它的值就被放在了HocContainer高階組件中,當不少這樣的input組件都用這個HocContainer高階組件時,那麼它們的值都將保存在這個HocContainer高階組件中ui

代理方式之 包裝組件

const HocStyleComponent = (WrappedComponent, style) =>
  class extends React.Component {
    render() {
      return (
        <div style={style}>
          <WrappedComponent {...this.props} {...newProps} />
        </div>
      )
    }
}

這樣使用this

import HocStyleComponent from  './HocStyleComponent';
const colorSytle ={color:'#ff5555'}
const  newComponent = HocStyleComponent(SampleComponent, colorSytle);

-代理方式的生命週期的過程相似於堆棧調用:
didmount 一> HOC didmount 一>(HOCs didmount) 一>(HOCs will unmount) 一>HOC will unmount一>unmount

在說繼承方式以前先看一個例子

const MyContainer = (WrappedComponent) =>
  class extends WrappedComponent {
    render() {
      return super.render();
    }
}

這個例子很簡單,至關於把WrappedComponent組件的render方法,經過super.render()方法吐到了MyContainer 中,能夠順序調用。

  • 繼承方式的生命週期的過程相似於隊列調用:
    didmount 一> HOC didmount 一>(HOCs didmount) 一>will unmount一>HOC will unmount一> (HOCs will unmount)

  • 代理方式下WrappedComponent會經歷一個完整的生命週期,產生的新組件和參數組件是兩個不一樣的組件,一次渲染,兩個組件都會經歷各自的生命週期,

  • 在繼承方式下,產生的新組件和參數組件合二爲一,super.render只是生命週期中的函數,變成一個生命週期。

來看下面的例子你就會明白了。

繼承方式之 操縱生命週期(渲染劫持)

首先建立一個高階,在建立一個使用高階組件的組件,也就是是輸入組件,最後我在改變這個輸入組件props

import * as React from 'react';

const HocComponent = (WrappedComponent) =>
  class MyContainer extends WrappedComponent {
    render() {
      if (this.props.time && this.state.success) {
        return super.render()
      }
      return <div>倒計時完成了...</div>
    }
}

這個高階組件會直接讀取輸入組件中的props,state,而後控制了輸入組件的render展現
只有在props.time和state.success同時爲真的時候纔會展現

import * as React from 'react';
import HocComponent from './HocComponent'

@HocComponent

class DemoComponent extends React.Component {
  constructor(props) {
    super(props);
   this.state = {
    success: true,
   };
 }
  render() {
    return   <div>我是一個組件</div>
  }
} 
export default DemoComponent;

而後調用,遞減time數值直到變爲0

最後頁面的效果就是,固然他不是循環的。先展現」我是一個組件「,我設置了兩秒,以後展現」倒計時完成「.
file

由此能夠看出高階組件也能夠控制state

可是最好要限制這樣作,可能會讓WrappedComponent組件內部狀態變得一團糟。建議能夠經過從新命名state,以防止混淆。

繼承方式之 操縱prop

const HOCPropsComponent = (WrappedComponent) =>
  class extends WrappedComponent {
    render() {
      const elementsTree = super.render();
      let newProps = {
        color: (elementsTree && elementsTree.type === 'div') ? '#fff' : '#ff5555'
      };

      const props = Object.assign({}, elementsTree.props, newProps)
      const newElementsTree = React.cloneElement(elementsTree, props, elementsTree.props.children)
      return newElementsTree
    }
}

這樣就傳入了新的props,。
React.cloneElement( element, [props], […children])
參數:TYPE(ReactElement),[PROPS(object)],[CHILDREN(ReactElement)]
克隆並返回一個新的 ReactElement ,新返回的元素會保留有舊元素的 props、ref、key,也會集成新的 props。

還有一個方式,在傳遞props上有着強於高階組件的優點不用關心命名,
class addProps extends React.Component {
  render() {
    const newProps = 'uid'
    return this.props.children(newProps)
  }
}

使用方式

<addProps>
{
   (argument) => <div>{argument}</div>
}
</addProps>

感受很方便,可是每次渲染都會從新定義一個新的函數,若是不想的話就不要定義匿名函數,

showUid(argument) {
    return <div>{argument}</div>
}
彩蛋recompose庫

recompose是一個很流行的庫,它提供了不少頗有用的高階組件(小工具),並且也能夠優雅的組合它們。

Step 1 扁平props.

咱們有這樣一個組件

const Profile = ({ user }) => ( 
<div> 
    <div>Username: {user.username}</div>  
    <div>Age: {user.age}</div>
 </div>
)

若是想要改變組件接口來接收單個 prop 而不是整個用戶對象,能夠用 recompose 提供的高 階組件 flattenProp 來實現。

const Profile = ({ username,age }) => ( 
<div> 
    <div>Username: {username}</div>  
    <div>Age: {age}</div>
 </div>
)

const ProfileWithFlattenUser = flattenProp(‘user’)(Profile);
如今咱們但願同時使用多個高階組件:一個用於扁平化處理用戶 prop,另外一個用於重命名用 戶對象的單個 prop,不過串聯使用函數的作法彷佛不太好。 此時 recompose 庫提供的 compose 函數就派上用場了。

const enhance = compose(
 flattenProp('user'),
 renameProp('username', 'name')
)

而後按照如下方式將它應用於原有組件:

 const EnhancedProfile = enhance(Profile)

還能夠將 compose 函數用 在咱們本身的高階組件上,甚至結合使用均可以:

const enhance = compose( 
 flattenProp('user'), 
 renameProp('username', 'name'), 
 withInnerWidth 
)
Step 2 提取輸入表單的State

咱們將從Recompose庫中使用withStateHandlers高階組件。 它將容許咱們將組件狀態與組件自己隔離開來。 咱們將使用它爲電子郵件,密碼和確認密碼字段添加表單狀態,以及上述字段的事件處理程序。

import { withStateHandlers, compose } from "recompose";

const initialState = {
  email: { value: "" },
  password: { value: "" },
  confirmPassword: { value: "" }
};

const onChangeEmail = props => event => ({
  email: {
    value: event.target.value,
    isDirty: true
  }
});

const onChangePassword = props => event => ({
  password: {
    value: event.target.value,
    isDirty: true
  }
});

const onChangeConfirmPassword = props => event => ({
  confirmPassword: {
    value: event.target.value,
    isDirty: true
  }
});

const withTextFieldState = withStateHandlers(initialState, {
  onChangeEmail,
  onChangePassword,
  onChangeConfirmPassword
});

export default withTextFieldState;
相關文章
相關標籤/搜索