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~
2022年5月7日 アルゴ式 番外編
プログラム上で入力を扱う
Ruby
N, V = gets.split(' ').map!(&:to_i) A = gets.split(' ').map!(&:to_i) puts N puts V puts A.class
JavaScript
function main(input) { const args = input.split("\n"); const inputArray = args.map((n) => n.split(" ")); const array1 = inputArray[0].map((n) => parseInt(n, 10)); const N = array1[0]; const V = array1[1]; const A = args[1].split(" ").map((n) => parseInt(n, 10)); console.log(N); console.log(V); console.log(A); } main(require('fs').readFileSync('/dev/stdin', 'utf8'));
- String.prototype.split()は、引数に渡した改行コードや空白文字にマッチする部分を取り除いて、その部分をコンマで要素を区切り配列を返します。
- parseInt()は整数の文字列をNumber型に変更します。第二引数は10進数を指定しています。
今回の問題
配列の全探索 2
Ryuji's answer
function main(input) { const args = input.split("\n"); const inputArray = args.map((n) => n.split(" ")); const array1 = inputArray[0].map((n) => parseInt(n, 10)); const N = array1[0]; const V = array1[1]; const A = inputArray[1].map((n) => parseInt(n, 10)); const results = A.filter(word => word === V); console.log(results.length); } main(require('fs').readFileSync('/dev/stdin', 'utf8'));
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) const results: number[] = list.filter(word => word === v); console.log(results.length);
- 次回から関数で書いていきます!
Yano's answer
N, V = gets.split(' ').map!(&:to_i) A = gets.split(' ').map!(&:to_i) count_v = A.select { |n| n == V }.size puts count_v
- rubyではfilterとselectは同じ結果が得られます。
function main(input) { const args = input.split("\n"); const inputArray = args.map((n) => n.split(" ")); const array1 = inputArray[0].map((n) => parseInt(n, 10)); const N = array1[0]; const V = array1[1]; const A = args[1].split(" ").map((n) => parseInt(n, 10)); countV = A.filter(n => n === V).length console.log(countV); } main(require('fs').readFileSync('/dev/stdin', 'utf8'));
- countVの定義時にconstをつけ忘れていましたが出力に問題はありませんでした。
- 変数宣言が行われていない変数に値を格納した場合は、グローバルスコープの変数として宣言したとみなされます。strictモードではエラーになります。
Yuki'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) type CountSameNum = { (targetNum: number, array: number[]): number; }; const countSameNum: CountSameNum = (targetNum, array) => { const resultArray = array.filter((num) => num === targetNum); return resultArray.length; }; console.log(countSameNum(v, list));
◆ 型エイリアスで呼び出し可能オブジェクトを定義する
省略なし
type Fn = { (num1: number, num2: number): number }
省略記法
type Fn = (num1: number, num2: number) => number
参考
2022年5月6日 りあクト! 第7章 Reactをめぐるフロントエンドの歴史 (p.86~)
フロントエンドの歴史
年 | 出来事 |
---|---|
2005 | Googleマップが登場 |
2006 | jQueryが登場 |
2008 | GoogleがChromeのJavaScriptエンジンであるV8をオープンソース化して公開 |
2009 | V8エンジンを採用したJavaScriptの実行環境であるNode.jsがリリース |
2009 | ES5を発表 |
2010 | Backbone.js、Knockout、AngularJSなどのフレームワークが登場(後のVue.jsにも影響を与えたKnockoutの影響でJQueryへの依存が断ち切られる) |
2011 | Flash Playerの開発が中止される |
2013 | Reactが公開 |
2021 | HTML5が廃止され、HTML Living Standardが標準になる |
Reactを読み解く6つのキーワード
- Declarative (宣言的)
- Component-Based (コンポーネントベース)
- Just The UI (UIにしか関知しない)
- Virtual DOM (仮想DOM)
- One-Way Dataflow (単方向データフロー)
- Learn Once, Write Anywhere (ひとたび習得すれば、あらゆるプラットフォームで開発できる)
Reactの宣言的とは (Declarative)
宣言的プログラミングとは
出力の性質やあるべき状態を記述してプログラムを構成する事です。
命令型プログラミングとは
最終的な出力を得るために、時系列に沿って直前の状態に依存しながら命令を順番に書いていく手法の事です。
Reactの特徴の一つに、宣言的が掲げられています。 Reactは宣言的というコンセプトを2つの系統から得ました。
- Web Components → JSXにおいて、React Elementsを記述するスタイル
- 関数型プログラミング指向 → 関数コンポーネント、Hooks
どんなデータを表示されるべきかを記述しておくことでReactがそのデータを表示して適切なタイミングで表示を更新します。 (UIを宣言的に表現する)
参照透過性とは
参照透過性とは、同じ入力に対しては必ず同じ出力が得られることです。参照透過性が担保された関数は、同じ引数に対して、必ず同じ戻り値が返ってきます。
参考
2022年5月5日 Railsガイド Active Record の関連付け
has_many :through 関連付け
Physician・・・・医者
Appointment・・・診察予約
Patient・・・患者
class Physician < ApplicationRecord has_many :appointments has_many :patients, through: :appointments end class Appointment < ApplicationRecord belongs_to :physician belongs_to :patient end class Patient < ApplicationRecord has_many :appointments has_many :physicians, through: :appointments end
- 医者は、複数の診察予約を持つことができ、複数の患者を担当することが出来ます。
- 患者は、複数の診療予約を持つことができ、複数の医者から担当されることが出来ます。
- 診療予約にはどの医者とどの患者が関連付けされているかを、2つの外部キーで判断することが出来ます。
- throughオプションで2つの外部キーを持つ診療予約を参照することできます。
- physician.patientsで医者に診療予約した患者のリストを出すことが出来ます。
- patient.physiciansで患者が担当される医者のリストを出すことが出来ます。
中間テーブルの存在意義
usersとcourcesテーブルがあるとして、多対多のアソシエーションを直接紐付けると、1カラムの中にデータは1つずつしか入れられないため、1人のuserがいくつのcourceを選択するかわからないため、選択をしなかった場合に空のカラムができてしまい良くないテーブル設計になってしまいます。
ここで中間テーブルでそれぞれのuser_idとcources_idという外部キーを持たせたテーブルを用意する事で、空のカラムを作る事なく多対多のアソシエーションを組むことができます。
中間テーブルを使わない場合のデメリット
カラム数が多くなる
未使用(NULL)のカラムが多くなる
NULLが沢山入ってしまうアンチパターンという良くない設計になる
中間テーブルとは?
"join table もしくは junction table"とも呼ばれ、その名の通り、2つのテーブルの中間にもう一つ、それぞれに接続されたテーブルを指します。
中間テーブルには、接続先の外部キー同士を紐付けて格納しています。
例) twitterの良いね機能を考えます。 この場合、良いねする人(userテーブル)と良いねされる投稿(boardテーブル)があります。1人のユーザーは、複数の投稿に良いねでき、1つの投稿に対して複数のユーザーから良いねをもらうことができます(多対多)。このような場合に中間テーブルを使うことで良いねしたユーザーのuser_idと良いねされた投稿のboard_idをそれぞれ紐づけて格納してくれます。中間テーブルを使う場合、throughを使います。
class User < ApplicationRecord has_many :boards has_many :favorites # 中間テーブルと紐づけるための記述 has_many :favorite_boards, through: :favorites, source: :board end # いいねする人といいねした投稿を紐付ける中間テーブル # 外部キーであるuser_idとboard_idを格納しています。 class Favorite < ApplicationRecord belongs_to :user belongs_to :board end class Board < ApplicationRecord has_many :users has_many :favorites # 中間テーブルと紐づけるための記述 has_many :favorite_users, through: :favorites, source: :user end
user.favorite_boards
でユーザーが良いねした投稿の一覧を取得します。board.favorite_users
で投稿に良いねしたユーザーの一覧を取得します。