現象と結論
Next.js/React.js + TypeScript で、react-papaparse や papaparse を利用すると、Parse 後の戻り値の型が unknown になるため、扱いづらい。というか扱えない。
解決策としては、any を付ける。
検証環境
Next.js + TypeScript
node: v16.13.1
next: 12.0.8
react: 17.0.2
react-papaparse: 4.0.2
(papaparse: 5.3.1)
CSV をパースしたかった
Next.js + TypeScript で、ローカルの CSV をロードして、parse したかった。とある事情により、ファイルのアップロードができなかったので(面倒くさかったので)、input type=file
を onChange
で直接 parse するようにした。
import { usePapaParse } from 'react-papaparse';
...
const [ csvContent, setCsvContent ] = useState<Array<CsvType>>([]);
...
const onChangeRead = async (e: ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files[0]) {
const csvdata = await e.target.files[0].text();
readString(csvdata, {
worker: true,
complete: (results) => {
setCsvContent(results.data);
},
header: true,
skipEmptyLines: true,
});
}
};
...
<input type="file" onChange={onChangeRead} />
エラー処理とかはいったん置いといて、こんな感じ。とりあえず動く。なお、react-papaparse は papaparse をラップした UI 系の Module?なので、CSV を読むだけなら papaparse で事足りる。
build できない
yarn dev
では動くけど、yarn build
はできない。問題はここ。
complete: (results) => {
setCsvContent(results.data);
},
Argument of type 'unknown[]' is not assignable to parameter of type 'SetStateAction<CsvType[]>'.
Type 'unknown[]' is not assignable to type 'CsvType[]'.
Type 'unknown' is not assignable to type 'CsvType'.ts
unknown... だと...?
results
は、
results = {
data: [ ... ], // parsed data
errors: [ ... ], // errors encountered
meta: { ... } // extra parse info
}
という Object
で返ってくる。
results.data
は、
(property) ParseResult<unknown>.data: unknown[]
an array of rows. If header is false, rows are arrays; otherwise they are objects of data keyed by the field name.
とあり、CSV の header 有り無しで内容が変わるようだけど、いずれにせよ Array
で返ってくる。
unknown
だと何にもできないので困る。return して変数に入れれば良い?
readString
は、
const readString: <unknown>(csvString: string, config: ParseWorkerConfig<unknown> & {
download?: false | undefined;
}) => void
void
... 読めるけど、まじ state 書けな〜い。ちょっと詰んだ感。読み込まれる CSV がなにか分からないから unknown
なの?そんなバカな!!
コードを当たる。
import PapaParse, { ParseWorkerConfig } from 'papaparse';
export function readString<T>(
csvString: string,
config: ParseWorkerConfig<T> & { download?: false | undefined },
) {
return PapaParse.parse(csvString, config);
}
Generics か。TypeScript が型推論を放棄してるんですね。
React, React + TypeScript, Next, Next + TypeScript の4パターンで確認したところ、TypeScript の場合は、unknown, それ以外は、any で返ってきました。any かぁ...うーん、何だかな。
ここの、readString<T>
が readString<T = any>
であったならば… と思いましたが、本質的でないし、papaparse 本体は、ちょっと読むの面倒だし(ちょっと読んでやめた)、他にも同様の現象が発生してるみたいなので、そっと閉じた。いま忙しい。
というわけで、現時点での解決策。
解決策
どうせ良くて any で返って来るなら、any をこっちで嫌々指定する。complete: (results: any) => {...}
ここの部分。
const [ csvContent, setCsvContent ] = useState<Array<CsvType>>([]);
...
const onChangeRead = async (e: ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files[0]) {
const csvdata = await e.target.files[0].text();
readString(csvdata, {
worker: true,
complete: (results: any) => {
setCsvContent(results.data);
},
header: true,
skipEmptyLines: true,
});
}
};
...
<input type="file" onChange={onChangeRead} />
tsconfig.json
をいじっても動くけど、超絶非推奨なので詳細割愛。
papaparse の場合
CSV を Parse するだけだったら、react-papaparse
を使う必要は全くない。react-papaparse
は、パースしする際・した後をいい感じにするための module なので、単純に CSV を Parse するだけなら、papaparse
本体を使うべきである。
papaparse
なら unknown
にならないか?というと、なる。ので、同様に any
をつける。
...
import Papa from 'papaparse';
...
const results = Papa.parse<any>(csvString, {
header: true,
});
setCsvContent(results.data);
...
おしまい。