HOC - 功能组件

IT技术 javascript reactjs components higher-order-components
2021-05-12 07:09:40

我已经创建了一个HOC在我react过来的应用程序下面的这个,其工作的罚款。但是我想知道是否有办法将 HOC 创建为功能组件(有或没有状态)???因为给定的示例是基于类的组件。

试图通过网络找到相同的内容,但什么也找不到。不确定这是否可能?或者永远做正确的事情?

任何线索将不胜感激:)

4个回答

我同意siraj,严格来说,接受的答案中的例子不是真正的 HOC。HOC 的显着特征是它返回一个组件,而PrivateRoute接受答案的组件一个组件本身。因此,虽然它完成了它的初衷,但我认为它不是 HOC 的一个很好的例子。

在函数式组件世界中,最基本的 HOC 如下所示:

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

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

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

使用它的一种方法是,如果您想使用临时(无双关语,大声笑)上下文提供程序。

假设我的应用程序有多个用户可以登录的点。我不想在所有这些点上复制登录逻辑(API 调用和成功/错误消息),所以我想要一个可重用的<Login />组件。但是,在我的情况下,所有这些登录点在视觉上都有显着差异,因此无法选择可重用的组件。我需要的是一个可重用的<WithLogin />组件,它将为其子组件提供所有必要的功能——API 调用和成功/错误消息。这是执行此操作的一种方法:

// 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)返回任何内容。

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现在将按预期工作。

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

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

HOC 本身不是 React API 的一部分。它们是从 React 的组合性质中出现的一种模式。

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

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

应用程序.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;

私有路由.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;

高阶组件不一定是类组件,它们的目的是将一个组件作为输入,并根据某种逻辑返回一个组件作为输出。

以下是将 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。相反,在任何提前返回之前,始终在 React 函数的顶层使用 Hook。通过遵循此规则,您可以确保每次渲染组件时都以相同的顺序调用 Hook。这就是允许 React 在多个 useState 和 useEffect 调用之间正确保留 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>
  );
}

代码沙盒

即使代码在 codeandbox 中工作,但由于上述规则它不会在您的本地机器上运行,如果您尝试运行此代码,您应该会收到以下错误

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>
  );
}

代码沙盒