HOC - 功能组件

HOC - Functional Component

我已经在 this 之后在我的 React 应用程序中创建了一个 HOC,并且工作正常。但是我想知道是否有办法将 HOC 创建为功能组件(有或没有状态)???因为给定的示例是基于 class 的组件。

试图在网上找到相同的内容,但一无所获。不确定那是否可能?或者曾经做过正确的事??

任何线索将不胜感激:)

您当然可以创建一个功能性无状态组件,它接受组件作为输入,return 一些其他组件作为输出,例如;

  1. 您可以创建一个 PrivateRoute 组件,它接受一个组件作为 prop 值,并且 return 一些其他组件取决于用户是否经过身份验证。
  2. 如果用户未通过身份验证(从上下文存储中读取),则您使用 <Redirect to='/login'/> 将用户重定向到登录页面,否则您 return 将组件作为 prop 传递并将其他 props 发送到该组件<Component {...props} />

App.js

const App = () => {
  return (
      <Switch>
        <PrivateRoute exact path='/' component={Home} />
        <Route exact path='/about' component={About} />
        <Route exact path='/login' component={Login} />
        <Route exact path='/register' component={Register} />
      </Switch>
  );
}

export default App;

PrivateRoute.jsx

import React, { useContext , useEffect} from 'react';
import { Route, Redirect } from 'react-router-dom'
import AuthContext from '../../context/auth/authContext'

const PrivateRoute = ({ component: Component, ...rest }) => {
  const authContext = useContext(AuthContext)
  const { loadUser, isAuthenticated } = authContext
  useEffect(() => {
    loadUser()
    // eslint-disable-next-line
  }, [])
  if(isAuthenticated === null){
    return <></>
  }
  return (
    <Route {...rest} render={props =>
      !isAuthenticated ? (
        <Redirect to='/login'/>
      ) : (
        <Component {...props} />
      )
    }
    />
  );
};
export default PrivateRoute;

Higher Order Components不一定是class个组件,它们的目的是根据某种逻辑把一个Component作为输入,return一个组件作为输出。

我同意 , strictly speaking the example in the 不是真正的 HOC。 HOC 的显着特征是它 returns 一个组件 ,而接受的答案 中的 PrivateRoute 组件是 组件本身。因此,虽然它很好地完成了它设定的目标,但我不认为它是 HOC 的一个很好的例子。

在功能组件的世界里,最基本的 HOC 看起来像这样:

const withNothing = Component => ({ ...props }) => (
  <Component {...props} />
);

调用 withNothing returns 另一个组件(不是实例,这是主要区别),然后可以像常规组件一样使用它:

const ComponentWithNothing = withNothing(Component);
const instance = <ComponentWithNothing someProp="test" />;

如果您想使用 ad-hoc(无双关语 lol)上下文提供程序,则可以使用此方法。

假设我的应用程序有多个用户可以登录的点。我不想在所有这些点上复制登录逻辑(API 调用和 success/error 消息),所以我想要一个可重用的 <Login /> 组件。然而,在我的例子中,所有这些登录点在视觉上都有很大不同,因此可重用组件不是一个选项。我需要的是一个可重用的 <WithLogin /> 组件,它将为其子组件提供所有必要的功能 - API 调用和 success/error 消息。这是执行此操作的一种方法:

// This context will only hold the `login` method.
// Calling this method will invoke all the required logic.
const LoginContext = React.createContext();
LoginContext.displayName = "Login";

// This "HOC" (not a true HOC yet) should take care of
// all the reusable logic - API calls and messages.
// This will allow me to pass different layouts as children.
const WithLogin = ({ children }) => {
  const [popup, setPopup] = useState(null);

  const doLogin = useCallback(
    (email, password) =>
      callLoginAPI(email, password).then(
        () => {
          setPopup({
            message: "Success"
          });
        },
        () => {
          setPopup({
            error: true,
            message: "Failure"
          });
        }
      ),
    [setPopup]
  );

  return (
    <LoginContext.Provider value={doLogin}>
      {children}

      {popup ? (
        <Modal
          error={popup.error}
          message={popup.message}
          onClose={() => setPopup(null)}
        />
      ) : null}
    </LoginContext.Provider>
  );
};

// This is my main component. It is very neat and simple
// because all the technical bits are inside WithLogin.
const MyComponent = () => {
  const login = useContext(LoginContext);

  const doLogin = useCallback(() => {
    login("a@b.c", "password");
  }, [login]);

  return (
    <WithLogin>
      <button type="button" onClick={doLogin}>
        Login!
      </button>
    </WithLogin>
  );
};

不幸的是,这不起作用,因为 LoginContext.Provider 实例化在 内部 MyComponent,所以 useContext(LoginContext) returns 什么都没有。

HOC 来救援!如果我添加一个小中间人会怎么样:

const withLogin = Component => ({ ...props }) => (
  <WithLogin>
    <Component {...props} />
  </WithLogin>
);

然后:

const MyComponent = () => {
  const login = useContext(LoginContext);

  const doLogin = useCallback(() => {
    login("a@b.c", "password");
  }, [login]);

  return (
    <button type="button" onClick={doLogin}>
      Login!
    </button>
  );
};

const MyComponentWithLogin = withLogin(MyComponent);

砰! MyComponentWithLogin 现在将按预期工作。

这可能不是处理这种特殊情况的最佳方式,但我有点喜欢它。

是的,它真的只是一个额外的函数调用,仅此而已!根据官方指南:

HOCs are not part of the React API, per se. They are a pattern that emerges from React’s compositional nature.

以下是将 HOC 与功能组件一起使用的过度简化示例。

要“包装”的功能组件:

import React from 'react'
import withClasses from '../withClasses'

const ToBeWrappedByHOC = () => {
return (
    <div>
        <p>I'm wrapped by a higher order component</p>
    </div>
       )
}

export default withClasses(ToBeWrappedByHOC, "myClassName");

高阶分量:

import React from 'react'


const withClasses = (WrappedComponent, classes) => {
return (props) => (
    <div className={classes}>
        <WrappedComponent {...props} />
    </div>
       );
};

export default withClasses;

该组件可以像这样在不同的组件中使用。

<ToBeWrappedByHOC/>

我可能会迟到,但这是我对 HOC 的两分钱

  • 以真正的 React 函数式组件方式创建 HOC 是不可能的,因为建议不要在嵌套函数中调用 hook。

Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function, before any early returns. By following this rule, you ensure that Hooks are called in the same order each time a component renders. That’s what allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls. (If you’re curious, we’ll explain this in-depth below.)

Rules of Hooks

这是我尝试过但失败了的方法

import React, { useState } from "react";

import "./styles.css";

function Component(props) {
  console.log(props);
  return (
    <div>
      <h2> Component Count {props.count}</h2>
      <button onClick={props.handleClick}>Click</button>
    </div>
  );
}

function Component1(props) {
  console.log(props);
  return (
    <div>
      <h2> Component1 Count {props.count}</h2>
      <button onClick={props.handleClick}>Click</button>
    </div>
  );
}

function HOC(WrapperFunction) {
  return function (props) {
    const handleClick = () => {
      setCount(count + 1);
    };

    const [count, setCount] = useState(0);
    return (
      <WrapperFunction handleClick={handleClick} count={count} {...props} />
    );
  }
}

const Comp1 = HOC((props) => {
  return <Component {...props} />;
});
const Comp2 = HOC((props) => {
  return <Component1 {...props} />;
});

export default function App() {
  return (
    <div className="App">
      <Comp1 name="hel" />
      <Comp2 />
    </div>
  );
}

CodeSandBox

即使代码在 codesandbox 中有效,但由于上述规则,它不会 运行 在您的本地计算机中,如果您尝试 运行 此代码

React Hook "useState" cannot be called inside a callback

为了解决这个问题,我做了以下工作

import "./styles.css";
import * as React from "react";
//macbook
function Company(props) {
  return (
    <>
      <h1>Company</h1>
      <p>{props.count}</p>
      <button onClick={() => props.increment()}>increment</button>
    </>
  );
}

function Developer(props) {
  return (
    <>
      <h1>Developer</h1>
      <p>{props.count}</p>
      <button onClick={() => props.increment()}>increment</button>
    </>
  );
}

//decorator
function HOC(Component) {
  // return function () {
  //   const [data, setData] = React.useState();
  //   return <Component />;
  // };
  class Wrapper extends React.Component {
    constructor(props) {
      super(props);
      this.state = { count: 0 };
    }
    handleClick = () => {
      this.setState({ count: this.state.count + 1 });
    };
    render() {
      return (
        <Component count={this.state.count} increment={this.handleClick} />
      );
    }
  }
  return Wrapper;
}

const NewCompany = HOC(Company);
const NewDeveloper = HOC(Developer);

export default function App() {
  return (
    <div className="App">
      <NewCompany name={"Google"} />
      <br />
      <NewDeveloper />
    </div>
  );
}

CodeSandbox