React教程之高階組件

前言

高階組件在React應用中,非常非常重要。空有一個想學好React的心,卻沒有一個好的教程。希望這篇文章可以幫組到你,把React學的更好。通過這篇文章你可以學到高階組件的定義及原理、高階組件在項目中的常見應用、通用高階組件如何封裝以及繼承方式高階組件和代理方式高階組件。

搭建項目

create-react-app myapp創建一個react項目,並在src目錄下新建一個components放置組件A、B、C三個組件。如圖

將A組件作爲高階組件

import React, { Component } from 'react'
function A(WrappedComponed) {
  return class A extends Component {
    render() {
      return (
        <div className="a-container">
          <div className="header">
            <div>提示</div>
            <div>x</div>
          </div>
          <div>
            <WrappedComponed />
          </div>
        </div>
      )
    }
  }
}
export default A

B組件和C組件作爲參數傳遞給A

import React, { Component } from 'react'
import A from './A'
class B extends Component {
  render() {
    return (
      <div>
        這是組件B
       <img src={require('../images/B.png')} alt=""/>
      </div>
    )
  }
}
export default A(B)

A組件其實就是一個function,通過接受一個參數返回一個react組件,而接收的參數又是一個組件,這就是一個簡單的高階組件。高階組件就是接受一個組件作爲參數並返回一個新組件的函數,高階組件是一個函數,並不是一個組件。高階組件帶來的好處是多個組件都需要某個相同的功能,使用高階組件減少重複的實現,比如我們上述的B/C組件都需要A。最後效果效果圖

高階組件的實現

一、編寫高階組件

  1. 實現一個普通組件
  2. 將普通組件使用函數包裹

二、使用高階組件

  1. higherOrderComponent(WrappedComponent)
  2. @ higherOrderComponent --裝飾器模式

    高階組件可以看做是裝飾器模式(Decorator Pattern)在React的實現。即允許向一個現有的對象添加新的功能,同時又不改變其結構,屬於包裝模式(Wrapper Pattern)的一種

ES7中添加了一個decorator的屬性,使用@符表示,可以更精簡的書寫。但在create-react-app中並不直接支持,大家可以自行google

創建一個D組件

import React, { Component } from 'react'

function d(WrappedComponent) {
  return class D extends Component {
    render() {
      return (
        <div>
          這是高階組件
          <WrappedComponent />
        </div>
      )
    }
  }
}
export default d

使用裝飾器@

import React, { Component } from 'react'
// import A from './A'
import d from './D'
@d
class B extends Component {
  render() {
    return (
      <div>
        這是組件B
       <img src={require('../images/B.png')} alt=""/>
      </div>
    )
  }
}
export default B

效果如下圖:裝飾器
如果學到這裏大家應該都學會了如何創建和使用高階組件了,但是高階組件就是這一點點知識嗎?答案肯定是NO,接下來讓我們一起看看在實戰中是如何應用高階組件的。

高階組件的應用

代理方式的高階組件

返回的新數組類直接繼承React.Component類,新組件扮演的角色傳入參數組件的一個代理,在新組件的render函數中,將被包裹組件渲染出來,除了高階組件自己要做的工作,其餘功能全部轉手給被包裹的組件。

代理方式的高階組件主要有以下四個方面的運用:
操縱prop、訪問ref、抽取狀態、包裝組件

操縱prop

修改下A組件,代理方式

import React, { Component } from 'react'
export default (title) => WrappedComponent => class A extends Component {
  render() {
    return (
      <div className="a-container">
        <div className="header">
          <div>{title}</div>
          <div>x</div>
        </div>
        <div>
          <WrappedComponent {...this.props}/>
        </div>
      </div>
    )
  }
}

在B中添加props:

import React, { Component } from 'react'
import A from './A'
class B extends Component {
  render() {
    return (
      <div>
        這是組件B
        <br />
        我的名字叫: {this.props.name}
        我的年齡是: {this.props.age}
       <img src={require('../images/B.png')} alt=""/>
      </div>
    )
  }
}
export default A('提示')(B)


現在我們要做的是通過高階組件對組件B屬性進行修改。我們先添加一個性別組件。我們不在APP.js中通過

這樣的方式將性別引入,而是在我們的高階組件A中進行操作

<WrappedComponent sex={'男'} {...this.props} />

高階組件操縱prop
上面講述的是屬性的增加,那麼屬性的刪減呢

import React, { Component } from 'react'
export default (title) => WrappedComponent => class A extends Component {
  render() {
    const {age, ...otherProps} = this.props
    return (
      <div className="a-container">
        <div className="header">
          <div>{title}</div>
          <div>x</div>
        </div>
        <div>
          <WrappedComponent sex={'男'} {...otherProps} />
        </div>
      </div>
    )
  }
}

這樣在我們的otherProps中是沒有age這個屬性的,因此就達到了屬性的刪減。

訪問ref

我們在C組件中定義一個getName方法,

 getName() {
    return '我是C組件'
  }

但是怎麼在高階組件A中調用到呢?其實i很簡單就是在高階組件中添加ref

import React, { Component } from 'react'
export default (title) => WrappedComponent => class A extends Component {
 refc(instance) {
   instance.getName && alert(instance.getName())
 } // instanc:WrappedComponent組件的實例
 render() {
   const {age, ...otherProps} = this.props
   return (
     <div className="a-container">
       <div className="header">
         <div>{title}</div>
         <div>x</div>
       </div>
       <div>
         <WrappedComponent sex={'男'} {...otherProps} ref={this.refc.bind(this)} />
       </div>
     </div>
   )
 }
}

操作ref
打印的我是C組件其實就是我們在C組件中定義的getName方法。通過這種方法可以操作任何被包裹組件的方法,甚至操作任何一個DOM。

抽取狀態

在B組件中增加一個輸入框

import React, { Component } from 'react'
import A from './A'
class B extends Component {
  constructor(props) {
    super(props)
    this.state = {
      value: ''
    }
  }
  changeInput(e) {
    console.log(e)
    this.setState({
      value: e.target.value
    })
  }
  render() {
    return (
      <div>
        這是組件B
        <input type='text' value={this.state.value} onInput={this.changeInput.bind(this)}/>
        <br />
        我的名字叫: {this.props.name}
        我的年齡是: {this.props.age}
        <br />
        我的性別是: {this.props.sex}
       <img src={require('../images/B.png')} alt=""/>
      </div>
    )
  }
}
export default A('提示')(B)


單個組件的狀態書寫方式,如果很多組件都需要input,那麼就會重複代碼,因此我們需要將狀態抽離到高階組件A中。

import React, { Component } from 'react'
export default (title) => WrappedComponent => class A extends Component {
  refc(instance) {
    // instance.getName && alert(instance.getName())
  }
  constructor(props) {
    super(props)
    this.state = {
      value: ''
    }
  }
  changeInput= (e) => {
    this.setState({
      value: e.target.value
    })
  }
  render() {
    const { age, ...otherProps } = this.props
    const newProps = {
      value: this.state.value,
      onInput: this.changeInput
    }
    return (
      <div className="a-container">
        <div className="header">
          <div>{title}</div>
          <div>x</div>
        </div>
        <div>
          <WrappedComponent {...newProps} sex={'男'} {...otherProps} ref={this.refc.bind(this)} />
        </div>
      </div>
    )
  }
}

在B組件我們接受一個newProps狀態

<input type='text' {...this.props}/>

回到頁面,發現跟上面的是一樣,這樣我們就將組件的狀態抽離出來了,如果C組件需要input,只需要將添加一個input輸入框就行了。極大的簡化了代碼。

繼承方式的高階組件

採用繼承關聯作爲參數的組件和返回的組件,加入傳入的組件參數是WrappedComponent,那麼返回的組件就是直接繼承自WrappedComponent


通過代碼的對比,我們不難發現代理方式的高階組件和繼承方式的高階組件的區別:

  1. 繼承的類不同。代理方式繼承的是React的Component,繼承方式繼承的則是WrappedComponent
  2. 返回的方式不同

操縱prop

新建一個E繼承高階組件

import React, { Component } from 'react';
const modifyPropsHOC = (WrappedComponent) => class NewComponent extends WrappedComponent {
  render() {
    const element = super.render()
    const newStyle = {
      color: element.type === 'div' ? 'red': 'green'
    }
    const newProps = { ...this.props, style: newStyle }
    return React.cloneElement(element, newProps,element.props.children)
  }
}
export default modifyPropsHOC

在F、G組件中使用繼承組件

import React, { Component } from 'react'
import E from './E'
@E
export default class G extends Component {
  render() {
    return (
      <p>
        我是p
      </p>
    )
  }
}


這就是我們通過繼承方式的高階組件來操縱props。高階組件需要根據參數來渲染組件,不建議使用。

操作生命週期

在G組件中

import React, { Component } from 'react'
import E from './E'
@E
export default class G extends Component {
  componentWillMount() {
    alert('我是原始生命週期')
  }
  render() {
    return (
      <p>
        我是p
      </p>
    )
  }
}

在繼承高階組件E中修改G中的屬性

import React, { Component } from 'react';
const modifyPropsHOC = (WrappedComponent) => class NewComponent extends WrappedComponent {
  componentWillMount() {
    alert('我是更改生命週期')
  }
  render() {
    const element = super.render()
    const newStyle = {
      color: element.type === 'div' ? 'red': 'green'
    }
    const newProps = { ...this.props, style: newStyle }
    return React.cloneElement(element, newProps,element.props.children)
  }
}
export default modifyPropsHOC

總結

高階組件最大的好處就是解耦和靈活性,在react的開發中還是很有用的。
當然這不可能是高階組件的全部用法。掌握了它的一些技巧,還有一些限制,你可以結合你的應用場景,發散思維,嘗試一些不同的用法。

你可以跟着文章嘗試一遍,也可以直接clone項目到本地跑跑。項目地址:React-hightComponet學習

當然也建議去慕課網觀看宋老師的詳細教學視頻慕課網地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章