Electron」カテゴリーアーカイブ

useEffect, useCallback, useMemoに関しての使い分け

Claudeに課金をしたので、先日より取り組んでいるElectron製のアプリをちまちまと助けを借りながらいじっているのですが、

まずは、提示された内容を元に書き写すような形で進めています。

その中で、AIあるあると言えばあるある何でしょうけれど、生成されるコードがコロコロ変わる問題にぶち当たっています。

結局のところ、目的を達成する上でプログラミングの書き方としては複数のやり方があって、その背景をAIに伝えない限りはどの手法が適切なのかはわからず、その生成されるコードが変わるわけですね。

言葉で言ってしまえば、そりゃそうなんだけど、なかなかのストレスです。
完全に任せるわけではない、AIサポートの開発における難しさを感じますね。。。

こういうものを見ていると、結局コードに関しての知識は必要になりそうだな、という気がしてきます。

というわけで、今回は自分の学習記録として、useEffectとuseCallback。そして調べているうちに出てきたuseMemoに関して書いてみようと思います。

useEffect – 副作用の管理

特徴

  • コンポーネントのレンダリング後に実行される
  • DOM操作、データフェッチング、購読、タイマーなどの副作用を扱う
  • クリーンアップ関数を返すことで、コンポーネントのアンマウント時や依存配列の値が変わる前に実行される処理を定義できる

使用例

import React, { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // 副作用: APIからデータをフェッチ
    setLoading(true);

    // userIdが変わるたびに新しいユーザーデータを取得
    fetch(`https://api.example.com/users/${userId}`)
      .then(response => response.json())
      .then(data => {
        setUser(data);
        setLoading(false);
      });

    // クリーンアップ関数
    return () => {
      // コンポーネントのアンマウント時やuserIdが変わる前に実行
      console.log('User profile cleanup');
    };
  }, [userId]); // 依存配列: userIdが変わったときだけ実行

  if (loading) return <div>Loading...</div>;
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

いつ使うべきか

  • 外部データの取得
  • イベントリスナーの設定と解除
  • DOM要素の直接操作
  • タイマーの設定とクリア
  • 外部サービスへの購読と解除

useCallback – 関数のメモ化

特徴

  • 関数をメモ化し、不要な再生成を防ぐ
  • 依存配列の値が変わるまで、同じ関数インスタンスを保持する
  • 子コンポーネントに渡す関数のパフォーマンス最適化に役立つ

使用例

import React, { useState, useCallback } from 'react';

// 子コンポーネント(React.memoでメモ化)
const ExpensiveList = React.memo(({ items, onItemClick }) => {
  console.log('ExpensiveList rendered');
  return (
    <ul>
      {items.map(item => (
        <li key={item.id} onClick={() => onItemClick(item.id)}>
          {item.name}
        </li>
      ))}
    </ul>
  );
});

function ItemManager() {
  const [items, setItems] = useState([
    { id: 1, name: '項目1' },
    { id: 2, name: '項目2' },
  ]);

  const [count, setCount] = useState(0);

  // useCallbackを使用して関数をメモ化
  const handleItemClick = useCallback((id) => {
    console.log(`Item ${id} clicked`);
  }, []); // 空の依存配列: 関数は再作成されない

  return (
    <div>
      <h1>アイテム管理</h1>
      <ExpensiveList items={items} onItemClick={handleItemClick} />
      <div>
        <p>カウント: {count}</p>
        <button onClick={() => setCount(count + 1)}>
          カウント増加
        </button>
      </div>
    </div>
  );
}

いつ使うべきか

  • `React.memo`でラップされた子コンポーネントに関数を渡す場合
  • `useEffect`の依存配列に関数を含める場合
  • イベントハンドラーが複雑で、不要な再作成を避けたい場合
  • 関数が他のフックの依存関係になっている場合

useMemo – 値のメモ化

特徴

  • 計算結果をメモ化し、不要な再計算を防ぐ
  • 依存配列の値が変わるまで、前回の計算結果を再利用する
  • 計算コストが高い処理の最適化に特に役立つ

使用例

import React, { useState, useMemo } from 'react';

function ExpensiveCalculation({ numbers }) {
  const [count, setCount] = useState(0);

  // 計算コストが高い処理をuseMemoでメモ化
  const sumResult = useMemo(() => {
    console.log('Heavy calculation running...');
    // 重い計算の例
    return numbers.reduce((total, num) => {
      // 人為的に処理を重くする
      for (let i = 0; i < 1000000; i++) {}
      return total + num;
    }, 0);
  }, [numbers]); // 依存配列: numbersが変わった時だけ再計算

  return (
    <div>
      <h2>計算結果: {sumResult}</h2>
      <p>カウント: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        カウント増加(計算には影響しない)
      </button>
    </div>
  );
}

function App() {
  const [numbers, setNumbers] = useState([1, 2, 3, 4, 5]);
  return (
    <div>
      <ExpensiveCalculation numbers={numbers} />
      <button onClick={() => setNumbers([...numbers, numbers.length + 1])}>
        数字を追加
      </button>
    </div>
  );
}

いつ使うべきか

  • 計算コストが高い処理の結果をキャッシュしたい場合
  • レンダリングごとに再生成したくないオブジェクトがある場合
  • 大きな配列やオブジェクトの変換や加工を行う場合
  • コンポーネントの再レンダリングが頻繁に発生する場合

まとめ

結局のところ、どこまでキャッシュさせることが可能な内容何だっけ?ってところなんでしょうね。

計算コストが高くても、再レンダリングが頻繁に発生しようとも、表示内容が再計算必要なものであればuseEffectにするしかないんだろうし。。。くらいに覚えておくことにします!

Tailwindcssの適用にてんやわんや

Electronのプロジェクト構成として、Next.jsをフロントエンドに選択。
CSSとしてはTailwindcssを適用しようとしていたのですが、見様見真似でやっているものの、動いているコードを見る限り正常にTailwidがあたっているように見えない。

ということで、もう一度インストールから見直してみることに。

$ npm install -D tailwindcss autoprefixer postcss

インストール自体は上記コマンドで問題なくインストールできているようです。
これに、初期化するコマンドが必要となるのですが

$ npx tailwindcss init -p
npm error could not determine executable to run
npm error A complete log of this run can be found in: /XXXXX

というようにエラーが出ていることがわかりました。

おかしい。。。Claudeに聞いてみてもセットアップの仕方は上記コマンドで問題ないはずなのですが、、、と調べてみるとどうやらtailwindcssのv4系ではセットアップ方法が変わったようで、上記のコマンドはv3系のもののよう。

そんなわけで、一旦インストールするtailwindcssをv3系にしてみます

$ npm install -D tailwindcss@3.4.17 autoprefixer postcss
$ npx tailwindcss init -p

上記でエラーが解消されました。

v4系のセットアップは、ViteかPostCSSなどによってやり方が変わっているよう。

もう、頭から引っかかりまくっているので大変です。。。
やっぱりフロントエンドは中途半端に手を出すときっついです。

Electron + Next.js + TypeScript

Electronを利用してのアプリケーション開発で、いくつかの選択肢が存在するけれど、どうせならNext.jsやTypeScriptを使ってみたいと思っていた。

いくつか探したところ、ちょうどいいサンプルがあったのでそれをベースにアプリを作ることにする

Electron with Typescript application example
https://github.com/vercel/next.js/tree/canary/examples/with-electron-typescript

利用の方法はReadmeにかかれているが、下記のような形(プロジェクト名をelectron-exampleにした場合
npx, yarn , pnpmそれぞれ書かれている

npx create-next-app --example with-electron-typescript electron-example
yarn create next-app --example with-electron-typescript electron-example
pnpm create next-app --example with-electron-typescript electron-example

これで作られるフォルダ構成としてはざっくりこんな感じ

npm run devを実行してみると

起動した。

ハマったこと

Electron超初心者のわたしがハマったこととしては、、build後のフォルダ構成が下記のように増えたことだった。

今振り返って考えてみると、これはTypeScriptをコンパイルしてできたファイルになるので、それらは管理対象外なんですよね。

チュートリアルやQiitaを見ながらいじっていたので、main/index.jsやそれをコピーしてmain/main.js等としたりしていたので全然動きませんでした。

それらを考えるとコンパイルによって自動生成されるコードはGithubの対象外にするべきなので、gitignoreは下記を追加しています

/.next/
/out/
main
dist
renderer/.next
renderer/out

修正してもビルドしたらもとに戻ったり、思ったものが表示されなかったりと、余計な時間を使ってしまいましたが、ようやくスタートできそう

Electronを触り始めた

ちょっと秘匿性の高いデータを取り扱うアプリケーションを作りたくなって、そう考えるとWebアプリは面倒かもな?と。

かといってAccessで作るのも手軽なんだけど芸がないな~と思ってElectronに手を出してみた

https://www.electronjs.org/ja

概要レベルは知っていたけれど、実際にコードを見て改めて面白いな、と思った

スタンドアロンのアプリケーションとなるのだから、いわゆるフロントエンドとバックエンドが一つになっているんだけど、ElectronはそれをWebの技術を使って実装している。

つまりは、一つのアプリケーションにNode.jsで作られたバックエンドであるメインプロセスと、Web技術。ReactやViewなどが利用可能なレンダラープロセスが明確に分離されて存在している。

そして、それらをpreload.jsでつなぎ合わせている。

通常httpなどでそれらをつなぐことになる部分はipcが唯一の通信手段として存在している。。と。

面白い仕組みだ。

悩ましいな、と思うのはいわゆるAIを用いたコーディングスタイルを考えるとこのあたりの仕組みの理解をすっ飛ばしてものができてしまうんですよね。
結果論で言えばそれで問題なくものが作られる世界であればそれはそれで効率のいい話ではあるんだけど、、、

とりあえず、最近ろくにフロントエンドに携わっていないのでNext.jsとかどこまでできるんだろう?という不安はあるものの、やれる範囲まで頑張ってみようかと思っています