Progressive

プログラミング、旅行、筋トレなど

UoPeople CS 1103を終えて

UoPeople に入ってから第4タームが完了した。今学期は、CS 1103 Programming 2 を受講し、無事最後までやり切ることができた。

www.uopeople.edu

学習内容

授業のシラバスに書いてある「Learning Objective and Outcomes」に沿って、学んだことをまとめていく。

Explain the programming techniques using Java. 

エラーハンドリングの方法や、パッケージなど、Javaプログラミングをする上で重要な概念について、体系的に学ぶことができた。

この講義で特に良かった部分としては、マルチスレッドプログラミングや、ストリーム、ソケット通信などに関して、インプットとアウトプットができたことである。実務では間接的に触れる機会しかなかったのだが、今回の講義を通して、自分が実務では触れていない領域に対する解像度が上がった。

Explain the advanced Object-Oriented concepts. 

上記で紹介した学んだことを通じて、OOPへの理解も進んだように感じる。特に、コーディング課題を出す際には、クラス設計などはこちらに任せられる部分も多く、高得点を取るために、良いクラス設計を考える機会が多かった。

Use tools such as the Eclipse and the Eclipse debugger. 

Eclipseに関しては、最低限の使い方は覚えたが、どれくらい習熟しているかはわからない。そもそもJavaを実務で使うことがあるかわからないので、大学の課題以外では使わない可能性が高く、それをこなせる程度に今後も習熟していきたい。

サマリ

全般的にプログラミングの授業を通して、「実務では触れたことがない領域のインプットとアウトプットを、体系的に行えたこと」が一番良かったこと。実務ベースでの学習は、そのプロダクトの属性や会社によって、学ぶ範囲が限定されるので、大学で別の視点から体系的に学ぶことができているのは非常に嬉しい部分である。

次の学期

「Digital Electronics & Computer Architecture(CS1105)」を受講する予定。ALUやメモリの仕組み、アセンブリなど、コンピュータの仕組み部分に入っていくことになりそうで、非常に楽しみ。

JavaのSocket通信について整理する

在籍している大学の課題で、JavaのSocket通信について学習する機会があったので、知識を整理する。

Socketとは

Oracleのサイトによると、Socketとは

A socket is one endpoint of a two-way communication link between two programs running on the network. A socket is bound to a port number so that the TCP layer can identify the application that data is destined to be sent to.

docs.oracle.com

Socketを利用することで、サーバーとクライアント間でコネクションを作り、相互で読み書きを通じたコミュニケーションを行うことができる。

サーバーの実装

クライアントから通信があった際に、その通信をクライアントにエコーするプログラム。

import java.net.Socket;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.IOException;

public class Server {
    public static void main(String[] args) {
        int port = 1234; // サーバーのポート番号
        ServerSocket serverSocket = null;
        Socket clientSocket = null;
        PrintWriter out = null;
        BufferedReader in = null;

        try {
            serverSocket = new ServerSocket(port);
            clientSocket = serverSocket.accept();

            out = new PrintWriter(clientSocket.getOutputStream(), true);
            in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));

            String inputLine;
            while ((inputLine = in.readLine()) != null) {
                System.out.println("Received from client: " + inputLine);
                out.println("Echo: " + inputLine); // クライアントにメッセージをエコー
                if ("exit".equalsIgnoreCase(inputLine)) {
                    break;
                }
            }
        } catch (IOException e) {
            System.out.println("Exception caught when trying to listen on port "
                + port + " or listening for a connection");
            System.out.println(e.getMessage());
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
                if (clientSocket != null) {
                    clientSocket.close();
                }
                if (serverSocket != null) {
                    serverSocket.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

クライアントの実装

上記プログラムのクライアント側の実装

import java.net.Socket;
import java.io.BufferedReader;
import java.io.PrintWriter;
import java.io.InputStreamReader;
import java.io.IOException;

public class Client {
    public static void main(String[] args) {
        String hostName = "localhost"; // サーバーのホスト名またはIPアドレス
        int port = 1234; // 接続するサーバーのポート番号

        try (Socket socket = new Socket(hostName, port);
             PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
             BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
             BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in))) {

            String userInput;
            while ((userInput = stdIn.readLine()) != null) {
                out.println(userInput);
                System.out.println("Server response: " + in.readLine());
                if ("exit".equalsIgnoreCase(userInput)) {
                    break;
                }
            }
        } catch (IOException e) {
            System.out.println("Exception caught when trying to connect to "
                + hostName + ":" + port);
            System.out.println(e.getMessage());
        }
    }
}

まとめ

  • Socketを利用することで、サーバーとクライアント間でコネクションを作り、相互で読み書きを通じたコミュニケーションを行うことができる。
  • サーバー側では、ServerSocketクラスを作成し、acceptメソッドでクライアントからの通信をまつ
  • クライアント側では、Socketクラスを作成し、サーバーとの接続を確立したのち、サーバーとデータを送受信できるようになる

JavaのI/Oストリームについて整理する

在籍している大学の課題で、JavaのI/Oストリームについて学習する機会があったので、知識を整理する。

I/Oストリームとは

wikipediaによると、ストリームの定義は以下。

ストリーム(英: stream)とは、データを、比較的小さい単位が連続したものと捉え、上流から下流へ「流れるもの」とみなし、そのデータの入出力・送受信(途中段階を含む)を最小限の滞留とさせ低遅延処理となるように扱う形態を指す。またその操作のための抽象データ型を指す。処理内部では適切なデータ分割・バッファリングが行われる。入力ストリーム (input stream) を利用してデータの読み出しを行ない、出力ストリーム (output stream) を利用してデータの書き込みを行なう。

また、Oracleサイトによると

I/Oストリームは、入力元(ソース)または出力先を表します。 ディスク・ファイル、デバイス、他のプログラム、メモリ配列など、多様なソースおよび出力先をストリームで表すことができます。ストリームは、単純なバイト・データをはじめ、プリミティブ型、ローカライズされた文字、オブジェクトなどさまざまな種類のデータに対応しています。

引用: https://docs.oracle.com/cd/E26537_01/tutorial/essential/io/streams.html

以上から、ファイルやネットワーク、メモリなどのデータソースに対して、データを入力したり出力するためのインターフェース、および実装クラス群をI/Oストリームと呼ぶ。

バイトストリーム

Oracleサイトによると、バイトストリームとは

バイト・ストリームは、プログラムで8ビット・バイトの入出力を実行するために使用します。 すべてのバイト・ストリーム・クラスは、InputStreamまたはOutputStreamの子孫クラスです。

docs.oracle.com

以下は、sourceからデータを入力し、destinationに出力するためのサンプルプログラム。

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileCopyExample {
    public static void main(String[] args) {
        FileInputStream in = null;
        FileOutputStream out = null;

        try {
            in = new FileInputStream("source.txt"); // 入力元のファイル
            out = new FileOutputStream("destination.txt"); // 出力先のファイル

            int c; // 読み込んだデータを一時保存する変数

            while ((c = in.read()) != -1) {
                out.write(c); // 読み込んだデータを出力
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) {
                    in.close(); // FileInputStream を閉じる
                }
                if (out != null) {
                    out.close(); // FileOutputStream を閉じる
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

文字ストリーム

Oracleサイトによると、文字ストリームとは、

Javaプラットフォームでは、文字値の保存にUnicode規則を使用します。 文字ストリームI/Oによって、内部のUnicode形式とローカル文字セットとの変換が自動的に行われます。 欧米のロケールでは、ローカル文字セットは通常、ASCII 8ビットのスーパーセットです。

docs.oracle.com

以下は、inputからデータを入力し、outputに出力するためのサンプルプログラム。

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class TextFileCopyExample {
    public static void main(String[] args) {
        FileReader in = null;
        FileWriter out = null;

        try {
            in = new FileReader("input.txt");  // 入力ファイル
            out = new FileWriter("output.txt");  // 出力ファイル

            int c;  // 読み込んだデータを一時保存する変数

            while ((c = in.read()) != -1) {
                out.write(c);  // 読み込んだデータを出力
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) {
                    in.close();  // FileReader を閉じる
                }
                if (out != null) {
                    out.close();  // FileWriter を閉じる
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

バッファ・ストリーム

Oracleサイトによると、バッファ・ストリームとは

これまでに見てきたサンプル・プログラムのほとんどは、非バッファI/Oを使用しています。これは、各読取り要求または書込み要求が、基盤となるOSによって直接処理されることを意味します。 このようなプログラムは、効率が大幅に低下する恐れがあります。多くの場合、こうした要求のたびに、ディスク・アクセスやネットワーク・アクティビティ、その他比較的負荷の高い処理が発生するためです。この種のオーバーヘッドを削減するために、JavaプラットフォームではバッファI/Oストリームを実装しています。 バッファ入力ストリームでは、バッファと呼ばれるメモリ領域からデータを読み取ります。これにより、ネイティブな入力APIが呼び出されるのは、バッファが空の場合のみとなります。 同様に、バッファ出力ストリームではデータをバッファに書き込み、ネイティブな出力APIが呼び出されるのはバッファがいっぱいの場合のみです。

docs.oracle.com

以下は、バッファ・ストリームを用いて、文字ストリームを初期化するコード

reader = new BufferedReader(new FileReader("input.txt"));  // 入力ファイル
writer = new BufferedWriter(new FileWriter("output.txt"));  // 出力ファイル

バッファ・ストリームを利用することで、リソースへのアクセス回数を減らし、処理のパフォーマンスの向上を期待することができる。

まとめ

  • I/Oストリームとは、ファイルやネットワーク、メモリなどのデータソースに対して、データを入力したり出力するためのインターフェース、および実装クラス群。
  • バイトストリームを利用することで、8ビット・バイトの入出力を実現できる
  • 文字ストリームを利用することで、文字コードに沿ったデータのやり取りを実現できる
  • バッファ・ストリームを利用することで、リソースへの回数を減らし、パフォーマンス向上を期待できる。

Ref

zenn.dev

Javaのマルチスレッドの実装方法

在籍している大学(UoPeople)の課題で、Javaでマルチスレッドを利用したプログラムを書くものがあった。自分はマルチスレッドが必要になるアプリケーション開発を実務で経験したことなく、学ぶことが多かったので、記事にまとめる。

マルチスレッドとは

そもそもスレッドに関して、wikipediaでの定義は以下

スレッド(thread)とは、コンピュータプログラムにおいて特定の処理を行うための一貫性のある命令の流れのことであり、プロセッサ利用の最小単位。プロセスは少なくとも1つ以上のスレッドを含む。一般的に各プロセスには独立した仮想アドレス空間が割り当てられるが、プロセス内のスレッド群はアドレス空間を共有する。そのためプログラムを実行するときのコンテキスト情報が最小で済み、同じプロセス内でスレッドを切り替える際はアドレス空間の切り替えが不要となるので、切り替えが高速になる。

複数スレッドを生成し、分けて処理を行うことで、高パフォーマンスを実現することができる。プロセス内で複数スレッドを作成して、処理を分散することを並行処理を呼ぶ。一方で、複数のプロセスを作成して、処理を分散することを並列処理と呼ぶ

プロセスとスレッド

前提として押さえておきたいのは、プロセスとスレッドの違いである。wikipediaでのプロセスの定義は以下。

プロセスとは、処理のことである。情報処理においてプログラムの動作中のインスタンスを意味し、プログラムのコードおよび全ての変数やその他の状態を含む。オペレーティングシステム (OS) によっては、プロセスが複数のスレッドで構成される場合があり、命令を同時並行して実行する。

スレッドとは、プロセス内の実行の単位であり、プロセスの中でスレッドが展開される。また、プロセスは独立したメモリアドレス空間を保持している。 ポイントは、複数スレッドを展開する場合、そのスレッドが展開されているプロセスのメモリアドレス空間が共有されている、という点である。つまり、スレッドから共有されているメモリアドレス空間をにアクセスすることができる。 共通のメモリにアクセスをして、データの整合性が保てずに、何かしらのバグが生じることをレースコンディションと呼ぶ。それを避けるために、各スレッド間では、共通のメモリアドレス空間を意識した実装 = スレッドセーフな実装をする必要がある。

マルチスレッドの作り方

Thread クラスを継承する

Threadクラスを継承し、runメソッドをオーバーライドしてクラスを作成する。

class MyThread extends Thread {
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("Thread: " + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("Thread interrupted.");
            }
        }
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.start(); // スレッドを開始
    }
}

Runnable インターフェースを実装する

Runnableインターフェースを実装したクラスを作成する。

class MyRunnable implements Runnable {
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("Runnable: " + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("Runnable interrupted.");
            }
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable());
        t.start(); // スレッドを開始
    }
}

スレッドセーフ

スレッドは、プロセスと異なり、同じメモリアドレス空間を共有する。そのため、同一プロセス内の複数のスレッドが同じメモリ領域にアクセスする場合、データの整合性を担保するための考慮が必要である。ここでは、synchronizedとvolatileについて触れる。

synchronized

synchronized キーワードは、そのインスタンスにおいて、synchronizedキーワードが記述されているメソッド、もしくはブロックの実行を、一つのスレッドに制限するもの。

class SynchronizededSample {
  synchronized void someMethod() {
    doSomething(); // この処理は、一つのスレッドからしか実行されない
  }
}

volatile

volatile キーワードは、変数の値が予期せぬ方法でキャッシュされるのを防ぎ、すべてのスレッドが常にメインメモリから最新の値を読み取ることを保証する。一方で排他制御を保証しているわけではない点は注意。

class SynchronizededSample {
  private static volatile boolean running = true; // この変数は、常にメモリアドレス空間の最新の値を参照する
  ...
}

まとめ

  • スレッドとは、プロセス内で展開できるプロセッサ利用の最小単位であり、複数のスレッドが同時に実行されることでマルチタスキングと並行処理が可能になる。
  • Javaでは、Threadクラスを継承した、もしくはRunnableインターフェースを実装したクラスを実装することで、マルチスレッドに対応した処理を実現することができる
  • 複数スレッドを作成する場合、メモリアドレス空間が共有される
  • レースコンディションを避けるために、synchronizedやvolatileを利用して、スレッドセーフな実装を考慮する必要がある

Computer Science の学位取得を目指して、University of the People に入学してから半年経った

昨年2023年の11月にUniversity of the People (以下UoPeople) に入学しました。Computer Science(以下CS)の学位の取得を目指しています。入学して半年間経ちましたが、継続的にCSを学習し続けることができる良い環境であると感じています。

今回は、入学のモチベーションと入学までに必要だった作業についてまとめていきます。私も、入学するにあたって、先人の方に残していただいたブログに非常に助けられたので、社会への還元という意味を込めてこのタイミングでまとめておきます。

モチベーション

CS の知識を体系的に習得する

私は文系の大学を卒業しており、学問としてのコンピューターサイエンスを体系的に学んだことはありません。 Leetcode などを活用して、アルゴリズムとデータ構造の基礎学習をしたり、友人と本を輪読しながら OS やメモリ周りの理解に努めたり、点ベースでの学習は今までしていました。 しかし、ソフトウェアエンジニアとして働いている以上、体系的にコンピューターサイエンスの知識を学びたいという気持ちが強くあり、それがモチベーションの大きな一因になりました

CS の学位を取得する

実務経験に加え、CSの学位を持っていた方が、将来の自分の可能性が広がると考えています。例えば、海外で仕事を獲得したくなったりとか、また、日本市場における転職においても、有利に働くと考えています。 また、将来大学院に進学したいというが出てきた際に、コンピューターサイエンス関連の大学の学位と適切な GPA を保持しておくことはプラスに働くと考えました。

入学までの準備

UoPeopleの入学申請は、オンラインで完結して行うことができます。基本的な流れは大学のホームページを見れば書いてあると思うので、自分が苦労した部分について、記述します。

※入学プロセスに関して参考になるサイト www.uopeople.edu

卒業した大学の成績証明書の提出

大学から成績証明書を取り寄せました。 私が卒業した大学は証明書がオンラインで申請できるようになっていたのですが、なぜかオンラインでは白黒印刷しか対応していなく、一方で UoPepole はカラーの証明書提出を求めてきたので、結局大学まで足を運びました。 学校によっては、自分で足を運んで証明書を取りにいく必要があると思うので、余裕を持っておくと良さそうです。

単位移行準備

Ucredo を利用して単位の互換を行いました。 ucredo.com

基本的には、大学から英語版の成績証明書を取得して、それをUcredoに提出することで、アメリカの基準で自分の成績を評価した証明書をもらうことができます。それをUoPeopleに提出することで、単位のトランスファー申請が可能になります。 私が通っていた学部は法学部でしたが、教養科目などを中心に、42単位を移行することに成功しました。他の方々のブログを見ていると、50くらい単位移行に成功している方もいらっしゃるみたいです。私の学生時代のGPAはかなり低かったりするので。それが良ければもう少し単位移行できたかもしれません。

Duolingo English Testの受験

英語力を証明する手段として、Duolingo English Test を受講しました。英語力が証明できると、UoPeopleでの一部コースの受講が免除されます。 englishtest.duolingo.com

対策として、テストの傾向を解説している動画を Youtube で眺めたり、サンプルテストの 1 回受講しました。結果的に UoPeople が求める点数を超えることができ、英語のコースを免除することに成功しました。 参考程度に、私は過去にTOEICで 915 点を取得おり、英語の基礎力はすでに持っていました。しかし、Duolingo English TestはTOEIC よりも実用的な内容のテストになっており、基礎英語力があったとしても多少の対策はしておいた方が安全だと思います。もちろん、英語力に不安がある方は相応の時間を取って対策する必要があると思います。 ちなみに、英語力が証明できないとしても、代わりにUoPeopleで英語のコースを受講することを条件に、入学することは可能です。

現在

11 月から学習を開始していて、現在3ターム目が終了しました。今まで取得した単位は、UoPeopleで9単位、Sophiaラーニング(CS関連の授業を受けることができるプラットフォームで、その成果をUoPeopleの単位として移行することができる)からの単位移行で3単位で合計12単位取得しています。次のタームまで余裕が少しあるので、Sophiaラーニングでコースを受講して、また単位移行することを考えています。

Sophia Learningについて www.sophia.org

Promise.allを実装してみる

Promise.allとは

このメソッドは複数のプロミスの結果を集約するのに便利です。このメソッドは、コード全体が正常に動作するために依存している複数の関連する非同期タスクがあり、コードの実行を続ける前にそれらすべてを履行させたい場合によく使われます。

実務ではnextjsでSSRをする際、必要な処理を一括で実行する場合などで利用したことがある。 互いに独立した非同期処理を実行する場合、Promise.allを利用すると並列実行されるので、全体処理が完了するまでの時間が短くなる。

developer.mozilla.org

interface

インプットについては待機状態のpromiseの配列、例えば以下のような感じ

const promise1 = fetch1()
const promise2 = fetch2()
const promises = [promise1, promise2]

返り値について、実行された非同期処理の結果を配列に格納した形で返却される

const promise1 = fetch1()
const promise2 = fetch2()
const promises = [promise1, promise2]

const res = await Promise.all(promises)
res = [promise1の返り値, promise2の返り値]

コード実装

配列の中身を一つずつ取り出し、全部の処理が終了したらresolve、何か一つでも失敗したらrejectをする関数を記述する。

function promiseAll(arr) {
  return new Promise((resolve, reject) => {
    const results = new Array(arr.length);
    let unresolved = arr.length;

    if (unresolved === 0) {
      resolve(results);
      return;
    }

    arr.forEach(async (item, index) => {
      try {
        const value = await item;
        results[index] = value;
        unresolved -= 1;

        if (unresolved === 0) resolve(results);
      } catch (err) {
        reject(err);
      }
    });
  });
}

状態管理に関しての知識整理

状態管理について、概念的にまとめた自分用メモ。

状態管理とは

コンポーネント間共通で管理したいデータを扱う手法のこと。  

状態管理の手法

useStateで管理してprops経由でバケツリレーをする

基本的な状態管理方法であるuseStateを用いる手法。複数感コンポーネントで状態をシェアしたい場合は、propsによるバケツリレーを行わないといけない。パフォーマンス観点から再レンダリングをコントロールしにくいという観点と、また責務の観点(単一責任の原則)から好まれる方法ではない。

useContextを使う

useContextは、親から子に状態を渡す場合に限って、propsバケツリレーを回避することができる。 そういえばflutterでアプリ開発をしていた時はChangenotifier&providerパターンで状態を管理していた。

propsによるバケツリレーの心配は無くなったが、ステートが複雑になった場合の管理が煩雑になる可能性がある。例えば、userに関する情報とquestionに関する情報を管理したいとなった時に、providerを二つ用意せねばならない(管理したい対象に応じて、n個ずつproviderが増えていく』)。代替案として、複数のproviderを用いずに単一のproviderを用いるという方法もあるが、パフォーマンスの低下が代償となる。

状態管理ライブラリを導入する

ステート管理のライブラリを導入する方法。代表的なものと、業務で使用しているものを整理する。

react-redux

一つのstoreにまとめて状態を管理する。 selecter経由でstoreから値を取得し、action経由でstoreに対して変更をdispatchする。storeが更新されたとしても、selecterで取得される値に変化がなければ、storeに接続しているコンポーネントは再レンダリングされない。また、useDispatchを使うことで、特定のコンポーネントが更新のみに責務を持つことができ、再レンダリングのコントロールをすることができる。 uhyoさんによると特有の点は以下。

React-reduxに特有の点としては、(グローバルに共有される)すべてのステートを一つのStoreで管理し、selectorによってそこから必要なものだけを取り出すというインターフェースが特徴となります。特に、selectorの型が(state: 全てのステート) => 必要なステートのような型となり、ステートの使用者は常に全体のステートを意識させられます(適宜カスタムフックを用意することでこの点はある程度緩和できますが)。

recoil

reduxをは異なるインターフェースを持った新興のステート管理ライブラリ。状態を管理するAtomと状態を取り出すSelectorのRecoilStateから成り立つ。 全てのステートが一箇所にまとまっていないのが特徴。reduxと異なり、statoのcode splittingが成立する。 uhyoさんまとめは以下。個人的に、良くも悪くもアーキテクチャを最初に固めないといけないので、ある程度経験がある人がチームにいる時に採用したほうがいい気もする。

まとめると、RecoilはAtom・Selectorというシンプルな概念をベースとし、特定のアーキテクチャを強制することなくグローバルなステート管理の部品を提供してくれる点が特徴です。subscriptionの管理などの面倒な部分をRecoilに任せつつ、思いのままのアーキテクチャでステート管理を行うことができるでしょう。

apollo client

今本業で使っている。graphqlサーバーからのdata fetchingだけでなく、cache機構も備えており、状態管理を実現することもできる。 apollo client経由でqueryを叩く際、初回のリクエストはgraphql serverに問い合わせるが、2回目以降はcacheに対して問い合わせるようになる。またcacheの値以外を管理したい場合、Reactive valiablesを用いれば、追加で値を管理することができる。

個人的には、クエリの結果と+アルファを管理したい場合には、apollo clientのcache機構をそのままステート管理に用いていいと思うが、呼び出し時に特別な処理や副作用を起こしたい場合、apollo clientで対処するのは難儀なので、何かしらの状態管理ライブラリに寄せていったほうがいいと思う。

uhyoさんの考察。

これらのライブラリはキャッシュされたデータを識別するための「キー」という概念を持ち、データは末端のコンポーネントが好き勝手に追加できることから、ステート管理のアーキテクチャとしてはRecoilに近いものです。一方で、これらのライブラリは使い勝手を第一に置いたハイレベルなAPIに重きを置いており、ローレベルなAPIを中心とするRecoilとは対照的です。 これらのライブラリによるステート管理は自己完結的5であり、もしデータフェッチングとは関係ないステートも多くある場合、ふたつのステート管理を融合させた状態で最適なパフォーマンスを得るのは難しいと思われます。ここに最適なパフォーマンスが必要な場合は、React QueryやuseSWRなどを使わずにRecoilなどに寄せる選択肢も考える余地があるでしょう。

まとめ

頭の中が整理できてよかった。現状apollo clientのcache機構を本業で、useContextによる管理を副業で使っているが、状況に応じて最適な状態管理をしていきたい。

参考記事

blog.uhy.ooo

blog.uhy.ooo

www.apollographql.com

react-query.tanstack.com