ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Relay `loadEntryPoint` 权威指南:以 “render-as-you-fetch“ 模式命令式预加载 EntryPoint 数据

Relay `loadEntryPoint` 权威指南:以 “render-as-you-fetch“ 模式命令式预加载 EntryPoint 数据 RelayloadEntryPoint权威指南以 render-as-you-fetch 模式命令式预加载 EntryPoint 数据【免费下载链接】relayRelay is a JavaScript framework for building>项目地址: https://gitcode.com/gh_mirrors/relay29/relayloadEntryPoint是 React Relay 提供的一个命令式 API用于在 React 渲染树之外主动加载一个 EntryPoint 及其关联查询的数据是实现 render-as-you-fetch边渲染边取数模式的关键入口。本文将以当前仓库relayRelay v19为基准完整讲解loadEntryPoint的参数、返回结构、底层行为、与EntryPointContainer、useEntryPointLoader的配合方式并结合 loadEntryPoint.js 与 loadQuery.js 的源码实现带你从 API 用法深入到数据预加载、store 保留与垃圾回收的底层机制最终掌握如何在真实应用中安全地预加载 EntryPoint 数据而不产生内存泄漏。一、loadEntryPoint是什么命令式预加载的入口官方 API 文档将loadEntryPoint定义为与EntryPointContainer配合、实现 render-as-you-fetch 模式的函数。它解决的核心问题是把发起数据请求这一动作从组件渲染中剥离出来提前到用户交互如点击、路由跳转发生时从而避免瀑布式请求waterfall round trips让内容更早呈现在用户面前。与 React 组件内渲染时才发请求的方式不同loadEntryPoint可以在任意时机事件回调、effect、路由守卫等被调用它接受一个 EntryPoint 和对应的参数立即开始加载该 EntryPoint 的组件代码通过root这个JSResourceReference以及其关联的所有查询数据。从仓库源码来看loadEntryPoint的实际定义位于 packages/react-relay/relay-hooks/loadEntryPoint.jsfunction loadEntryPoint( environmentProvider, entryPoint, entryPointParams, ): PreloadedEntryPointTEntryPointComponent { // 1. 按需开始加载 EntryPoint 的组件代码 let loadingPromise null; if (entryPoint.root.getModuleIfRequired() null) { loadingPromise entryPoint.root.load(); } // 2. 调用 getPreloadProps 获取预加载描述 const preloadProps entryPoint.getPreloadProps(entryPointParams); const {queries, entryPoints, extraProps} preloadProps; // 3. 为每个查询调用 loadQuery为每个嵌套 EntryPoint 递归调用 loadEntryPoint ... }可以看到一次loadEntryPoint调用内部会做三件事加载组件代码、解构getPreloadProps的返回值、为所有声明的查询与嵌套 EntryPoint 启动预加载。基本用法示例以下示例来自官方 API 文档load-entrypoint.md展示了最基本的调用方式const EntryPoint require(MyComponent.entrypoint.js); const {loadQuery} require(react-relay); // 通常组件应当从 React context 中获取 environment // 并把这个 environment 传给本函数。 const getEntrypointReference environment loadEntryPoint( { getEnvironment: () environment }, EntryPoint, {id: 4}, ); // 之后把 entryPointReference 传给 EntryPointContainer // 注意EntryPoint reference 应该被调用 .dispose() // 该示例中省略了这一步。要点第一个参数是environmentProvider一个提供了getEnvironment方法的对象第二个参数是.entrypoint.js文件导出的 EntryPoint第三个参数是entryPointParams会被原样传给 EntryPoint 的getPreloadProps方法。二、参数详解environmentProvider一个用于提供 Relay Environment 实例的 Provider其类型为IEnvironmentProvider。源码中定义如下见 EntryPointTypes.flow.jsexport type IEnvironmentProviderTOptions Readonly{ getEnvironment: (options: ?TOptions) IEnvironment, };如果你是在 React 组件内发起请求通常应当使用useRelayEnvironment获取到的 environment再包装成{ getEnvironment: () environment }传入。从源码看loadEntryPoint内部为每个查询调用loadQuery时会通过environmentProvider.getEnvironment(environmentProviderOptions)获取对应的 environment见 loadEntryPoint.js这意味着不同查询可以通过environmentProviderOptions使用不同的 environment如多 actor 环境。EntryPoint要加载的 EntryPoint 对象通常由require(Component.entrypoint.js)获得。其类型定义EntryPointTypes.flow.js要求必须包含两个字段export type EntryPoint in TEntryPointParams, out TEntryPointComponent extends EntryPointComponentany, any, any, any, any, Readonly{ getPreloadProps: (entryPointParams: TEntryPointParams) PreloadProps..., root: JSResourceReferenceTEntryPointComponent, };getPreloadProps(...)接收entryPointParams返回预加载描述queries、entryPoints、extraPropsroot指向负责渲染这些预加载数据的根组件的JSResourceReference。entryPointParams一个参数对象会原样传递给 EntryPoint 的getPreloadProps方法。它通常包含路由参数、查询变量等执行预加载所需的全部信息。其具体结构由各个 EntryPoint 自行定义并通过 Flow 类型参数TEntryPointParams约束。三、返回值的完整结构loadEntryPoint返回一个EntryPoint reference源码中的完整类型为PreloadedEntryPointEntryPointTypes.flow.jsexport type PreloadedEntryPointTEntryPointComponent Readonly{ dispose: () void, entryPoints: ElementConfigTEntryPointComponent[entryPoints], extraProps: ElementConfigTEntryPointComponent[extraProps], getComponent: () TEntryPointComponent, isDisposed: boolean, queries: ElementConfigTEntryPointComponent[queries], rootModuleID: string, };官方文档明确强调返回值的具体格式是不稳定的、极有可能变化的强烈建议不要依赖除dispose之外的任何其他属性否则在升级到未来版本时极容易出错。正确的做法是把loadEntryPoint()的结果整体交给EntryPointContainer渲染。其中唯一被文档明确保证、且你应该手动关注的是dispose一个方法调用后会释放该 EntryPoint 加载的所有查询引用包括通过其他嵌套 EntryPoint 间接加载的使这些查询引用不再被 store 保留其引用的数据便可能被垃圾回收。从源码实现看loadEntryPoint.jsdispose做了以下工作let isDisposed false; return { dispose() { if (isDisposed) { return; } if (preloadedQueries ! null) { Object.values(preloadedQueries).forEach(({dispose: innerDispose}) { innerDispose(); }); } if (preloadedEntryPoints ! null) { Object.values(preloadedEntryPoints).forEach(({dispose: innerDispose}) { innerDispose(); }); } isDisposed true; }, ... };即递归地对每个预加载查询调用loadQuery返回引用的dispose()对每个嵌套 EntryPoint 调用其loadEntryPoint返回引用的dispose()并用isDisposed标志保证幂等性。四、Flow 类型参数loadEntryPoint是一组泛型函数其类型参数在官方文档中有明确说明load-entrypoint.md类型参数含义TEntryPointParams对应 EntryPoint 的getPreloadProps方法第一个参数的类型TPreloadedQueries传给 EntryPoint 组件的queries参数的类型TPreloadedEntryPoints传给 EntryPoint 组件的entrypoints参数的类型TRuntimeProps传给EntryPointContainer的propsprop 的类型该对象会作为props传给 EntryPoint 组件TExtraProps若 EntryPoint 的getPreloadProps返回了extraProps属性这些额外 props 会作为extraProps传给 EntryPoint 组件TEntryPointComponentEntryPoint 组件的类型TEntryPointEntryPoint 的类型这些类型参数在源码函数签名中一一对应loadEntryPoint.js并且彼此之间有严格的约束关系TEntryPointComponent必须是一个EntryPointComponentTPreloadedQueries, TPreloadedEntryPoints, TRuntimeProps, TExtraProps而TEntryPoint必须是EntryPointTEntryPointParams, TEntryPointComponent。五、底层行为加载查询数据与 AST、写入 store官方文档给出了三个关键行为下面结合源码逐一深入。1. 查询数据与查询 AST 都会加载就绪后写入 store当调用loadEntryPoint()时EntryPoint 关联的每个查询如果有的话都会加载其查询数据和查询 AST。当查询 AST 与数据都可用之后数据才会被写入 store。这与prepareEntryPoint_DEPRECATED的行为不同——后者只有在查询被usePreloadedQuery渲染时才会把关联查询的数据写入 store。这条行为在源码中的体现位于 loadEntryPoint.js它为每个查询构造参数对象并调用loadQuerypreloadedQueries[queryPropName] loadQuery( environment, parameters, variables, { __nameForWarning: loadEntryPoint, fetchPolicy: options?.fetchPolicy, networkCacheConfig: options?.networkCacheConfig, }, environmentProviderOptions, );而loadQuery见 loadQuery.js正是实现AST 与数据就绪后写 store的核心对于PreloadableConcreteRequest类型的可预加载查询若查询 AST 尚未注册它会立即发起网络请求并通过PreloadableQueryRegistry.onLoad(queryId, callback)注册回调等待 AST 加载完成后才用createOperationDescriptor创建 operation、environment.retain(operation)保留数据并执行写入loadQuery.js网络请求本身通过fetchQueryDeduped以(environment, identifier)维度去重保证同一查询不会重复发起多个原始网络请求loadQuery.js。loadQuery还有一个值得注意的实现细节每次调用都会自增一个内部fetchKey从100001开始确保每个新创建的查询引用都会被usePreloadedQuery独立求值不会错误地复用 Suspense 缓存结果loadQuery.js。2. 查询引用被 store 保留.dispose()后才可能被垃圾回收EntryPoint reference 关联的查询引用会被 Relay store 保留retain防止数据被垃圾回收。只有当你调用.dispose()之后关联查询的数据才可能被垃圾回收。这在源码中体现为两层 retainloadQuery内部通过environment.retain(operation)保留 operation 数据loadQuery.jsdispose()时调用releaseQuery()释放loadEntryPoint的dispose()再递归释放所有子查询与子 EntryPoint 的引用loadEntryPoint.js。因此在文档中明确警告由loadEntryPoint返回的 EntryPoint reference 会向 Relay store 泄漏数据如果它有关联查询除非在不再引用时对其调用.dispose()。因此文档建议只要可能优先使用useEntryPointLoader因为它会保证正确为你 dispose EntryPoint reference。3. 在 React 渲染阶段调用会抛错loadEntryPoint如果在 React 的 render 阶段被调用可能抛出错误。这条约束同样适用于useEntryPointLoader返回的loadEntryPoint回调见 use-entrypoint-loader.md。正确的使用时机是事件处理器、useEffect等非渲染阶段。六、配合EntryPointContainer消费预加载结果loadEntryPoint返回的 reference 应当交给EntryPointContainer渲染。EntryPointContainer的定义见 EntryPointContainer.react.js其组件签名来自官方文档 entrypoint-container.mdfunction EntryPointContainer({ entryPointReference, props, }: { entryPointReference: PreloadedEntryPointTEntryPointComponent, props: TRuntimeProps, }): ReactElemententryPointReferenceloadEntryPoint的返回值或useEntryPointLoader提供的引用props额外的运行时 props会传给 EntryPoint 组件。从源码看EntryPointContainer会检查entryPointReference.isDisposed并给出 warning——如果引用已被 dispose 就不应再渲染EntryPointContainer.react.js未来这会成为硬错误调用getComponent()获取根组件首次调用时若代码尚未加载完成会抛出 loading promise 触发 Suspense 降级并对 ES module 的{default: module}包装做解包处理loadEntryPoint.js将queries、entryPoints、extraProps、props透传给根组件EntryPointContainer.react.js。// 典型用法 Suspense fallbackLoading... EntryPointContainer entryPointReference{entryPointReference} props{{}} / /Suspense七、更安全的替代方案useEntryPointLoader因为手动管理dispose()很容易遗漏官方文档强烈推荐优先使用useEntryPointLoaderHook。它会让一个 EntryPoint reference 保持在 state 中并在它不再可访问时将其 dispose从根本上避免数据泄漏。useEntryPointLoader的用法示例来自 use-entrypoint-loader.mdconst {useEntryPointLoader, EntryPointContainer} require(react-relay); const ComponentEntryPoint require(Component.entrypoint); function EntryPointRevealer() { const environmentProvider useMyEnvironmentProvider(); const [ entryPointReference, loadEntryPoint, disposeEntryPoint, ] useEntryPointLoader(environmentProvider, ComponentEntryPoint); return ( { entryPointReference null ( Button onClick{() loadEntryPoint({})} Click to reveal the contents of the EntryPoint /Button ) } { entryPointReference ! null ( Button onClick{disposeEntryPoint} Click to hide and dispose the EntryPoint. /Button Suspense fallbackLoading... EntryPointContainer entryPointReference{entryPointReference} props{{}} / /Suspense / ) } / ); }useEntryPointLoader内部实现useEntryPointLoader.js展示了它如何安全地管理生命周期它用一个 ref 集合undisposedEntryPointReferencesRef记录所有尚未 dispose 的引用每当新的loadEntryPoint结果 commit 时遍历该集合把当前 commit 之前的所有旧引用全部dispose()useEntryPointLoader.js在 Hook 卸载时通过 effect cleanup 把剩余未处理的引用全部 disposeuseEntryPointLoader.js它还处理了 Offscreen API 隐藏组件与 Fast Refresh 导致 effect cleanup 多次执行的情况maybeHiddenOrFastRefreshrefuseEntryPointLoader.js确保引用被重新加载而不是错误地保持已 dispose 状态。其返回值为三元组[entryPointReference, loadEntryPoint, disposeEntryPoint]其中loadEntryPoint回调接受TEntryPointParams类型参数disposeEntryPoint会把引用置为null并调用其.dispose()。八、源码佐证与测试验证loadEntryPoint的源码位于 packages/react-relay/relay-hooks/loadEntryPoint.js其核心类型定义位于 packages/react-relay/relay-hooks/EntryPointTypes.flow.js。仓库中与之配套的测试覆盖了关键行为可以进一步印证本文所述内容loadEntryPoint-test.js验证loadEntryPoint的参数传递、嵌套 EntryPoint 的递归加载、dispose释放行为等EntryPointContainer-test.js验证容器渲染已预加载的 EntryPoint、透传 props 等useEntryPointLoader-test.js验证 Hook 对引用的自动 dispose 与生命周期管理。若想了解 render-as-you-fetch 模式在 Relay 文档体系中的完整定位可参见 guided-tour/rendering/queries.md 中关于提前发起 fetch、避免瀑布式请求、与 React Suspense 的 render-as-you-fetch 模式配合的论述。九、最佳实践小结优先使用useEntryPointLoader它能自动完成 dispose避免数据泄漏仅在确实需要命令式控制例如在 React 树之外、或在路由层预加载时才直接调用loadEntryPoint。必须调用.dispose()如果直接使用loadEntryPoint务必在引用不再需要时组件卸载、路由切换等调用.dispose()并注意其幂等性设计可以安全地重复调用。不要在 render 阶段调用loadEntryPoint与useEntryPointLoader的 load 回调都可能在 React render 阶段抛错应在事件回调或 effect 中调用。只依赖dispose属性返回值格式不稳定除dispose外不要直接读取其他属性始终整体交给EntryPointContainer。善用 Suspense 降级组件代码未加载完成时EntryPointContainer的getComponent()会抛出 loading promise配合Suspense fallback即可呈现加载态。理解保留与释放语义查询数据在.dispose()之前会被 store 保留以防垃圾回收调用dispose()之后数据才可能被回收因此不要依赖 dispose 后数据立即可用。通过合理组合loadEntryPoint或useEntryPointLoader与EntryPointContainer你可以在交互发生的瞬间就启动组件代码与查询数据的预加载用最小的心智负担实现高性能的 render-as-you-fetch 数据获取模式。【免费下载链接】relayRelay is a JavaScript framework for building>项目地址: https://gitcode.com/gh_mirrors/relay29/relay创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表