2022年5月17日 りあクト! 第8章 コンポーネントのメンタルモデル (p.126~)
Reactの差分検出処理エンジン
Reactではstateの値とpropsの値に差分を検出すると、コンポーネントのレンダリング処理を再実行します。
stateとprops
- stateは、コンポーネントが持つ状態のことです。
- stateでは、エラーがあるか、モーダルウィンドウが開いているか、ボタンを押せるかなどの状態を管理します。
Reactの関数コンポーネントでstateを使うためには、useStateという関数を使います。
const [count, setCount] = useState(初期値);
- countが、stateを持った変数になります。
- setCountには、countというstateを更新するための関数が分割代入されます。
- propsは、親コンポーネントから子コンポーネントに渡すことができる引数のようなものです。
- stateの値をpropsという仕組みを使って、子コンポーネントに渡すこともできます。
stateとpropsの受け渡し
import "./styles.css"; import { useState } from "react"; // propsとして渡す値の型エイリアスを定義 type Books = { name: string; cost: number; onCount: React.Dispatch<React.SetStateAction<number>>; }; // 関数コンポーネントに渡ってくるpropsの型と戻り値の型を定義(呼び出し可能オブジェクトを型エイリアスで定義) type BookType = { ({ name, cost, onCount }: Books): JSX.Element; }; const Book: BookType = ({ name, cost, onCount }) => { // propsを分割代入で受け取る return ( <> <div>{name}</div> <div>{cost}円</div> <button onClick={() => onCount((prevCount) => prevCount + 1)}>ボタン</button> </> ); }; export default function App() { const [count, setCount] = useState(300); // useStateを使ってstateを定義(初期値200) return ( <div className="App"> <h1>Hello CodeSandbox</h1> <h2>Start editing to see some magic happen!</h2> <Book name={"book"} cost={count} onCount={setCount} /> </div> ); }
◆ Appコンポーネント
- Bookコンポーネントを呼び出しています。
- propsにname、cost、onCountという変数を渡しています。
- cost、onCountはuseStateです。初期値は300です。
◆ Bookコンポーネント
- Booksという型エイリアスを定義し、関数の引数に使用しています。
- 関数の引数と戻り値を呼び出し可能オブジェクトを用いてBookTypesとして型定義を行っています。
- Appコンポーネントで指定したpropsを分割代入で受け取ります。
- Bookコンポーネント内でpropsを用いてカウント処理を実装しています。
◆ useState
useStateではstateを更新する際にsetCountを用います。
const [count, setCount] = useState(0); const increment = () => setCount(count + 1);
上記の書き方のようにstateを使わなくてもアロー関数の引数にstateを受け取ることができるため、下記のように記述する方が好ましいです。
const increment = () => setCount((prevCount) => prevCount + 1);
参考
2022年5月16日 りあクト! 第7章 他のフレームワークとの比較 (p.113~)
世代におけるフロントエンド技術の特徴
フロントエンド技術を世代分けすると、第1世代が prototype.jsやjQuery といった DOM 操作を中心に据えたユーティリティライブラリ、第2世代がAngularJSが挙げられます。
現在はReact、Vue.js、Angularが第3世代と呼ばれています。
世代 | 特徴 | 代表的なライブラリ/フレームワーク |
---|---|---|
第1世代 | DOM操作を中心に据えたユーティリティライブラリ | prototype.js jQuery |
第2世代 | MV*アーキテクチャを採用した、modelとのデータバインディングを行うためのフレームワーク | AngularJS Backborn.js Knockout |
第3世代 | コンポーネントベースアーキテクチャなフレームワーク | React Angular Vue.js |
npm trends というサイトで最新のダウンロード数を見ることが出来ます。 https://www.npmtrends.com/angular-vs-react-vs-vue
Angular
◆ メリット
- サーバサイドで主流であるMVCデザインパターンを採用しているため馴染みがあり、取り掛かりやすいです。
- 直感的な双方向データバインディングによるHTMLテンプレートが採用されている。
- フレームワークとしてひととおりのものがフルスタックで提供されている。
◆ デメリット
- 覚えることが多く、学習コストが高いです。そして、一から十までフレームワークが決めたやり方の冗長な記述を強いられます。
- コンポーネントベースといいつつもモジュール、サービス、プロバイダなど覚えるべき概念が多い。
- コンポーネントを作るにはHTMLテンプレート、CSSスタイルシート、スクリプトの3つのファイルが必要 (Vue.jsはシングルファイルコンポーネントです。)
- 非同期処理やイベントを扱う際に、RxJSというライブラリを使う(可読性が低いと言われています。)
- TypeScriptのバージョンが上がっても、Angularがそれを追随するまで最新の機能を使えない。
Vue.js
◆ メリット
- AngularJSの機能から必要最低限の機能に絞った軽量なフレームワークである。
- Reactのコンポーネントアーキテクチャを取り入れている。
- HTMLベースで馴染みやすい設計で、仮想DOMを採用してパフォーマンスを向上させる改良も加えられている。
◆ デメリット
- シングルファイルコンポーネントは長期的に見るとコンポーネントの再分割を難しくリファクタリングがしづらい(大規模開発に適していない)
- 色々なフレームワークやライブラリの良いところを取りいれているので、一貫した思想がない。思想がないことが思想である。ポジティブに捉えると、変化に柔軟です。
- コミュニティベースのプロジェクトなので、大企業が後ろ盾にいるReactやAngularに比べると、サポートの安心感がない。
- Vue.jsの特有のエラーがあるため、ルールやエラー構文を覚えるまで少々開発がしづらい。
Web Componentsとは
ウェブコンポーネントは、再利用可能なカスタム要素を作成し、ウェブアプリの中で利用するための、一連のテクノロジーです。
https://developer.mozilla.org/ja/docs/Web/Web_Components
ReactとWeb Componentsは異なる課題を解決する為に構築されました。 Web Components はコンポーネントをパッケージ化して、高い再利用性を与えます。 一方で React は DOM とデータを同期させる為の宣言型のライブラリを提供しています。 Web Components内でReactを使用することも、React内でWeb Components を使用することも、あるいはその両方を行うこともできます。
ただ、あまり使用はされないそうです。
https://ja.reactjs.org/docs/web-components.html
参考
2022年5月14日 アルゴ式 番外編
Ryuji's answer
TypeScript
import * as fs from 'fs' const input = fs.readFileSync("/dev/stdin", "utf8") const [nv, alist] = input.split("\n") const [n,v,..._] = nv.split(" ").map(Number) const list = alist.split(" ").map(Number) type LastIndex = { (numArray: number[], v: number): number }; const lastIndex: LastIndex = (numArray, v) => { return numArray.lastIndexOf(v); }; console.log(lastIndex(list, v));
- javascript 配列インデックス 取得で検索したところ
Array.prototype.indexOf()
のメソッドがヒットし、javascript indexOf() 配列で再検索したら、lastIndexOf()の説明の記事がヒットしたのでjavascript lastindexof 配列で再再建策したところ、Array.prototype.lastIndexOf()のMDN)がヒットし使用しました。 - 命名がよくないので次回は命名をそれを読んだだけでわかるほど具体的で読めば理解できるようなものを選ぶようにします。
Yano's answer
- 『IndexOf 最後から JavaScript』と検索したところlastIndexOfという関数があることがわかりました。
- 関数の命名(fetchIndexMatchTheLastV)は『最後のVとマッチする』を英訳すると"match the last V"と出てきたので命名しました。
import * as fs from 'fs' const input = fs.readFileSync("/dev/stdin", "utf8") const [nv, alist] = input.split("\n") const [n, v] = nv.split(" ").map(Number) const a = alist.split(" ").map(Number) type FetchIndexMatchTheLastV = { (v: number, a: number[]): number; } const fetchIndexMatchTheLastV: FetchIndexMatchTheLastV = (v, a) => { return a.lastIndexOf(v); } console.log(fetchIndexMatchTheLastV(v, a));
- 命名にtheなどの冠詞は使わない方が良いそうです。
Yui's answer
import * as fs from 'fs' // fsモジュールは、Node.jsでファイルの読み書きを行うための基本的な関数を提供するモジュール const input = fs.readFileSync("/dev/stdin", "utf8") // 同期形式でファイルを読み込むreadFileSyncメソッドです。今回は入力値を受け取ります。 const [nv, alist] = input.split("\n") // 受け取った入力を改行で分割代入 const [n,v,..._] = nv.split(" ").map(Number) // 要素数nと探索に使う整数vを空白文字で分割してからそれぞれの変数に代入します(その時mapメソッドで数値に変換) const list = alist.split(" ").map(Number) // 受け取った配列の要素をnumber型に変換 type ArrayType = { (list: number[], v: number): number; } const arraySearchIndex: ArrayType = (list, v) => { return list.lastIndexOf(v); }; console.log(arraySearchIndex(list, v));
- 今回は、型エイリアスを定義し、関数に適用しました。
- 「javascript 何番目にあるかを調べる」で調べたところindexOfメソッドに辿り着き、特に配列の最後の要素から探索するlastIndexOfメソッドを見つけました。
- lastIndexOfメソッドは、与えられた要素が配列内で見つかった最後のインデックス番号を返し、見つからなければ-1を返します。
- 最初、問題の意図を正しく理解していなかったため、なかなか正解にならず苦戦しました。
- 具体的には、「最も右にあるVは前から何番目にあるかを出力」という部分をインデックス0番を含めない順番で数えようとしていました。
- しかし、「N 個の整数のうち先頭の要素 A[0]を、前から0番目であると数えることとします。」と書かれていることからインデックス番号0番目から何番目であるかを調べれば要件を満たせたと反省しました。
- 問題を正しく読むように心がけたいと思います。
Yuno's answer
import * as fs from 'fs' const input = fs.readFileSync("/dev/stdin", "utf8") const [nv, alist] = input.split("\n") const [n,v,..._] = nv.split(" ").map(Number) const list:number[] = alist.split(" ").map(Number) const selectLastIndexOfIntegers = (list:number[],v:number)=>{ return list.lastIndexOf(v); } console.log(selectLastIndexOfIntegers(list,v))
Yuki's answer
- lastIndexOfを知らなかったので、for文とif文でゴリ押ししました。
- 標準でメソッドが用意してあるので、そちらを使った方が良いです。
- 良い命名をつける、適したメソッドを見つけるのが今後の課題です。
import * as fs from 'fs' const input = fs.readFileSync("/dev/stdin", "utf8") const [nv, alist] = input.split("\n") const [n,v,..._] = nv.split(" ").map(Number) const list = alist.split(" ").map(Number) type IdentifyMostRightPosition = { (v: number, list: number[]): number; }; const identifyMostRightPosition: IdentifyMostRightPosition = ( v, list ) => { let mostRightPosition = -1; for(let i = 0; i < list.length; i++) { if(list[i] === v) { mostRightPosition = i; } } return mostRightPosition }; console.log(identifyMostRightPosition(v, list));
参考
2022年5月13日 りあクト! 第7章 単方向データフロー (p.108~)
単方向データフローとは
Reactでは親コンポーネントから子コンポーネントにpropsという形でデータが流れ落ちます。子コンポーネントから親コンポーネントにデータが逆流することはありません。
双方向バインディング
親コンポーネントと子コンポーネント間でデータの共有する方法です。
双方向バインディングを使用すると、親と子コンポーネント間でデータが同時に更新されます。
Reactでは双方向バインディングの機能は備えておらず、単方向データフローを採用しています。
Reactでフォームを実装する場合
- 親コンポーネントでフォームデータのstateを定義する。
- stateを変更する関数を定義し、子コンポーネントにpropsとして渡す。
- 子コンポーネント側で入力データを関数の引数として渡して実行し、stateを更新する。
↓フォームの例
import { useState } from "react"; import "./styles.css"; export default function App() { const [state, setState] = useState(""); const handleSubmit = () => { alert(state); }; const handleChange = (e: any) => { console.log(e.target.value); setState(e.target.value); }; return ( <form onSubmit={handleSubmit}> <label> Name: <input type="text" name="name" value={state} onChange={(e) => handleChange(e)} /> </label> <input type="submit" value="Submit" /> </form> ); }
stateのリフトアップ
子コンポーネントのstateを親コンポーネントに移して、子コンポーネントのpropsに対してstateの値を渡すように実装することです。
Learn Once, Write Anywhere (ひとたび習得すれば、あらゆるプラットフォームで開発できる)
コードを丸々共有することはできないが、コンポーネントベースなReactの書き方を学べば、レンダラーを変更する事でいろんなプラットフォームの開発ができるという意味でこのフレーズが使われています。
参考
2022年5月12日 アルゴ式 番外編
Ryuji's answer
TypeScript
import * as fs from 'fs' const input = fs.readFileSync("/dev/stdin", "utf8") const [nv, alist] = input.split("\n") const [n,v,..._] = nv.split(" ").map(Number) const list = alist.split(" ").map(Number) type PlusNumber = { (array: number[]): number; }; const plusNumber: PlusNumber = (array) => { const result = array.filter(word => word > 0); return result.length; }; console.log(plusNumber(list));
- 前回関数を使わずに実装していて、今回は型エイリアスと関数を使ってやると決めて取り組みました。
Yano's answer
- 型エイリアスを使用してみました。
- 『JavaScript 自然数 判定 メソッド』で検索しました。
Math.sign() - JavaScript | MDN
import * as fs from 'fs' const input = fs.readFileSync("/dev/stdin", "utf8") const [nv, alist] = input.split("\n") const [n] = nv.split(" ").map(Number) const list = alist.split(" ").map(Number) type CountNaturalNumber = { (numArray: number[]): number; } const countNaturalNumber: CountNaturalNumber = (numArray) => { return numArray.filter(num => Math.sign(num) === 1).length; } console.log(countNaturalNumber(list));
- Math.sign()は引数に渡した数字が自然数の場合に1を返します。
(マイナスの場合は戻り値が-1になります。) - 利点としては0 < num とする場合は小数点を含めて判定しますが、こちらの場合は整数のみを判定します。
- デメリットは、Math.sign()のメソッドの性質が分からないと読みにくいコードであることです。
- 型エイリアスと関数式の後にセミコロンが抜けていました。
Yui's answer
import * as fs from 'fs' const input = fs.readFileSync("/dev/stdin", "utf8") const [nv, alist] = input.split("\n") const [n,v,..._] = nv.split(" ").map(Number) const list = alist.split(" ").map(Number) const listNumber = (list: number[]): number => { return list.filter( num => num > 0).length; }; console.log(listNumber(list));
Yuki's answer
import * as fs from 'fs' const input = fs.readFileSync("/dev/stdin", "utf8") const [nv, alist] = input.split("\n") const [n, ..._] = nv.split(" ").map(Number) const list = alist.split(" ").map(Number) const findNumThanZero = (list: number[]): number => { return list.filter((num) => num > 0).length; }; console.log(findNumThanZero(list));
参考
2022年5月10日 Railsガイド ポリモーフィック関連付け
ポリモーフィック関連付けとは
ある一つのモデルが複数のモデルと関連付けがある事をpolymorphic: true
と1つの関連付けのみで示すことができます。モデルにポリモーフィック関連付けを行なった場合、データベースにもテーブル同士の関連付けがある事をマイグレーションファイルを通して伝える必要があります。
ポリモーフィックを設定するにはマイグレーションでpolymorphic関連付けを行うための外部キーを設定し、モデル側でそのbelongs_to: 外部キー名
としpolymorphicをtrueに設定することで一括でhas_manyとの関連付けを行うことが出来ます。has_manyにはasオプションで外部キーとの紐付けを行う必要があります。
関連付けを行う時にモデルとマイグレーションファイルにそれぞれ設定を記述する必要があるのは、モデルとマイグレーションファイル同士の直接の関係性はないためです。
モデルにアソシエーションを設定するとSQLを使わずRailsのメソッドを使うことでデータを取得することができるようになり、DBに設定を行うと関連付けのあるデータを取得・格納できるようになります。
◆ マイグレーション
class CreatePictures < ActiveRecord::Migration[5.2] def change create_table :pictures do |t| t.string :name t.references :imageable, polymorphic: true t.timestamps end end end
- imageable_idとimageable_typeの外部キーを通して、アソシエーション関係を構築しています。
◆ モデル
class Picture < ApplicationRecord belongs_to :imageable, polymorphic: true end class Employee < ApplicationRecord has_many :pictures, as: :imageable end class Product < ApplicationRecord has_many :pictures, as: :imageable end
例)
オンラインの塾があるとします。
アニキ先生に100人の生徒がbelongs to
を記述して紐づいています。belongs to
という記述を1つ1つ100人分書くのではなく、polymorphic: true
とasオプションを書くことで一度に100人の受講生が塾の生徒であるということを証明できるようになります。
モデルとは
モデルは、データベースや外部サービスへのアクセスをはじめ、ビジネスロジック全般を担当します。
また、データの取得や追加、更新など データベースとのやりとりを行います。
モデルにバリデーションをつけた場合、モデルを経由したデータベースとのやりとり時にバリデーションチェックが行われ、制約を満たさないものは弾かれます。
後述していますが、RailsではActive Recordを継承しているためデータベースとのやりとりを通常はSQLで行うところをRailsのメソッドで行えます。
マイグレーションファイルとは
Railsにおけるデータベースのテーブルを作成、追加、削除などの操作を行うための指示書です。
モデルの設定とは別に外部キー制約やNOTNULL制約を行うことが出来ます。
rails db:migrate
を実行することで実際にテーブルに指示が反映されます。
schema.rbでデータベース内の構造を確認することが出来ます。
モデルとマイグレーションファイルの関係性
モデルとマイグレーションファイルどちらもデータベースとやりとりをします。しかし、モデルとマイグレーションファイル同士の直接の関係性はありません。
テーブルとは
データを実際に格納している場所のことです。テーブルにNOTNULL制約などをつけた場合、データベースで弾くことが出来ます。
また構造は、以下のようになっています。
データベースサーバー▶スキーマ(データベースのカテゴリ分け)▶テーブル
画像引用
- NotNull制約はデータベースではじく
- モデルでpresence: true ではモデルを経由したときにはじく
ORM (オブジェクト/リレーショナルマッピング)とは
ORMを用いると、SQL文を直接書く代わりにわずかなアクセスコードを書くだけで、アプリケーションにおけるオブジェクトの属性やリレーションシップをデータベースに保存することもデータベースから読み出すことも可能にします。
RailsのO/RマッパーはActive Recordというライブラリです。
RailsではActive Recordを継承したクラスを利用することでRubyのコードをSQLに翻訳してやり取りを行ってくれます。 やり取り時に、リレーショナルデータベース(表形式のデータ)は、あたかもオブジェクトであるかのように操作できます。 画像引用
参考
2022年5月9日 りあクト! 第7章 Reactをめぐるフロントエンドの歴史 (p.99~)
Component-Based(コンポーネントベース)、Just The UI(UIにしか関知しない)
ReactにはRuby on RailsのようなMVCという概念はなく、model, controllerはアプリケーションを構築するのに必要ないと考えているのではないかと書籍では語られています。
コンポーネントを適切に分ける観点ではmodelは邪魔だとも考えられます。 純粋にUIから切り分けされるべきであり、データ抽象化の単位やそれを扱う手続きに左右されるべきではないからです。
カプセル化されたコンポーネントをベースとしてそれに必要なデータが宣言的に取得されるようにしておけば、modelというデータ抽象化が必要かどうかは問題ではありません。
modelという縛りがないReactではAtomic Designのように純粋なUIデザイン手法に則ってコンポーネントを分割することが可能です。
Reactはフレームワークではなくライブラリであるわけ
Reactは自身を A JavaScript library for building user interfaces(UIを構築するためのJavaScriptライブラリ) と名乗っています。
実際にReact単体でアプリケーション開発はできません。
ワンストップにしない理由は変化の早い世界において技術の進歩の妨げになると考えているからです。
あえてフルスタックのフレームワークにしない事で、後に出てくる秀逸なライブラリ(Reduxなど)を柔軟に取り入れられるようにしています。
◆ MVVM
MVVMはアーキテクチャパターンの一つで、Model/View/ViewModelの3つでアプリケーションを構築するものです。 モデル(M)とビュー(V)間のやり取りをビューモデル(VM)を介して行います。
Vue.jsやAngularJSはMVVMパターンのフレームワークです。
フレームワーク/ライブラリ | アーキテクチャ |
---|---|
Vue.js | コンポーネントシステムかつMVVM |
AngularJS | MVCかつMVVM |
React | コンポーネントベース |
Virtual DOM(仮想DOM)
仮想DOMとは、メモリに展開されたイミュータブルな要素ツリーのことです。
(イミュータブルは不変であるという性質を指します)
変更前の仮想DOMと変更後の仮想DOMをチェックして差分のあった部分をリアルDOMに反映させる仕組みです。
処理が走っても結果の DOM に差分がなければ余分な再レンダリングは発生せず、DOMの更新もピンポイントで最小限に抑えられます。
従来のDOM操作では、情報に変化が生じた場合にDOMを全て再構築し、それを元に再描画を行って画面を変化させるため表示が遅くなってしまいます。
Reactの仮想DOMの利点はDOMの更新を効率良く行うことでユーザー体験を良くすることができる点です。
レンダリングされるタイミング
- コンポーネントの初回レンダリング時
- 親コンポーネントがレンダリングされた時すべての子コンポーネントは無条件にレンダリングされる
- コンポーネント内で定義されたuseState()の状態変数/現在の値に変化があった時
- カスタムフックからコンポーネントが受け取っている変数が変化した時
再レンダリングされるタイミング
バケツリレーとは
孫コンポーネントにPropsを渡すときに、親・子コンポーネントを経由させなくてはならない状態を指します。
グローバルステートを定義する事で、孫コンポーネントに直接Propsを渡せるようにする事で、バケツリレーになることを防ぎます。
参考
りあクト! TypeScriptで始めるつらくないReact開発 第3.1版【Ⅱ. React基礎編】 p.99~