サイトアイコンFronted Code

私の知識や、知ってたらよかったこと等が書いてあるブログです

2021/04/06

ReactReduxにArray.pushは禁物

Redux に Array.push は禁物

はじめに

Redux を使っていると、「あれなんでこれレンダーされないんだ?」ということがあると思います。今回は Array.push は Redux では禁物だよという題目で話していこうと思います。

Array.pushとは

Array.push とは、Javascript の組み込み関数の一つで配列の末尾に指定した配列を追加します(詳しくはこちら

今回はこの関数を使用しますので覚えておいてください。

レンダリングされないState更新

例えば、このようなコードで Todo リストの Add 機能を作ろうとします。

//aciton
export const ADD_TASK="ADD_TASK";

export const addTask=({text})=>{
	return{
		type:ADD_TASK,
		payload:text
	}
}

//reducer
const initialState=[
	{
		id:1,
		text:"課題を終わらせる"
	}
];

const id=0;

const task=(state=initialState,action)=>{
	switch(action.type){
		case ADD_TASK:
			id++;
			state.push({
				id,
				text
			})
		default:
			return state;
	}
}

//View画面は無視

action から text が送られてきて、それをインクリメントされた id と一緒に state に push する。

これは一見正常に動きそうに見えますが、これはダメなコードです。

いったい何がダメなのでしょうか、理由は追加に用いた Array.push 関数にあります。

浅いコピー / 深いコピー

実は、Array.push 関数は深いコピーであるため、レンダリング(更新)がされないのです。

少し聞き慣れない言葉が出てきましたね。深いコピーとはなんでしょうか。

深いコピーとは、オブジェクトの最深部までコピーする方式です。

例えば、以下のようなコードがあるとします。

let A = {
	id:0,
	text:"歯医者行く"
};

let B = JSON.parse(JSON.stringify(A));

B.text="焼肉食べに行く";

console.log(A);
//{id:0,text:"歯医者行く"}

console.log(B);
//{id:0,text:"焼肉食べに行く"}

オブジェクト A を B がコピーして、B のオブジェクトの text を変更しました。

これにより、A と B でそれぞれ違う text をもったオブジェクトが出来ました。

この時、A と B の text が違うのはB が A の値を最深部まで読み取ったからです。

つまり、例えば B から見て A がこのように見えていた場合、A と B どちらも変更された値になってしまうわけです。

//キーしか見えない
let A = {
	id:~~~~,
	text:"~~~~"
};

このように、オブジェクトの一番上しか見えない方式のコピーを浅いコピーといいます。

浅いコピーを用いて、先ほどのコードを書き直してみましょう。

let A = {
	id:0,
	text:"歯医者行く"
};

let B = A;

B.text="焼肉食べに行く";

console.log(A);
//{id:0,text:"焼肉食べに行く"}

console.log(B);
//{id:0,text:"焼肉食べに行く"}

今度は A,B どちらの text も変更されてしまいましたね。これは先ほど説明した通り、B には A の深い部分が見えないために手持ちの情報を用いて代入するしかないからです。

これは Javascript の基盤となる知識なのでしっかり覚えておきましょう。

Reduxは再レンダリング判断に浅いコピーを採用している

Redux は再レンダリングするべきかどうかの判断に対して、浅いコピーが行われたかどうかで決めています。

理由はパフォーマンスをよくするため、だそうです。

正しい書き方

という訳で先ほどの Todo サンプルコードを書き換えてみましょう。

//aciton
export const ADD_TASK="ADD_TASK";

export const addTask=({text})=>{
	return{
		type:ADD_TASK,
		payload:text
	}
}

//reducer
const initialState=[
	{
		id:1,
		text:"課題を終わらせる"
	}
];

const id=0;

const task=(state=initialState,action)=>{
	switch(action.type){
		case ADD_TASK:
			id++;
			//スプレッド構文を用いて、浅いコピーをおこないそれをリターンする
			return {
				...state,
				{
					id,
					text,
				}
			}
		default:
			return state;
	}
}

//View画面は無視

浅いコピーということなので、Object.assign でも良いのですがせっかくなので、es6 で新しく追加された構文のスプレッド構文を採用しました。

使い方はこちらの Qiita の記事からわかりやすく説明されています。

まとめ

いかがだったでしょうか。

Redux は複雑で難しいという印象をもっている方も多いと思います。(私もその一人です)

ですが、一つ一つの問題は全て qiita や github 等に載ってることがほとんどです。

トライアンドエラーを繰り返して理解を深めていきましょう。