ReactをNode.jsで起動するとwebsocket通信をデフォルトでする?

先日のサンプルを動かしていて気づいたこと、

propsとstate(@React)

%npm startでサーバー起動してブラウザの開発者ツール(Chrome)で見ているとサーバー停止後に以下のようなメッセージが出てきます。

public/srcディレクトリに該当しそうなソースは無いからnode_modulesの中?しかしライブラリが勝手に通信はしないだろうから今のところはアンノーンな出来事です。

 

admin

propsとstate(@React)

propsとstateというのはReactで重要な役割を果たします。props(properties)は親のコンポーネント(ここでは一つの関数がコンポーネントに相当、常に大文字で定義されます)から子のコンポーネントに値(値の型はなんでも受け入れそうなので型定義する仕掛けが必要でしょう)を渡し、stateはReact内での状態保持のための仕組みです。

以下非常に単純なbuttonをクリックするとカウントアップする仕組みをpropsとstateを使って作ってみたものです。

ただし、button className="square"に対応するCSSは定義していません。

<App.js>

import { useState } from "react";

function Value() {
  let [value, setValue] = useState(0);

  function handleClick() {
    value += 1;
    setValue(value);
  }

  return (
    <button className="square" onClick={handleClick}>
      {value}
    </button>
  );
}

function Message({ mes }) {
  return <p>{mes}</p>;
}

export default function Count() {
  return (
    <>
      <Message mes="Hello counter" />
      <Value />
    </>
  ); // mes is a 'props'
}

% rpm startでブラウザ起動され、ボタンをクリックすると数字がカウントアップされます。

おそらくReactでインタラクティブに動作する最もシンプルなサンプルだろうと思います。

もう少し複雑になるとtutorialの三目並べですが、

https://ja.react.dev/learn/tutorial-tic-tac-toe

ここでも機能として使っているのはpropsとstateですが、JavaScriptは関数も引数で渡せるので、それを多用しています。

また、handleClick():関数、onClick:プロパティの命名則はhandleXxx/onYyyのようにhandleとonで始めるのだそうです。

 

admin

 

Reactを使ってみよう

フロントエンドではReactを使うのが標準になっているようで、特に最近流行のSPAなどとの相性が良いらしい。

Reactが優れている点はいくつかあるようですが、個人的には仮想DOMを使うことでページの更新が速やかにできる(レンダリングコストの低減)はメリットだと思う。

Reactを学ぶサイトにはいくつかありますが、標準的なところでは、

https://ja.react.dev/learn

あるいは、

https://learn.microsoft.com/ja-jp/windows/dev-environment/javascript/react-beginners-tutorial

あたりが良さそうです。いずれにしても全体構造をさっと解説してあるようなサイトはなさそうで、ステップバイステップか。

ReactはJavaScriptのライブラリに過ぎないのですが、裏の仕組みとしてはNode.jsが主要な役割を果たしているようです。

% npx create-react-app some_app_name

で作成されるディレクトリ構造を見るとNode.jsに似ている(そっくり)ように思うし、

% npm start

でWebサーバーを起動するのも同じじゃないかと思う。Webサーバーを起動しているからサーバーサイドとクライアント間の通信もできるはず。

プロダクションビルドは、

% npm start

ではなく、

% npm run build

を使えと言われて、このコマンドを実行すると新たにbuildディレクトが作成されました。(以下の図(hello_reactディレクト)はVScodeから見えるbuildディレクトリ存在前のディレクトリ構造)中身を見ると、そのままサーバーに配置すれば使えるようになっているようだし、npm run buildの実行時のメッセージには以下のような出力がされるので、serveをインストールして起動するとnpm startと同様なページが表示されます。

The build folder is ready to be deployed.
You may serve it with a static server:

  npm install -g serve
  serve -s build

Reactの環境を作るのはコマンド一発で作成されますが、そこから作成されるファイル構造は上の図のようになっています。

 

webサーバー起動して表示される画面はこんな画面。

react.devの最初のtutorial(三目並べ)をbuildするのもNode.jsで変換されるんだろうと思いますが、以下のreact.devのtutorialのサンドボックスから見えるディレクトリ構造は、

/public/index.html、App.js/index.jsという同じ作りになっているから、この構造がスタートポイントと思えば良さそうです。index.html -> index.js -> App.jsと呼び出しているようです。

左上のメニューを選択して、File -> Export to ZIPでFilesメニューに見えるプロダクションビルドがZIPファイルでダウンロードできます。

 

admin

JavaScriptってシングルスレッドしかできなかと思ってた

それゆえにpromiseとか、async/awaitとかの非同期処理のための機能が用意されているのですが、

① ブラウザならばWeb Worker:一部制限はあるけれどもマルチスレッド動作ができる。こういう機能無かったらブラウザでのマルチスレッド処理は著しく不便になりそう、元々ブラウザは同時にたくさんのスレッドが動いているし、

スレッド間通信(postMessage/onmessage API)はevent emitterのemit/onと類似だし、Golangのチャネルとも似ていますが、安全に通信(スレッド間)を行おうとするとこのようなAPIが必要になるのはどの言語でも共通でしょう。

② Node.jsならば子プロセス、マルチスレッドのためにはworker_threadというAPIが用意されているようです。子プロセスなのでスレッドよりもリソースの分離性は良くなるでしょう、負荷は高くなりそうですが。

プロセス間通信はsend/on APIを使うのはWeb Workerとも類似だし、子プロセスの起動にforkを使うのもほぼそうだろうと思います。

個人的にはNode.jsではマルチスレッド(プロセス)を使ってみる機会がありそうです。

 

admin

timemachine必要ディスク容量(MacBook)

以前はNASのSSDをバックアップ先にしていて、容量比だとMac SSD使用容量の二倍ぐらい(500GBぐらい)しか確保していなかったから、割と早く(一年以内)いっぱいになって、いっぱいになると実は挙動不審な現象が出てくる。

で4月に3.5インチ/6TBのHDDにしたから一挙に五倍ぐらいの容量にして、Mac2台のバックアップを取るようにしてみた。どちらも使用容量は250GBぐらいか。それ故使用容量比でいくと十倍ぐらいまで増量。

MacBook 16(intel)/MacBook Air(M1) ==> 6GB

で二ヶ月半経過しましたが、ディスク使用量はまだ11%台で、一週間単位の圧縮があるからなかなか増えていかない。おそらくパソコンのライフかHDDのライフまで持ってしまいそうです。

結論:timemachineのバックアップ容量は五倍以上は確保しましょう

 

admin

 

assertion(@TypeScript)

TypeScriptにおいてannotationは頻繁に登場しますが、assertionは他の言語と同じく使い方は例外的(例えばテスト用とか)だろうと思います。

以下のコードではnumber | stringの戻りの型をformatInput()の引数とするために(string型)とするためにinput as stringが使われています。”is“(user defined type guard)と似てはいますが、ここは”as“ではないといけません。

// escape hatch(assert the type)
function formatInput(input: string) {
  console.log(input)
}

function getUserInput(): number | string {
  return 123;
}
let input = getUserInput();
formatInput(input as string);
表記は、
formatInput(<string>input);
でも等価ですが、TSX(React)構文と衝突の可能性があるので”as”で明示する表現を使うべきだと。いずれにしても使用シーンは限定的で常時使う機能ではありません。

 

admin

distributive conditional type(@TypeScript)

日本語だと、分配条件型という訳になるようですが、以下のコード例でT extends U ? never : Tで表現される三項演算子的な演算で型を決めるやり方です。

TとUというジェネリック型を実際の型で呼び出すと、Uに含まれる型(boolean)はnever扱いされるようになって、number/string型だけがAの有効型となります。型の集合演算的な機能ですね。

// distributive conditional type
type WithOut<T, U> = T extends U ? never : T;
//type WithOut<T, U> = T | U
type A = WithOut<boolean | number="" | string="", boolean="">; // type of A : number/string

let a: A = "sd";
a = 123;
//a = false   // error

 

admin

user defined type guard(@TypeScript)

条件ブロック内のオブジェクトの型の制限(Type Guard)の一つのやり方としてユーザー定義(user defined)をする方法があります。typeof/instanceof/inによる方法もありますが、これらは全てインラインで行う必要がありますが、関数定義時点で型制限することで読みやすいコードになるでしょう。

<o’reillyのサンプル>

// user defined type guard
function isString(a: unknown): a is string {
  // if a is not defined, parseInput isString cause error
  return typeof a === "string";
}

function parseInput(input: string | number) {
  let formatedInput: string;
  if (isString(input)) {
    formatedInput = input.toUpperCase();
    console.log(formatedInput)
  }
}

parseInput('qwerty')

 

<TypeScript Deep Diveのサンプル>

// TypeScript deep dive sample
interface Foo {
  foo: number;
  common: string;
}

interface Bar {
  bar: number;
  common: string;
}

function isFoo(arg: any): arg is Foo {
  return arg.foo !== undefined;
}

function doStuff(arg: Foo | Bar) {
  if (isFoo(arg)) {
    console.log(arg.foo);
    //console.log(arg.bar)  // VScode detects as an error
    console.log(arg.common);
  } else {
    //console.log(arg.foo)  // error
    console.log(arg.bar);
    console.log(arg.common);
  }
}

doStuff({ foo: 234, common: "abc" });
doStuff({ bar: 567, common: "efg" });

実行結果は、

QWERTY
234
abc
567
efg

 

どちらも xx is yyyで型を限定することで意図した動作を実現できます。

 

admin

companion object pattern(@TypeScript)

サバイバルTypeScriptに書かれているように、クラスを作るまでもないけど、値(value)と型(type)を同名にして扱いを簡単にしたいというケースで使います。これはTypeScriptは値と型は異なる空間に存在しているので、同名で扱えるということに他なりません。

<Currency.ts>

type Unit = "EUR" | "GBP" | "JPY" | "USD";

export type Currency = {
  unit: Unit;
  value: number;
};

export let Currency = {
  from(value: number, unit: Unit): Currency {
    return {
      unit: unit,
      value,
    };
  },
};

 

<呼び出し側>

importは共通名Currencyで、amountDueはtypeでotherAmountDueはvalueですが、同名で扱えます。

// companion object pattern
import { Currency } from "./Currency"; // works like a class

let amountDue: Currency = {   // set object directly as a 'type'
  unit: "JPY",
  value: 12000,
};

let otherAmountDue = Currency.from(85, "EUR"); // use 'from' function as a 'value'

 

admin

Record & mapped type(@TypeScript)

https://isehara-3lv.sakura.ne.jp/blog/2023/07/02/型定義しない気持ち悪さtypescript/

の続編になりますが、recordとmapped typeというのはオブジェクトの型表現に関わるTypeScriptに固有の機能です。どちらもできることはリンク先のコードと同じですが、記述方法がよりスマートだと思います。

type WeekDay = "Mon" | "Tue" | "Wed" | "Thu" | "Fri";
type Day = WeekDay | "Sat" | "Sun";

// record type(to use above types)
let nextDay: Record<WeekDay, Day> = {
  Mon: "Tue",
  Tue: "Wed",
  Wed: "Thu",
  Thu: "Fri",
  Fri: "Sat",
};

console.log(nextDay.Wed);   //Thu

// mapped type
let nextDayM: { [k in WeekDay]: Day } = {
  Mon: "Tue",
  Tue: "Wed",
  Wed: "Thu",
  Thu: "Fri",
  Fri: "Sat",
};

console.log(nextDayM.Mon);  //Tue

この例だとrecordもmappedもできることは変わらないのですが、mapped typeの方がルックアップ型と組み合わせて使うことでrecord typeよりもできることが広いです。

また、強力なのでTypeScriptには組み込み型でmapped typeが存在していますが、Record<keys, value>などは代表的な物です。

 

admin