一個高階組件就是一個函數,這個函數接受一個組件作為輸入,然后返回一個新的組件作為結果,而且,返回的新組件擁有了輸入組件所不具有的功能。我們可以這么打比方,每個組件最后都返回了一個jsx,而jsx實質上一個對象,相當于我們傳入一個對象,最后返回了一個新對象,它具有參數對象不具有的功能
// 刪除user這個props
function removeUserProp(WrapperComponent){
return class WrappingComponent extends React.Component{
render(){
const {user,...otherProps} = this.props
return <WrapperComponent {...otherProps}>
}
}
}
定義高階組件的意義何在呢?
首先,重用代碼 有時候很多 React 組件都需要公用同樣一個邏輯,比如說 react-redux中容器組件的部分,沒有必要讓每個組件都實現一遍 shouldComponentUpdate 這些生命周期函數,把這部分邏輯提取出來,利用高階組件的方式應用出去,就可以減少很多組件的重復代碼
其次,修改現有 React 組件的行為 有些現成 React 組件并不是開者自己開發的,來自于第3方,或者,即使是我們自己開發的,但是我們不想去觸碰這些組件的內部邏輯,這時候高階組件有了用武之地 通過一個獨立于原有組件的函數,可以產生新的組件,對原有組件沒有任何侵害。
根據返回的新組件和傳人組件參數的關系,高階組件的實現方式可以分為兩大類:
代理方式的高階組件
繼承方式的高階組件
代理方式的高階組件
上面的 removeUserProp 例子就是一個代理方式的高階組件,特點是返回的新組件類直接繼承自 React. Component 新組件扮演的角色是傳入參數組件的一個“代理”,在新組建的 render 函數中,把被包裹組件渲染出來,除了高階組件自己要做的工作,其余功能全都轉手給了被包裹的組件。
代理方式的高階組件,可以應用在下列場景中:
操縱 prop
訪問 ref
抽取狀態
包裝組件
- 操縱 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}>
}
}
}
- 抽取狀態
const doNothing = () => ({})
function connect(mapStateToProps=doNothing,mapDispatchToProps=doNothing){
return function(WrapperComponent){
class HOCComponent extends React.Component{
//定義聲明周期函數
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>
}
}
}
}
繼承方式的高階組件
繼承方式的高階組件采用繼承關系關聯作為參數的組件和返回的組件,假如傳入的組件參數是 WrComponeappednt ,那么返回的組件就直接繼承自 WrappedComponent
function removeUserProp(WrapperComponent){
//繼承于參數組件
return class NewComponent extends WrapperComponent{
render(){
const {user,...otherProps} = this.props
this.props = otherProps
//調用WrapperComponent的render方法
// 只是一個render函數,不是一整個生命周期
return super.render()
}
}
}
繼承方式的高階組件可以應用于下列場景:
操縱 prop
操縱生命周期函數
- 操縱 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)
}
}
}
- 操縱生命周期函數:修改參數組件的生命周期
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
}
}
}
以函數為子組件
高階組件并不是唯一可用于提高 React 組件代碼重用的方法 在上 節的介紹中可以體會到,高階組件擴展現有組件功能的方式主要是通過 props ,增加 props 或者減少props ,或者修改原有的 props 以代理方式的高階組件為例,新產生的組件和原有的組件說到底是兩個組件,是父子關系,而兩個 React 組件之間通信的方式自然是 props 因為每個組件都應該通過 propTypes 聲明自己所支持的 props 高階組件利用原組件的 props擴充功能,在靜態代碼檢查上也占優勢>
但是,高階組件也有缺點,那就是對原組件的 props 有了固化的要求 也就是說,能不能把一個高階組件作用于某個組件 ,要先看一下這個組件 是不是能夠接受高階組件傳過來的 props ,如果組件 并不支持這些 props ,或者對這些 props 的命名有不同,或者使用方式不是預期的方式,那也就沒有辦法應用這個高階組件。
“以函數為子組件”的模式就是為了克服高階組件的這種局限而生的 在這種模式下,實現代碼重用的不是一個函數,而是一個真正的 React 組件,這樣的 React 組件有個特點,要求必須有子組件的存在,而且這個子組件必須是一個函數 在組件實例的生命周期函數中, this props children 引用的就是子組件, render 函數會直接this.props.children當做函數來調用,得到的結果就可以作為 render 返回結果的一部分
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>
以函數為子組件這種方法非常合適做動畫,作為子組件的函數主要專注于參數來渲染就可以了;但是它難以做性能優化,因為子組件是函數,沒有生命周期,無法利用shouldComponentUpdate