React中Algolia Insights中间件发送重复事件问题咨询
Algolia Insights中间件触发重复事件的原因及解决
我是React新手,开发了一个Algolia Insights中间件组件用于向Algolia发送事件。但页面加载时Algolia收到重复事件,匿名用户和授权用户的浏览事件均重复两次,请问该组件为何会触发重复事件?
组件代码
import aa from 'search-insights'; import { createInsightsMiddleware } from 'instantsearch.js/es/middlewares'; import { useInstantSearch } from 'react-instantsearch-hooks-web'; import { useLayoutEffect } from 'react'; import { useQuery } from '@tanstack/react-query'; var userToken = '000000'; export function InsightsMiddleware() { const { use } = useInstantSearch(); const token = useQuery('function to get user token'); if (token.data !== undefined && token.data != null) { userToken = token.data?.userToken; } useLayoutEffect(() => { const middleware = createInsightsMiddleware({ insightsClient: aa, insightsInitParams: { useCookie: true }, }); aa('setUserToken', userToken); return use(middleware); }, [use, token.data?.userToken]); return null; }
组件使用位置
<div className={styles.wrapper}> <InstantSearchSSRProvider {...serverState}> <InstantSearch searchClient={searchClient} indexName={indexName} routing={{ router: history({ getLocation: () => typeof window === 'undefined' ? new URL(url) : window.location, }), }} > <InsightsMiddleware /> <Container fluid="lg"> <div className="mb-3"> <SearchBox /> </div> <CurrentRefinements /> <Hits /> </Container> </InstantSearch> </InstantSearchSSRProvider> </div>
重复事件的核心原因
- 中间件重复注册未清理:
useLayoutEffect的依赖包含token.data?.userToken,当用户token从初始值000000更新为实际值时,effect会重新执行,创建新的中间件并调用use(middleware)注册。但旧的中间件没有被卸载,导致两个实例同时运行,各自触发事件。 - 全局变量的闭包副作用:全局变量
userToken被修改后,旧中间件的闭包仍然持有该变量引用,可能导致重复设置用户token并触发事件。 - SSR hydration的重复执行:
useLayoutEffect在SSR渲染和客户端hydration阶段都会执行,可能导致中间件被初始化两次。
解决办法
1. 正确清理旧中间件
use(middleware)返回的函数用于卸载中间件,在effect重新执行前先清理旧实例:
useLayoutEffect(() => { const middleware = createInsightsMiddleware({ insightsClient: aa, insightsInitParams: { useCookie: true }, }); const cleanup = use(middleware); aa('setUserToken', token.data?.userToken || '000000'); // 清理时卸载旧中间件 return () => cleanup(); }, [use, token.data?.userToken]);
2. 移除全局变量,避免闭包问题
直接在effect内使用token.data的值,不要依赖全局变量:
export function InsightsMiddleware() { const { use } = useInstantSearch(); const token = useQuery('function to get user token'); useLayoutEffect(() => { const currentToken = token.data?.userToken || '000000'; const middleware = createInsightsMiddleware({ insightsClient: aa, insightsInitParams: { useCookie: true }, }); const cleanup = use(middleware); aa('setUserToken', currentToken); return () => cleanup(); }, [use, token.data?.userToken]); return null; }
3. 替换useLayoutEffect为useEffect(针对SSR场景)
useLayoutEffect会在SSR和客户端hydration各执行一次,换成useEffect可避免SSR阶段的额外初始化:
// 替换useLayoutEffect为useEffect useEffect(() => { const currentToken = token.data?.userToken || '000000'; const middleware = createInsightsMiddleware({ insightsClient: aa, insightsInitParams: { useCookie: true }, }); const cleanup = use(middleware); aa('setUserToken', currentToken); return () => cleanup(); }, [use, token.data?.userToken]);
4. 避免token变化时重建中间件(可选优化)
如果不需要在token更新时重建中间件,单独处理token更新逻辑:
useLayoutEffect(() => { const middleware = createInsightsMiddleware({ insightsClient: aa, insightsInitParams: { useCookie: true }, }); const cleanupMiddleware = use(middleware); // 单独处理token更新 const updateUserToken = () => { const currentToken = token.data?.userToken || '000000'; aa('setUserToken', currentToken); }; updateUserToken(); // 监听token查询的变化 const unsubscribe = token.subscribe(updateUserToken); return () => { cleanupMiddleware(); unsubscribe?.(); }; }, [use]);
内容的提问来源于stack exchange,提问作者JPil
相关产品推荐
相关产品推荐

