带有 React Hooks 的 HoC
Posted
技术标签:
【中文标题】带有 React Hooks 的 HoC【英文标题】:HoC with React Hooks 【发布时间】:2020-01-06 23:25:38 【问题描述】:我正在尝试使用Context API
从class component
移植到react hooks
,但我无法弄清楚收到错误的具体原因。
首先,我的代码:
// contexts/sample.jsx
import React, createContext, useState, useContext from 'react'
const SampleCtx = createContext()
const SampleProvider = (props) =>
const [ value, setValue ] = useState('Default Value')
const sampleContext = value, setValue
return (
<SampleCtx.Provider value=sampleContext>
props.children
</SampleCtx.Provider>
)
const useSample = (WrappedComponent) =>
const sampleCtx = useContext(SampleCtx)
return (
<SampleProvider>
<WrappedComponent
value=sampleCtx.value
setValue=sampleCtx.setValue />
</SampleProvider>
)
export
useSample
// Sends.jsx
import React, Component, useState, useEffect from 'react'
import useSample from '../contexts/sample.jsx'
const Sends = (props) =>
const [input, setInput ] = useState('')
const handleChange = (e) =>
setInput(e.target.value)
const handleSubmit = (e) =>
e.preventDefault()
props.setValue(input)
useEffect(() =>
setInput(props.value)
, props.value)
return (
<form onSubmit=handleSubmit>
<input value=input onChange=handleChange />
<button type="submit">Submit</button>
</form>
)
我得到的错误:
Invariant Violation:无效的钩子调用。钩子只能在函数组件的主体内部调用。这可能是由于以下原因之一: 1. 你可能有不匹配的 React 版本和渲染器(例如 React DOM) 2. 你可能违反了 Hooks 规则 3. 你可能有多个 React 副本相同的应用程序 请参阅https://reactjs.org/warnings/invalid-hook-call-warning.html 以获取有关如何调试和修复此问题的提示。
我的代码说明:
我使用Context API
来管理状态,之前我使用class component
s 来制作视图。我希望结构简单明了,不需要更多细节。
我认为它应该也可以工作,<Sends />
组件被传递到 useSample
HoC 函数中,并被 sample.jsx
的 <SampleProvider>
组件包裹,因此 <Sends />
可以使用 props
由SampleCtx
上下文提供。但结果是失败。
HoC
模式与React hooks
一起使用是否无效?还是通过props
将突变函数(即useState()
制作的setValue
)交给其他组件无效?或者,使用hooks
将2 个或更多function components
放在一个文件中是否无效?请指正具体是什么原因。
【问题讨论】:
@JosephD。我修正了错字,这是一个微不足道的错字。我没有使用<Consumer />
,因为我想改用useContext
。即使我使用了useContext
钩子,我是否必须使用<Consumer />
?
不。 useContext()
和 <Consumer />
是等价的。
【参考方案1】:
所以 HOC 和 Context 是不同的 React 概念。因此,让我们将其分为两部分。
提供者
提供者的主要职责是提供上下文值。上下文值通过useContext()
使用
const SampleCtx = createContext();
export const SampleProvider = props =>
const [value, setValue] = useState("Default Value");
const sampleContext = value, setValue ;
useEffect(() => console.log("Context Value: ", value)); // only log when value changes
return (
<SampleCtx.Provider value=sampleContext>
props.children
</SampleCtx.Provider>
);
;
HOC
消费者。使用 useContext()
钩子并添加额外的道具。返回一个新组件。
const withSample = WrappedComponent => props => // curry
const sampleCtx = useContext(SampleCtx);
return (
<WrappedComponent
...props
value=sampleCtx.value
setValue=sampleCtx.setValue
/>
);
;
然后使用 HOC:
export default withSample(Send)
组成提供者和消费者 (HOC),我们有:
import SampleProvider from "./provider";
import SampleHOCWithHooks from "./send";
import "./styles.css";
function App()
return (
<div className="App">
<SampleProvider>
<SampleHOCWithHooks />
</SampleProvider>
</div>
);
完整代码见Code Sandbox。
【讨论】:
eslint 会对 HOC 产生警告,因为在回调方法中不能调用钩子。React Hook "useContext" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function.eslint(react-hooks/rules-of-hooks)
【参考方案2】:
高阶组件是接受一个组件并返回另一个组件的函数,返回的组件可以是类组件、带有钩子的功能组件或者它可以没有状态逻辑。 在您的示例中,您从 useSample 返回 jsx。
const useSample = (WrappedComponent) =>
const sampleCtx = useContext(SampleCtx)
return ( // <-- here
<SampleProvider>
<WrappedComponent
value=sampleCtx.value
setValue=sampleCtx.setValue />
</SampleProvider>
)
如果你想制作一个 HOC,你可以这样做
const withSample = (WrappedComponent) =>
return props =>
const sampleCtx = useContext(SampleCtx)
<WrappedComponent
value=sampleCtx.value
setValue=sampleCtx.setValue ...props />
【讨论】:
我试过了,但在这种情况下,context
无法正确生成。 sampleCtx
是undefined
,这意味着useContext
不会正确生成context
。
另外,我们不是还要使用<SampleProvider />
吗?
provider 应该包装你想要访问上下文状态的应用程序的整个部分,你不需要将每个组件包装在一个 provider 中。例如,如果您希望这些数据在您的整个应用程序中可用,则使用提供程序包装您的根组件。以上是关于带有 React Hooks 的 HoC的主要内容,如果未能解决你的问题,请参考以下文章
带有回调的 React Hooks useState [重复]
等待带有 React Hooks 的 Redux Action
在 React Hooks 中使用带有样式组件的 CSS attr()
状态上不存在属性 - 使用带有 TypeScript 的 React Router Hooks
使用带有graphql的react hooks时,按钮元素中的onClick不会触发react-table中的重新渲染
React Hooks + Firebase Firestore onSnapshot - 正确使用带有反应钩子的 Firestore 监听器