入:從 React 原生實(shí)現(xiàn)到 useImport 鉤子的完整實(shí)踐)
Refine 中的 CSV 文件導(dǎo)入從 React 原生實(shí)現(xiàn)到 useImport 鉤子的完整實(shí)踐【免費(fèi)下載鏈接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.項(xiàng)目地址: https://gitcode.com/GitHub_Trending/re/refine本文以 Refine 官方博客《How to Import CSV File with React》為主線完整講解如何在 React 中接收用戶上傳的 CSV 文件并用 JavaScript 解析、展示再進(jìn)一步結(jié)合 Refine 核心的useImport鉤子與ImportButton組件把CSV 導(dǎo)入變成管理后臺中開箱即用的功能。讀完本文你將掌握用input typefileFileReader讀取 CSV 原始文本的完整流程、用slice/split將 CSV 文本手工轉(zhuǎn)換為對象數(shù)組Array of Object的算法細(xì)節(jié)以及 Refine 源碼層面useImport的分批提交batchSize、進(jìn)度回調(diào)onProgress與結(jié)果匯總onFinish的實(shí)現(xiàn)原理。一、目標(biāo)與整體思路CSV 導(dǎo)入是管理面板、內(nèi)部工具類應(yīng)用的常見需求用戶從本地選擇一個 CSV 文件應(yīng)用讀取它、把每一行轉(zhuǎn)換成結(jié)構(gòu)化對象然后渲染到表格或提交給后端。原文檔將整個過程拆成兩個階段純 React JavaScript 階段創(chuàng)建一個帶文件選擇框的表單用FileReader讀取 CSV 文件內(nèi)容為純文本再用字符串操作把它解析成對象數(shù)組最后渲染進(jìn)table元素Refine 集成階段借助useImport鉤子和ImportButton組件把解析 CSV 批量寫入后端封裝成標(biāo)準(zhǔn)能力支持batchSize分批提交。第一階段幫助你理解底層原理第二階段則是 Refine 提供的生產(chǎn)級方案。二、React 原生實(shí)現(xiàn)第一步創(chuàng)建文件上傳表單第一步是創(chuàng)建一個簡單的表單讓用戶選擇 CSV 文件。關(guān)鍵點(diǎn)在input元素的accept屬性它指定了只允許選擇 CSV 格式的文件function App() { return ( div style{{ textAlign: center }} h1REACTJS CSV IMPORT EXAMPLE /h1 form input type{file} accept{.csv} / buttonIMPORT CSV/button /form /div ); }accept{.csv}只是在文件選擇器中過濾擴(kuò)展名并不是嚴(yán)格的安全校驗(yàn)服務(wù)端仍應(yīng)校驗(yàn)文件內(nèi)容。三、用 FileReader 讀取 CSV 文件內(nèi)容accept只負(fù)責(zé)選文件真正讀文件要交給瀏覽器原生的 FileReader API。下面的完整代碼在表單中增加了狀態(tài)管理file狀態(tài)保存用戶選中的File對象handleOnChange在文件變化時更新它handleOnSubmit在提交時通過fileReader.readAsText(file)異步讀取文件文本讀取完成后fileReader.onload中的event.target.result就是 CSV 的原始字符串import React, { useState } from react; function App() { const [file, setFile] useState(); const fileReader new FileReader(); const handleOnChange (e) { setFile(e.target.files[0]); }; const handleOnSubmit (e) { e.preventDefault(); if (file) { fileReader.onload function (event) { const csvOutput event.target.result; }; fileReader.readAsText(file); } }; return ( div style{{ textAlign: center }} h1REACTJS CSV IMPORT EXAMPLE /h1 form input type{file} id{csvFileInput} accept{.csv} onChange{handleOnChange} / button onClick{(e) { handleOnSubmit(e); }} IMPORT CSV /button /form /div ); }選擇并加載 CSV 文件后csvOutput中就是文件的純文本內(nèi)容可以在瀏覽器控制臺中直接查看。此時我們拿到的是一整段文本還不能直接渲染或提交下一步要把它結(jié)構(gòu)化為數(shù)組。四、把 CSV 文本解析為對象數(shù)組并渲染成表格原文檔的核心手法是用 JavaScript 的 slice 和 split 方法把純文本 CSV 拆成表頭數(shù)組 數(shù)據(jù)行數(shù)組兩個獨(dú)立數(shù)組再把 Header 和 Rows 按 Key/Value 對應(yīng)關(guān)系轉(zhuǎn)換成 Array of Object 格式function App() { const [file, setFile] useState(); const [array, setArray] useState([]); const fileReader new FileReader(); const handleOnChange (e) { setFile(e.target.files[0]); }; const csvFileToArray (string) { const csvHeader string.slice(0, string.indexOf(\n)).split(,); const csvRows string.slice(string.indexOf(\n) 1).split(\n); const array csvRows.map((i) { const values i.split(,); const obj csvHeader.reduce((object, header, index) { object[header] values[index]; return object; }, {}); return obj; }); setArray(array); }; const handleOnSubmit (e) { e.preventDefault(); if (file) { fileReader.onload function (event) { const text event.target.result; csvFileToArray(text); }; fileReader.readAsText(file); } }; const headerKeys Object.keys(Object.assign({}, ...array)); return ( div style{{ textAlign: center } h1REACTJS CSV IMPORT EXAMPLE /h1 form input type{file} id{csvFileInput} accept{.csv} onChange{handleOnChange} / button onClick{(e) { handleOnSubmit(e); }} IMPORT CSV /button /form br / table thead tr key{header} {headerKeys.map((key) ( th{key}/th ))} /tr /thead tbody {array.map((item) ( tr key{item.id} {Object.values(item).map((val) ( td{val}/td ))} /tr ))} /tbody /table /div ); }解析邏輯可以逐行拆開看string.slice(0, string.indexOf(\n))取第一行作為表頭.split(,)得到列名數(shù)組例如[id, title, status]string.slice(string.indexOf(\n) 1)跳過表頭行后按換行符split(\n)得到每一行的字符串每行再按逗號split(,)得到values然后用csvHeader.reduce按列索引把values[index]掛到以列名為 key 的對象上最終得到一個對象數(shù)組headerKeys通過Object.keys(Object.assign({}, ...array))從數(shù)據(jù)行對象中聚合出所有列名用于渲染thead。這套手工解析適合簡單場景但對字段內(nèi)包含逗號/換行/引號轉(zhuǎn)義的復(fù)雜 CSV 無能為力——這正是 Refine 官方實(shí)現(xiàn)改用成熟解析庫的原因見后文。原文檔中的 CodeSandbox 在線示例可以按同樣代碼直接復(fù)制運(yùn)行驗(yàn)證效果。五、Refine 集成useImport 與 ImportButton在 Refine 中CSV 導(dǎo)入是開箱即用的功能。最小用法只需引入useImport鉤子和ImportButton組件import { List, Table, useTable, useImport, ImportButton, } from refinedev/antd; export const PostList: React.FC () { const { tableProps } useTableIPost(); const importProps useImportIPostFile(); return ( List pageHeaderProps{{ extra: ImportButton {...importProps} /, }} Table {...tableProps} rowKeyid Table.Column dataIndexid titleID / Table.Column dataIndextitle titleTitle / Table.Column dataIndexstatus titleStatus / /Table /List ); }; interface IPostFile { title: string; categoryId: string; } interface IPost { id: string; title: string; status: string; }useImportIPostFile()的泛型參數(shù)是 CSV 解析后每條記錄的類型importProps展開到ImportButton上后用戶點(diǎn)擊按鈕選文件即可觸發(fā)導(dǎo)入數(shù)據(jù)會通過dataProvider寫入List當(dāng)前所在路由對應(yīng)的資源。官方倉庫中還有完整的 import/export 示例工程如 import-export-antd 示例對應(yīng)博客中鏈接的import-export示例目錄可直接參考。六、源碼剖析useImport 是如何工作的上一節(jié)的用法來自 useImport 源碼它的實(shí)現(xiàn)比博客示例聰明得多核心要點(diǎn)如下用 PapaParse 替代手工 slice/splithandleChange內(nèi)部調(diào)用papaparse.parse(file, { complete, ...paparseOptions })解析文件而不是手寫字符串切割因此天然支持引號轉(zhuǎn)義、換行等標(biāo)準(zhǔn) CSV 邊界情況。你還可以通過paparseOptions傳入自定義的 PapaParseParseConfig表頭到對象的映射由 importCSVMapper 完成解析出的二維數(shù)組交給 importCSVMapper它用fromPairs(zip(headers, entry))把表頭行 數(shù)據(jù)行逐行壓成對象再對每條記錄調(diào)用mapData回調(diào)默認(rèn)恒等。也就是說博客第四節(jié)的csvFileToArray在 Refine 內(nèi)部由importCSVMapper等價實(shí)現(xiàn)但多了 lodash 的穩(wěn)定處理batchSize 決定走 create 還是 createMany從源碼結(jié)構(gòu)看batchSize默認(rèn)值為Number.MAX_SAFE_INTEGER即所有記錄合并為一批、走useCreateMany要求dataProvider實(shí)現(xiàn)了createMany當(dāng)batchSize 1時改為逐條走useCreate。分批通過lodash/chunk切塊并用 sequentialPromises 順序執(zhí)行避免并發(fā)打爆后端。這就是博客可以配合batchSize選項(xiàng)在插入過程中把數(shù)據(jù)分塊的底層出處進(jìn)度與結(jié)果回調(diào)每處理完一條/一塊setProcessedAmount累加useEffect監(jiān)聽到totalAmount/processedAmount變化后觸發(fā)onProgress({ totalAmount, processedAmount })全部結(jié)束后handleFinish把結(jié)果按type success | error拆成succeeded與errored兩組交給onFinish便于你統(tǒng)計(jì)成功條數(shù)或提示失敗原因資源與元數(shù)據(jù)resource未顯式傳入時從當(dāng)前路由中讀取useResourceParamsmeta會被useMeta()合并后透傳給dataProvider存在多個dataProvider時可用dataProviderName指定目標(biāo)。完整的選項(xiàng)類型定義在ImportOptions中包括resource、mapData、paparseOptions、batchSize、onFinish、onProgress、meta、dataProviderName返回值則包含可直接綁定的inputProps固定type: file、accept: .csv的輸入屬性、mutationResult、isLoading與handleChange。七、當(dāng)前文檔版本中的用法參考官方文檔站中的 useImport 交互式示例展示了與上文一致的完整用法用useImportIProduct({ resource, onFinish, onProgress })拿到inputProps和isLoading把inputProps直接展開到一個input namecsv {...inputProps} /上導(dǎo)入期間界面實(shí)時顯示processed / total進(jìn)度完成后通過onFinish彈出提示。需要注意該示例基于較新版本的 API列表數(shù)據(jù)用useList獲取而本博客最初撰寫于 Refine 3.x 時代、代碼示例已更新到 4.x官方博客注明 4.x 與 3.x 向后兼容遷移差異可參考倉庫中的 migration guide 文檔。八、小結(jié)純 React 方案input typefile accept.csv選擇文件 →FileReader.readAsText讀取文本 →slice/split拆表頭和數(shù)據(jù)行 →reduce映射成對象數(shù)組 → 渲染table。原理清晰但解析能力有限Refine 方案useImport用 PapaParse 做健壯解析importCSVMapper完成表頭到對象的映射batchSize控制useCreateMany批量提交或逐條useCreateonProgress/onFinish提供進(jìn)度與結(jié)果鉤子配合ImportButton一行代碼即可掛到List頁頭兩個關(guān)鍵源碼入口值得深入閱讀useImport 實(shí)現(xiàn) 與 importCSVMapper以及各 UI 框架下的按鈕實(shí)現(xiàn)如 antd 的 ImportButton它用 antd 的Upload包裹Button并復(fù)用useImportButton的本地化文案?!久赓M(fèi)下載鏈接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.項(xiàng)目地址: https://gitcode.com/GitHub_Trending/re/refine創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考