十、「深入React源码」--- 高阶组件

267 阅读3分钟

一、准备

高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。

具体而言,高阶组件是参数为组件,返回值为新组件的函数。

高阶组件两大功能:属性代理、反向继承

二、属性代理

高阶组件可以实现属性代理,给组件添加额外的属性,以实现特定的逻辑,可以实现逻辑复用,可以操作组件的props

@装饰器语法需安装第三方库支持

1. 支持装饰器

1-1. 安装

npm i react-app-rewired customize-cra @babel/plugin-proposal-decorators -D

1-2. 修改package.json

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

1-3. 添加config-overrides.js

const { override,addBabelPlugin } = require('customize-cra'); 
module.exports = override(
    addBabelPlugin(
        [ "@babel/plugin-proposal-decorators", { "legacy": true } ]
    ) 
)

1-4. 添加jsconfig.json

{
    "compilerOptions": {
        "experimentalDecorators": true
    }
}

2. 属性代理应用

2-1. src/index.js

import React from "react";
import ReactDOM from "react-dom";

const WithLoading = (OldComponent) => {
  let state = {
    show() {
      console.log("show");
    },
    hide() {
      console.log("hide");
    },
  };
  return class extends React.Component {
    render() {
      return <OldComponent {...this.props} {...state} />;
    }
  };
};

@WithLoading
class Hello extends React.Component {
  render() {
    console.log(this.props);
    return (
      <>
        <p>hello</p>
        <button onClick={this.props.show}>显示</button>
        <button onClick={this.props.hide}>隐藏</button>
      </>
    );
  }
}

// let LoadingHello = WithLoading(Hello);
// ReactDOM.render(<LoadingHello title="标题" />, document.querySelector("#root"));
ReactDOM.render(<Hello title="标题" />, document.querySelector("#root"));

三、反向继承

1. 继承

1-1. 正向继承

父子关系出现,一般来讲应该是先走父组件的逻辑,后走子组件的逻辑: image.png

1-2. 反向继承

但是,使用高阶组件后,可以实现反向继承,即先走子组件的逻辑: image.png

其实,页面渲染时,只渲染了新组件,因为新组件继承了老组件,所以保留了老组件的状态。那么我们在实现时,只需要实现cloneElement方法:把新的属性赋值给老组件即可。

2. 实现反向继承

2-1. src/index.js

import React from "./react";
import ReactDOM from "./react-dom";
class Button extends React.Component {
  state = { name: "张三" };

  componentWillMount() {
    console.log("父 componentWillMount");
  }

  componentDidMount() {
    console.log("父 componentDidMount");
  }

  render() {
    console.log("父 render");
    return <button name={this.state.name} title={this.props.title} />;
  }
}
const wrapper = (OldComponent) => {
  return class NewComponent extends OldComponent {
    state = { number: 0 };

    componentWillMount() {
      console.log("子 componentWillMount");
      super.componentWillMount();
    }

    componentDidMount() {
      console.log("子 componentDidMount");
      super.componentDidMount();
    }

    handleClick = () => {
      this.setState({ number: this.state.number + 1 });
    };

    render() {
      console.log("子 render");
      let renderElement = super.render();
      let newProps = {
        ...renderElement.props,
        ...this.state,
        onClick: this.handleClick,
      };
      return React.cloneElement(renderElement, newProps, this.state.number);
    }
  };
};

let WrappedButton = wrapper(Button);

ReactDOM.render(
  <WrappedButton title="标题" />,
  document.getElementById("root")
);

2-2. src/react.js

import { wrapToVdom } from "./utils";
import { Component } from "./component";
import {
  REACT_FORWARD_REF,
  REACT_ELEMENT,
  REACT_PROVIDER,
  REACT_CONTEXT,
} from "./constants";

function createElement(type, config, children) {
  //children永远都是数组
  let ref, key;
  if (config) {
    delete config.__source; // source:bable编译时产生的属性
    delete config.__self;
    ref = config.ref; // ref可以用来引用这个真实DOM元素
    key = config.key; // 用来进行DOM-DIFF优化的,是用来唯一标识某个子元素的
    delete config.ref;
    delete config.key;
  }
  let props = { ...config };
  if (arguments.length > 3) {
    // 如果入参多余3个,说明有多个子元素,截取后,以数组形式保存
    props.children = Array.prototype.slice.call(arguments, 2).map(wrapToVdom);
  } else if (arguments.length === 3) {
    props.children = wrapToVdom(children); // 可能是React元素对象,也可能是string/number/null/und
  }
  return {
    $$typeof: REACT_ELEMENT,
    type,
    ref,
    key,
    props,
  };
}

function createRef() {
  // current属性的值:是等ref属性所在的原生dom元素变成真实dom后,把真实dom的地址赋给了current
  return { current: null };
}

/**
 * 接收转发ref的函数组件
 * @param {*} render 函数组件
 */
function forwardRef(render) {
  return {
    $$typeof: REACT_FORWARD_REF,
    render,
  };
}

function createContext() {
  let context = { $$typeof: REACT_CONTEXT };
  context.Provider = {
    $$typeof: REACT_PROVIDER,
    _context: context,
  };
  context.Consumer = {
    $$typeof: REACT_CONTEXT,
    _context: context,
  };
  return context;
}

> > > function cloneElement(oldElement, props, children) {
> > >   // 同createElement
> > >   if (arguments.length > 3) {
> > >     props.children = Array.prototype.slice.call(arguments, 2).map(wrapToVdom);
> > >   } else if (arguments.length === 3) {
> > >     props.children = wrapToVdom(children);
> > >   }
> > >   // 把新的属性,覆盖老的属性
> > >   return { ...oldElement, props };
> > > }

const React = {
  createElement,
  Component,
  createRef,
  forwardRef,
  createContext,
> > >   cloneElement,
};
export default React;

四、 总结

属性代理: 返回一个新组件,新组件会渲染老组件,有两个组件

反向继承: 返回一个新组件,新组件继承老组件,只有一个组件