端 RNR 遷移實(shí)踐:shadcn 式組件體系、CSS 變量暗色主題與三階段落地方案)
Multica 移動(dòng)端 RNR 遷移實(shí)踐shadcn 式組件體系、CSS 變量暗色主題與三階段落地方案【免費(fèi)下載鏈接】multicaMake humans and AI agents work as one team — open-source and self-hostable.項(xiàng)目地址: https://gitcode.com/GitHub_Trending/mu/multica本篇基于 Multica 移動(dòng)端的遷移設(shè)計(jì)文檔 rnr-migration.md完整拆解一次手寫(xiě) React Native 組件 → react-native-reusablesRNR 類級(jí)暗色模式的遷移工程為什么選 RNR 而不是 Tamagui/Gluestack如何用iOS 原生 RNR 討論瀑布收斂 18 個(gè)手寫(xiě) Sheet如何用 CSS 變量 darkMode: class搭出 Light / Dark / System 三檔主題切換以及三階段Phase 0/1/2/3的推進(jìn)節(jié)奏與已驗(yàn)證的源碼實(shí)現(xiàn)。讀完你可以復(fù)用到自己的 NativeWind 4 Expo SDK 55 項(xiàng)目中并了解每個(gè)設(shè)計(jì)決策背后的源碼依據(jù)。1. 背景遷移要解決的三個(gè)問(wèn)題文檔開(kāi)篇給出了遷移前的基線狀態(tài)注意這描述的是 Phase 1 之前的狀態(tài)主題部分如今已修復(fù)組件部分仍是待辦apps/mobile/components/ui/下有21 個(gè)手寫(xiě)組件約 1,379 行全部基于裸View/Text/Pressable/Modal構(gòu)建apps/mobile/components/下有18 個(gè)手寫(xiě) sheet/modal 文件全部復(fù)制同一種形態(tài)Modal transparent fade 手繪背景遮罩。移動(dòng)端基線文檔 CLAUDE.md 的 Lesson 6 早已記錄這種模式對(duì)多數(shù)內(nèi)容形態(tài)是錯(cuò)的并由此衍生出一串 bug鍵盤擠壓、maxHeight截?cái)?FlatList、Modal內(nèi)部useSafeAreaInsets返回 0沒(méi)有任何暗色/亮色主題基礎(chǔ)設(shè)施tailwind.config.js用硬編碼 hex 值global.css只有三條tailwind指令沒(méi)有 CSS 變量、沒(méi)有darkMode、沒(méi)有主題切換器。移動(dòng)端基線CLAUDE.md自 SDK 55 bootstrap 起就把 RNR 定位為 shadcn 的移動(dòng)端等價(jià)物但 RNR 從未真正安裝。本文檔記錄了為什么遷移到 RNR、評(píng)估過(guò)哪些替代方案、遷移如何分階段推進(jìn)三件事面向所有會(huì)動(dòng)到apps/mobile/components/ui/或給移動(dòng)端加新 UI 的開(kāi)發(fā)者。2. 備選方案評(píng)估評(píng)估維度有四項(xiàng)(a) 與現(xiàn)有技術(shù)棧的契合度NativeWind 4、Tailwind 3.4、Expo SDK 55、React 19(b) 所有權(quán)模型鎖定 vs 復(fù)制粘貼(c) 打包/編譯開(kāi)銷(d) 無(wú)障礙基線。庫(kù)棧契合度所有權(quán)構(gòu)建成本無(wú)障礙結(jié)論react-native-reusables (RNR)NativeWind 4 RN-Primitives CVA ——與本項(xiàng)目完全一致復(fù)制粘貼代碼歸你零只是 CSS JSXRN-Primitives焦點(diǎn)管理、ARIA 等價(jià)物入選Tamagui自帶編譯器 自帶 styled API非 NativeWind庫(kù)鎖定Babel/bundler 配置 學(xué)習(xí)成本內(nèi)置否決 —— 核心價(jià)值是 webnative 同代碼庫(kù)移動(dòng)端獨(dú)立用不上Gluestack UI v2自帶 styled API可以配合 NativeWind 用但非原生路徑可復(fù)制粘貼低react-native-aria強(qiáng)否決 —— 已有 NativeWind 在跑換樣式體系沒(méi)有收益NativeBase / RN Paper / RN UI Lib成熟但庫(kù)綁定鎖定低視情況否決 —— 長(zhǎng)生命周期安裝型 App 需要不依賴上游發(fā)版就能打組件補(bǔ)丁的能力復(fù)制粘貼哲學(xué)很關(guān)鍵RNR 的組件歸你所有模型還與桌面/Web 端的模式packages/ui/components/ui/也是 shadcn 復(fù)制粘貼保持一致——整個(gè)代碼庫(kù)一種心智模型。3. 決策RNR 及其治理承諾采用 RNR 時(shí)文檔立下了若干承諾。前兩條是原則約束遷移期每個(gè) PR 以及之后的每個(gè) UI 決策3.1 默認(rèn)值優(yōu)先Defaults first使用任何 RNR 組件時(shí)接受它的默認(rèn) variant、默認(rèn)尺寸、默認(rèn)間距、默認(rèn)調(diào)色板。除非有明確的產(chǎn)品需求不要加包裝層、不要做改進(jìn)版默認(rèn)值、不要造variantmulticaCustom之類的樣式。手寫(xiě)遺留代碼恰恰就是因?yàn)橛腥巳ヒ藰?biāo)準(zhǔn)原語(yǔ)的稍微改進(jìn)版——在 RNR 底下重演這個(gè)模式等于白遷。具體推論P(yáng)hase 1 原樣使用 shadcn 默認(rèn) neutral 調(diào)色板light dark。Multica 自有自定義 tokenbrand、success、warning、info、priority、code-surface追加進(jìn)去但暗色值不在有實(shí)證需求之前預(yù)先創(chuàng)作——它們先復(fù)制亮色值直到真正使用它們的頁(yè)面在暗色下出問(wèn)題為止。Phase 2/3npx rnr/cli add component寫(xiě)出文件后不要立刻調(diào)它的樣式API 有差異時(shí)改調(diào)用方。Tier C基礎(chǔ)升級(jí)指的是把裸Text換成 RNR 的Text、把內(nèi)聯(lián)條件判斷換成cva——不指重新設(shè)計(jì)或添加組件原本沒(méi)有的 variant。3.2 iOS 原生 RNR 討論新增任何交互時(shí)走這條瀑布命中即停iOS / RN 有原生 API直接用不要用Modal包一層去模擬RNR 有對(duì)應(yīng)組件npx rnr/cli add name用默認(rèn) variant都沒(méi)有。停下來(lái)問(wèn)用戶不要悶頭手寫(xiě)。第 4 節(jié) Tier B 的修訂分類就依賴這個(gè)原生 API 層——很多既有 sheet 換成ActionSheetIOS/Alert.prompt/ 原生日期選擇器后會(huì)整個(gè)消失。場(chǎng)景原生 API文本輸入提示單字段Alert.prompt(title, msg, callback)確認(rèn) / 破壞性操作提示Alert.alertN 選 1 動(dòng)作表ActionSheetIOS.showActionSheetWithOptions日期 / 時(shí)間選擇react-native-community/datetimepicker已安裝圖片 / 相機(jī)expo-image-picker已安裝文檔expo-document-picker已安裝分享react-native的Share.share觸感反饋確認(rèn) / 錯(cuò)誤 / 選中expo-haptics已安裝3.3 主題方案類級(jí)暗色模式darkMode: class CSS 變量應(yīng)用內(nèi) Settings → Appearance 提供light/dark/system三檔選擇選擇持久化在expo-secure-store。RNR 的默認(rèn) Tailwind 配置就是 class 模式與需要顯式用戶設(shè)置的需求匹配純 media-query 方案不允許用戶覆蓋 OS 偏好。3.4 組件三層分類不做一刀切全量遷移。部分遺留組件是領(lǐng)域 UI 而非通用原語(yǔ)留在原地但使用 RNR 的地基Text組件、語(yǔ)義化 token、CVA variant。3.5 硬規(guī)則寫(xiě)入 CLAUDE.md新組件要么來(lái)自 RNR要么先走原生 API 層。遷移刪除手寫(xiě)遺留 variant這條規(guī)則防止再次積累。這一規(guī)則如今已固化在 CLAUDE.md 的 UI components theming 章節(jié)作為遷移完成后依然有效的持久規(guī)則。4. 組件清單與三層分類Tier A —— RNR 直接替換遷移對(duì)象通用原語(yǔ)中 RNR 有近乎同形等價(jià)物的直接用npx react-native-reusables/clilatest add name的輸出替換手寫(xiě)文件然后清掃調(diào)用方現(xiàn)有文件行數(shù)RNR 組件備注components/ui/button.tsx63buttonPhase 2 的 canarycomponents/ui/input.tsx9input平凡components/ui/text-field.tsx34inputlabel用 RNR 組合components/ui/card.tsx36card可直接替換components/ui/text.tsx18text到處都在用要小心做清掃所有 importcomponents/ui/autosize-textarea.tsx89textarea需核對(duì)一致性 —— 自動(dòng)增高行為可能要基于 RNR textarea 重實(shí)現(xiàn)components/ui/otp-input.tsx68RNR 目前無(wú)等價(jià)物繼續(xù)用input-otp-native移入 Tier Bcomponents/ui/modal-close-button.tsx25無(wú)平凡 —— sheet 遷移后并入Dialog關(guān)閉模式合計(jì)約 270 行待替換含調(diào)用方清掃約 4 小時(shí)工作量。從當(dāng)前倉(cāng)庫(kù)結(jié)構(gòu)看components/ui/ 目錄在文檔撰寫(xiě)之后已經(jīng)出現(xiàn)了collapsible、dropdown-menu、radio-group、separator、skeleton、switch、tabs等文件可以推斷 Tier A 之后的組件引入已按 RNR 模式在推進(jìn)但按文檔 Status 行Phase 2/3 的收尾工作仍以文檔記錄為準(zhǔn)。Tier B —— Sheets / Modals套用 §3.2 瀑布這一層收益最大CLAUDE.md Lesson 6 已給重復(fù)出現(xiàn)的 sheet bug 建了檔。按iOS 原生 RNR 討論瀑布18 個(gè) sheet 三分B.1 —— 原生 API 替換文件直接刪現(xiàn)有 sheet替換為原因components/issue/comment-action-sheet.tsxActionSheetIOS.showActionSheetWithOptionsN 選 1 動(dòng)作菜單 —— 正是 ActionSheetIOS 的用途。推薦作 Phase 3 起手可見(jiàn)的刪除、無(wú)樣式問(wèn)題components/issue/pickers/due-date-picker-sheet.tsxreact-native-community/datetimepicker內(nèi)聯(lián)選擇器日期選擇 —— 原生 API 已安裝B.2 —— 改為 formSheet 路由已完成原計(jì)劃是把每個(gè) picker-sheet 換成 RNRSelect。mobile-sheet-rollout PR 系列最終收斂到了不同形態(tài)每個(gè)原 picker-sheet 都拆成components/domain/pickers/下的純XxxPickerBody組件嵌入 Expo Router formSheet 路由app/(app)/[workspace]/context/picker/field.tsx。這拿到了 iOSUISheetPresentationController的原生 chromegrabber detents 彈簧拖拽關(guān)閉同時(shí)省掉了 RNRSelect仍需要的每個(gè)調(diào)用點(diǎn)狀態(tài)與可見(jiàn)性 prop 的周折。本行文件已全部刪除無(wú)后續(xù)動(dòng)作。B.3 —— 真正需要自定義內(nèi)容 sheetRNRDialogpageSheet現(xiàn)有 sheet為什么保留為 Dialogcomponents/issue/issue-filter-sheet.tsx一個(gè) sheet 里多個(gè)控件篩選表單不是列表選擇components/issue/runs-sheet.tsx運(yùn)行歷史行 操作不是 N 選 1components/chat/session-sheet.tsx待定 —— 到時(shí)重新審視可能歸 B.2components/chat/agent-picker-sheet.tsx大概率 B.2Select—— 待重審components/project/add-resource-sheet.tsx待定 —— 取決于單選還是小表單B.4 —— RNR 沒(méi)有的空。唯一一項(xiàng)components/issue/emoji-picker-sheet.tsx已通過(guò)采納rn-emoji-keyboard解決評(píng)論表情反應(yīng)流程遷移到 formSheet 路由app/(app)/[workspace]/issue/[id]/comment/[commentId]/emoji-picker.tsx。移動(dòng)端現(xiàn)在在每條評(píng)論動(dòng)作表的更多表情溢出項(xiàng)后提供完整表情集與 Web 對(duì)齊。執(zhí)行規(guī)則不要批量替換sheet-shell.tsx。它被 18 個(gè)文件引用原子替換 18 處同時(shí)壞掉。按 CLAUDE.md Lesson 6一個(gè) sheet 一個(gè) PR一個(gè) PR 一次驗(yàn)證。B.1 是刪除類 —— 應(yīng)當(dāng)先做因?yàn)槊總€(gè)都只是刪文件、零替換代碼從父組件調(diào)ActionSheetIOS即可。復(fù)合收益代碼更少 符合默認(rèn)值優(yōu)先 符合iOS 原生優(yōu)先。B.2 簡(jiǎn)化調(diào)用點(diǎn)但引入的組件是新代碼排在 B.1 之后。B.3 保留結(jié)構(gòu)復(fù)雜度遷移主要是Modal換 RNRDialog。視覺(jué)變化最小但 bug 修復(fù)最大拖拽關(guān)閉、焦點(diǎn)管理、安全區(qū)都由 RNR 處理。Tier C —— 領(lǐng)域 UI保留升級(jí)地基這些不是通用 shadcn 組件 —— RNR 沒(méi)有優(yōu)先級(jí)圖標(biāo)或參與者頭像的等價(jià)物。它們留在components/ui/以兼容現(xiàn)有 import但內(nèi)部構(gòu)建塊必須遷到 RNR 地基用 RNR 的Text替代裸Text用cva做 variant 表而非內(nèi)聯(lián)條件用語(yǔ)義化 tokentext-foreground永不用#1f1f23actor-avatar.tsx158 行—— 底層用 RNR 的avatar原語(yǔ)但業(yè)務(wù)邏輯保留app-header-actions.tsx51、avatar-stack.tsx97、presence-dot.tsx44priority-icon.tsx80、project-icon.tsx49、project-priority-icon.tsx71、project-status-icon.tsx130、status-icon.tsx163pulse-dot.tsx52、screen-header.tsx37Tier C 不排期 —— 某個(gè) bug 或功能觸碰到文件時(shí)順手在該 PR 里升級(jí)地基機(jī)會(huì)主義而非排期驅(qū)動(dòng)。5. 主題架構(gòu)CSS 變量 類級(jí)暗色模式目標(biāo)Settings → Appearance 提供Light / Dark / System三檔選擇持久化在expo-secure-store鍵theme-preference值light/dark/system。5.1 分層結(jié)構(gòu)global.css CSS 變量定義于 :root 與 .dark:root顏色的唯一事實(shí)來(lái)源 tailwind.config.js darkMode: class 工具類映射到 hsl(var(--...)) lib/theme.ts CSS 變量的 TypeScript 鏡像導(dǎo)出給 React Navigation 用的 NAV_THEME lib/use-color-scheme.ts 包裝 NativeWind 的 useColorScheme expo-secure-store 持久化 app/_layout.tsx 啟動(dòng)時(shí)讀取持久化偏好調(diào) setColorScheme(...) 用 ThemeProvider(NAV_THEME[...]) 包 Stack 為 RNR dialog/popover 掛載 PortalHost /5.2 各層實(shí)現(xiàn)細(xì)節(jié)源碼佐證global.cssPhase 1 之前tailwind.config.js里約 20 個(gè)語(yǔ)義化 token 是硬編碼 hex?,F(xiàn)在它們成了global.css中:root亮色與.dark:root暗色下的 CSS 變量。亮色基礎(chǔ)值即 shadcn neutral 默認(rèn)如--background: 0 0% 100%、--primary: 0 0% 9%Multica 自定義 token 追加其后/* Multica custom tokens */ --brand: 225 71% 58%; --brand-foreground: 0 0% 98%; --success: 142 71% 45%; --warning: 48 89% 47%; --info: 217 91% 60%; --priority: 25 95% 53%; --code-surface: 240 4% 92%;實(shí)際實(shí)現(xiàn)比文檔更進(jìn)一步補(bǔ)了一套 5 級(jí)表面高程階梯--surface-1/--surface-2按 Refactoring UI≥5% L 差異為可感知閾值校準(zhǔn)亮色下 L100 頁(yè)面底 → L98 surface-1評(píng)論氣泡→ L96.1 shadcn secondary → L90 surface-2氣泡內(nèi)嵌代碼塊→ L84 border暗色下明度隨高程升高陰影在暗色下不成立色調(diào)抬升才是高程線索--code-surface是自定義 token 中唯一需要真實(shí)暗色值的例外240 4% 18%——亮色值近白暗色下會(huì)把代碼塊襯得比頁(yè)面還亮。tailwind.config.jsdarkMode: class所有顏色工具類映射為hsl(var(--background))形式borderWidth注冊(cè)了hairline: hairlineWidth()來(lái)自nativewind/theme插件區(qū)注冊(cè)tailwindcss-animate并保留移動(dòng)端的borderRadius覆蓋module.exports { darkMode: class, content: [./app/**/*.{ts,tsx}, ./components/**/*.{ts,tsx}], presets: [require(nativewind/preset)], theme: { extend: { colors: { background: hsl(var(--background)), primary: { DEFAULT: hsl(var(--primary)), foreground: hsl(var(--primary-foreground)) }, // ... 以及 brand / success / warning / code-surface 等自定義 token }, borderWidth: { hairline: hairlineWidth() }, }, }, plugins: [require(tailwindcss-animate)], };lib/theme.tsCSS 變量的 TS 鏡像。THEME是原始 token 對(duì)象供內(nèi)聯(lián)樣式、動(dòng)畫(huà)等 Tailwind class 夠不到的場(chǎng)景NAV_THEME是給react-navigation/native的ThemeProvider用的主題header、modal、返回鍵跟隨明暗。lib/use-color-scheme.ts包裝 NativeWind 的useColorScheme()加上expo-secure-store持久化鍵theme-preference。暴露{ colorScheme, isDarkColorScheme, setPreference }。文檔沒(méi)有細(xì)說(shuō)但源碼里寫(xiě)明的一個(gè)取舍首掛載時(shí)是異步讀取保存的偏好讀取完成前 NativeWind 默認(rèn)行為跟隨 OS生效——意味著選了 Dark 的用戶在亮色 OS 上冷啟動(dòng)可能短暫閃一下亮色可接受因?yàn)?secure-store 沒(méi)有同步后端。app/_layout.tsx實(shí)際接線處。RootLayout調(diào)用useColorScheme()拿到colorScheme/isDarkColorScheme用ThemeProvider value{NAV_THEME[colorScheme]}包住StackStatusBar顏色隨模式翻轉(zhuǎn)PortalHost /rn-primitives/portal掛在 provider 子樹(shù)末尾供 RNR dialog/popover 使用。components.json標(biāo)準(zhǔn) RNR/shadcn 配置 ——style: new-york、baseColor: neutral、cssVariables: true別名指向/components、/lib/utils。metro.config.jswithNativeWind(config, { input: ./global.css, inlineRem: 16 })——inlineRem: 16是 Phase 1 清單里的關(guān)鍵項(xiàng)保證 CSS 變量按 16px 基準(zhǔn)編譯。5.3 為什么是 class 模式而不是 media-query 模式media-query 模式media (prefers-color-scheme: dark)是更簡(jiǎn)單的默認(rèn)但應(yīng)用無(wú)法覆蓋它。Settings → Appearance 需要覆蓋 OS 偏好必須 class 模式。代價(jià)是啟動(dòng)時(shí)一次setColorScheme()調(diào)用來(lái)應(yīng)用保存的偏好首次繪制前一次性付清。5.4system選項(xiàng)用戶選system時(shí)調(diào)用setColorScheme(system)NativeWind v4 原生支持框架通過(guò)Appearance.addChangeListener跟隨 OS無(wú)需自己訂閱isDarkColorScheme響應(yīng)式更新。5.5 暗色調(diào)色板策略舊配置里不存在暗色值必須創(chuàng)作。兩個(gè)選項(xiàng)直接用 shadcn neutral-base 暗色調(diào)色板RNR 默認(rèn)安裝的--background: 0 0% 3.9%等—— 最快立刻有能跑的暗色模式品牌對(duì)齊可能要二遍手工創(chuàng)作暗色對(duì)齊 Web/桌面暗色主題 —— 慢但與桌面視覺(jué)一致。Phase 1 選了選項(xiàng) 1換取速度。現(xiàn)在基礎(chǔ)設(shè)施已驗(yàn)證后續(xù)可以按桌面的 tokens.css 暗色主題做校準(zhǔn)但注意桌面用 oklch / Tailwind v4移動(dòng)端用 hsl / Tailwind v3.4跨版本共享不現(xiàn)實(shí)分歧是刻意保留的基線。6. 三階段推進(jìn)計(jì)劃Phase 0 —— 研究與文檔本文檔?通讀 RNR 安裝與定制文檔、檢查移動(dòng)端現(xiàn)狀tailwind.config.js、global.css、app/_layout.tsx、metro.config.js、babel.config.js、更新 CLAUDE.md 的 UI 與主題規(guī)則、寫(xiě)下本文檔最后過(guò)用戶驗(yàn)證關(guān)卡。Phase 1 —— 基礎(chǔ)基礎(chǔ)設(shè)施 ? 已完成目標(biāo)裝上 RNR 腳手架但不碰任何一個(gè)現(xiàn)有組件。驗(yàn)證標(biāo)準(zhǔn) App 構(gòu)建運(yùn)行與之前完全一致設(shè)置里的主題切換器端到端可用。Phase 1 的 10 項(xiàng)清單全部交付依賴按 RNR 手動(dòng)安裝第 3 步npx expo install tailwindcss-animate class-variance-authority clsx tailwind-merge rn-primitives/portalcva/clsx/tailwind-merge/rn-primitives/slot確認(rèn)已存在新增的只有tailwindcss-animate和rn-primitives/portalmetro.config.js設(shè)inlineRem: 16重寫(xiě)global.css:root.dark:root雙調(diào)色板含 Multica 自定義 token重寫(xiě)tailwind.config.jshsl(var(--...))映射、darkMode: class、tailwindcss-animate插件、hairlineWidth()邊框?qū)挾缺A粢苿?dòng)端專屬覆蓋新建lib/theme.tsTS 鏡像 NAV_THEME新建lib/use-color-scheme.ts持久化包裝新建components.json標(biāo)準(zhǔn) RNR 配置更新app/_layout.tsx啟動(dòng)時(shí)讀持久化偏好、首繪前setColorScheme(...)、ThemeProvider(NAV_THEME[...])包Stack、provider 末尾掛PortalHost /Settings → Appearance 選擇器三行Light / Dark / System調(diào)setPreference 持久化UI 復(fù)用既有行模式無(wú)新依賴驗(yàn)證構(gòu)建通過(guò)、亮色下所有既有頁(yè)面渲染不變、切 Dark 背景/文字翻轉(zhuǎn)、切 System 跟隨模擬器 OS 外觀、殺進(jìn)程重開(kāi)偏好仍在。Phase 1 不替換任何現(xiàn)有組件。按鈕、輸入、sheet 仍是手寫(xiě)版。暗色模式之所以對(duì)既有 hex 派生的語(yǔ)義 token 能用是因?yàn)橹皇前阉鼈兏牡澜?jīng)過(guò) CSS 變量 —— 同一個(gè)bg-backgroundclass 現(xiàn)在解析到有一個(gè)值變兩個(gè)值的變量。殘留風(fēng)險(xiǎn)帶入 Phase 3硬編碼 hex 的組件如Ionicons color#71717a、bg-[#fafafa]不響應(yīng)主題切換。Phase 1 做過(guò)一次#[0-9a-fA-F]{3,6}的 grep 清掃每個(gè)命中要么換 token要么標(biāo)TODO(rnr-migration):留給 Phase 3。Phase 2 —— 首個(gè)組件 canary未開(kāi)始目標(biāo)在批量做 20 個(gè)之前用最簡(jiǎn)單且非平凡的組件驗(yàn)證遷移機(jī)械流程。選定button.tsx。理由調(diào)用方數(shù)量高能驗(yàn)證 import 清掃模式、RNR 的 button 帶variant/sizeprop 與 shadcn 同形驗(yàn)證 API 對(duì)等、到處可見(jiàn)視覺(jué)回歸立刻明顯。步驟npx react-native-reusables/clilatest add button—— CLI 覆蓋現(xiàn)有文件舊文件成為 git 里的 diff 基線新舊 diff記錄 prop 或視覺(jué)差異RNRvariant枚舉 vs 自有的、默認(rèn)尺寸差異清掃所有調(diào)用方API 有差異就在同一 PR 里改調(diào)用方模擬器視覺(jué) diff逐個(gè)打開(kāi)用到 button 的頁(yè)面截圖與 main 對(duì)比亮色暗色雙模式驗(yàn)證。Phase 2 就是一個(gè) PR。驗(yàn)收標(biāo)準(zhǔn)所有既有按鈕仍工作亮色無(wú)回歸暗色下默認(rèn)值合理。Phase 3 —— 其余全部順序其余 Tier A 原語(yǔ)input、text-field、card、text、textarea—— 一個(gè)組件或一組一個(gè) PR同 canary 模式text特殊處理幾乎每個(gè)文件都 import 它單獨(dú)排 PRcodemod 是機(jī)械的import { Text } from /components/ui/text已存在背后文件變化即可Tier B sheets —— 一個(gè) sheet 一個(gè) PR順序 B.1原生 API 替換刪文件→ B.2Select替換→ B.3Dialog遷移。首個(gè) sheet PR comment-action-sheet.tsx→ActionSheetIOS干凈的刪除、且在實(shí)踐中驗(yàn)證 §3.2 瀑布。每個(gè) PR 后停下來(lái)重新驗(yàn)證Tier C 地基升級(jí) —— 機(jī)會(huì)主義無(wú)排期 PR收尾 PR刪除不再被引用的遺留文件、清掉 Phase 1 hex 清掃留下的 TODO 注釋最終pnpm typecheck pnpm lint干凈。停止規(guī)則連續(xù) 3 個(gè) PR 引入視覺(jué)回歸就暫停重新審視 token 映射不要硬推Tier B sheet 遷不干凈RNRdialog不適合該場(chǎng)景時(shí)在本文檔記錄分歧保留手寫(xiě)版并標(biāo)記為刻意例外而非待辦。7. 已知陷阱寫(xiě)進(jìn)反射弧研究階段踩過(guò)的坑動(dòng)手前先固化darkMode: class.dark:root是 NativeWind v4 類控制模式下唯一可用的組合。不要用標(biāo)準(zhǔn).dark選擇器 —— NativeWind 需要:root后綴才能全局應(yīng)用。global.css 中的選擇器就是.dark:root。setColorScheme()來(lái)自 NativeWind不是 React Native。從react-nativeimport 得到的是只讀的 OS 值。NativeWind 版支持setColorScheme(light | dark | system)并觸發(fā)重渲染。同步陷阱lib/theme.ts與global.css必須互為鏡像。只改一邊Tailwind class 樣式化的組件看著對(duì)但直接讀THEME的地方內(nèi)聯(lián)樣式、動(dòng)畫(huà)、React Navigation chrome就錯(cuò)了。兩份文件的頭部注釋都寫(xiě)明改一邊必須改另一邊見(jiàn) rnr-migration.md §5。AbortSignal.timeout/AbortSignal.any在 Hermes 上仍不存在CLAUDE.md Lesson 5。與 RNR 遷移無(wú)直接關(guān)系但任何自己發(fā)網(wǎng)絡(luò)請(qǐng)求的新組件都要手動(dòng) AbortController 模式。RNRDialog內(nèi)部的useSafeAreaInsets與裸Modal一樣不可靠。CLAUDE.md Lesson 6 的 pageSheet 陷阱仍適用 —— 在父組件讀 insets把bottomInset當(dāng) prop 傳進(jìn)去。rn-primitives/portal的PortalHost /位置敏感。必須是所有 provider 的最后一個(gè)子節(jié)點(diǎn)如果掛在頻繁重渲染的 provider 內(nèi)dialog 會(huì)被意外卸載。只在 app/_layout.tsx 放一次。CLI 覆蓋文件無(wú)確認(rèn)。components/ui/button.tsx存在時(shí)add button直接替換。對(duì)遷移是期望行為但對(duì) Tier C 文件誤跑是災(zāi)難 —— 每次add后都查 git status。NativeWind 5 尚未采納?;€釘在 v4RNR v1 兩者都支持本項(xiàng)目走 v4 安裝路徑。8. 開(kāi)放問(wèn)題暗色品牌色當(dāng)前品牌色#4571e0僅亮色。暗色等價(jià)值要定 —— 保留深色底上對(duì)比度高還是偏移留給設(shè)計(jì) pass。code-surfacetoken亮色下是#e8e8eb比secondary深 5%。暗色等價(jià)是比暗色secondary亮 5%。實(shí)際實(shí)現(xiàn)已在 .dark:root 中給出240 4% 18%理由注釋齊全。設(shè)置頁(yè) UXAppearance 選擇器可內(nèi)聯(lián)三行直接擺也可做成打開(kāi) picker sheet 的行。v1 選內(nèi)聯(lián)更簡(jiǎn)單sheet 變體等 Tier B 遷完再說(shuō)。暗色 token 是否與桌面共享桌面packages/ui/styles/tokens.css用 oklchTailwind v4移動(dòng)端 hslTailwind 3.4跨版本共享不現(xiàn)實(shí)接受分歧為既有基線移動(dòng)端升級(jí)到 NativeWind 5 Tailwind v4 時(shí)再議。9. 小結(jié)與延伸閱讀這次遷移的方法論可以濃縮為四條復(fù)制粘貼所有權(quán)優(yōu)先于庫(kù)綁定默認(rèn)值優(yōu)先拒絕稍微改進(jìn)版原語(yǔ)用原生 組件庫(kù) 討論瀑布消滅不必要的抽象層基礎(chǔ)設(shè)施先行、canary 組件驗(yàn)證、批量推進(jìn)時(shí)設(shè)停止規(guī)則。配套文檔遷移主文檔 rnr-migration.md、持久 UI/主題規(guī)則 CLAUDE.md UI components theming 章節(jié)、主題實(shí)現(xiàn)的四個(gè)文件 global.css / tailwind.config.js / lib/theme.ts / lib/use-color-scheme.ts以及接線入口 app/_layout.tsx?!久赓M(fèi)下載鏈接】multicaMake humans and AI agents work as one team — open-source and self-hostable.項(xiàng)目地址: https://gitcode.com/GitHub_Trending/mu/multica創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考