Memoizationとは
コンピュータプログラムが同じ計算を繰り返さなければならない時、以前に計算した値をメモリに保存することで、同じ計算の反復実行を除去してプログラム実行速度を速くする技術です。
React.memo
reactでMemoizaitonを実現するためにはReact.memoを使います。同一のpropsでレンダリングをするならば(propsが変わっていなかったら、)、React.momoを使用し、性能向上を実現することができます。 memoを使用すれば、Reactはコンポーネントをレンダリングしないで
最後にレンダリングされた結果を再利用する。
memoを使用しない場合
import React, { useState } from 'react'
import { useEffect } from 'react';
import Comments from './Comments';
const commentList = [
{title: "comment1", content: "message1", likes: 1},
{title: "comment2", content: "message2", likes: 1},
{title: "comment3", content: "message3", likes: 1},
];
export default function Memo() {
const [comments, setComments] = useState(commentList);
useEffect(() => {
const interval = setInterval(() => {
// functional setState, prevComment は前のstate値!
setComments((prevComment) => [
...prevComment,
{
title: `comment${prevComment.length + 1}`,
content: `message1${prevComment.length + 1}`,
likes: 1
},
]);
}, 1000);
// componentがunmountする時に、returnが実行される。
return () => {
clearInterval(interval);
}
})
return (
<Comments commentList={comments} />
)
}
Memoコンポーネントは、1秒ごとに新しいcommentListを作成して、prevComment配列に足して
Commentsコンポーネントにpropsにて渡します!
import React from 'react'
import CommentItem from './CommentItem'
// 親componentからcommentList propsを受け取る。
export default function Comments({commentList}) {
return (
<div>
{commentList.map(comment => <CommentItem
key={comment.id}
title={comment.title}
content={comment.content}
likes={comment.likes}
/>
)}
</div>
)
}
Commentsコンポーネントは、親コンポーネントから受け取ったcommentListを子コンポーネントにpropsにて
渡します。
import React, { memo } from 'react'
import './CommentItem.css'
function CommentItem({title, content, likes}) {
return (
<div className="CommentItem">
<span>{title}</span>
<br />
<span>{content}</span>
<br />
<span>{likes}</span>
<span>{test}</span>
</div>
)
}
export default CommentItem;
結果、1秒ごとに新しいコンポーネントが追加されます。
果たしてレンダの状況はどうでしょう?それが知りたい場合は、Profile APIを使えば性能チェックが可能です。
function onRenderCallbackを作成して、タグを性能チェックをしたいロジックをで囲めば終わりです。
import React, { Profiler, memo } from 'react'
import './CommentItem.css'
function CommentItem({title, content, likes}) {
function onRenderCallback(
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime,
interactions
) {
// レンダリング度にログを出力する!
console.log(`actualDuration(${title}: ${actualDuration})`);
}
return (
<Profiler id="CommentItem" onRender={onRenderCallback}>
<div className="CommentItem" onClick={handleClick}>
<span>{title}</span>
<br />
<span>{content}</span>
<br />
<span>{likes}</span>
</div>
</Profiler>
)
}
export default CommentItem;
Provider APIを使ってレンダリングの状況を把握してみました。
その結果は、新しいCommentItemが追加されるたびに、新しいのみではなく全てのコンポーネントが
レンダリングされてしまいます。既に作成済みのcommentItemもまたレンダリングされるので
相当に非効率です。
memoを使用した場合
export default memo(CommentItem); // memoはHCO!
memoの使い方は簡単です。memoはHCO(Higher Order Compoenet)として使います。
memo()にMemoizationしたいcomponentを入れます。
commentItemが12まで作成された時点のログです。最新のコンポーネントだけがレンダリングされ
前に作成したコンポーネントはmemoにメモされ再レンダリングは発生しないことがわかります。
上記のロジックで言うと、memoはCommentItemの親のpropsと同一の結果を出力するなら、それらをレンダリングせず前回のレンダリングした結果を再使用します!
React.memoは概ね以下の流れで動作します。
①コンポーネントをレンダリングする。
②①の結果をMemoizingします。
③次回のレンダリングされるもののpropsと前回のレンダリングのpropsが同じだったら、reactはMemoizingしたものを再使用し、(shallow compare)
④新しく作成されたコンポーネントのみレンダリングします。
ここで、もしオブジェクトをpropsにて渡すとどうなるでしょ!もみてみたいですね。