簡體   English   中英

HOC - 功能組件

[英]HOC - Functional Component

我已經在我的反應應用程序中創建一個 HOC,並且它工作正常。 但是我想知道是否有辦法將 HOC 創建為功能組件(有或沒有狀態)??? 因為給定的示例是基於 class 的組件。

試圖在 web 上找到相同的東西,但什么也得不到。 不確定那是否可能? 還是永遠做對的事??

任何線索將不勝感激:)

我同意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 />組件,它將為其子組件提供所有必要的功能<WithLogin />調用和成功/錯誤消息。 這是執行此操作的一種方法:

// 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.ProviderMyComponent內部實例化,因此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 組件,該組件接受一個 Component 作為 prop 值,並根據用戶是否經過身份驗證返回一些其他 Component。
  2. 如果用戶未通過身份驗證(從上下文存儲中讀取它),則使用<Redirect to='/login'/>將用戶重定向到登錄頁面,否則返回作為道具傳遞的組件並將其他道具發送到該組件<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。

不要在循環、條件或嵌套函數中調用 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>
  );
}

代碼沙盒

我認為對於功能組件,這很好用

import {useEffect, useState} from 'react';

// Target Component
function Clock({ time }) {
  return <h1>{time}</h1>
}

// HOC 
function app(C) {
  return (props) => {
    const [time, setTime] = useState(new Date().toUTCString());
    useEffect(() => {
      setTimeout(() => setTime(new Date().toUTCString()), 1000);
    })
    return <C {...props} time={time}/>
  }
}

export default app(Clock);

你可以在這里測試它: https://codesandbox.io/s/hoc-s6kmnv

對的,這是可能的

 import React, { useState } from 'react'; const WrapperCounter = OldComponent =>{ function WrapperCounter(props){ const[count,SetCount] = useState(0) const incrementCounter = ()=>{ SetCount(count+1) } return(<OldComponent {...props} count={count} incrementCounter={incrementCounter}></OldComponent>) } return WrapperCounter } export default WrapperCounter

 import React from 'react'; import WrapperCounter from './WrapperCounter'; function CounterFn({count,incrementCounter}){ return( <button onClick={incrementCounter}>Counter inside functiona component {count}</button> ) } export default WrapperCounter(CounterFn)

當然,您可以在 react 中創建功能性 HOC,也可以為此創建任何其他文件夾,例如“Utils”。 例如,這是我在 Utils 文件夾中的 amountUtil.js 文件:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM