2022年4月23日 りあクト! 第5章 Reactの組み込みコンポーネント(p.39~)
復習
トランスパイル
一つのプログラミング言語で書かれたプログラムのソースコードを別のプログラミング言語と同等のソースコードを生成すること また、それを行うプログラムをトランスパイラといいます。
- BabelはES2015以降のJavaScriptやJSXをトランスパイルするトランスパイラです。
- tscは、TypeScriptで書かれたソースコードをJavaScriptへとトランスパイルします。
コンパイル
人間が理解しやすい言語や数式で記述されたプログラムを機械語に変換すること。 また、それを行うプログラムをコンパイラといいます。
- tscはJavaScriptの構文チェックのみで構文ではない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をトランスパイルするトランスパイラ」 なのか
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 />
参考
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> </> );
- 空のタグは
React.Fragment
というコンポーネントの省略記法です。
参考
2022年4月18日 りあクト! 第5章 なぜReactはテンプレートを使わないのか(p.18~)
なぜReactはテンプレートを使わないのか
JSファースト(思想)
- ReactではJavaScriptで一貫してviewのレンダリングも行います。
- JSXはオブジェクトを生成するためのJavaScriptの純粋な式であり、フレームワークから特別扱いされることはありません。
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()
などの関数コールに変換してくれます。
関数コール(関数呼び出し)
関数呼び出しとは、プログラムから関数サブプログラムや関数ライブラリを呼び出すことである。 プログラムはいわば関数の組み合わせであるから、プログラムの処理は関数呼び出しを行いながらなされる。
参考
2022年4月16日 りあクト! 第5章 JSXでUIを表現する(p.11~)
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はコンポーネント自身が、自身の描画に必要な処理を自分でやり、レンダリングもコンポーネントごとに個別で行われます。
参考
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の値も同時に取得したい場合やイテレータのスプレッド構文による展開)に対応させる設定です。
参考
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
上記の大元のリポジトリから直接、型定義ファイルを探すのではなく、いくつかの検索方法があります。
目当ての型定義ファイルが決まっていて検索する方法
- 『TypeSearch』で検索をする方法
yarn info @types/任意のファイル名で検索する方法
npmで検索する方法
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'
でパッケージがインポートされるとこの型定義が適用されます。
型定義ファイルの優先順位
型定義ファイルには優先順位が存在します。以下の順番 で型定義が適用されます。
- ローカルでの型宣言
- モジュールがパッケージ内に持っている型ファイル
- node_modules/@types配下の型ファイル
復習
ビルドとコンパイルの違い
コンパイルとは、"ソースコードをコンピュータが認識できるオブジェクトコードに変換すること"です。
ビルドとは、"コンパイルしたファイルを一つにまとめ、実際に実行まですること"です。
node_modules
package.jsonを元にしてインストールされた各種パッケージが配置されるディレクトリを指します。