使用 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 回调结束时解决承诺。