React高阶组件(HOC)入门简介及运用

235 阅读3分钟

1 概念

  • 高阶组件(Higher-Order Components)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。
  • 具体而言,高阶组件是参数为组件,返回值为新组件的函数。
const EnhancedComponent = higherOrderComponent(WrappedComponent);
  • 组件是将 props 转换为 UI,而高阶组件是将组件转换为另一个组件。
  • 使用高阶组件可实现:
    • 复用逻辑
    • 增强props,例如react-router的withRouter组件
    • 控制渲染
  • 常用的高阶组件有两种方式
    • 正向代理
    • 反向继承

2 正向代理

  • 传给源组件的props属性都需要经过包裹函数HOC,也就是HOC函数代理了源组件的属性,在这一层中我们可以对props做一些额外操作
function HOC(WrapComponent) {
  return function HocComponent(props) {
    return <WrapComponent {...props} />;
  };
}
function Inner(props) {
  const { name } = props;
  return (
    <>
      <div>inner component</div>
      <div>name:{name}</div>
    </>
  );
}
const HocInner = HOC(Inner);
ReactDOM.render(<HocInner name="weison" />, document.getElementById("root"));

2.1 增强props

function HOC(WrapComponent) {
  //方式一
  /*   return function HocComponent(props) {
    const city = "广州";
    return <WrapComponent {...props} city={city} />;
  }; */

  //方式二
  return function HocComponent(props) {
    const enhancedProps = {
      ...props,
      city: "广州",
    };
    return <WrapComponent {...enhancedProps} />;
  };
}
function Inner(props) {
  const { name, city } = props;
  return (
    <>
      <div>inner component</div>
      <div>name:{name}</div>
      <div>city:{city}</div>
    </>
  );
}
const HocInner = HOC(Inner);
ReactDOM.render(<HocInner name="weison" />, document.getElementById("root"));

image.png

2.2 复用逻辑

  • 增加公共部分<h1>简介</h1>
function HOC(WrapComponent) {
  return function HocComponent(props) {
    const enhancedProps = {
      ...props,
      city: "广州",
    };
    return (
      <>
        <h1>简介</h1>    // + 
        <WrapComponent {...enhancedProps} />
      </>
    );
  };
}
function Inner(props) {
  const { name, city } = props;
  return (
    <>
      <div>inner component</div>
      <div>name:{name}</div>
      <div>city:{city}</div>
    </>
  );
}
const HocInner = HOC(Inner);
ReactDOM.render(<HocInner name="weison" />, document.getElementById("root"));

2.3 抽离state控制更新

  • 这里把count的状态放到HocComponent函数内管理
function HOC(WrapComponent) {
  return function HocComponent(props) {
    const [count, setCount] = useState(0);
    const handleCountClick = () => setCount(count + 1)
    const enhancedProps = {
      ...props,
      city: "广州",
    };
    return (
      <>
        <h1>简介</h1>
        <WrapComponent {...enhancedProps} count={count} handleCountClick={handleCountClick} />
      </>
    );
  };
}
function Inner(props) {
  const { name, city, count, handleCountClick } = props;
  return (
    <>
      <div>inner component</div>
      <div>name:{name}</div>
      <div>city:{city}</div>
      <div onClick={handleCountClick}>count:{count}</div>
    </>
  );
}
const HocInner = HOC(Inner);
ReactDOM.render(<HocInner name="weison" />, document.getElementById("root"));

2.4 条件渲染

  • 在外层控制被包裹组件是否渲染,这种情况应用于,权限隔离懒加载 ,延时加载等场景
  • 路由权限控制示例
//PrivateRoute.js
import { message } from "antd";
import React, { useEffect } from "react";
import { Redirect } from "react-router-dom";
export default function HOC(Route) {
  return function HocComponent(props) {
    const isLogin = localStorage.getItem("token");
    useEffect(() => {
      if (!isLogin) {
        message.warning("请先登录");
      }
    }, []);
    return isLogin ? <Route {...props} /> : <Redirect to="/login" />;
  };
}
//index.js 模拟使用
import React from "react";
import ReactDOM from "react-dom";
import { Router, Switch, Route } from "react-router-dom";
import history from "./history";
import Login from "./pages/login";
import Home from "./pages/home";
import HOC from "./router/PrivateRoute.js";
const PrivateRoute = HOC(Route);
export default function Index() {
  return (
      <Router history={history}>
          <Switch>
            <Route path="/login" component={Login} />
            <PrivateRoute path="/" component={Home} />
          </Switch>
      </Router>
  );
}
ReactDOM.render(<Index />, document.getElementById("root"));
//这里的被包裹组件是固定的Route组件,有更简单的实现方式
import { message } from "antd";
import React, { useEffect } from "react";
import { Redirect, Route } from "react-router-dom";
function PrivateRoute() {
  return function HocComponent(props) {
    const isLogin = localStorage.getItem("token");
    useEffect(() => {
      if (!isLogin) {
        message.warning("请先登录");
      }
    }, []);
    return isLogin ? <Route {...props} /> : <Redirect to="/login" />;
  };
}
export default PrivateRoute();
//后续直接导入使用
//import PrivateRoute from "./router/PrivateRoute.js";
//<PrivateRoute path="/" component={Home} />

3 反向继承

  • 返回的高阶组件直接继承源组件
function HOC(WrapComponent) {
  return class extends WrapComponent {};
}
class Inner extends React.Component {
  render() {
    return <>Inner</>;
  }
}
const HocInner = HOC(Inner);
ReactDOM.render(<HocInner />, document.getElementById("root"));
  • 反向继承我现阶段觉得不常用,更多介绍和用法可看文末的参考资料写得很详细

4 注意事项

//高阶组件不转发ref,此时赋值的ref挂载到了外层HocComponent上
function HOC(WrapComponent) {
  return class extends React.Component {
    state = {
      test: "test",
    };
    render() {
      return <WrapComponent {...this.props} />;
    }
  };
}
function Inner(props) {
  const { name } = props;
  return (
    <>
      <div>inner component</div>
      <div>name:{name}</div>
    </>
  );
}
const HocInner = HOC(Inner);
function App() {
  const myRef = useRef();
  useEffect(() => {
    console.log("myRef", myRef.current);
  }, []);
  return <HocInner name="weison" ref={myRef} />;
}
ReactDOM.render(<App />, document.getElementById("root"));

image.png

//转发ref,则把接收到的ref转发到被包裹组件(内部通过props接收到ref后可挂载到意向节点)
function HOC(WrapComponent) {
  class HocComponent extends React.Component {
    render() {
      return <WrapComponent {...this.props} />;
    }
  }
  //转发ref
  return React.forwardRef((props, ref) => <HocComponent {...props} forwardRef={ref} />);
}
function Inner(props) {
  const { name,forwardRef } = props;
  return (
    <>
      <div ref={forwardRef}>inner component</div>
      <div>name:{name}</div>
    </>
  );
}
const HocInner = HOC(Inner);
function App() {
  const myRef = useRef();
  useEffect(() => {
    console.log('myRef',myRef.current);
  }, []);
  return <HocInner name="weison" ref={myRef} />;
}
ReactDOM.render(<App />, document.getElementById("root"));

image.png

参考资料

「react进阶」一文吃透React高阶组件(HOC)