TypeScript は JavaScript に型の機能を加えた言語です。型を用いた静的解析によって開発を助けてくれます。この型の機能を活かしたま再帰的な機能を作るちょっとしたコツがあります。普通に再帰をつくると再帰時と外から呼び出される時の型のミスマッチに悩まされがちですが、後述の例の様に関数の中に関数を作り、関数毎に型をつけることでこの悩みを避けられます。この関数の中に関数を置く方法で作った配列を再帰的に逆順にする関数とテストが次です。
/**
* 再帰的に配列を逆順にする。
* 再帰用の型でなく、一般的な配列を加工する関数的な引数と返り値の型を付ける
*/
export const arrReverseRecursive = <T extends unknown[]>(arr: T): T => {
/**
* 再帰本体。引数と返り値の型のために外と内で分ける。
* 引数と同じ型を返すという点では外側と同じ。配列以外を引数にとれる点が異なる
*/
function recursiveReverseFn<U>(v: U): U {
if (!Array.isArray(v)) {
// 配列でないならそのままを返す
return v;
}
// 返り値となる配列を用意
const ret = [];
// 配列ならば逆順にして返り値となる配列に要素を一つずつ追加していく
for (const vEl of v.reverse()) {
// 追加する要素をこの再帰的逆順関数に渡す。
// 渡した結果の返り値は配列でないならばそのままで、配列ならば逆順になっている
ret.push(recursiveReverseFn<typeof vEl>(vEl));
}
// 全部の要素を渡り終わったら全部逆順になっているので返す
return ret as U;
}
return recursiveReverseFn<T>(arr);
};
import 'jest';
import { arrReverseRecursive } from '@/common/helpers/arr/arrReverseRecursive';
describe(__filename, () => {
test('再帰配列逆順', () => {
expect(JSON.stringify(arrReverseRecursive(['a', 2, 3]))).toBe('[3,2,"a"]');
expect(JSON.stringify(arrReverseRecursive(['a', 'b', 3]))).toBe('[3,"b","a"]');
expect(JSON.stringify(arrReverseRecursive([1, 2, 3]))).toBe('[3,2,1]');
expect(JSON.stringify(arrReverseRecursive([1, [1, 2, 3], 3, 4]))).toBe('[4,3,[3,2,1],1]');
expect(JSON.stringify(arrReverseRecursive([1, [1, 2, 3], 3, [7, 8, [7, 8, 9]]]))).toBe(
'[[[9,8,7],8,7],3,[3,2,1],1]'
);
});
});
TypeScriptの型は便利な機能ですが成果物がブラウザ上で実行される場合、型そのものは機能に関係ありません。もし開発時に型にこだわって時間を使いすぎるなら、anyやasを使った方がマシです。とはいえうまく使えれば便利なことには変わりありません。上述のような小技でも知っていると案外役に立ちます。