基于Apollo与GraphQL的跨组件通信解决方案问询
哈哈,这个跨组件操作触发的问题我之前做表单向导时也碰到过!刚好可以结合Apollo和React本身的方案给你梳理清楚:
首先要说明下,现在apollo-link-state已经整合到@apollo/client的本地状态管理里了,不用单独安装包。针对你的场景,有两种比较顺畅的实现方式:
绑定表单状态与提交逻辑到Apollo本地缓存
给每个FormContainer分配唯一标识(比如stepId),在组件内部用useMutation定义好提交当前表单的GraphQL mutation,同时把表单输入状态存在Apollo的本地缓存中。当NavigationContainer需要触发提交时,可以通过查询本地缓存拿到当前表单的提交mutation并调用。注册提交方法到Apollo本地状态
你可以在初始化Apollo Client时,定义一个用于注册提交方法的local mutation。每个FormContainer挂载时,把自己的提交函数注册到Apollo缓存里;NavigationContainer则通过查询local state获取当前激活表单的提交函数,点击「下一步」时直接执行。给你贴个简化的代码示例:
// 初始化Apollo Client,配置local state resolvers const client = new ApolloClient({ uri: '/your-graphql-endpoint', cache: new InMemoryCache(), resolvers: { Mutation: { setCurrentSubmit: (_, { handler }, { cache }) => { cache.writeData({ data: { currentSubmitHandler: handler } }); return null; }, }, Query: { getCurrentSubmit: (_, __, { cache }) => { const { currentSubmitHandler } = cache.readQuery({ query: gql`query GetCurrentSubmit { currentSubmitHandler }` }); return currentSubmitHandler; }, }, }, }); // FormContainer组件:注册提交方法 const Step1Form = () => { const [submitStep1] = useMutation(SUBMIT_STEP1_MUTATION); const [setCurrentSubmit] = useMutation(gql`mutation SetCurrentSubmit($handler: String) { setCurrentSubmit(handler: $handler) }`); useEffect(() => { // 挂载时注册提交函数 setCurrentSubmit({ variables: { handler: submitStep1 } }); return () => { // 卸载时清除 setCurrentSubmit({ variables: { handler: null } }); }; }, [submitStep1, setCurrentSubmit]); // 表单渲染逻辑... return <form>{/* 表单输入项 */}</form>; }; // NavigationContainer组件:调用提交方法 const Navigation = () => { const { data } = useQuery(gql`query GetCurrentSubmit { currentSubmitHandler }`); const handleNext = async () => { if (data?.currentSubmitHandler) { await data.currentSubmitHandler(); // 提交成功后切换步骤的逻辑 } }; return <button onClick={handleNext}>下一步</button>; };
如果不想用Apollo来处理,React自身也有成熟的方案解决这种跨组件通信问题:
React Context 方案
这是最常用的无外部依赖方案:创建一个专门的WizardContext,把当前表单的提交方法、步骤状态等存入Context。FormContainer挂载时将自己的提交方法注入Context,NavigationContainer从Context中取出方法并触发。完全基于React原生特性,没有额外依赖。示例代码:
import { createContext, useContext, useState, useEffect } from 'react'; // 创建向导上下文 const WizardContext = createContext({ currentSubmit: null, setCurrentSubmit: () => {}, }); // 上下文Provider组件 const WizardProvider = ({ children }) => { const [currentSubmit, setCurrentSubmit] = useState(null); return ( <WizardContext.Provider value={{ currentSubmit, setCurrentSubmit }}> {children} </WizardContext.Provider> ); }; // 外层容器组件 const WizardContainer = () => { return ( <WizardProvider> <Step1Form /> <Navigation /> </WizardProvider> ); }; // 表单组件 const Step1Form = () => { const { setCurrentSubmit } = useContext(WizardContext); const submitForm = async () => { // 执行GraphQL mutation提交逻辑 await fetch('/graphql', { method: 'POST', body: JSON.stringify({ query: YOUR_SUBMIT_MUTATION }), }); }; useEffect(() => { setCurrentSubmit(submitForm); return () => setCurrentSubmit(null); }, []); return <form>{/* 表单内容 */}</form>; }; // 导航组件 const Navigation = () => { const { currentSubmit } = useContext(WizardContext); const handleNext = () => { currentSubmit?.(); }; return <button onClick={handleNext}>下一步</button>; };回调函数传递(适合层级较浅的场景)
如果你的组件层级不深,可以直接把FormContainer的提交方法通过props传递给NavigationContainer。但这种方式在多步骤向导这种层级可能复杂的场景里,容易出现「props drilling」(属性层层传递)的问题,维护起来比较麻烦,所以不太推荐。
另外你提到Redux无法解决这个问题,其实是因为Redux的核心是管理可序列化的状态,而提交操作是一个函数,Redux并不适合存储和共享函数(违反了Redux的纯函数和状态可序列化原则)。所以Redux确实不是这个场景的最佳选择。
内容的提问来源于stack exchange,提问作者dre-hh

