一、准备
高阶组件(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. 正向继承
父子关系出现,一般来讲应该是先走父组件的逻辑,后走子组件的逻辑:
1-2. 反向继承
但是,使用高阶组件后,可以实现反向继承,即先走子组件的逻辑:
其实,页面渲染时,只渲染了新组件,因为新组件继承了老组件,所以保留了老组件的状态。那么我们在实现时,只需要实现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;
四、 总结
属性代理: 返回一个新组件,新组件会渲染老组件,有两个组件
反向继承: 返回一个新组件,新组件继承老组件,只有一个组件