在我的react应用程序中,我有一些复杂的组件树。
在这个组件树中,我有一个带有按钮的<Footer/>
组件。在树的其他地方也有<SomeComponent/>
分量。这个组件实际上是从加载的一些动态代码并且并不总是相同的(类似于某些小部件引擎,其中容器由应用程序引擎处理,内容是动态加载的)。这意味着上下文不知道什么是真正的组件。
为了插入其他所有内容,我有一个自定义react上下文,其中包含一些字段和方法,这是通过自定义useMyContext
钩子暴露的。
这工作得很好,除了一个问题:
在我的<Footer />
我有一个按钮,应该调用<SomeComponent/>
组件内的东西。作为一个例子,我可能有一个"刷新"按钮,应该要求组件获取最新的数据。
基本上我有这个反应树:
- 应用程序
- SomeContextProvider
- 页脚
- RefreshButton
- 深/嵌套/组件/结构
- SomeComponent
- (包含刷新功能)
- SomeComponent
- 页脚
- SomeContextProvider
如何从页脚调用组件中的刷新函数?
我试着玩转发refs和useImperativeHandler钩子,这可能工作,但组件树的深度嵌套导致转发参考的大混乱。
我还尝试扩展上下文提供程序,但没有找到"反向"的方法。回调(上下文可以对刷新按钮动作作出反应,但我不能在组件树的兄弟分支中对此作出反应)。
我该如何处理?
PS:如果它的关系,我使用react 16.13.1和typescript 4.5
我想我有一个干净的解决方案的开始。
基本上,我可以通过实现一个订阅/取消订阅模式来处理我的场景。
通过这种方式,我可以从外部上下文发出某种事件,并让树中的组件根据需要订阅和处理这些事件。
有的repro: https://codesandbox.io/s/infallible-chaplygin-79c3gq?file=/src/App.tsx.
以下相关部分:
自定义反应上下文
type Subscribe = (cb: () => void) => () => void;
type AppContextData = {
subscribe: Subscribe;
onSubmit: () => void;
};
const AppContext = createContext<AppContextData | undefined>(undefined);
const useAppContext = (): AppContextData => {
const context = useContext(AppContext);
if (!context)
throw new Error(`useAppContext must be used within a AppContextProvider`);
return context;
};
容器组件const AppContextProvider: React.FC<PropsWithChildren<{}>> = ({ children }) => {
const subscribtions: (() => void)[] = [];
const subscribe: Subscribe = (cb) => {
subscribtions.push(cb);
return () => {
subscribtions.splice(subscribtions.indexOf(cb), 1);
};
};
const emitSubmit = () => {
subscribtions.forEach((cb) => cb());
};
const appContext: AppContextData = {
subscribe,
onSubmit: emitSubmit
};
return (
<AppContext.Provider value={appContext}>{children}</AppContext.Provider>
);
};
应用
export default function App() {
return (
<AppContextProvider>
<div className="App">
<Main />
<Footer />
</div>
</AppContextProvider>
);
}
最后,订阅和提交触发器:
带有按钮的组件
const Footer: React.VFC = () => {
const { onSubmit } = useAppContext();
return <button onClick={onSubmit}>Submit</button>;
};
订阅(和取消订阅)的组件
const Main: React.VFC = () => {
const [myString, setMyString] = useState("initial");
const context = useAppContext();
useEffect(() => {
return context.subscribe(() => setMyString("from context"));
}, [context]);
return <p>{myString}</p>;
};