[英]ReactJs, Typescript protected route with HOC as functional component
[英]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.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 的組合性質中出現的一種模式。
例如,您當然可以創建一個功能性無狀態組件,它接受組件作為輸入並返回一些其他組件作為輸出;
<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 的兩分錢
不要在循環、條件或嵌套函數中調用 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.