首頁  >  文章  >  web前端  >  React高階組件使用詳解

React高階組件使用詳解

php中世界最好的语言
php中世界最好的语言原創
2018-05-24 15:24:111774瀏覽
高階元件的定義類比於高階函數的定義。高階函數接收函數作為參數,並且傳回值也是一個函數。類似的,高階元件接收React元件作為參數,並且傳回一個新的React元件。 高階元件本質上也是函數,並不是一個元件,這一點一定不要弄錯。

為什麼React引入高階元件的概念?它到底有何威力?讓我們先透過一個簡單的例子來說明一下。

假設有一個元件MyComponent,需要從LocalStorage取得數據,然後渲染資料到介面。我們可以這樣寫元件程式碼:

import React, { Component } from 'react'
class MyComponent extends Component {
  componentWillMount() {
      let data = localStorage.getItem('data');
      this.setState({data});
  }
  
  render() {
    return <p>{this.state.data}</p>
  }
}

程式碼很簡單,但當有其他元件也需要從LocalStorage取得相同的資料展示出來時,需要在每個元件都重複componentWillMount中的程式碼,這顯然是很冗餘的。下面讓我們來看看使用高階元件可以怎麼改寫這部分程式碼。

import React, { Component } from 'react'
function withPersistentData(WrappedComponent) {
  return class extends Component {
    componentWillMount() {
      let data = localStorage.getItem('data');
        this.setState({data});
    }
    
    render() {
      // 通过{...this.props} 把传递给当前组件的属性继续传递给被包装的组件WrappedComponent
      return <wrappedcomponent></wrappedcomponent>
    }
  }
}
class MyComponent2 extends Component {  
  render() {
    return <p>{this.props.data}</p>
  }
}
const MyComponentWithPersistentData = withPersistentData(MyComponent2)

withPersistentData就是一個高階元件,它傳回一個新的元件,在新元件的componentWillMount中統一處理從LocalStorage中取得資料的邏輯,然後將取得的資料以屬性的方式傳遞給被包裝的元件WrappedComponent,這樣在WrappedComponent中就可以直接使用this.props. data取得需要展示的資料了,如MyComponent2所示。當有其他的元件也需要這段邏輯時,繼續使用withPersistentData這個高階元件包裝這些元件就可以了。

透過這個例子,可以看出高階元件的主要功能是封裝並分離元件的通用邏輯,讓通用邏輯在元件間更好地被重複使用。高階組件的這種實現方式,本質上是一個裝飾者設計模式。

高階元件的參數並非只能是一個元件,它還可以接收其他參數。例如,元件MyComponent3需要從LocalStorage中取得key等於name的數據,而不是上面範例中寫死的key等於data的數據,withPersistentData這個高階元件就不滿足我們的需求了。我們可以讓它接收額外的一個參數,來決定從LocalStorage中取得哪個資料:

import React, { Component } from 'react'
function withPersistentData(WrappedComponent, key) {
  return class extends Component {
    componentWillMount() {
      let data = localStorage.getItem(key);
        this.setState({data});
    }
    
    render() {
      // 通过{...this.props} 把传递给当前组件的属性继续传递给被包装的组件WrappedComponent
      return <wrappedcomponent></wrappedcomponent>
    }
  }
}
class MyComponent2 extends Component {  
  render() {
    return <p>{this.props.data}</p>
  }
  
  //省略其他逻辑...
}
class MyComponent3 extends Component {  
  render() {
    return <p>{this.props.data}</p>
  }
  
  //省略其他逻辑...
}
const MyComponent2WithPersistentData = withPersistentData(MyComponent2, 'data');
const MyComponent3WithPersistentData = withPersistentData(MyComponent3, 'name');

新版本的withPersistentData就滿足我們取得不同key的值的需求了。高階元件中的參數當然也可以是函數,我們將在下一節進一步說明。

3. 進階用法

高階元件最常見的函式簽章形式是這樣的:

HOC([param])([WrappedComponent])

用這種形式改寫withPersistentData,如下:

import React, { Component } from 'react'
const withPersistentData = (key) => (WrappedComponent) => {
  return class extends Component {
    componentWillMount() {
      let data = localStorage.getItem(key);
        this.setState({data});
    }
    
    render() {
      // 通过{...this.props} 把传递给当前组件的属性继续传递给被包装的组件WrappedComponent
      return <wrappedcomponent></wrappedcomponent>
    }
  }
}
class MyComponent2 extends Component {  
  render() {
    return <p>{this.props.data}</p>
  }
  
  //省略其他逻辑...
}
class MyComponent3 extends Component {  
  render() {
    return <p>{this.props.data}</p>
  }
  
  //省略其他逻辑...
}
const MyComponent2WithPersistentData = withPersistentData('data')(MyComponent2);
const MyComponent3WithPersistentData = withPersistentData('name')(MyComponent3);

實際上,此時的withPersistentData和我們最初對高階組件的定義已經不同。它已經變成了一個高階函數,但這個高階函數的回傳值是一個高階元件。 HOC([param])([WrappedComponent])這種形式中,HOC([param])才是真正的高階元件,我們可以把它看成高階組件的變種形式。這種形式的高階組件因其特有的便利性——結構清晰(普通參數和被包裹組件分離)、易於組合,並大量出現在第三方庫中。如react-redux中的connect就是一個典型。 connect的定義如下:

connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])(WrappedComponent)

這個函數會將一個React元件連接到Redux 的 store。在連接的過程中,connect透過函數類型的參數mapStateToProps,從全域store中取出目前元件所需的state,並將state轉換成目前元件的props;同時透過函數類型的參數 mapDispatchToProps,把目前元件用到的Redux的action creators,以props的方式傳遞給目前元件。

例如,我們把元件ComponentA連接到Redux上的寫法類似於:

const ConnectedComponentA = connect(mapStateToProps, mapDispatchToProps)(ComponentA);

我們可以把它拆分來看:

// connect 是一个函数,返回值enhance也是一个函数
const enhance = connect(mapStateToProps, mapDispatchToProps);
// enhance是一个高阶组件
const ConnectedComponentA = enhance(ComponentA);

当多个函数的输出和它的输入类型相同时,这些函数是很容易组合到一起使用的。例如,有f,g,h三个高阶组件,都只接受一个组件作为参数,于是我们可以很方便的嵌套使用它们:f( g( h(WrappedComponent) ) )。这里可以有一个例外,即最内层的高阶组件h可以有多个参数,但其他高阶组件必须只能接收一个参数,只有这样才能保证内层的函数返回值和外层的函数参数数量一致(都只有1个)。

例如我们将connect和另一个打印日志的高阶组件withLog联合使用:

const ConnectedComponentA = connect(mapStateToProps)(withLog(ComponentA));

这里我们定义一个工具函数:compose(...functions),调用compose(f, g, h) 等价于 (...args) => f(g(h(...args)))。用compose函数我们可以把高阶组件嵌套的写法打平:

const enhance = compose(
  connect(mapStateToProps),
  withLog
);
const ConnectedComponentA = enhance(ComponentA);

像Redux等很多第三方库都提供了compose的实现,compose结合高阶组件使用,可以显著提高代码的可读性和逻辑的清晰度。

4.与父组件区别

有些同学可能会觉得高阶组件有些类似父组件的使用。例如,我们完全可以把高阶组件中的逻辑放到一个父组件中去执行,执行完成的结果再传递给子组件。从逻辑的执行流程上来看,高阶组件确实和父组件比较相像,但是高阶组件强调的是逻辑的抽象。高阶组件是一个函数,函数关注的是逻辑;父组件是一个组件,组件主要关注的是UI/DOM。如果逻辑是与DOM直接相关的,那么这部分逻辑适合放到父组件中实现;如果逻辑是与DOM不直接相关的,那么这部分逻辑适合使用高阶组件抽象,如数据校验、请求发送等。

5. 注意事项

1)不要在组件的render方法中使用高阶组件,尽量也不要在组件的其他生命周期方法中使用高阶组件。因为高阶组件每次都会返回一个新的组件,在render中使用会导致每次渲染出来的组件都不相等(===),于是每次render,组件都会卸载(unmount),然后重新挂载(mount),既影响了效率,又丢失了组件及其子组件的状态。高阶组件最适合使用的地方是在组件定义的外部,这样就不会受到组件生命周期的影响了。

2)如果需要使用被包装组件的静态方法,那么必须手动拷贝这些静态方法。因为高阶组件返回的新组件,是不包含被包装组件的静态方法。hoist-non-react-statics可以帮助我们方便的拷贝组件所有的自定义静态方法。有兴趣的同学可以自行了解。

3)Refs不会被传递给被包装组件。尽管在定义高阶组件时,我们会把所有的属性都传递给被包装组件,但是ref并不会传递给被包装组件。如果你在高阶组件的返回组件中定义了ref,那么它指向的是这个返回的新组件,而不是内部被包装的组件。如果你希望获取被包装组件的引用,你可以把ref回调函数定义成一个普通属性(给它一个ref以外的名字)。下面的例子就用inputRef这个属性名代替了常规的ref命名:

function FocusInput({ inputRef, ...rest }) {
  return <input>;
}
//enhance 是一个高阶组件
const EnhanceInput = enhance(FocusInput);
// 在一个组件的render方法中...
return (<enhanceinput> {
    this.input = input
  }
}>)
// 让FocusInput自动获取焦点
this.input.focus();</enhanceinput>

下篇预告:

React 深入系列7:React 常用模式


我的新书《React进阶之路》已上市,请大家多多支持!
链接:京东 当当

React高階組件使用詳解

React 深入系列,深入讲解了React中的重点概念、特性和模式等,旨在帮助大家加深对React的理解,以及在项目中更加灵活地使用React。

1. 基本概念

高阶组件是React 中一个很重要且比较复杂的概念,高阶组件在很多第三方库(如Redux)中都被经常使用。在项目中用好高阶组件,可以显著提高代码质量。

高階組件的定義類比於高階函數的定義。高階函數接收函數作為參數,並且傳回值也是一個函數。類似的,高階元件接收React元件作為參數,並且傳回一個新的React元件。 高階元件本質上也是一個函數,並不是一個元件,這一點一定不要弄錯。

2. 應用場景

為什麼React引入高階元件的概念?它到底有何威力?讓我們先透過一個簡單的例子來說明一下。

假設有一個元件MyComponent,需要從LocalStorage取得數據,然後渲染資料到介面。我們可以這樣寫元件程式碼:

import React, { Component } from 'react'
class MyComponent extends Component {
  componentWillMount() {
      let data = localStorage.getItem('data');
      this.setState({data});
  }
  
  render() {
    return <p>{this.state.data}</p>
  }
}

程式碼很簡單,但當有其他元件也需要從LocalStorage取得相同的資料展示出來時,需要在每個元件都重複componentWillMount中的程式碼,這顯然是很冗餘的。下面讓我們來看看使用高階元件可以怎麼改寫這部分程式碼。

import React, { Component } from 'react'
function withPersistentData(WrappedComponent) {
  return class extends Component {
    componentWillMount() {
      let data = localStorage.getItem('data');
        this.setState({data});
    }
    
    render() {
      // 通过{...this.props} 把传递给当前组件的属性继续传递给被包装的组件WrappedComponent
      return <wrappedcomponent></wrappedcomponent>
    }
  }
}
class MyComponent2 extends Component {  
  render() {
    return <p>{this.props.data}</p>
  }
}
const MyComponentWithPersistentData = withPersistentData(MyComponent2)

withPersistentData就是一個高階元件,它傳回一個新的元件,在新元件的componentWillMount中統一處理從LocalStorage中取得資料的邏輯,然後將取得的資料以屬性的方式傳遞給被包裝的元件WrappedComponent,這樣在WrappedComponent中就可以直接使用this.props. data取得需要展示的資料了,如MyComponent2所示。當有其他的元件也需要這段邏輯時,繼續使用withPersistentData這個高階元件包裝這些元件就可以了。

透過這個例子,可以看出高階元件的主要功能是封裝並分離元件的通用邏輯,讓通用邏輯在元件間更好地被重複使用。高階組件的這種實現方式,本質上是一個裝飾者設計模式。

高階元件的參數並非只能是一個元件,它還可以接收其他參數。例如,元件MyComponent3需要從LocalStorage中取得key等於name的數據,而不是上面範例中寫死的key等於data的數據,withPersistentData這個高階元件就不滿足我們的需求了。我們可以讓它接收額外的一個參數,來決定從LocalStorage中取得哪個資料:

import React, { Component } from 'react'
function withPersistentData(WrappedComponent, key) {
  return class extends Component {
    componentWillMount() {
      let data = localStorage.getItem(key);
        this.setState({data});
    }
    
    render() {
      // 通过{...this.props} 把传递给当前组件的属性继续传递给被包装的组件WrappedComponent
      return <wrappedcomponent></wrappedcomponent>
    }
  }
}
class MyComponent2 extends Component {  
  render() {
    return <p>{this.props.data}</p>
  }
  
  //省略其他逻辑...
}
class MyComponent3 extends Component {  
  render() {
    return <p>{this.props.data}</p>
  }
  
  //省略其他逻辑...
}
const MyComponent2WithPersistentData = withPersistentData(MyComponent2, 'data');
const MyComponent3WithPersistentData = withPersistentData(MyComponent3, 'name');

新版本的withPersistentData就滿足我們取得不同key的值的需求了。高階元件中的參數當然也可以是函數,我們將在下一節進一步說明。

3. 進階用法

高階元件最常見的函式簽章形式是這樣的:

HOC([param])([WrappedComponent])

用這種形式改寫withPersistentData,如下:

import React, { Component } from 'react'
const withPersistentData = (key) => (WrappedComponent) => {
  return class extends Component {
    componentWillMount() {
      let data = localStorage.getItem(key);
        this.setState({data});
    }
    
    render() {
      // 通过{...this.props} 把传递给当前组件的属性继续传递给被包装的组件WrappedComponent
      return <wrappedcomponent></wrappedcomponent>
    }
  }
}
class MyComponent2 extends Component {  
  render() {
    return <p>{this.props.data}</p>
  }
  
  //省略其他逻辑...
}
class MyComponent3 extends Component {  
  render() {
    return <p>{this.props.data}</p>
  }
  
  //省略其他逻辑...
}
const MyComponent2WithPersistentData = withPersistentData('data')(MyComponent2);
const MyComponent3WithPersistentData = withPersistentData('name')(MyComponent3);

實際上,此時的withPersistentData和我們最初對高階組件的定義已經不同。它已經變成了一個高階函數,但這個高階函數的回傳值是一個高階元件。 HOC([param])([WrappedComponent])這種形式中,HOC([param])才是真正的高階元件,我們可以把它看成高階組件的變種形式。這種形式的高階組件因其特有的便利性——結構清晰(普通參數和被包裹組件分離)、易於組合,並大量出現在第三方庫中。如react-redux中的connect就是一個典型。 connect的定義如下:

connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])(WrappedComponent)

這個函數會將一個React元件連接到Redux 的 store。在連接的過程中,connect透過函數類型的參數mapStateToProps,從全域store中取出目前元件所需的state,並將state轉換成目前元件的props;同時透過函數類型的參數 mapDispatchToProps,把目前元件用到的Redux的action creators,以props的方式傳遞給目前元件。

例如,我們把元件ComponentA連接到Redux上的寫法類似於:

const ConnectedComponentA = connect(mapStateToProps, mapDispatchToProps)(ComponentA);

我們可以把它拆分來看:

// connect 是一个函数,返回值enhance也是一个函数
const enhance = connect(mapStateToProps, mapDispatchToProps);
// enhance是一个高阶组件
const ConnectedComponentA = enhance(ComponentA);

當多個函數的輸出和它的輸入類型相同時,這些函數是很容易組合到一起使用的。例如,有f,g,h三個高階元件,都只接受一個元件作為參數,於是我們可以很方便的巢狀使用它們:f( g( h(WrappedComponent) ) )。這裡可以有一個例外,即最內層的高階元件h可以有多個參數,但其他高階元件必須只能接收一個參數,只有這樣才能確保內層的函數回傳值和外層的函數參數數量一致(都只有1個)。

例如我們將connect和另一個列印日誌的高階元件withLog結合使用:

const ConnectedComponentA = connect(mapStateToProps)(withLog(ComponentA));

这里我们定义一个工具函数:compose(...functions),调用compose(f, g, h) 等价于 (...args) => f(g(h(...args)))。用compose函数我们可以把高阶组件嵌套的写法打平:

const enhance = compose(
  connect(mapStateToProps),
  withLog
);
const ConnectedComponentA = enhance(ComponentA);

像Redux等很多第三方库都提供了compose的实现,compose结合高阶组件使用,可以显著提高代码的可读性和逻辑的清晰度。

4.与父组件区别

有些同学可能会觉得高阶组件有些类似父组件的使用。例如,我们完全可以把高阶组件中的逻辑放到一个父组件中去执行,执行完成的结果再传递给子组件。从逻辑的执行流程上来看,高阶组件确实和父组件比较相像,但是高阶组件强调的是逻辑的抽象。高阶组件是一个函数,函数关注的是逻辑;父组件是一个组件,组件主要关注的是UI/DOM。如果逻辑是与DOM直接相关的,那么这部分逻辑适合放到父组件中实现;如果逻辑是与DOM不直接相关的,那么这部分逻辑适合使用高阶组件抽象,如数据校验、请求发送等。

5. 注意事项

1)不要在组件的render方法中使用高阶组件,尽量也不要在组件的其他生命周期方法中使用高阶组件。因为高阶组件每次都会返回一个新的组件,在render中使用会导致每次渲染出来的组件都不相等(===),于是每次render,组件都会卸载(unmount),然后重新挂载(mount),既影响了效率,又丢失了组件及其子组件的状态。高阶组件最适合使用的地方是在组件定义的外部,这样就不会受到组件生命周期的影响了。

2)如果需要使用被包装组件的静态方法,那么必须手动拷贝这些静态方法。因为高阶组件返回的新组件,是不包含被包装组件的静态方法。hoist-non-react-statics可以帮助我们方便的拷贝组件所有的自定义静态方法。有兴趣的同学可以自行了解。

3)Refs不会被传递给被包装组件。尽管在定义高阶组件时,我们会把所有的属性都传递给被包装组件,但是ref并不会传递给被包装组件。如果你在高阶组件的返回组件中定义了ref,那么它指向的是这个返回的新组件,而不是内部被包装的组件。如果你希望获取被包装组件的引用,你可以把ref的回调函数定义成一个普通属性(给它一个ref以外的名字)。下面的例子就用inputRef这个属性名代替了常规的ref命名:

function FocusInput({ inputRef, ...rest }) {
  return <input>;
}
//enhance 是一个高阶组件
const EnhanceInput = enhance(FocusInput);
// 在一个组件的render方法中...
return (<enhanceinput> {
    this.input = input
  }
}>)
// 让FocusInput自动获取焦点
this.input.focus();</enhanceinput>

相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!

推荐阅读:

react实现选中li高亮步骤详解

EasyCanvas绘图库在Pixeler项目开发中使用实战总结

以上是React高階組件使用詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn