【发布时间】:2020-01-11 02:58:15
【问题描述】:
我已经在 this 之后在我的 react 应用程序中创建了一个 HOC,并且它工作正常。但是我想知道是否有办法将 HOC 创建为功能组件(有或没有状态)???因为给定的示例是基于类的组件。
试图通过网络找到相同的内容,但什么也得不到。不确定那是否可能?还是该做对的事??
任何线索将不胜感激:)
【问题讨论】:
标签: javascript reactjs components higher-order-components
我已经在 this 之后在我的 react 应用程序中创建了一个 HOC,并且它工作正常。但是我想知道是否有办法将 HOC 创建为功能组件(有或没有状态)???因为给定的示例是基于类的组件。
试图通过网络找到相同的内容,但什么也得不到。不确定那是否可能?还是该做对的事??
任何线索将不胜感激:)
【问题讨论】:
标签: javascript reactjs components higher-order-components
例如,您当然可以创建一个功能性无状态组件,该组件接受组件作为输入并返回一些其他组件作为输出;
<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;
高阶组件不一定是类组件,它们的目的是根据某种逻辑将一个组件作为输入并返回一个组件作为输出。
【讨论】:
当然,您可以在 react 中创建功能性 HOC,您可以为此创建任何其他文件夹,例如“Utils”。例如,它是我在 Utils 文件夹中的 amountUtil.js 文件:
export const getFormattedAmount = (amount?: Amount) => (
金额 && ${amount.formattedAmount} ${amount.currency}
);
【讨论】:
我同意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 调用和成功/错误消息),所以我想要一个可重用的<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 的组合特性中出现的一种模式。
【讨论】:
以下是一个将 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 函数的顶层使用 Hooks。通过遵循此规则,您可以确保每次渲染组件时都以相同的顺序调用 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>
);
}
即使代码在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>
);
}
【讨论】: