高阶组件(Higher-Order Components):

高阶组件(HOC)是 React 中用于重用组件逻辑的高级技术。 HOC 本身不是 React API 的一部分。 它们是从 React 构思本质中浮现出来的一种模式。
具体来说,高阶组件是一个函数,能够接受一个组件并返回一个新的组件。
在我们项目中使用react-redux框架的时候,有一个connect的概念,这里的connect其实就是一个高阶组件。也包括类似react-router-dom中的withRouter的概念。

实现高阶组件的方法:

实现高阶组件的方法有如下两种:

  • 属性代理。高阶组件通过被包裹的React组件来操作props。
  • 反向继承。高阶组件继承于被包裹的React组件。

渲染属性:
解决的是有状态组件的可复用问题。
示例代码:
User的数据并不是app提供的,而是有provider来提供的。

  1. import React, {Component} from 'react';
  2. import ReactDOM from 'react-dom';
  3. import * as serviceWorker from './serviceWorker';
  4. class Provider extends Component {
  5. constructor(props) {
  6. super(props);
  7. this.state = {
  8. name: 'Provider--'
  9. };
  10. }
  11. render(){
  12. return this.props.render(this.state.name);
  13. }
  14. }
  15. class User extends Component {
  16. render(){
  17. return (<div>{this.props.data}</div>);
  18. }
  19. }
  20. class App extends Component{
  21. constructor(props){
  22. super(props);
  23. }
  24. render() {
  25. return (<div>
  26. <Provider render={(data) => <User data={data}/> }/>
  27. </div>);
  28. }
  29. componentWillUpdate(){
  30. console.log('will update!');
  31. }
  32. }
  33. ReactDOM.render(<App />, document.getElementById('root'));

上下文Context示例代码:
每个子组件里都需要定义出上下文数据类型,很麻烦。

  1. import React, {Component} from 'react';
  2. import ReactDOM from 'react-dom';
  3. import * as serviceWorker from './serviceWorker';
  4. import PropTypes from 'prop-types';
  5. class Provider extends Component {
  6. getChildContext(){
  7. return this.props.store;
  8. }
  9. static childContextTypes = {
  10. name: PropTypes.string,
  11. age: PropTypes.number
  12. }
  13. constructor(props) {
  14. super(props);
  15. this.state = {
  16. name: 'Provider--'
  17. };
  18. }
  19. render(){
  20. return this.props.children;
  21. }
  22. }
  23. class BaseUser extends Component {
  24. static contextTypes = {
  25. name: PropTypes.string
  26. }
  27. render(){
  28. return (<div>{this.context.name}</div>);
  29. }
  30. }
  31. class BasePost extends Component {
  32. static contextTypes = {
  33. age: PropTypes.number
  34. };
  35. render(){
  36. return (<div>{this.context.age}</div>);
  37. }
  38. }
  39. // 数据仓库
  40. const store = {
  41. name: 'David',
  42. age: 26
  43. };
  44. class App extends Component{
  45. constructor(props){
  46. super(props);
  47. }
  48. render() {
  49. return (<div>
  50. <Provider store={store}>
  51. <div>
  52. <BaseUser/>
  53. <BasePost/>
  54. </div>
  55. </Provider>
  56. </div>);
  57. }
  58. componentWillUpdate(){
  59. console.log('will update!');
  60. }
  61. }
  62. ReactDOM.render(<App />, document.getElementById('root'));

优化后的代码:

  1. import React, {Component} from 'react';
  2. import ReactDOM from 'react-dom';
  3. import * as serviceWorker from './serviceWorker';
  4. import PropTypes from 'prop-types';
  5. class Provider extends Component {
  6. getChildContext(){
  7. return this.props.store;
  8. }
  9. static childContextTypes = {
  10. name: PropTypes.string,
  11. age: PropTypes.number
  12. }
  13. constructor(props) {
  14. super(props);
  15. this.state = {
  16. name: 'Provider--'
  17. };
  18. }
  19. render(){
  20. return this.props.children;
  21. }
  22. }
  23. class BaseUser extends Component {
  24. static contextTypes = {
  25. name: PropTypes.string
  26. }
  27. render(){
  28. return (<div>{this.props.name}</div>);
  29. }
  30. }
  31. class BasePost extends Component {
  32. render(){
  33. return (<div>{this.props.age}</div>);
  34. }
  35. }
  36. const connect = (Com) => {
  37. class ConnectComponent extends Component {
  38. static contextTypes = Provider.childContextTypes;
  39. displayName = Com.displayName;
  40. render() {
  41. return <Com {...this.context}/>;
  42. }
  43. }
  44. return ConnectComponent;
  45. };
  46. const User = connect(BaseUser);
  47. const Post = connect(BasePost);
  48. // 数据仓库
  49. const store = {
  50. name: 'David',
  51. age: 26
  52. };
  53. class App extends Component{
  54. constructor(props){
  55. super(props);
  56. }
  57. render() {
  58. return (<div>
  59. <Provider store={store}>
  60. <div>
  61. <User/>
  62. <Post/>
  63. </div>
  64. </Provider>
  65. </div>);
  66. }
  67. componentWillUpdate(){
  68. console.log('will update!');
  69. }
  70. }
  71. ReactDOM.render(<App />, document.getElementById('root'));
  72. serviceWorker.unregister();
  1. import React, {Component} from 'react';
  2. import ReactDOM from 'react-dom';
  3. import * as serviceWorker from './serviceWorker';
  4. import PropTypes from 'prop-types';
  5. const connect = key => Com => {
  6. class ConnectComponent extends Component {
  7. constructor(props){
  8. super(this);
  9. this.state = {
  10. [key]: store[key]
  11. };
  12. }
  13. render() {
  14. return <Com {...this.state}/>;
  15. }
  16. componentDidMount() {
  17. window.store = new Proxy(store, {
  18. get: (target, key, receiver) => {
  19. console.log('get: ' + key);
  20. return Reflect.get(target, key, receiver);
  21. },
  22. set: (target, key, value, receiver) => {
  23. console.log('set: ' + key + ', value: ' + value);
  24. this.setState({
  25. [key]: value
  26. });
  27. return Reflect.set(target, key, value, receiver);
  28. }
  29. });
  30. }
  31. }
  32. return ConnectComponent;
  33. };
  34. @connect('age')
  35. class User extends Component{
  36. render(){
  37. return (<div>{this.props.age}</div>);
  38. }
  39. }
  40. let store = {
  41. name: 'David',
  42. age: 26
  43. };
  44. class App extends Component{
  45. render() {
  46. return (<User/>);
  47. }
  48. componentWillUpdate(){
  49. console.log('will update!');
  50. }
  51. }
  52. ReactDOM.render(<App />, document.getElementById('root'));
  53. serviceWorker.unregister();

上面的代码在运行过程中报错:

Support for the experimental syntax ‘decorators-legacy’ isn’t currently enabled.

解决办法:

  1. npm install customize-cra react-app-rewired @babel/plugin-proposal-decorators —save
  2. 项目根目录新建config-overrides.js文件加入以下代码:

const { override, addDecoratorsLegacy } = require(‘customize-cra’);
module.exports = override(
addDecoratorsLegacy()
);

  1. 修改package.json文件如下:

“scripts”: {
“start”: “react-app-rewired start”,
“build”: “react-app-rewired build”,
“test”: “react-app-rewired test”,
“eject”: “react-app-rewired eject”
}

属性代理预先将业务组件进行了数据的封装,便于获取数据。

高阶组件的另外一种实现方式是反向继承,下面是学习代码。它实现的是交互的封装。

  1. import React, {Component} from 'react';
  2. import ReactDOM from 'react-dom';
  3. import * as serviceWorker from './serviceWorker';
  4. import PropTypes from 'prop-types';
  5. const loading = com => {
  6. class LoadingComponent extends com {
  7. render(){
  8. return (<div>
  9. loading
  10. {super.render()}
  11. </div>);
  12. }
  13. showLoading() {
  14. console.log('show loading');
  15. }
  16. hideLoading() {
  17. console.log('hide loading');
  18. }
  19. }
  20. return LoadingComponent;
  21. }
  22. @loading
  23. class User extends Component{
  24. render(){
  25. return (<div>user</div>);
  26. }
  27. componentDidMount() {
  28. this.showLoading();
  29. this.hideLoading();
  30. }
  31. }
  32. class App extends Component{
  33. render() {
  34. return (<User/>);
  35. }
  36. }
  37. ReactDOM.render(<App />, document.getElementById('root'));
  38. serviceWorker.unregister();