Array.reduce TIPS

自己紹介



  • 清水正行
  • DataViz / GIS エンジニア
  • 某新聞社勤務
  • 毎日東京に通ってます

Array.reduceとは

reduceメソッドは、配列の各要素に対して順番にコールバック関数を実行します。
コールバック関数の一つ目の引数は、アキュームレーターと呼ばれ、コールバック関数が処理された際の戻り値が蓄積されます。
//書式
Array.reduce(コールバク関数(アキュームレーター, 値){ 
	return 戻り値 
}, 初期値)

reduceを使って総和を求める

//処理対象となる配列
const array = [1,2,3,4,5]
//reduceに渡す総和を求めるコールバック
const sum = (acc, cur) => acc + cur;
//コールバックを適用する
const result = array.reduce(sum, 0); //0は初期値

output("example1", result)

動作フロー


配列:[1,2,3,4,5]
初期値: 0

callback1 -> 0 + 1 -> 1
callback2 -> 1 + 2 -> 3
callback3 -> 3 + 3 -> 6
callback4 -> 6 + 4 -> 10
callback5 -> 10 + 5 -> 15

-> 15

なにが便利なのか?

プログラムでは、「前の処理の結果を受けて次の処理を行う」という順番が重要となる逐次処理が頻出します。
reduceメソッドを使うことで、こういった処理を完結に記述することができます。

ファンクション・チェイン

関数をネスト(入れ子)せずともチェインできる

const state = 0; //初期値

const add1 = (d) => d + 1;	//1を足す
const sub1 = (d) => d - 1;	//1を引く

//初期値に対して配列に格納された関数を適用するreducer
const chainFn = (acc, fn)=>{ return fn(acc)}

//逐次処理を行う関数を配列に格納し、reduceを使って逐次処理を行う
const result = [add1, add1, add1, sub1].reduce(chainFn, state);
	
output("example3", result)
	

プロミス・チェイン

通常のプロミスチェイン

//1秒後に値を返すプロミス
const p1 = () => {
	return new Promise((resolve) => {
		setTimeout(() => {
			output("example4", "Run p1", true)
			resolve(10);
		}, 1000);
	});
};

//引数を倍増して2秒後に値を返すプロミス
const p2 = (d) => {
	return new Promise((resolve) => {
		setTimeout(() => {
			output("example4", "Run p2", true)
			resolve(d * 2);
		}, 2000);
	});
};

//引数に2を足して1秒後に値を返すプロミス
const p3 = (d) => {
	return new Promise((resolve) => {
		setTimeout(() => {
			output("example4", "Run p3", true)
			resolve(d + 2);
		}, 1000);
	});
};

//プロミスを順番に実行する
const promise = Promise.resolve();
promise
	.then(p1)
	.then(p2)
	.then(p3)
	.then((result) =>{
		output("example4", result)
	});

	

プロミスALLではチェインできない

// Promise.allにはコールバックでなく、promiseそのものを渡す必要がある
var arrayPromise = [ p1(), p2(), p3() ];

//戻り値の順序は保証されるが、処理自体は並列に行われる
Promise.all(arrayPromise)
	.then((result) =>{
		output("example5", result)
	});	

reduceを使ったプロミスチェイン

var arrayPromise = [ p1, p2, p3 ];

//プロミスを同期処理するreducer		
async function chain(acc, cur) {
	const obj = await acc; //解決済みプロミスを蓄積する
	const val = await cur(obj.sum).then((d) => d); //戻り値を抽出
	return { ...obj, sum: val };
}

//初期値として空の解決済みプロミスオブジェクトを指定する
var result = arrayPromise.reduce(chain, Promise.resolve({}));
	
result.then((result) =>{
	output("example6", result)
});


Redux/Reducer

Redux/Reducerとは?

ReduxはJavaScriptで単方向データフーローを実装するための、サポートライブラリです。
Reduxに含まれる機能の一つであるReducerは、現在のステートとステートを変更させる指示(アクション)を受け取り、新しいステートを生成します。
reducerを副作用のない純粋関数で記述することで、照透過性を保ったままステートのハンドリングを行うことができます。

Pure Javascriptを使って簡易的なReducerを実装してみる。

ベースとなるファンクションチェイン

const state = 0; //初期値
	
const add1 = (d) => d + 1;	//1を足す
const sub1 = (d) => d - 1;	//1を引く

const chainFn = (acc, fn)=>{ return fn(acc)}

const result = [add1, add1, add1, sub1].reduce(chainFn, state);
	
output("example7", result)
		

関数ではなくアクション(符号)に変更

const state = 0; //初期値

//ステートとアクションを受け取り、新しいstateを返す	
const reducer = (state, action)=>{
	switch(action){
		case 'ADD_1':
			return state + 1 //1を足す
		break;
		case 'SUB_1':
			return state -1 //1を引く
		break;
		default:
			return state
	}
}

const newState = ["ADD_1", "ADD_1", "ADD_1", "SUB_1"].reduce(reducer, state);
	
output("example8", newState)
		

汎用性をUP!

//初期state
const currentState = {
	sum: 0
};

// Reducer
const sumReducer = (state, action = {}) => {
	const { type, payload } = action;
	switch (type) {
		case 'ADD_VALUE':
			return Object.assign({}, state, {
				sum: state.sum + payload.value
			});
		break;
		case 'SUB_VALUE':
			return Object.assign({}, state, {
				sum: state.sum - payload.value
			});
		break;
		default:
			return state;
	}
};

//action creater
const addActionCreater = (value) => {
	return {
		type: 'ADD_VALUE',
		payload: { value: value } 
	}
}
const subActionCreater = (value) => {
	return {
		type: 'SUB_VALUE',
		payload: { value: value }
	}
}


const actions  = [
	addActionCreater(1),
	addActionCreater(2),
	addActionCreater(3),
	subActionCreater(6)
]

//reducerを使ってアクションを処理し新しいstateを生成する
const newState = actions.reduce(sumReducer, currentState); 
output("example9", newState)

まとめ

reduceを使うと、関数と関数の間での値の受け渡しを副作用のない形で記述することができます。
また、値をやり取りするための変数を外部のスコープに置く必要もなく、再代入させる必要もありません。

reduceはとても便利な機能なのでぜひ使ってみてください