搜尋
首頁web前端js教程React高階組件使用詳解

React高階組件使用詳解

May 24, 2018 pm 03:24 PM
react使用詳解

高階元件的定義類比於高階函數的定義。高階函數接收函數作為參數,並且傳回值也是一個函數。類似的,高階元件接收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
JavaScript的角色:使網絡交互和動態JavaScript的角色:使網絡交互和動態Apr 24, 2025 am 12:12 AM

JavaScript是現代網站的核心,因為它增強了網頁的交互性和動態性。 1)它允許在不刷新頁面的情況下改變內容,2)通過DOMAPI操作網頁,3)支持複雜的交互效果如動畫和拖放,4)優化性能和最佳實踐提高用戶體驗。

C和JavaScript:連接解釋C和JavaScript:連接解釋Apr 23, 2025 am 12:07 AM

C 和JavaScript通過WebAssembly實現互操作性。 1)C 代碼編譯成WebAssembly模塊,引入到JavaScript環境中,增強計算能力。 2)在遊戲開發中,C 處理物理引擎和圖形渲染,JavaScript負責遊戲邏輯和用戶界面。

從網站到應用程序:JavaScript的不同應用從網站到應用程序:JavaScript的不同應用Apr 22, 2025 am 12:02 AM

JavaScript在網站、移動應用、桌面應用和服務器端編程中均有廣泛應用。 1)在網站開發中,JavaScript與HTML、CSS一起操作DOM,實現動態效果,並支持如jQuery、React等框架。 2)通過ReactNative和Ionic,JavaScript用於開發跨平台移動應用。 3)Electron框架使JavaScript能構建桌面應用。 4)Node.js讓JavaScript在服務器端運行,支持高並發請求。

Python vs. JavaScript:比較用例和應用程序Python vs. JavaScript:比較用例和應用程序Apr 21, 2025 am 12:01 AM

Python更適合數據科學和自動化,JavaScript更適合前端和全棧開發。 1.Python在數據科學和機器學習中表現出色,使用NumPy、Pandas等庫進行數據處理和建模。 2.Python在自動化和腳本編寫方面簡潔高效。 3.JavaScript在前端開發中不可或缺,用於構建動態網頁和單頁面應用。 4.JavaScript通過Node.js在後端開發中發揮作用,支持全棧開發。

C/C在JavaScript口譯員和編譯器中的作用C/C在JavaScript口譯員和編譯器中的作用Apr 20, 2025 am 12:01 AM

C和C 在JavaScript引擎中扮演了至关重要的角色,主要用于实现解释器和JIT编译器。1)C 用于解析JavaScript源码并生成抽象语法树。2)C 负责生成和执行字节码。3)C 实现JIT编译器,在运行时优化和编译热点代码,显著提高JavaScript的执行效率。

JavaScript在行動中:現實世界中的示例和項目JavaScript在行動中:現實世界中的示例和項目Apr 19, 2025 am 12:13 AM

JavaScript在現實世界中的應用包括前端和後端開發。 1)通過構建TODO列表應用展示前端應用,涉及DOM操作和事件處理。 2)通過Node.js和Express構建RESTfulAPI展示後端應用。

JavaScript和Web:核心功能和用例JavaScript和Web:核心功能和用例Apr 18, 2025 am 12:19 AM

JavaScript在Web開發中的主要用途包括客戶端交互、表單驗證和異步通信。 1)通過DOM操作實現動態內容更新和用戶交互;2)在用戶提交數據前進行客戶端驗證,提高用戶體驗;3)通過AJAX技術實現與服務器的無刷新通信。

了解JavaScript引擎:實施詳細信息了解JavaScript引擎:實施詳細信息Apr 17, 2025 am 12:05 AM

理解JavaScript引擎內部工作原理對開發者重要,因為它能幫助編寫更高效的代碼並理解性能瓶頸和優化策略。 1)引擎的工作流程包括解析、編譯和執行三個階段;2)執行過程中,引擎會進行動態優化,如內聯緩存和隱藏類;3)最佳實踐包括避免全局變量、優化循環、使用const和let,以及避免過度使用閉包。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

EditPlus 中文破解版

EditPlus 中文破解版

體積小,語法高亮,不支援程式碼提示功能

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強大的PHP整合開發環境

Safe Exam Browser

Safe Exam Browser

Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)