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。最後效果
高階元件的實現
一、編寫高階元件
- 實現一個普通元件
- 將普通元件使用函式包裹
二、使用高階元件
- higherOrderComponent(WrappedComponent)
-
@ 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} />
上面講述的是屬性的增加,那麼屬性的刪減呢
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> ) } }
列印的我是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
通過程式碼的對比,我們不難發現代理方式的高階元件和繼承方式的高階元件的區別:
- 繼承的類不同。代理方式繼承的是React的Component,繼承方式繼承的則是WrappedComponent
- 返回的方式不同
操縱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學習
當然也建議去慕課網觀看宋老師的詳細教學視訊 慕課網地址