1
/
5

Reactで複雑な状態管理を行わずに、レンダリングを最適化する【株式会社ライトコード】

※弊社エンジニアの記事になります。

はじめに

突然ですが、みなさんはReactで状態管理を行う際に、どのような手法を用いますか?
もちろん、useStateやuseEffectなどをはじめとするHooksや、コンテキストなどを使用し、状態管理をされていると思います。このような状態管理の手法は非常に便利ですし、Reactを使用して開発を行っていく上では、欠かせないものですよね!
しかし、開発するアプリケーションが大規模になればなるほど、状態管理の複雑性が増していき、コードが煩雑になったり、逆にパフォーマンスを下げてしまったりなど、状態管理の仕組みを深く理解した上で、コードを書いていく必要があるため、難易度が高いと言われています。
今回は、複雑な状態管理を行わずに、レンダリングを最適化するというお題で記事を書きたいと思います。

コンポーネントの再レンダリングが発生するタイミングはいつ?

本題に入る前に、コンポーネントが再レンダリングされるタイミングについてさらっと触れておきます。再レンダリングの発生タイミングは以下の4つだと言われています。

  1. propsが更新された時
  2. stateが更新された時
  3. 親コンポーネントが再レンダリングされた時
  4. 参照しているコンテキストが更新された時

詳細については、この記事では割愛しますが、頭の片隅に置いていただき、記事を読んでいただけたら幸いです。

レンダリングを確認する

では本題です。
簡単なTODOアプリを作成してレンダリングを追っていきたいと思います!
今回は、viteを用いて開発環境を整えました。
主に使用するsrc配下の構成は以下のとおりです。


├── src
│   ├── App.jsx // 親コンポーネント
│   ├── components // 子コンポーネント
│   │   ├── Navbar.jsx
│   │   ├── Sidebar.jsx
│   │   └── TodoList.jsx

TODOアプリの概要

  1. TodoList: フォーム内でタスクを追加すると、下にチェックボックス形式でTODOリストが作成される。
  2. Navbar: TODOリストにチェックを入れるとDoneになり、NavbarのDoneのカウントが1増える。
  3. Sidebar: レンダリングを確認するためのもので、ただの静的なコーディング。(機能なし)
  4. 作成されたTODOリストはLocalStorageに保存される。

至ってシンプルな実装です。

上記の機能を、Reactで実現するためには、
親コンポーネントであるApp.jsxファイル内で状態管理を行い、それを子コンポーネントであるNavbar、TodoListコンポーネントにpropsとして渡してあげる必要があります。
実際に、ReactのHooksを用いて状態管理を行ってみます。レンダリングされたかどうかを確認するためにログ確認も行います。(CSSはtailwindを使用しています)


import { Navbar } from './components/Navbar'
import { Sidebar } from './components/Sidebar'
import { TodoList } from './components/TodoList'
import { useState, useEffect } from 'react'
 
const LOCAL_STORAGE_KEY = 'TODOS'
function App() {
  console.log('Rendering App')
 
  const [todos, setTodos] = useState(() => {
    const value = localStorage.getItem(LOCAL_STORAGE_KEY)
    if (value === null) return []
    return JSON.parse(value)
  })
 
  const addTodo = (name) => {
    setTodos((prevTodos) => {
      return [...prevTodos, { id: crypto.randomUUID(), name, complete: false }]
    })
  }
 
  const toggleTodo = (id, completed) => {
    setTodos((prevTodos) => {
      return prevTodos.map((todo) => {
        if (todo.id === id) {
          return { ...todo, completed }
        }
        return todo
      })
    })
  }
 
  useEffect(() => {
    localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(todos))
  }, [todos])
 
  return (
    <div>
      <Navbar todos={todos} />
      <div className="min-h-screen pt-14">
        <main className="flex-1 p-10 flex justify-center mr-32">
          <TodoList addTodo={addTodo} toggleTodo={toggleTodo} todos={todos} />
        </main>
        <Sidebar />
      </div>
    </div>
  )
}
 
export default App
import { useState } from 'react'
 
export const TodoList = ({ addTodo, toggleTodo, todos }) => {
  console.log('Rendering TodoList')
 
  const [newTodoName, setNewTodoName] = useState('')
 
  const handleSubmit = (e) => {
    e.preventDefault()
 
    addTodo(newTodoName)
 
    setNewTodoName('')
  }
 
  return (
    <div className="w-full">
      <form
        onSubmit={handleSubmit}
        className="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4"
      >
        <label
          className="block text-gray-700 text-sm font-bold mb-2"
          htmlFor="new-todo"
        >
          New Task
        </label>
        <input
          className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
          id="new-todo"
          type="text"
          placeholder="Add a new task"
          value={newTodoName}
          onChange={(e) => setNewTodoName(e.target.value)}
        />
        <div className="pt-2 flex justify-center">
          <button
            className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
            type="submit"
          >
            TODO!!
          </button>
        </div>
      </form>
      <ul className="list-inside">
        {todos.map((todo) => (
          <li
            key={todo.id}
            className={<code>p-2 ${todo.completed ? 'line-through' : ''}</code>}
          >
            <label className="flex items-center space-x-3">
              <input
                type="checkbox"
                className="form-checkbox h-5 w-5"
                checked={todo.completed}
                onChange={(e) => toggleTodo(todo.id, e.target.checked)}
              />
              <span>{todo.name}</span>
            </label>
          </li>
        ))}
      </ul>
    </div>
  )
}

では、TODOリストにチェックを入れた場合、どのコンポーネントがレンダリングされているのかを確認します。


結果は、全てのコンポーネントがレンダリングされていますね。
これは、状態が変更されると、親のコンポーネントが再レンダリングされ、それにより子コンポーネントも再レンダリングされてしまうからです。
少なくとも、Sidebarに関しては、レンダリングさせる必要はないですよね!
この問題を解決するために、メモ化などを使用して、不要なレンダリングを防ぐことは可能です。この程度のアプリケーションでは、問題にはならないと思いますが、大規模開発になると、その管理は複雑になります。また、Hooksには、トップレベルでしか使用できない、if文の中では使用しないなどのルールがあり、処理自体がややこしいという部分もあると思います。
そのため今回は、そのような問題を排除してくれるpreact signalsを使用してレンダリングを最適化します!

Signalsとは

At its core, a signal is an object with a .value property that holds some value. Accessing a signal's value property from within a component automatically updates that component when the value of that signal changes.
In addition to being straightforward and easy to write, this also ensures state updates stay fast regardless of how many components your app has. Signals are fast by default, automatically optimizing updates > > behind the scenes for you.

要約
signalsは、valueプロパティを持つオブジェクトです。コンポーネント内からsignalsのvalueプロパティにアクセスすると、シグナルの値が変更されたときに、そのコンポーネントが自動的に更新されます。
分かりやすく書きやすいだけでなく、アプリのコンポーネントの数に関係なく、状態の更新を高速に保つことができます。
https://preactjs.com/blog/introducing-signals/

とても魅力的ですね、実際に使用してみましょう。

記事の続きは下のリンクをクリック!

https://rightcode.co.jp/blog/information-technology/react-rendering-optimization-syain

【2024年卒】新卒採用エントリー開始しました!

特設ページはこちら:https://rightcode.co.jp/recruit/entry-2024

※募集は終了致しました。次回の募集までもうしばらくお待ちください

インターン募集!未経験ok、チャレンジ精神ある方求む

WEBエンジニア:https://rightcode.co.jp/recruit/intern-web-engineer

メディア運営:https://rightcode.co.jp/recruit/intern-media

社長と一杯飲みながらお話しませんか?(転職者向け)

特設ページはこちら: https://rightcode.co.jp/gohan-sake-president-talk

もっとワクワクしたいあなたへ

現在、ライトコードでは「WEBエンジニア」「スマホアプリエンジニア」「ゲームエンジニア」、「デザイナー」「WEBディレクター」「エンジニアリングマネージャー」「営業」などを積極採用中です!

有名WEBサービスやアプリの受託開発などの企画、開発案件が目白押しの状況です。

  • もっと大きなことに挑戦したい!
  • エンジニアとしてもっと成長したい!
  • モダンな技術に触れたい!

現状に満足していない方は、まずは、エンジニアとしても第一線を走り続ける弊社代表と気軽にお話してみませんか?

ネット上では、ちょっとユルそうな会社に感じると思いますが(笑)、
実は技術力に定評があり、沢山の実績を残している会社ということをお伝えしたいと思っております。

  • ライトコードの魅力を知っていただきたい!
  • 社風や文化なども知っていただきたい!
  • 技術に対して熱意のある方に入社していただきたい!

一度、【Wantedly内の弊社ページ】や【コーポレートサイト】をのぞいてみてください。

【コーポレートサイト】https://rightcode.co.jp/

【採用募集】https://rightcode.co.jp/recruit(こちらからの応募がスムーズ)

【wantedlyぺージ】https://www.wantedly.com/companies/rightcode

株式会社ライトコードでは一緒に働く仲間を募集しています
今週のランキング
株式会社ライトコードからお誘い
この話題に共感したら、メンバーと話してみませんか?