【问题标题】:HOC - Functional ComponentHOC - 功能组件
【发布时间】:2020-01-11 02:58:15
【问题描述】:

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

试图通过网络找到相同的内容,但什么也得不到。不确定那是否可能?还是该做对的事??

任何线索将不胜感激:)

【问题讨论】:

    标签: javascript reactjs components higher-order-components


    【解决方案1】:

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

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

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

    【讨论】:

    • 它看起来不像 HOC,更像是一个包装器组件。 HOC 返回 FC 而不是 ReactElement。
    • 如果我想在这里有管理员路由怎么办?我该怎么做?
    • 那么您的应用是否支持多重授权,例如用户和管理员?如果是这样,您可以创建一个名为 的组件并检查人员是否是管理员(而不是像这里的 isAuthenticated ),这样只有拥有属性 admin 的人(从身份验证的响应中检查)才能访问该组件
    • 请查看下面的答案并修改您的答案或删除。感谢您试图提供帮助但误导性的答案。私有路由不会返回一个组件,因为这听起来很奇怪。在这种情况下,组件是 props 的函数,而不是某些 JSX 的结果。
    • 你们是对的,但我不能删除我的答案,因为它被接受了
    【解决方案2】:

    当然,您可以在 react 中创建功能性 HOC,您可以为此创建任何其他文件夹,例如“Utils”。例如,它是我在 Utils 文件夹中的 amountUtil.js 文件:

    export const getFormattedAmount = (amount?: Amount) => ( 金额 && ${amount.formattedAmount} ${amount.currency} );

    【讨论】:

      【解决方案3】:

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

      在函数式组件世界中,最基本的 HOC 应该是这样的:

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

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

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

      如果您想使用临时(没有双关语lol)上下文提供程序,则使用此方法的一种方法。

      假设我的应用程序有多个用户可以登录的点。我不想在所有这些点上复制登录逻辑(API 调用和成功/错误消息),所以我想要一个可重用的&lt;Login /&gt; 组件。但是,在我的例子中,所有这些登录点在视觉上都存在显着差异,因此可重用组件不是一种选择。我需要的是一个可重用的&lt;WithLogin /&gt; 组件,它将为其子组件提供所有必要的功能——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 的组合特性中出现的一种模式。

      【讨论】:

      • 除了最初的问题要求 HOC 之外,我想知道有什么错误接受的答案不是真正的 HOC,而是作为包装器组件?
      • @Netero 我对你的问题感到困惑:] 接受的答案中的代码是有效的,它可以工作,我怀疑在 React 中包装比 HOC 模式更常见(引用需要)。因此,它是不错的代码,它只是一个错误的答案。包装有局限性,这就是人们可能需要使用 HOC 的时候。那时接受的答案将毫无价值。
      【解决方案4】:

      以下是一个将 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/>
      

      【讨论】:

        【解决方案5】:

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

        • 以真正的反应功能组件方式创建 HOC 是不可能的,因为建议不要在嵌套函数内调用钩子。

        不要在循环、条件或嵌套函数中调用 Hook。相反,在任何提前返回之前,始终在 React 函数的顶层使用 Hooks。通过遵循此规则,您可以确保每次渲染组件时都以相同的顺序调用 Hook。这就是允许 React 在多个 useState 和 useEffect 调用之间正确保留 Hooks 状态的原因。 (如果您好奇,我们将在下面深入解释。)

        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

        【讨论】:

          猜你喜欢
          • 2021-04-07
          • 2018-08-09
          • 1970-01-01
          • 2018-08-30
          • 1970-01-01
          • 2020-07-24
          • 1970-01-01
          • 2018-06-08
          • 1970-01-01
          相关资源
          最近更新 更多