類(lèi)型檢查中的 Relay 集成評(píng)估環(huán)境:relay_core Eval Context 設(shè)計(jì)與實(shí)現(xiàn))
Flow 靜態(tài)類(lèi)型檢查中的 Relay 集成評(píng)估環(huán)境relay_core Eval Context 設(shè)計(jì)與實(shí)現(xiàn)【免費(fèi)下載鏈接】flowAdds static typing to JavaScript to improve developer productivity and code quality.項(xiàng)目地址: https://gitcode.com/gh_mirrors/flow30/flow導(dǎo)讀本文圍繞 Flow 倉(cāng)庫(kù)中 evals/evals/03_project_patterns/relay_core/README.md 所定義的relay_core評(píng)估上下文展開(kāi)剖析這套用于真實(shí)項(xiàng)目模式Real-World Project Patterns類(lèi)別評(píng)估的獨(dú)立 Flow 工程它如何通過(guò)手寫(xiě)的 Relay 類(lèi)型存根、relay_integration配置與graphql產(chǎn)物類(lèi)型文件讓 AI 模型在無(wú)需引入完整 relay-compiler 的前提下編寫(xiě)出能夠通過(guò)flow full-check零錯(cuò)誤校驗(yàn)的 Relay Flow 組件。讀完本文你將掌握該評(píng)估環(huán)境的目錄組織、存根類(lèi)型的設(shè)計(jì)意圖、relay_integration的解析機(jī)制以及新增評(píng)估 prompt 的標(biāo)準(zhǔn)流程。背景為什么需要一個(gè)獨(dú)立的 Relay 評(píng)估根Flow 的評(píng)估體系evals按類(lèi)別組織其中 Category 3 專(zhuān)門(mén)針對(duì)真實(shí)世界項(xiàng)目模式。要評(píng)估模型能否產(chǎn)出正確的 Relay Flow 組件代碼最直接的辦法是搭建一個(gè)獨(dú)立的 Flow 根目錄standalone Flow root里面只放置必要的 Relay 類(lèi)型存根與手寫(xiě)的生成產(chǎn)物類(lèi)型讓模型在這個(gè)沙箱里自由編輯文件并接受類(lèi)型檢查。relay_core正是這樣一個(gè)沙箱它不依賴(lài)真實(shí)安裝的relay-compiler也不需要真實(shí)的 GraphQL Schema 與代碼生成流程而是用一套最小化但語(yǔ)義準(zhǔn)確的類(lèi)型存根還原出 wwwMeta 內(nèi)部環(huán)境中 Relay Flow 的典型類(lèi)型推導(dǎo)行為。這樣做的價(jià)值在于評(píng)估時(shí)模型只需編寫(xiě)組件源碼無(wú)需處理 Node 依賴(lài)安裝與編譯流程類(lèi)型檢查結(jié)果零錯(cuò)誤/有錯(cuò)誤可以自動(dòng)化判定模型輸出是否正確存根設(shè)計(jì)刻意復(fù)刻了relay_integrationtrue下graphql標(biāo)簽的解析語(yǔ)義使評(píng)估結(jié)論對(duì)真實(shí)工程具備參考意義。目錄結(jié)構(gòu)總覽relay_core的完整結(jié)構(gòu)如下見(jiàn) READMErelay_core/ └── context/ # Standalone Flow root — models edit files here ├── .flowconfig ├── libdefs_xplat/ # Minimal libdefs needed by the evals ├── RelayHooks.js # Declare stub for all relay hooks PreloadedQuery type ├── relay-runtime/ │ ├── package.json # haste_commonjs:true — resolves relay-runtime module name │ └── index.js # Declare stubs: OperationType, FragmentType, etc. └── *.graphql.js # Hand-authored generated query/fragment types per eval其中context/就是模型進(jìn)行編輯的 Flow 根所有評(píng)估相關(guān)的類(lèi)型存根與手寫(xiě)生成產(chǎn)物都放在這里。除context/之外倉(cāng)庫(kù)內(nèi)還配套有candidate_prompts/每個(gè)評(píng)估任務(wù)的 prompt 文件與ideals/理想組件實(shí)現(xiàn)用于校驗(yàn)例如candidate_prompts/01_notification_inbox.mdcandidate_prompts/02_activity_feed.mdideals/01_notification_inbox/NotificationInbox.react.jsideals/02_activity_feed/ActivityFeed.react.js類(lèi)型設(shè)計(jì)核心一RelayHooks.js 中的 Hook 存根RelayHooks.js 是該評(píng)估環(huán)境最關(guān)鍵的聲明文件。它聲明了graphql標(biāo)簽函數(shù)與一組 Relay HooksusePreloadedQuery、useFragment、useMutation、useLazyLoadQuery全部基于從relay-runtime導(dǎo)入的基礎(chǔ)類(lèi)型展開(kāi)。以下是文件的完整存根內(nèi)容import type { Disposable, FragmentType, GraphQLTaggedNode, MutationParameters, OperationType, PayloadError, } from relay-runtime; // Opaque type for a preloaded query reference. export opaque type PreloadedQueryout TQuery extends OperationType: Readonly{ variables: TQuery[variables], ... } Readonly{ variables: TQuery[variables], __id: string, ... }; export type UseMutationConfigTMutation extends MutationParameters { variables: TMutation[variables], onCompleted?: ?( response: TMutation[response], errors: ?ReadonlyArrayPayloadError, ) void, onError?: ?(error: Error) void, ... }; declare export function graphql( strings: ReadonlyArraystring, ): GraphQLTaggedNode; declare export hook usePreloadedQueryTQuery extends OperationType( query: GraphQLTaggedNode, queryRef: PreloadedQueryTQuery, ): TQuery[response]; // TData is inferred from the keys $data property (populated by relay-compiler). declare export hook useFragment TFragmentType extends FragmentType, TKey extends Readonly{ $fragmentSpreads: TFragmentType, $data?: unknown, ... }, ( fragment: GraphQLTaggedNode, key: TKey, ): NonNullableTKey[$data]; declare export hook useMutationTMutation extends MutationParameters( mutation: GraphQLTaggedNode, ): [ commit: (config: UseMutationConfigTMutation) Disposable, isPending: boolean, ]; declare export hook useLazyLoadQueryTQuery extends OperationType( query: GraphQLTaggedNode, variables: TQuery[variables], ): TQuery[response];useFragment的$data索引訪問(wèn)推導(dǎo)README 特別強(qiáng)調(diào)了一個(gè)設(shè)計(jì)要點(diǎn)useFragment的返回類(lèi)型是通過(guò)**索引訪問(wèn)類(lèi)型indexed access**從 key 的$data屬性推導(dǎo)出來(lái)的即NonNullableTKey[$data]。這帶來(lái)兩個(gè)直接約束.graphql.js片段 key 類(lèi)型必須包含$data?: DataType這一可選屬性否則TKey[$data]無(wú)法解析出數(shù)據(jù)形狀調(diào)用useFragment(fragment, key)時(shí)Flow 會(huì)從傳入的 key 反推TData無(wú)需顯式指定類(lèi)型參數(shù)。以實(shí)際的 NotificationItem_notification.graphql.js 為例其$key類(lèi)型就嚴(yán)格遵循了這一約定export type NotificationItem_notification$key Readonly{ $data?: NotificationItem_notification$data, $fragmentSpreads: NotificationItem_notification$fragmentType, ... };而在理想實(shí)現(xiàn) NotificationItem.react.js 中組件直接消費(fèi)這一 key 類(lèi)型useFragment自動(dòng)返回$data的完整形狀import type {NotificationItem_notification$key} from NotificationItem_notification.graphql; import {graphql, useFragment} from RelayHooks; const notificationFragment graphql fragment NotificationItem_notification on Notification { title timestamp isRead } ; export default component NotificationItem( notificationRef: NotificationItem_notification$key, ) { const notification useFragment(notificationFragment, notificationRef); return ( div strong{notification.title}/strong span{notification.timestamp}/span span{notification.isRead ? Read : Unread}/span /div ); }PreloadedQuery的不透明類(lèi)型封裝PreloadedQuery被聲明為opaque type并帶有out TQuery extends OperationType的協(xié)變類(lèi)型參數(shù)與experimental.opaque_type_new_bound_syntaxtrue的新式上界語(yǔ)法見(jiàn)下文 .flowconfig 分析。外部可見(jiàn)的上界暴露了variables而實(shí)際表示representation中還包含__id字段。組件層面只能看到variables這正是對(duì)真實(shí)PreloadedQuery語(yǔ)義的近似——調(diào)用方拿到的是一個(gè)已經(jīng)預(yù)加載的查詢引用無(wú)需關(guān)心內(nèi)部實(shí)現(xiàn)細(xì)節(jié)。類(lèi)型設(shè)計(jì)核心二relay-runtime 存根與模塊解析最小化的 runtime 類(lèi)型relay-runtime/index.js 只聲明了評(píng)估所需的最小類(lèi)型集文件頭部注釋也明確寫(xiě)著 Minimal type stubs for relay-runtime, used in relay_core evalsexport type OperationType Readonly{ variables: {...}, response: {...}, ... }; export interface FragmentType {} export type MutationParameters { variables: {...}, response: {...}, rawResponse?: {...}, ... }; export type GraphQLTaggedNode Readonly{ kind: string, ... }; export type Disposable { dispose: () void, ... }; export type PayloadError { message: string, locations?: ReadonlyArray{line: number, column: number, ...}, ... };注意GraphQLTaggedNode被定義為Readonly{kind: string, ...}——只讀且只有一個(gè)kind: string字段。這個(gè)看似簡(jiǎn)單的定義是支撐relay_integration解析機(jī)制的關(guān)鍵詳見(jiàn)下一節(jié)。haste_commonjs 與 haste 路徑排除relay-runtime/package.json 的內(nèi)容為{ name: relay-runtime, haste_commonjs: true, main: index.js }README 明確指出relay-runtime/目錄被排除在 haste 路徑掃描之外haste.paths.excludes這樣模塊名relay-runtime才能通過(guò) package.json 的haste_commonjs機(jī)制解析——而不是被當(dāng)作 haste 模塊按文件路徑注冊(cè)。這與真實(shí)項(xiàng)目中import ... from relay-runtime的解析方式保持一致讓模型寫(xiě)的 import 語(yǔ)句與生產(chǎn)代碼完全同構(gòu)。對(duì)應(yīng)到 .flowconfig 中的配置是module.systemhaste module.system.haste.module_ref_prefixm# module.system.haste.paths.excludes.*/__tests__/.* module.system.haste.paths.excludes.*/__mocks__/.* module.system.haste.paths.excludesPROJECT_ROOT/relay-runtime/.*relay_integrationgraphql 標(biāo)簽如何解析到生成產(chǎn)物這是整個(gè)評(píng)估環(huán)境類(lèi)型推導(dǎo)正確性的核心機(jī)制。README 說(shuō)明評(píng)估環(huán)境開(kāi)啟了relay_integrationtrue與 www 保持一致to match www開(kāi)啟后graphql標(biāo)簽?zāi)0遄址?*調(diào)用點(diǎn)call site**會(huì)被 Flow 解析為與之匹配的.graphql.js產(chǎn)物模塊的導(dǎo)出而不是聲明文件RelayHooks.js中g(shù)raphql函數(shù)聲明的返回類(lèi)型GraphQLTaggedNode因此模型無(wú)需顯式 import 查詢/片段的類(lèi)型——只要寫(xiě)了graphql標(biāo)簽Flow 就能自動(dòng)關(guān)聯(lián)到對(duì)應(yīng)的生成文件。要讓被解析到的產(chǎn)物模塊滿足 Hooks 的GraphQLTaggedNode參數(shù)約束每個(gè)產(chǎn)物存根都必須導(dǎo)出一個(gè)運(yùn)行時(shí)kind哨兵常量查詢query產(chǎn)物導(dǎo)出export const kind: Request Request片段fragment產(chǎn)物導(dǎo)出export const kind: Fragment Fragment。同時(shí)relay-runtime中的GraphQLTaggedNode被定義為Readonly{kind: string, ...}其中kind為只讀——這恰好與const導(dǎo)出的只讀語(yǔ)義匹配。const kind: Request Request的類(lèi)型是字面量類(lèi)型Request可以賦值給string從而滿足GraphQLTaggedNode。查詢產(chǎn)物NotificationInboxQuery.graphql.js以 NotificationInboxQuery.graphql.js 為例可以看到查詢產(chǎn)物同時(shí)具備變量/響應(yīng)類(lèi)型與kind 哨兵兩部分import type {NotificationItem_notification$fragmentType} from NotificationItem_notification.graphql; export type NotificationInboxQuery$variables {userId: string}; export type NotificationInboxQuery$data Readonly{ notifications: ReadonlyArray Readonly{ id: string, $fragmentSpreads: NotificationItem_notification$fragmentType, }, , }; export type NotificationInboxQuery Readonly{ variables: NotificationInboxQuery$variables, response: NotificationInboxQuery$data, }; // Runtime sentinel required by relay_integration so the graphql tag resolves // to a GraphQLTaggedNode-compatible type. export const kind: Request Request;片段產(chǎn)物NotificationItem_notification.graphql.js片段產(chǎn)物則額外定義$fragmentType、$data與$key三種類(lèi)型其中$key的$data?可選屬性正是useFragment索引訪問(wèn)推導(dǎo)的數(shù)據(jù)來(lái)源import type {FragmentType} from relay-runtime; declare export opaque type NotificationItem_notification$fragmentType: FragmentType; export type NotificationItem_notification$data Readonly{ title: string, timestamp: string, isRead: boolean, $fragmentType: NotificationItem_notification$fragmentType, }; export type NotificationItem_notification$key Readonly{ $data?: NotificationItem_notification$data, $fragmentSpreads: NotificationItem_notification$fragmentType, ... }; // Runtime sentinel required by relay_integration so the graphql tag resolves // to a GraphQLTaggedNode-compatible type. export const kind: Fragment Fragment;.flowconfig一份貼近 www 的 Flow 配置.flowconfig 完整還原了生產(chǎn)級(jí) Flow 工程的配置面貌逐段分析如下[ignore] .*/__tests__/.* .*/__mocks__/.* .*/__benchmarks__/.* [declarations] # Do NOT add entries here. Fix type stubs instead. [include] [options] module.systemhaste module.system.haste.module_ref_prefixm# module.system.haste.paths.excludes.*/__tests__/.* module.system.haste.paths.excludes.*/__mocks__/.* module.system.haste.paths.excludesPROJECT_ROOT/relay-runtime/.* facebook.fbsFbs facebook.fbtFbtElement munge_underscorestrue relay_integrationtrue experimental.opaque_type_new_bound_syntaxtrue experimental.module.automatic_require_defaulttrue experimental.facebook_module_interoptrue experimental.strict_es6_import_exporttrue babel_loose_array_spreadtrue react.runtimeclassic ban_spread_key_propstrue format.bracket_spacingfalse format.single_quotestrue [lints] deprecated-typeerror untyped-type-importerror unused-promiseerror [version] 0.317.0各配置項(xiàng)的作用配置項(xiàng)作用module.systemhaste使用 haste 模塊系統(tǒng)與 Meta 內(nèi)部工程一致module.system.haste.module_ref_prefixm#haste 模塊引用前綴m#module.system.haste.paths.excludesPROJECT_ROOT/relay-runtime/.*將relay-runtime排除出 haste 路徑掃描使其通過(guò)haste_commonjs解析facebook.fbsFbs/facebook.fbtFbtElement啟用 FBS/FBT 相關(guān)內(nèi)建類(lèi)型映射munge_underscorestrue啟用下劃線屬性名改寫(xiě)與 tests/config_munging_underscores 等測(cè)試目錄驗(yàn)證的行為一致relay_integrationtrue核心開(kāi)關(guān)讓graphql標(biāo)簽在調(diào)用點(diǎn)解析到對(duì)應(yīng).graphql.js產(chǎn)物模塊的導(dǎo)出experimental.opaque_type_new_bound_syntaxtrue允許opaque type ... : Bound Repr的新式上界語(yǔ)法PreloadedQuery依賴(lài)此語(yǔ)法experimental.module.automatic_require_defaulttrueCommonJS 模塊自動(dòng)添加默認(rèn)導(dǎo)出experimental.facebook_module_interoptrueFacebook 模塊互操作與 tests/facebook_module_interop 對(duì)應(yīng)experimental.strict_es6_import_exporttrue嚴(yán)格 ES6 import/export 檢查babel_loose_array_spreadtrue數(shù)組展開(kāi)采用 loose 語(yǔ)義react.runtimeclassicReact 運(yùn)行時(shí)為 classic非 automatic JSX transformban_spread_key_propstrue禁止在 JSX key 屬性上使用展開(kāi)format.bracket_spacingfalse/format.single_quotestrue代碼格式化偏好對(duì)象字面量無(wú)括號(hào)空格、單引號(hào)lints中deprecated-type/untyped-type-import/unused-promise均為error三條 lint 以錯(cuò)誤級(jí)別強(qiáng)制執(zhí)行[version] 0.317.0要求 Flow 版本不低于 0.317.0[declarations]段中的注釋明確提示不要在此處添加條目應(yīng)當(dāng)修復(fù)類(lèi)型存根——這是刻意設(shè)計(jì)評(píng)估環(huán)境的正確性應(yīng)通過(guò)修好存根來(lái)保證而不是用 ignore/declaration 掩蓋錯(cuò)誤。從理想實(shí)現(xiàn)看模型的目標(biāo)答案評(píng)估環(huán)境正確性的最終判據(jù)是理想組件能否在context/下通過(guò)flow full-check。兩個(gè)已有 eval 的理想實(shí)現(xiàn)展示了兩種典型的 Relay 組件形態(tài)。通知收件箱父子組件 片段引用傳遞NotificationInbox.react.js 演示了標(biāo)準(zhǔn)的預(yù)加載查詢 子組件片段引用傳遞模式——注意它使用了 Flow 的component 語(yǔ)法component關(guān)鍵字并且graphql標(biāo)簽內(nèi)聯(lián)寫(xiě)在組件文件里無(wú)需 import 任何.graphql.js類(lèi)型這正是relay_integrationtrue的效果import type {NotificationInboxQuery} from NotificationInboxQuery.graphql; import type {PreloadedQuery} from RelayHooks; import NotificationItem from NotificationItem.react; import {graphql, usePreloadedQuery} from RelayHooks; import * as React from react; const notificationInboxQuery graphql query NotificationInboxQuery($userId: String!) { notifications(userId: $userId) { id ...NotificationItem_notification } } ; export default component NotificationInbox( queryRef: PreloadedQueryNotificationInboxQuery, ) { const data usePreloadedQuery(notificationInboxQuery, queryRef); return ( ul {data.notifications.map(notification ( li key{notification.id} NotificationItem notificationRef{notification} / /li ))} /ul ); }活動(dòng)流match 表達(dá)式 配置類(lèi)型組合ActivityFeed.react.js 則展示了更復(fù)雜的場(chǎng)景聯(lián)合類(lèi)型判別__typenamematch表達(dá)式、顯示選項(xiàng)配置compact、maxItems、showTimestamps獨(dú)立成ActivityFeedConfig類(lèi)型文件并通過(guò)...ActivityFeedConfig展開(kāi)組合進(jìn) props。對(duì)應(yīng)的查詢產(chǎn)物 ActivityFeedQuery.graphql.js 中activityFeed是一個(gè)包含PostActivity、FriendRequestActivity、EventReminderActivity、LikeActivity、CommentActivity以及%other兜底分支的判別聯(lián)合match表達(dá)式可以窮盡地、類(lèi)型安全地處理每一種條目例如match (entry) { {__typename: PostActivity, visibility: public | friends, const author, const preview} compact true ? ${author} posted : ${author} posted: ${preview}, {__typename: PostActivity, visibility: private, const author, ...} ${author} shared a private post, {__typename: FriendRequestActivity, const requester, const mutualFriendCount, group: {const name}} ${requester} sent a friend request — ${mutualFriendCount} mutual friend${mutualFriendCount 1 ? : s} in ${name}, ... _ New activity, }match表達(dá)式配合%other兜底分支正是 Flow 新版模式匹配tests/match、tests/match_exhaustive在真實(shí)項(xiàng)目模式中的落地用法。新增一個(gè)評(píng)估任務(wù)的標(biāo)準(zhǔn)流程README 給出了清晰的四步流程任何新增的 Relay 評(píng)估場(chǎng)景都按此執(zhí)行編寫(xiě).graphql.js類(lèi)型文件在context/下為新的場(chǎng)景手寫(xiě)查詢/片段產(chǎn)物類(lèi)型包含$variables、$data、$key、kind哨兵等編寫(xiě) prompt在candidate_prompts/NN_name.md中書(shū)寫(xiě)任務(wù)描述可選編寫(xiě)理想組件并驗(yàn)證在context/中編寫(xiě)理想組件文件用flow full-check確認(rèn)零錯(cuò)誤提交前清理移除理想文件或移入獨(dú)立的ideals/目錄確保提交的內(nèi)容只包含上下文與 prompt。candidate_prompts/01_notification_inbox.md與candidate_prompts/02_activity_feed.md就是這一流程的現(xiàn)成范例——兩者的 prompt 都明確要求模型使用flow strict-local、使用component語(yǔ)法、從RelayHooks導(dǎo)入 Hooks并以flow零錯(cuò)誤作為驗(yàn)收標(biāo)準(zhǔn)。上下文潔凈度驗(yàn)證評(píng)估環(huán)境的可靠性依賴(lài)于一個(gè)前提在沒(méi)有模型文件時(shí)context 本身必須是零錯(cuò)誤的。README 給出了標(biāo)準(zhǔn)的驗(yàn)證命令cd context flow full-check --show-all-errors--show-all-errors用于展示全部錯(cuò)誤而非默認(rèn)的截?cái)噍敵龃_保任何存根設(shè)計(jì)缺陷都會(huì)被立即發(fā)現(xiàn)。這條命令應(yīng)該作為每次修改存根或新增產(chǎn)物類(lèi)型后的回歸檢查來(lái)執(zhí)行——只有在裸 context零錯(cuò)誤的前提下后續(xù)模型代碼產(chǎn)生的錯(cuò)誤才能被準(zhǔn)確歸因于模型輸出而不是評(píng)估環(huán)境自身的問(wèn)題。總結(jié)relay_core評(píng)估環(huán)境的設(shè)計(jì)精髓可以概括為三點(diǎn)最小化存根最大化語(yǔ)義保真RelayHooks.js與relay-runtime/index.js用不到百行聲明還原了usePreloadedQuery、useFragment等 Hook 的類(lèi)型推導(dǎo)行為尤其是通過(guò)NonNullableTKey[$data]索引訪問(wèn)推導(dǎo)片段數(shù)據(jù)的機(jī)制relay_integrationtruekind哨兵 haste_commonjs的組合讓graphql標(biāo)簽在調(diào)用點(diǎn)自動(dòng)解析到.graphql.js產(chǎn)物導(dǎo)出使模型書(shū)寫(xiě)的代碼與生產(chǎn)環(huán)境 Relay 工程完全同構(gòu)且無(wú)需顯式 import 類(lèi)型可驗(yàn)證性優(yōu)先[declarations]段禁止堆疊 ignore、flow full-check --show-all-errors作為潔凈度回歸手段保證評(píng)估信號(hào)干凈可信。如果你要為本倉(cāng)庫(kù)新增 Relay 類(lèi)評(píng)估任務(wù)直接按照上述四步流程參考candidate_prompts/與ideals/中的現(xiàn)有實(shí)現(xiàn)即可快速上手。【免費(fèi)下載鏈接】flowAdds static typing to JavaScript to improve developer productivity and code quality.項(xiàng)目地址: https://gitcode.com/gh_mirrors/flow30/flow創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考