Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
532
Help us understand the problem. What is going on with this article?
@Yametaro

5歳娘「パパのReact、めっちゃ遅いね!」

新しい記事もよろしくやで!
ハスケル子「タグごとに色がついてたらいいのにな…」

38歳無職ワイ

ワイ「(カタカタカタカタ・・・ッターン!)」

娘(5歳)「パパ、今日は何してるの?」

ワイ「今日はな、むかしWordPressで作った自分用TODOリストの」
ワイ「デザインをリニューアルしてんねん」

よめ太郎「(そんなことより職を探せや)」

娘「へぇ〜」
娘「WordPressってことは、PHPを書いてるの?」

ワイ「いや、ちゃうで」
ワイ「リニューアル後は、フロント部分をReactで実装しようと思ってな」
ワイ「そこで、WordPressをREST APIモードで使うことにしたんや」
ワイ「つまり、WordPressを管理画面つきAPIみたいに使うってことや」

娘「要は、WordPressをヘッドレスCMSとして使うんだね」

ワイ「ヘッドレス・・・?」
ワイ「ちゃうちゃう、管理画面つきAPIや」

娘「・・・まぁいいや」
娘「とにかく、APIからTODOリストのデータを取得して」
娘「それをReactで表示したいわけだね」

ワイ「そういうことや」

現在の進捗状況

娘「それで、フロントの実装はどこまでできたの?」

ワイ「ほぼ完成してるで」
ワイ「↓こんな感じや」

スクショ

よめ太郎「(酒ばっかりやないかい・・・)」

ワイ「だいぶ下のほうに『職を探す』っていうタスクも」
ワイ「ちゃんと入ってるで!」

よめ太郎「(ちゃんととは・・・)」

娘「へぇ〜」
娘「ちょっと触ってみてもいい?」

ワイ「もちろんええで」

娘「(ポチポチポチ・・・)」
娘「う〜ん・・・」

ワイ「え、どしたん」

娘「パパのReact、めっちゃ遅いね!」

ワイ「え、マジ・・・?」

娘「なんか動きが重いよ?」

ワイ「え、そんなことないやろ・・・」
ワイ「(ポチポチポチ・・・)」
ワイ「あれ、ほんまや」
ワイ「検索フォームにタスク名を入力するときに、なんかモタついてんな」
ワイ「キーを叩いてから文字が表示されるまでの間に、タイムラグが1秒近くあるで」
ワイ「何やこれ・・・」

娘「パパ、もしかしたらコンポーネントたちが無駄に再レンダーされてるのかもよ」
娘「そんな時は、React Developer Toolsを使って再レンダー状況を可視化してみようよ」

ワイ「そ、そんな機能あんの?」

娘「あるよ」

  1. ChromeにReact Developer Toolsという拡張機能を追加する
  2. Chrome Devtoolsを開く
  3. Componentsタブを開く
  4. 歯車アイコンをクリック
  5. Highlight updates when components render.にチェックを入れる

娘「手順としては↑こんな感じだね」

ワイ「なるほど・・・」

スクショ

ワイ「↑ここにチェックを入れるんやな!」

娘「そうだね!」

さっそくレンダー状況を見てみる

1.gif

ワイ「何やこれ」
ワイ「タスク名の入力フォームに文字を1つ入力するたびに
ワイ「100件くらいあるTODOリスト全体が再レンダーされてるやん」
ワイ「まだ検索ボタンを押してもないのに・・・!」

娘「そうだね・・・」

ワイ「検索ボタンを押して、そんで検索結果が変わったことでリストが再レンダーされるならまだしも」
ワイ「文字を1つ入力したり消したりするたびにリスト全体が再レンダーされるんかい」
ワイ「何やそれ・・・」

娘「たぶん、タスク名のinputタグにイベントハンドラが設定されてて」
娘「文字を入力するたびに何かページの状態が変わって」
娘「それでページが再レンダーされてるんじゃない?」
娘「ページ内でuseStateとか使ってるよね?」

ワイ「おお、それはその通りや」

src/pages/TodoListPage.tsx
  const [taskName, setTaskName] = useState('')

ワイ「↑この、useStateで作ったsetTaskNameっていう関数があってな」
ワイ「その関数が、タスク名の入力フォームに文字を入力するたびに実行される仕組みやねん」

娘「なるほどね」
娘「その時に、TODOリストも毎回ぜんぶ再レンダーされちゃってるんだね」

React.memoで、コンポーネントをメモ化しよう

娘「パパ、そんな時はReact.memoを使って」
娘「TODOリストのコンポーネントをメモ化してみようよ」

ワイ「ど、どうやんの・・・?」

娘「ええと・・・」

src/components/TodoList.tsx
- const TodoList: React.FC<Props> = ({ list }) => {
+ const TodoList: React.FC<Props> = React.memo(({ list }) => {
    return (
      <ul>
        {list.map((task) => (
          <TodoItem task={task} key={task.id} />
        ))}
      </ul>
    )
- }
+ })

+ TodoList.displayName = 'TodoList'

娘「↑こうだね」

ワイ「おお、React.memoっていう関数に」
ワイ「TodoListコンポーネントの中身をぶち込んであげるだけなんやな」

娘「うん!」

よめ太郎「(前々から思ってたけど)」
よめ太郎「(引数をぶち込むって何やねん・・・)」

モタつきは解消されたのか

2.gif

ワイ「おお、TODOリストがピカピカせんようになっとるわ」
ワイ「文字の入力が重たい感じもなくなっとる」

娘「よかったね!」

ワイ「・・・これはどういうことなん?」

娘ちゃんによる解説

娘「えっとね、今までは」

React「お、検索フォームに文字が入力されたで!」
React「ほな、ページの状態が変わったことやし、再レンダーや!」
React「TodoListコンポーネントも再レンダーや!」

娘「↑こんな感じだったんだけど」
娘「コンポーネントをメモ化すると・・・」

React「お、検索フォームに文字が入力されたで!」
React「でも、TODOリストの中身は変わってないから」
React「TodoListコンポーネントはレンダーし直す必要ないか・・・」
React「さっきメモしといた結果を再利用しとこか」

娘「↑こんな感じだね」

よめ太郎「(なんでReactも関西弁やねん)」

ワイ「なるほどなぁ」
ワイ「useStateで管理している状態が、文字入力によって変わったとしても」
ワイ「TODOリストに関係のない変更だけが起こっていた場合には」
ワイ「再レンダーをスキップしてくれるんやね」

娘「そういうことだね!」

ワイ「ありがとうやで、娘ちゃん!」

しかし30分後・・・

ワイ「こ、今度はタスクの削除機能を実装したら」
ワイ「また画面の動きが遅くなってもうた・・・」

娘「パパ、今度はどうしたの?」

ワイ「おお、娘ちゃん・・・」
ワイ「あのな・・・」

src/pages/TodoListPage.tsx
+ const onDelete = (taskId: number): void => {
+   /* 省略 */
+ }

  return (
    <>
      <h1>TODOリスト</h1>
      <form>
        {/* 省略 */}
      </form>
-     <TodoList list={todoList} />
+     <TodoList list={todoList} onDelete={onDelete} />
    </>
  )

ワイ「↑こんな感じで、タスク削除用の関数を作って」
ワイ「それをTodoListコンポーネントに渡すようにしただけなんや・・・」
ワイ「それなのに・・・」

3.gif

ワイ「また文字入力のたびにTODOリスト全体がピカピカしてんねん・・・」

娘「そんな時は、またメモ化だね!

useCallbackで、関数もメモ化しよう

src/pages/TodoListPage.tsx
-  const onDelete = (taskId: number): void => {
+  const onDelete = useCallback((taskId: number): void => {
     setTodoList((prevList) => {
       return prevList.filter((todoItem) => todoItem.id !== taskId)
     })
-  }
+  }, [])

ワイ「おお、今度はonDelete関数の中身を」
ワイ「useCallbackというやつにぶち込んだるわけか」

娘「そうそう」
娘「これでもう一度、画面を触ってみて?」

4.gif

ワイ「おお、チカチカしないし、サクサク入力できとるわ」
ワイ「・・・でも、これ何でなん?」
ワイ「TodoListコンポーネントに渡すonDelete関数は、毎回おんなじ関数なんやから」
ワイ「TodoListは再レンダーされないはずちゃうの・・・?」

娘「それはね・・・」

src/pages/TodoListPage.tsx
  const onDelete = (taskId: number): void => {
    /* 省略 */
  }

  return (
    <>
      <h1>TODOリスト</h1>
      <form>
        {/* 省略 */}
      </form>
      <TodoList list={todoList} onDelete={onDelete} />
    </>
  )

娘「↑ここで、onDelete関数を作って」
娘「TodoListコンポーネントに渡してるでしょ?」
娘「そこが問題・・・というかポイントなの」
娘「つまり・・・」

React「よっしゃ、onDelete関数を作って」
React「TodoListコンポーネントにぶち込むでぇ!」

娘「↑こんな感じで、作りたての新しい関数TodoListコンポーネントに渡す」
娘「そういうことになっちゃうの」

ワイ「作りたて・・・」
ワイ「そう言われてみれば、アロー関数式で関数を作って
ワイ「それをTodoListコンポーネントに渡してるわけやもんな」

娘「そう」
娘「だから・・・」

React「さっきまでとは違う、今作ったばかりのonDelete関数を渡すから」
React「TodoListコンポーネントも再レンダーや!」

娘「↑こんな感じになっちやうの」

ワイ「毎回新しい関数が作られて、別物扱いになってまうわけか」
ワイ「つまり、ReactはObject.isによる比較アルゴリズムを使用しているわけやな・・・」

よめ太郎「(いや、そこは急に理解早いんかい)」

娘「そういうことだね」
娘「そして、useCallbackを使うと」

ワイ「この関数は毎回作り直さないで、前回と同じのを使ってな!」

娘「ってことをReactに伝えられるわけだね」

ワイ「ふーん、そうするとTodoListコンポーネントが再レンダーされなくなるん?」

娘「そうだよ」

React「TODOリストの内容もonDelete関数も、前回と変わってへんな」
React「ほな今回はTodoListコンポーネントの再レンダーはスキップや!」

娘「↑って感じになるの」

ワイ「なるほどなぁ」
ワイ「ちなみにuseCallbackの第二引数の配列、これは何なん?」

娘「これは・・・」

ワイ「hogeっていう変数の中身が変わった場合には」
ワイ「前回と同じ関数を使いまわさずに、新しく作ってな!」

娘「↑的なことをReactに伝えるときのための配列だね」

ワイ「ほぇ〜」
ワイ「そうやって新しく関数が作り直された場合には」
ワイ「その関数を受け取ったコンポーネントは再レンダーされるわけか」

娘「そうそう」

ちなみに

娘「ちなみに、useCallbackを使わないでも」
娘「ページコンポーネントの外で関数を定義するだけで」
娘「再レンダーを抑止できる場合もあるよ!」

ワイ「そうかそうか」
ワイ「ページコンポーネントの外側で定義された関数は」
ワイ「ページが再レンダーされても変わらへんわけやな」
ワイ「まぁ、とにかく激重サイトにならんくてよかったわ!」

まとめ

  • Reactアプリの動作が重い時は、React Developer Toolsで再レンダー状況をチェックしてみよう
  • React.memoを使ってコンポーネントをメモ化し、無駄な再レンダーを抑止しよう
  • メモ化したコンポーネントに渡すための関数もuseCallbackでメモ化しておこう

ワイ「↑ってことやな!」

娘「そうだね!」

ワイ「いやー、娘ちゃんのおかげで良いTODOリストができそうやわ・・・!」

娘「あっ!」
娘「・・・パパ・・・!」

ワイ「なんや?」

娘「大変なことに気づいたんだけど・・・」

娘「このTODOリスト、どこからタスクを追加するの?」

ワイ「あ・・・」
ワイ「タスクの作成機能を作るの、忘れてたわ・・・!」

娘「・・・」

ワイ「で、でも大丈夫や!」

  • タスク追加機能を実装する

ワイ「↑っていうタスクを、このTODOリストに追加しておけばええねん!」
ワイ「そしたらもう忘れへん!」

娘「で、そのタスクはどこから追加するの?

ワイ「それな

〜おしまい〜

参考文献

新しい記事もよろしくやで!

ハスケル子「タグごとに色がついてたらいいのにな…」

補足

532
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Yametaro
関西型言語が得意です。
yumemi
みんなが知ってるあのサービス、実はゆめみが作ってます。スマホアプリ/Webサービスの企画・UX/UI設計、開発運用。Swift, Kotlin, PHP, Vue.js, React.js, Node.js, AWS等エンジニア・クリエイターの会社です。Twitterで情報配信中https://twitter.com/yumemiinc

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
532
Help us understand the problem. What is going on with this article?