6時だョ!!全員集合!!

Rails・JavaScrictを中心にアウトプットします。

2022年4月23日 りあクト! 第5章 Reactの組み込みコンポーネント(p.39~)

復習

トランスパイル

一つのプログラミング言語で書かれたプログラムのソースコード別のプログラミング言語と同等のソースコードを生成すること また、それを行うプログラムをトランスパイラといいます。

  • BabelはES2015以降のJavaScriptやJSXをトランスパイルするトランスパイラです。
  • tscは、TypeScriptで書かれたソースコードJavaScriptへとトランスパイルします。

コンパイル

人間が理解しやすい言語や数式で記述されたプログラムを機械語に変換すること。 また、それを行うプログラムをコンパイラといいます。

  • tscJavaScriptの構文チェックのみで構文ではないPromiseなどの組み込みオブジェクトは型変換出来ないため、babelと併用する必要があります。

Reactの組み込みコンポーネント

Reactのコンポーネントにはユーザー定義コンポーネント組み込みコンポーネントの2種類があります。

ユーザー定義コンポーネント命名規則

  • 大文字から始めなければなりません。
  • 小文字から始めるとJSXから組み込みコンポーネントと解釈されるため呼び出し不可です。

Reactの組み込みコンポーネントのpropsと標準のHTML要素の属性の違い

JavaScriptの挙動とかぶってしまった事で名前自体が変更されているもの

  • class ⇨ className
  • for ⇨ htmlFor

HTMLと挙動が異なり、値の属性がBooleanになってるもの

  • checked
  • disabled
  • selected

value

Reactではtextareaとselectタグもvalue属性が持てます。

<form>
  <textarea value="Fixed Text" />
  <select value="uranus">
    <option value="saturn">Saturn</option>      
  </select>
</form>

小文字の名前でコンポーネントを定義してみる

小文字の名前でコンポーネントを定義すると、JSXからコンポーネントとして呼び出すことができません。JSXでは小文字から始まる名前のコンポーネントは、全て組み込みコンポーネントだと解釈されるからです。そのため、ユーザー定義コンポーネントコンポーネント名は、必ず大文字から始めましょう。

const message = (): JSX.Element => (
  <h1>ハロー</h1>
);

export default function App() {
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
      <message />
      // => Property 'message' does not exist on type 'JSX.IntrinsicElements'.ts(2339)
    </div>
  );
}

組み込みコンポーネントだけが持つ属性

ref属性やkey属性はHTMLには存在しない組み込みコンポーネントが持つ属性です。

ref属性

コンポーネントを実際にレンダリングされるリアルDOMへ結びつける参照のための属性です。

Ref は render メソッドで作成された DOM ノードもしくは React の要素にアクセスする方法を提供します。 子要素を変更するには、新しい props でそれを再レンダーします。ただし、この一般的なデータフロー以外で、子要素を命令型のコードを使って変更する必要がある場合もあります。 公式: https://ja.reactjs.org/docs/refs-and-the-dom.html

いつ Ref を使うか

Refに適した使用例は以下の通りです。

  • フォーカス、テキストの選択およびメディアの再生の管理
  • アニメーションの発火
  • サードパーティの DOM ライブラリとの統合

子要素を変更するには、新しい props でそれを再レンダーします。ただし、この一般的なデータフロー以外で、子要素を命令型のコードを使って変更する必要がある場合もあります。 宣言的に行えるものには ref を使用しないでください。

key属性

Reactが再レンダリングのための差分検出を効率的に行うのに必要とするものです。

Key は、どの要素が変更、追加もしくは削除されたのかを React が識別するのに役立ちます。配列内の項目に安定した識別性を与えるため、それぞれの項目に key を与えるべきです。 公式:https://ja.reactjs.org/docs/lists-and-keys.html#keys

基本ルールとしては、map() 呼び出しの中に現れる要素に key が必要です。 keyを指定しない場合はコンソールに警告が表示されます。

keyにmapの第二引数に指定するインデックスを使うのはユニークな値がない場合の最終手段です。

const Message = (): JSX.Element => (
  <div>
    {[...Array(5)].map((_, index) => {
      return <div key={index}>アイウエオ</div>;
    })}
  </div>
);

基本的には、そのオブジェクトなどが持つユニークなidをkey属性に指定します。

ユニークなidを持つオブジェクトの例

const listData = [{id:1,text:'1st'},{id:2,text:'2nd'},{id:3,text:'3rd'}];

参考

りあクト! TypeScriptで始めるつらくないReact開発 第3.1版【Ⅱ. React基礎編】 p.39~

Babelは「ES2015をコンパイルするコンパイラ」なのか、それとも「ES2015をトランスパイルするトランスパイラ」 なのか

tscとBabel みどりのさるのエンジニア

2022年4月22日 りあクト! 第5章 JSXとコンポーネントの関係(p.35~)

JSXの基本構文

JSX構文

<MyComponent foo="bar">baz</MyComponent>

コンパイルされると 以下のReact.createElementの形式に変換されます。

React.createElement(component, props, ...children)

React.createElement(MyComponent, { foo: 'bar' }, 'baz');

React.createElementの実行結果が以下の形式になります。

{
  type: 'MyComponent',
  props: { foo: 'bar', children: 'baz' },
  key: null,
  ref: null,
}
children

React.createElement()の第3引数に相当するものです。 JSX においては属性値ではなく子要素として記述され、呼び出された側のコンポーネントでは暗黙のpropsとして渡されます。

Props

/src/App.tsx

import React from 'react';
import Greets from './components/Greets';
.
.
.
const App: React.FunctionComponent = () => (
  <div className="App">
    <Greets name="Patty" times={4}>
      "yano"
    </Greets>
  </div> 
);

export default App;

Greetsタグに注目してください。

  • Propsとしてname="Patty" times={4} Props内のchildrenとして"yano"が渡されます。
  • childrenは暗黙的に渡されます。

/src/components/Greets.tsx

import React from 'react';

type Props = { name: string; times?: number };

const Greets: React.FunctionComponent<Props> = (props) => {
   const { name, times = 1, children } = props;
    
   return (
      <>
         {[...Array(times)].map((_) => (
          <p>Hello, {name}! {children}</p>
         ))}
      </>
  );
};

export default Greets;
  • 受け取ったPropsを分割代入しています。
  • childrenは引数として渡すときは暗黙的に渡しますが、受け取るときは明示的にchilderenとして分割代入される形で受け取る事で値が扱えます。
  • timesだけデフォルト値の1が設定されています。
  • [...Array(4)]はconsole.log([...Array(4)]);で出力すると[ undefined, undefined, undefined, undefined ]となりmapメソッドで繰り返し処理を行う回数を指定しています。

通常のPropsの渡し方

  • times={4}のように{}による式埋め込みで値を渡します。
  • 文字列の場合はname="patty" のようにクォーテーションで囲む形式によって渡すこともできます。

HTMLエスケープされた文字列をPropsとして渡した場合

子要素として文字列を記述するときの挙動

  • 行の先頭と末尾の空白文字と空白行が削除されます。
  • 改行は空白文字に置き換えられます。

propsとしてtrueを渡す場合

propsとしてBoolean値のtrueを渡す場合、値の記述を省略できます。

省略前

<MyButton color="blue" disable={true} />

省略後

<MyButton color="blue" disable />

参考

りあクト! TypeScriptで始めるつらくないReact開発 第3.1版【Ⅱ. React基礎編】 p.35~

2022年4月21日 りあクト! 第5章 JSXの書き方(p.29~)

JSXの基本文法

tsconfig.jsonのjsxオプション

reactに設定している場合

  • JSXの記述はReact.createElement(...) のように変換されます。
  • createElement メソッドの上位モジュールのReactをインポートする必要があります。

react-jsxにしている場合

  • TypeScript4.1以降のReact17.0から導入された新しいJSX変換方式ではimport React from 'react'省略できます。

jsxの構文

JSXは最終的にReactElement オブジェクトの生成式になります。 式であるがゆえに変数の代入、狭義のオブジェクトのプロパティ値にするなど、関数の引数や戻り値にするなどが出来ます。

{ }を使用して、JSXの式の中に別の式を埋め込むことも出来ます。

const name = 'Patty';
const greet = (name: string) => <p>Hello, {name || 'Guest'}!</p>;
return <div>{greet(name)}</div>;
  • { }の中でgreet(name)で関数コールをしています。
  • greetの中でも||で条件分岐して要素を返します。

null, undefined, boolean値を{ }の中で使う

null, undefined, boolean値を{ }の中で使うと、何も出力されません。boolean値が{ }内で表示されないという性質を、三項演算子や論理演算子に応用することで、条件に応じたJSXや値を返すことができます。

const n = Math.floor(Math.random() * 10); // 0 〜 9 の整数を生成 
const threshold = 5;
return (
  <div>
    {n > threshold && <p>`n` is larger than {threshold}</p>}
    <p>`n` is {n % 2 === 0 ? 'even' : 'odd'}</p>
  </div>
);
  • return文の最初の{ }内では、ランダムな整数が5より小さい場合はtrueとなり何も返しません。大きい場合は&&の左辺の要素が返されます。
  • 最後のpタグの部分では、nが奇数が偶数かを三項演算子で判定して文字列を返しています。

JSXでの繰り返し処理

const list ['Patty', 'Rolley', 'Bobby'];
  if(!list) { return <p>no list</p>}
return (
  <ul>
    { 
      list.map((name) => (
        <li>Hello, {name}!</li>
      ))
    }
  </ul>
);
  • JSXが式を返す値なので.filter(...).map(...) のようにメソッドチェーンや演算子を用いることもできます。

JSXにおけるコメントの書き方

  • JavaScriptと同様に // コメント /* コメント */ の記法で行います。

JSXに複数の要素が含まれるときはトップレベルが一つの要素でなければならない

以下コンパイルエラーになる例

const elems = (
  <div>foo</div>
  <div>bar</div>
  <div>baz</div>
);

解決するには一つのタグで囲います。 フラグメントを用いるのが良いです。

const elems = (
  <>
   <div>foo</div>
   <div>bar</div>
   <div>baz</div>
  </>
);

参考

りあクト! TypeScriptで始めるつらくないReact開発 第3.1版【Ⅱ. React基礎編】 p.29~

2022年4月18日 りあクト! 第5章 なぜReactはテンプレートを使わないのか(p.18~)

なぜReactはテンプレートを使わないのか

JSファースト(思想)

HTML テンプレート派 AngularJS、Angular、Vue.js、Ember.js、Aurelia、Svelte など

JS ファースト派 React、Preact、Mithril、Cycle.js、hyperapp など

JSファーストのメリット

  • 決まりごとが少なく、JavaScriptの表現を活用してより短いコードでコンポーネントを記述することが出来ます。
  • ReactのエラーはJavaScriptのエラーのため特定がしやすいです。
  • JSXが純粋な式のため、静的解析や型推論に適しているため、IDEやLintといったツールのサポートが受けやすいことに加えて、TypeScriptとの相性が良いです。

テンプレートのデメリット

なぜReactはViewをタグツリーで表現するのか

JSファースト派フレームワークの内、React、Preact以外はHyperSctiptというライブラリをviewのレンダリングに使っています。

HyperScript

ReactにおけるReact.createElement()のメソッドコールに相当するものを汎用的に簡略化できるライブラリです。

クライアントまたはサーバー上で、JavaScriptを使用してハイパーテキストを作成します。 https://github.com/hyperhype/hyperscript (公式より)

var h = require('hyperscript')
h('div#page',
  h('div#header',
    h('h1.classy', 'h', { style: {'background-color': '#22f'} })),
  h('div#menu', { style: {'background-color': '#2f2'} },
    h('ul',
      h('li', 'one'),
      h('li', 'two'),
      h('li', 'three'))),
    h('h2', 'content title',  { style: {'background-color': '#f22'} }),
    h('p',
      "so it's just like a templating engine,\n",
      "but easy to use inline with javascript\n"),
    h('p',
      "the intention is for this to be used to create\n",
      "reusable, interactive html widgets. "))

このようにJSON形式で記述することにより冗長なタグを書かなくて済む点がメリットです。

ただし、属性を持った要素がその子として複数のテキストまたは別の要素を持ってというようなノードツリーによるデータ構造を視覚的にわかりづらいです。

JSファーストで制御ロジックにマークアップが混在するからこそ、マークアップ部分が 視認できるタグを用いた構文が好まれているようです。そのため、Reactでは、JSXを用いてUIを表現しています。

React用の各種レンダラー

HyperScriptはハイパーテキスト、つまりHTMLのみを対象にしていますが、それに対してJSXはXML全般を表現するためのものです。 各プラットフォームに合わせて具現化するためのものがレンダラーで、レンダラーの共通の記述言語がJSXです。

レンダラー 内訳
React DOM HTML DOM
react-test-renderer JavaScript
React ART HTML5 Canvas
React Native iOSおよびAndroidのネイティブアプリケーション
React Native for Windows + macOS WindowsおよびmacOSのネイティブアプリケーション
React 360 ブラウザ上で動くVRアプリケーション
React-pdf PDFドキュメント
react-theree-fiber WebGLによる3Dグラフィック
React Figma Figmaプラグイン
React Sketch.app Sketchファイル

JSXは初見ではわかりにくく感じますが、JSXを使うことのメリットを学習してきました。次章からJSXの書き方を見ていきます。

JSXを使うメリット

  • JavaScriptの構文が使えます。
  • タグを使うので視認性に優れています。
  • ESLintの恩恵を受けられます。
  • JSXはJavaScriptの構文拡張なので、条件分岐や繰り返しにJavaScriptの構文を使うことが出来ます。
  • TypeScriptは言語レベルでJSXをサポートしており、tsconfig.json の設定にJSXのオプションがあり、コンパイラtscがJSXをReact.createElement() などの関数コールに変換してくれます。

関数コール(関数呼び出し)

関数呼び出しとは、プログラムから関数サブプログラムや関数ライブラリを呼び出すことである。 プログラムはいわば関数の組み合わせであるから、プログラムの処理は関数呼び出しを行いながらなされる。

参考

りあクト! TypeScriptで始めるつらくないReact開発 第3.1版【Ⅱ. React基礎編】 p.18~

2022年4月16日 りあクト! 第5章 JSXでUIを表現する(p.11~)

image

JSXとは

  • 名前の由来は『JavaScript』と『XML』の組み合わせです。
  • XMLライクな記述を出来るようにしたECMAScript2015を構文拡張したものです。
  • JSXは、コンパイル後にReact.createElementになります。
  • React.createElementを実行すると、ReactElementオブジェクトを生成します。

JSXで何が出来る?

  • JSXによって、XMLのタグとその組み合わせによるノードツリーをJavaScriptの中で書くことが出来るようになります。
  • JSXはJavaScriptにおけるオブジェクトを表現する式に還元するため、変数に代入したり、狭義のオブジェクトのプロパティ値にしたり、関数の引数や 戻り値にすることも出来ます。
const element = <span>foo</span>;
const obj = { bar: element };
const wrap = (element) => <div>{element}</div>;

なぜReactは見た目とロジックを混在させるのか

コンポーネントのロジック、たとえば表示のためのデータをどうやって取得するかとか、 起こったイベントをどう処理するかとか、状態の変化がコンポーネントにどう影響するかとかというのは、そのコンポーネントレンダリングと本質的に結合したものであって、それを無理に分割してしまうことのほうが非効率だとReactでは考える

コンポーネント志向

  • 機能単位で分割された独立性の高いパーツを組み合わせることでアプリケーションを構築するのがReactの開発思想です。(コンポーネント志向)
  • 独立した機能単位のパーツとして分割するためには、そのパーツの中に見た目とロジックを閉じ込める必要があります。
  • Reactはコンポーネント自身が、自身の描画に必要な処理を自分でやり、レンダリングコンポーネントごとに個別で行われます。

参考

りあクト! TypeScriptで始めるつらくないReact開発 第3.1版【Ⅱ. React基礎編】

2022年4月15日 りあクト! 第4章 TypeScriptの環境設定(p218~)

TypeScriptのコンパイルオプション

tsconfig.json

TypeScriptプロジェクトのコンパイラ設定を保存しておくためのファイルです。

コンパイルが実行される際、デフォルトではプロジェクトルート配下から順に探索し、最初に見つかったtsconfig.json ファイルが読み込まれて記述されている設定がコンパイラオプションとして有効になります。

config/tsconfig.json

{
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true, // 複数のオプションがまとめて有効になる設定
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx"
  },
  "include": [
    "src"
  ]
}

tsconfig.jsonはcreate-react-appコマンドでtypescriptを指定してプロジェクトを作成した際に生成される設定ファイルです。上記はデフォルトの内容です。

strictプロパティには以下の設定が含まれています

noImplicitAny

暗黙的にanyが指定されている式や宣言があればエラーになる

noImplicitThis

thisが暗黙的にanyを表現していればエラーになる

alwaysStrict

すべてのソースファイルの先頭に 'use strict' が記述されているものとみなし、ECMAScriptのstrictモードでパースする

strictBindCallApply

bind()、call()、apply()メソッド使用時に、その関数に渡される引数の型チェックを行う

strictNullChecks

他のすべての型から null および undefined が代入不可になる

strictFunctionTypes

関数の引数の型チェックが「共変的(Bivariant)」ではなく、「反変的 (Contravariant)」 に行われるようになる

strictPropertyInitialization

宣言だけで初期化されないクラスプロパティ(=メンバー変数)があるとエラーになる(※ strictNullChecks も併せて有効にしておく必要あり)

tsconfig.jsonのカスタマイズ

オプションの説明の続きです。

target

コンパイル先のJavaScriptのバージョンを指定するものです。

esnext

コンパイルに使われるバージョンのTypeScriptがサポートしているECMAScriptの最も新しいバージョンを示します。

lib

コンパイルに含めるライブラリを指定します。 ライブラリdomとdom.iterableはDOM操作を行うためのライブラリです。 esnextは最新のEXMAScript構文をサポートするライブラリです。

module

コンパイル後のモジュール構文をどのモジュールシステム形式にするかを設定します。 動作環境がサーバサイドのNode.jsであれば、commonjsを指定します。

noEmit

trueにするとコンパイル結果を出力しなくなります。 現行のCRAによるプロジェクト設定では tsc は構文チェックしか行わず、実際の TypeScriptのコンパイルはBabelが行ってるためです。

@babel/preset-typescript というプリセット(プラグインを特定のカテゴリーに よってまとめたもの)で TypeScript のコードから型情報を除去して ES2015 相当のコードに変換して、さらに @babel/preset-envで ES5 にコンパイルしてる

tscで型チェック・Babelでコンパイルを行う理由

tscコンパイルを行うと、Promise等の組み込みオブジェクトをコンパイルできません。そして、babelのみでコンパイルを行うと、型チェックが行われません。tscで型チェック・Babelでコンパイルをすることによって、型チェックを行いつつコンパイルすることができます。

jsx

tsxファイルをjsxやjsにコンパイルする際の出力の形式を指定します。 react-jsxという設定は、TypeScript 4.1 から導入されたオプションで、React 17.0 以降の新しいJSX変換形式に対応するものです。

include

コンパイル対象となるファイルを指定するためのものです。 デフォルトではsrcが設定されているため、ルート直下のsrc/ディレクトリにTypeScriptファイルを置くことでコンパイルが適用されます。 もしsrcディレクトリ以外にtsファイルを配置するのならばこの設定に追加する必要があります。

カスタマイズ(デフォルトに加えて追加)した設定

baseurl

モジュールのインポートのパス指定に絶対パスを使えるようにし、起点となるディレクトリを指定するオプションです。

設定前

import MenberList from '../../organisms/MenberList';

設定後

import MenberList from 'components/organisms/MenberList';

TypeScriptの公式ドキュメントでは、非相対インポートと書かれているが、開発者の間では絶対インポートと呼ばれています。

この設定はVSCodeと相性が悪い可能性があり、新規作成したファイルでは絶対パス指定を認識してくれないことがあります。

解消方法

  • shift+command+PまたはF1キーでコマンドパレットを開き >typeを入力するとリストアップされるTypeScript: Reload Projectを選択・実行する
  • コンソールからtouch tsconfig.json を実行する

downlevelIteration

コンパイルターゲットが ES5 以前 に設定されている場合でも、ES2015 から導入された各種イテレータ周りの記述(for...ofでindexの値も同時に取得したい場合やイテレータのスプレッド構文による展開)に対応させる設定です。

参考

りあクト! 【Ⅰ. 言語・環境編】 p.218~

tscとBabel

2022年4月14日 りあクト! 第4章 型定義ファイルはどのように探索されるか(p.215~)

npmのパッケージに含まれている型定義ファイルをプロジェクトに関連付ける方法は大きく分けて2つあります。

1. JavaScriptファイルと同じ階層に同じ名前で.d.ts拡張子の型定義ファイルを置く

例を挙げる『ky』というライブラリは、パッケージ内で同じ階層に同名のjsファイルと.d.tsファイルがあるため、型定義も読み込まれます。

https://github.com/sindresorhus/ky

node_modules/ky

├─ index.d.ts
├─ index.js
├─ license
├─ package.json
├─ readme.md
├─ umd.d.ts
└─ umd.js

2. パッケージ配下のpackage.jsonに"types"または"typings"プロパティで型定義ファイルのパスを指定する

例を挙げると『Immer』というイミュータブルなオブジェクトツリーを扱うためのライブラリがこの形式を取っています。 https://github.com/immerjs/immer/blob/master/package.json

package.json
{
  "name": "immer",
  "version": "9.0.0-beta.1",
  "description": "Create your next immutable state by mutating the current one",
  "main": "dist/index.js",
  "module": "dist/immer.esm.mjs",
  "exports": {
    ".": {
      "import": "./dist/immer.esm.mjs",
      "require": "./dist/index.js"
    },
    "./*": "./*"
  },
  "umd:main": "dist/immer.umd.production.min.js",
  "unpkg": "dist/immer.umd.production.min.js",
  "jsdelivr": "dist/immer.umd.production.min.js",
  "jsnext:main": "dist/immer.esm.mjs",
  "react-native": "dist/immer.esm.mjs",
  "source": "src/immer.ts",
  "types": "./dist/immer.d.ts",
    .
    .
  • typesプロパティに型定義ファイルをパス付きで設定してあります。
  • typingsというプロパティ名でも可能です。
  • パッケージのルートディレクトリにindex.d.ts という名前で配置すればこの設定は省略可能です。(多くの場合、このファイルがある上でpackage.jsonの設定も省略されていないようです)

公式型定義ファイルが配布されていない場合

有志の第三者の方が作ってくれていることが多く、まとめて公開されている『Definitely Typed』があります。

https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types

上記の大元のリポジトリから直接、型定義ファイルを探すのではなく、いくつかの検索方法があります。

目当ての型定義ファイルが決まっていて検索する方法

TypeScriptがnode_modules/@typesディレクトリから(型定義ファイルがインストールされている前提)自動で検索し対象のパッケージと関連付けしてくれます。

三者の方が作ってくれた型定義ファイルも存在しない場合

src/ディレクトリに任意の名前で.d.ts ファイルを配置します。 今回はsrc/types.d.ts というファイルでフォーマットを作ります。

declare module awesomelib {
  export type Amazing = { ... };
  declare function fabulous(arg: Amazing): void;
    .
    .
  export default fabulous;
}
  • import fabulours from 'awesomelib' でパッケージがインポートされるとこの型定義が適用されます。

型定義ファイルの優先順位

型定義ファイルには優先順位が存在します。以下の順番 で型定義が適用されます。

  1. ローカルでの型宣言
  2. モジュールがパッケージ内に持っている型ファイル
  3. node_modules/@types配下の型ファイル

復習

ビルドとコンパイルの違い

コンパイルとは、"ソースコードをコンピュータが認識できるオブジェクトコードに変換すること"です。

ビルドとは、"コンパイルしたファイルを一つにまとめ、実際に実行まですること"です。

node_modules

package.jsonを元にしてインストールされた各種パッケージが配置されるディレクトリを指します。

参考

りあクト! 【Ⅰ. 言語・環境編】 p.215~

コンパイルとビルドの違いを理解しよう

この話において、それぞれのソースコードを機械語に翻訳する作業がコンパイルです。