使用 redux-sagas 在 React 应用程序中导入 XLSX 文件
Import XLSX file in React app with redux-sagas
我是 redux-sagas 的新手,正在努力使下面的代码工作。
这是我正在使用的 saga,它应该读取一个 xlsx 文件并在完成后分派一个动作。
该操作应该在文件被完全读取后分派,但事实并非如此,我不知道我做错了什么。
import { put } from 'redux-saga/effects';
import * as XLSX from 'xlsx';
import * as actionTypes from '../actions/actionTypes';
export function* importFileSaga(action) {
console.log('[importFileSaga]', action.fileToUpload);
const response = yield importFile(action.fileToUpload);
console.log('[importFileSaga]', response);
yield put ({type: actionTypes.SET_DATA, payload: response});
}
const importFile = file2upload => {
console.log('[importFile]', file2upload);
const reader = new FileReader();
reader.onload = (evt) => {
/* Parse data */
const bstr = evt.target.result;
const wb = XLSX.read(bstr, { type: 'binary' });
/* Get first worksheet */
const wsname = wb.SheetNames[0];
const ws = wb.Sheets[wsname];
const data = XLSX.utils.sheet_to_csv(ws, { header: 1 });
const processedData = processData(data);
console.log('[importFile]',processedData);
return processedData;
};
reader.readAsBinaryString(file2upload);
}
// process CSV data
const processData = dataString => {
const dataStringLines = dataString.split(/\r\n|\n/);
const headers = dataStringLines[0].split(/,(?![^"]*"(?:(?:[^"]*"){2})*[^"]*$)/);
const list = [];
for (let i = 1; i < dataStringLines.length; i++) {
const row = dataStringLines[i].split(/,(?![^"]*"(?:(?:[^"]*"){2})*[^"]*$)/);
if (headers && row.length === headers.length) {
const obj = {};
for (let j = 0; j < headers.length; j++) {
let d = row[j];
if (d.length > 0) {
if (d[0] === '"')
d = d.substring(1, d.length - 1);
if (d[d.length - 1] === '"')
d = d.substring(d.length - 2, 1);
}
if (headers[j]) {
obj[headers[j]] = d;
}
}
// remove the blank rows
if (Object.values(obj).filter(x => x).length > 0) {
list.push(obj);
}
}
}
// prepare columns list from headers
const columns = headers.map(c => ({
name: c,
selector: c,
}));
const processedData = {header: columns, data: list};
console.log('[processData]', processedData);
return processedData;
}
上传新文件时,这是我在控制台中收到的输出序列
[importFileSaga]
File { name: "test.csv", lastModified: 1603705847000, webkitRelativePath: "", size: 1580, type: "text/csv" }
importFile.js:7
[importFile]
File { name: "test.csv", lastModified: 1603705847000, webkitRelativePath: "", size: 1580, type: "text/csv" }
importFile.js:14
[importFileSaga] undefined importFile.js:9
SET_DATA reducers.js:31
undefined
TypeError: action.payload is undefined
The above error occurred in task importFileSaga
created by takeEvery(UPLOAD_FILE_START, importFileSaga)
created by watchImport
Tasks cancelled due to error:
takeEvery(UPLOAD_FILE_START, importFileSaga)
takeEvery(TEST_SAGA_INIT, importDeviceSaga) index.js:1
[processData]
Object { header: (50) […], data: (2) […] }
importFile.js:71
[importFile]
Object { header: (50) […], data: (2) […] }
这出乎意料...但我想我可能还是有点困惑。
我期待这样的事情
[importFileSaga]
File { name: "test.csv", lastModified: 1603705847000, webkitRelativePath: "", size: 1580, type: "text/csv" }
importFile.js:7
[importFile]
File { name: "test.csv", lastModified: 1603705847000, webkitRelativePath: "", size: 1580, type: "text/csv" }
importFile.js:14
[processData]
Object { header: (50) […], data: (2) […] }
importFile.js:71
[importFile]
Object { header: (50) […], data: (2) […] }
[importFileSaga] some data here importFile.js:9
SET_DATA reducers.js:31
我明白了这行代码
const response = yield importFile(action.fileToUpload);
表示在 importFile 完成之前不会执行下面的行。那是对的吗?
我该如何解决?
-- 编辑--
感谢 markerikson 我修改了 importFile 函数
const importFile = file2upload => {
return new Promise((resolve, reject) => {
console.log('[importFile]', file2upload);
const reader = new FileReader();
reader.onload = (evt) => {
/* Parse data */
const bstr = evt.target.result;
const wb = XLSX.read(bstr, { type: 'binary' });
/* Get first worksheet */
const wsname = wb.SheetNames[0];
const ws = wb.Sheets[wsname];
const data = XLSX.utils.sheet_to_csv(ws, { header: 1 });
const processedData = processData(data);
console.log('[importFile]',processedData);
//return processedData;
resolve(processedData);
};
reader.readAsBinaryString(file2upload);
});
}
现在它就像一个魅力!
概念上有两个问题:
yield importFile()
将 undefined
传递给 saga 中间件,这可能会继续进行
- 文件reader不会立即执行回调,您的代码确实需要等到它完成。
您需要重构代码,使其等到 reader 逻辑完成。
一个选项可能是在 importFile
和 return 中手动创建一个 Promise
,并在 .onload
回调结束时解决承诺。
我是 redux-sagas 的新手,正在努力使下面的代码工作。
这是我正在使用的 saga,它应该读取一个 xlsx 文件并在完成后分派一个动作。 该操作应该在文件被完全读取后分派,但事实并非如此,我不知道我做错了什么。
import { put } from 'redux-saga/effects';
import * as XLSX from 'xlsx';
import * as actionTypes from '../actions/actionTypes';
export function* importFileSaga(action) {
console.log('[importFileSaga]', action.fileToUpload);
const response = yield importFile(action.fileToUpload);
console.log('[importFileSaga]', response);
yield put ({type: actionTypes.SET_DATA, payload: response});
}
const importFile = file2upload => {
console.log('[importFile]', file2upload);
const reader = new FileReader();
reader.onload = (evt) => {
/* Parse data */
const bstr = evt.target.result;
const wb = XLSX.read(bstr, { type: 'binary' });
/* Get first worksheet */
const wsname = wb.SheetNames[0];
const ws = wb.Sheets[wsname];
const data = XLSX.utils.sheet_to_csv(ws, { header: 1 });
const processedData = processData(data);
console.log('[importFile]',processedData);
return processedData;
};
reader.readAsBinaryString(file2upload);
}
// process CSV data
const processData = dataString => {
const dataStringLines = dataString.split(/\r\n|\n/);
const headers = dataStringLines[0].split(/,(?![^"]*"(?:(?:[^"]*"){2})*[^"]*$)/);
const list = [];
for (let i = 1; i < dataStringLines.length; i++) {
const row = dataStringLines[i].split(/,(?![^"]*"(?:(?:[^"]*"){2})*[^"]*$)/);
if (headers && row.length === headers.length) {
const obj = {};
for (let j = 0; j < headers.length; j++) {
let d = row[j];
if (d.length > 0) {
if (d[0] === '"')
d = d.substring(1, d.length - 1);
if (d[d.length - 1] === '"')
d = d.substring(d.length - 2, 1);
}
if (headers[j]) {
obj[headers[j]] = d;
}
}
// remove the blank rows
if (Object.values(obj).filter(x => x).length > 0) {
list.push(obj);
}
}
}
// prepare columns list from headers
const columns = headers.map(c => ({
name: c,
selector: c,
}));
const processedData = {header: columns, data: list};
console.log('[processData]', processedData);
return processedData;
}
上传新文件时,这是我在控制台中收到的输出序列
[importFileSaga]
File { name: "test.csv", lastModified: 1603705847000, webkitRelativePath: "", size: 1580, type: "text/csv" }
importFile.js:7
[importFile]
File { name: "test.csv", lastModified: 1603705847000, webkitRelativePath: "", size: 1580, type: "text/csv" }
importFile.js:14
[importFileSaga] undefined importFile.js:9
SET_DATA reducers.js:31
undefined
TypeError: action.payload is undefined
The above error occurred in task importFileSaga
created by takeEvery(UPLOAD_FILE_START, importFileSaga)
created by watchImport
Tasks cancelled due to error:
takeEvery(UPLOAD_FILE_START, importFileSaga)
takeEvery(TEST_SAGA_INIT, importDeviceSaga) index.js:1
[processData]
Object { header: (50) […], data: (2) […] }
importFile.js:71
[importFile]
Object { header: (50) […], data: (2) […] }
这出乎意料...但我想我可能还是有点困惑。 我期待这样的事情
[importFileSaga]
File { name: "test.csv", lastModified: 1603705847000, webkitRelativePath: "", size: 1580, type: "text/csv" }
importFile.js:7
[importFile]
File { name: "test.csv", lastModified: 1603705847000, webkitRelativePath: "", size: 1580, type: "text/csv" }
importFile.js:14
[processData]
Object { header: (50) […], data: (2) […] }
importFile.js:71
[importFile]
Object { header: (50) […], data: (2) […] }
[importFileSaga] some data here importFile.js:9
SET_DATA reducers.js:31
我明白了这行代码
const response = yield importFile(action.fileToUpload);
表示在 importFile 完成之前不会执行下面的行。那是对的吗? 我该如何解决?
-- 编辑--
感谢 markerikson 我修改了 importFile 函数
const importFile = file2upload => {
return new Promise((resolve, reject) => {
console.log('[importFile]', file2upload);
const reader = new FileReader();
reader.onload = (evt) => {
/* Parse data */
const bstr = evt.target.result;
const wb = XLSX.read(bstr, { type: 'binary' });
/* Get first worksheet */
const wsname = wb.SheetNames[0];
const ws = wb.Sheets[wsname];
const data = XLSX.utils.sheet_to_csv(ws, { header: 1 });
const processedData = processData(data);
console.log('[importFile]',processedData);
//return processedData;
resolve(processedData);
};
reader.readAsBinaryString(file2upload);
});
}
现在它就像一个魅力!
概念上有两个问题:
yield importFile()
将undefined
传递给 saga 中间件,这可能会继续进行- 文件reader不会立即执行回调,您的代码确实需要等到它完成。
您需要重构代码,使其等到 reader 逻辑完成。
一个选项可能是在 importFile
和 return 中手动创建一个 Promise
,并在 .onload
回调结束时解决承诺。