我正在使用React Context构建一个简单的Toast通知系统。这是一个简化但可以正常运行的示例的链接,该示例显示了问题https://codesandbox.io/s/currying-dust-kw00n。
我的页面组件包装在HOC中,使我能够以编程方式在此页面内添加,删除和删除所有Toast。该演示有一个按钮,用于添加祝酒通知和一个按钮,用于更改activeStep
(假设这是一个多步骤表单)。当activeStep更改时,我希望所有烤面包都被移除。
最初,我是使用以下方法完成的...
useEffect(() => {
toastManager.removeAll();
}, [activeStep]);
...这按我的预期工作,但是有一个react-hooks / exhaustive-deps ESLint警告,因为toastManager不在依赖项数组中。将toastManager添加到数组中后,会导致敬酒一经添加就被删除。
我以为我可以使用useCallback
...
const stableToastManager = useCallback(toastManager, []);
useEffect(() => {
stableToastManager.removeAll();
}, [activeStep, stableToastManager]);
...但是,这不仅行不通,而且我希望从源头上解决该问题,所以我不需要每次都想要这种功能时就这样做,因为它很可能会用于很多地方。
这就是我被困住的地方。我不确定如何更改上下文,这样就不需要在HOC封装的组件中添加其他逻辑。
export const ToastProvider = ({ children }) => {
const [toasts, setToasts] = useState([]);
const add = (content, options) => {
// We use the content as the id as it prevents the same toast
// being added multiple times
const toast = { content, id: content, ...options };
setToasts([...toasts, toast]);
};
const remove = id => {
const newToasts = toasts.filter(t => t.id !== id);
setToasts(newToasts);
};
const removeAll = () => {
if (toasts.length > 0) {
setToasts([]);
}
};
return (
<ToastContext.Provider value={{ add, remove, removeAll }}>
{children}
<div
style={{
position: `fixed`,
top: `10px`,
right: `10px`,
display: `flex`,
flexDirection: `column`
}}
>
{toasts.map(({ content, id, ...rest }) => {
return (
<button onClick={() => remove(id)} {...rest}>
{content}
</button>
);
})}
</div>
</ToastContext.Provider>
);
};
export const withToastManager = Component => props => {
return (
<ToastContext.Consumer>
{context => {
return <Component toastManager={context} {...props} />;
}}
</ToastContext.Consumer>
);
};
答案 0 :(得分:1)
如果要“从核心修复”,则需要修复ToastProvider
:
const add = useCallback((content, options) => {
const toast = { content, id: content, ...options };
setToasts(pToasts => [...pToasts, toast]);
}, []);
const remove = useCallback(id => {
setToasts(p => p.filter(t => t.id !== id));
}, []);
const removeAll = useCallback(() => {
setToasts(p => (p.length > 0 ? [] : p));
}, []);
const store = useMemo(() => ({ add, remove, removeAll }), [
add,
remove,
removeAll
]);
然后,useEffect
将按预期工作,因为问题在于您需要在每个渲染中将ToastProvider
功能重新初始化为单例时。
useEffect(() => {
toastManager.removeAll();
}, [activeStep, toastManager]);
此外,我建议添加自定义钩子功能作为默认用例,并仅为类组件提供包装器。
换句话说,请勿在功能组件上使用包装器(withToastManager
),将其用于类,因为它被认为是反模式,因此您得到了useContext
,因此您的库应暴露它。
// @ toastContext.js
export const useToast = () => {
const context = useContext(ToastContext);
return context;
};
// @ page.js
import React, { useState, useEffect } from 'react';
import { useToast } from './toastContext';
const Page = () => {
const [activeStep, setActiveStep] = useState(1);
const { removeAll, add } = useToast();
useEffect(() => {
removeAll();
}, [activeStep, removeAll]);
return (
<div>
<h1>Page {activeStep}</h1>
<button
onClick={() => {
add(`Toast at ${Date.now()}!`);
}}
>
Add Toast
</button>
<button
onClick={() => {
setActiveStep(activeStep + 1);
}}
>
Change Step
</button>
</div>
);
};
export default Page;