React GraphQL:能否用switch语句切换多个useLazyQuery?
在React GraphQL中切换多个useLazyQuery的可行方案
首先明确:你不能像代码示例那样在switch语句(或其他条件判断、嵌套函数)里调用useLazyQuery——这违反了React Hooks的核心规则:必须在组件的顶层作用域调用Hooks,不能在条件分支、循环、嵌套函数内调用。React需要依赖Hooks的调用顺序来维护状态,条件内调用会破坏这个顺序,导致不可预测的bug。
不过你想要的「用同一逻辑切换不同查询、避免拆分组件」的需求完全可以实现,下面是两种靠谱的方案:
方案1:提前声明所有useLazyQuery,按类型映射选择
先在组件顶层声明所有需要的useLazyQuery,再通过对象映射或switch,根据当前listType获取对应的查询函数和状态。
import { useLazyQuery } from '@apollo/client'; // 导入四个查询文档 import LIST_ONE_QUERY from './queries/ListOne.graphql'; import LIST_TWO_QUERY from './queries/ListTwo.graphql'; import LIST_THREE_QUERY from './queries/ListThree.graphql'; import LIST_FOUR_QUERY from './queries/ListFour.graphql'; function TabbedList() { const [listType, setListType] = useState("List One"); // 提前声明所有useLazyQuery(遵守Hooks规则) const [getListOne, { data: dataOne, loading: loadingOne, error: errorOne, refetch: refetchOne }] = useLazyQuery(LIST_ONE_QUERY); const [getListTwo, { data: dataTwo, loading: loadingTwo, error: errorTwo, refetch: refetchTwo }] = useLazyQuery(LIST_TWO_QUERY); const [getListThree, { data: dataThree, loading: loadingThree, error: errorThree, refetch: refetchThree }] = useLazyQuery(LIST_THREE_QUERY); const [getListFour, { data: dataFour, loading: loadingFour, error: errorFour, refetch: refetchFour }] = useLazyQuery(LIST_FOUR_QUERY); // 用对象映射关联类型与查询资源 const queryMap = { "List One": { getData: getListOne, data: dataOne, loading: loadingOne, error: errorOne, refetch: refetchOne }, "List Two": { getData: getListTwo, data: dataTwo, loading: loadingTwo, error: errorTwo, refetch: refetchTwo }, "List Three": { getData: getListThree, data: dataThree, loading: loadingThree, error: errorThree, refetch: refetchThree }, "List Four": { getData: getListFour, data: dataFour, loading: loadingFour, error: errorFour, refetch: refetchFour } }; // 根据当前类型获取对应的查询函数和状态 const { getData, data, loading, error, refetch } = queryMap[listType] || queryMap["List One"]; // 标签点击逻辑:切换类型并触发对应查询 const handleTabClick = (type) => { setListType(type); queryMap[type].getData({ variables: {} }); }; return ( <div> {/* 标签页区域 */} <div className="tabs"> <button onClick={() => handleTabClick("List One")}>列表1</button> <button onClick={() => handleTabClick("List Two")}>列表2</button> <button onClick={() => handleTabClick("List Three")}>列表3</button> <button onClick={() => handleTabClick("List Four")}>列表4</button> </div> {/* 统一数据展示 */} {loading && <div>加载中...</div>} {error && <div>出错了:{error.message}</div>} {data && ( <table> <thead> <tr> <th>列1</th> <th>列2</th> {/* 其他统一列 */} </tr> </thead> <tbody> {/* 假设所有查询返回的列表字段一致,比如data.items */} {data.items.map(item => ( <tr key={item.id}> <td>{item.field1}</td> <td>{item.field2}</td> </tr> ))} </tbody> </table> )} </div> ); }
方案2:单个useLazyQuery动态传入查询文档
如果四个查询的返回数据结构完全一致,只是查询语句不同,可以把查询文档存在映射对象里,根据listType动态选择,只用一个useLazyQuery即可。
import { useLazyQuery } from '@apollo/client'; import LIST_ONE_QUERY from './queries/ListOne.graphql'; import LIST_TWO_QUERY from './queries/ListTwo.graphql'; import LIST_THREE_QUERY from './queries/ListThree.graphql'; import LIST_FOUR_QUERY from './queries/ListFour.graphql'; function TabbedList() { const [listType, setListType] = useState("List One"); // 映射类型与查询文档 const queryDocMap = { "List One": LIST_ONE_QUERY, "List Two": LIST_TWO_QUERY, "List Three": LIST_THREE_QUERY, "List Four": LIST_FOUR_QUERY }; // 初始化useLazyQuery,用当前类型对应的查询文档 const [getData, { data, loading, error, refetch }] = useLazyQuery(queryDocMap[listType]); // 标签点击逻辑:切换类型并传入新的查询文档触发查询 const handleTabClick = (type) => { setListType(type); getData({ query: queryDocMap[type], variables: {} }); }; return ( {/* 标签页和数据展示逻辑与方案1一致 */} <div> <div className="tabs"> <button onClick={() => handleTabClick("List One")}>列表1</button> <button onClick={() => handleTabClick("List Two")}>列表2</button> <button onClick={() => handleTabClick("List Three")}>列表3</button> <button onClick={() => handleTabClick("List Four")}>列表4</button> </div> {loading && <div>加载中...</div>} {error && <div>出错了:{error.message}</div>} {data && ( <table> <thead> <tr> <th>列1</th> <th>列2</th> </tr> </thead> <tbody> {data.items.map(item => ( <tr key={item.id}> <td>{item.field1}</td> <td>{item.field2}</td> </tr> ))} </tbody> </table> )} </div> ); }
方案对比
- 方案1的优势是每个查询的状态独立,切换标签时不会丢失之前的查询结果(若需要保留历史数据);
- 方案2更简洁,但切换标签后之前的
data会被新查询覆盖,适合不需要保留历史数据的场景; - 若查询变量需要随
listType变化,只需在调用getData时传入对应variables即可。
内容的提问来源于stack exchange,提问作者Danny Thomas
相关产品推荐
相关产品推荐

