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

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

2022年5月13日 りあクト! 第7章 単方向データフロー (p.108~)

単方向データフローとは

Reactでは親コンポーネントから子コンポーネントにpropsという形でデータが流れ落ちます。子コンポーネントから親コンポーネントにデータが逆流することはありません。

双方向バインディング

コンポーネントと子コンポーネント間でデータの共有する方法です。

双方向バインディングを使用すると、親と子コンポーネント間でデータが同時に更新されます。

Reactでは双方向バインディングの機能は備えておらず、単方向データフローを採用しています。

Reactでフォームを実装する場合

↓フォームの例

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の書き方を学べば、レンダラーを変更する事でいろんなプラットフォームの開発ができるという意味でこのフレーズが使われています。

参考

りあクト! TypeScriptで始めるつらくないReact開発 第3.1版【Ⅱ. 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

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に翻訳してやり取りを行ってくれます。 やり取り時に、リレーショナルデータベース(表形式のデータ)は、あたかもオブジェクトであるかのように操作できます。 画像引用

参考

Railsガイド-ポリモーフィック関連付け

Railsガイド-Active Record の基礎

書籍転載:Ruby on Rails 4アプリケーションプログラミング-モデルの基本

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の更新を効率良く行うことでユーザー体験を良くすることができる点です。

レンダリングされるタイミング

レンダリングされるタイミング

バケツリレーとは

コンポーネントにPropsを渡すときに、親・子コンポーネントを経由させなくてはならない状態を指します。

グローバルステートを定義する事で、孫コンポーネントに直接Propsを渡せるようにする事で、バケツリレーになることを防ぎます。

参考

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

【React】仮想DOMって何!?コンポーネントのレンダリングと描画について理解しよう!

Reactコンポーネントの再レンダリング発生条件と防止方法

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

Image from Gyazo

Ryuji's answer

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 = 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

参考

Tricky use case

2022年5月6日 りあクト! 第7章 Reactをめぐるフロントエンドの歴史 (p.86~)

フロントエンドの歴史

出来事
2005 Googleマップが登場
2006 jQueryが登場
2008 GoogleChromeJavaScriptエンジンである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つの系統から得ました。

どんなデータを表示されるべきかを記述しておくことでReactがそのデータを表示して適切なタイミングで表示を更新します。 (UIを宣言的に表現する)

参照透過性とは

参照透過性とは、同じ入力に対しては必ず同じ出力が得られることです。参照透過性が担保された関数は、同じ引数に対して、必ず同じ戻り値が返ってきます。

参考

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

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で投稿に良いねしたユーザーの一覧を取得します。

参考

Railsガイド Active Record の関連付け
Qiita-やさしい図解で学ぶ 中間テーブル 多対多 概念編