- 投稿日:2020-02-22T23:09:31+09:00
[WIP] MDN web docs JavaScriptの要点メモ書き
名称
- JavaScript® は、米国およびその他の国における、Oracle の商標または登録商標。
- JSとも略される。
歴史
- Mozilla の Brendan Eich によって考案された。
規格
- ECMA International が ECMA-262 および ECMA-402 として規格化。ECMA-262 がJS本体。1992年制定。ECMA-402 はi18n対応。2012年制定。
- 2015年に制定された規格は、公式にはECMA2015、非公式にはES6と略される。
特長
- 第一級関数
- 動的なスクリプト型
- プロトタイプベース
- mixinが可能
- 実行時に型解決
- マルチパラダイム
- オブジェクト指向
- 命令型
- 関数型
- マルチプラットフォーム
- サーバサイド(Node.js等)
- クライアントサイド(ECMAScript)
字句
リテラル
NULLリテラル
null。
真偽値リテラル
Boolean型と真偽値リテラルは別物。
new Boolean(false)
はtrue
になる。そして、BooleanオブジェクトとBoolean関数も別物。まとめるとこんな感じになる。// Booleanオブジェクト new Boolean(true) // true new Boolean(false) // true // Boolean関数 Boolean(true) // true Boolean(false) // false // 真偽値リテラル true //true false // false要するにBooleanオブジェクトを使うとfalseがtrueになってしまうらしい。
以下サイト参照。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Boolean数値リテラル
- Decimal
- 10進数。0始まりで以降が8未満の数字のみだと8進数扱いされる。
- Binary
0b
または0B
はじまりの2進数。- Octal
0o
または0O
はじまりの8進数。- Hexiadecimal
0x
または0X
はじまりの16進数。- 浮動小数点リテラル
オブジェクトリテラル
リテラルを文の先頭で使わないようにしてください。{ がブロックの始まりと解釈されるため、エラーや予期せぬ動作を引き起こすことになります。」とのこと。怖すぎる。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Grammar_and_types#Object_literals
配列リテラル
文字列リテラル
正規表現リテラル
スラッシュで囲まれた文字列。
/xxx/テンプレートリテラル
- ES6から登場
- バックスラッシュで囲まれた文字列。
`Hello ${user} !`プリミティブ型
以下7つ。
Boolean
true
とfalse
のみ。Null
null
のみ。undefined
undefined
のみ。Number:数値
以下の3種類。
- 数値リテラル
- 正数、負数
+, -
と0から9までの数字で表現される。- 値はすべてIEEE 754として扱われる。つまり、64ビット倍精度浮動小数点数。
- 0
+0
と-0
がある。- 数値オブジェクト
+Infinity
、-Infinity
とNaN
がある。String
Cとは異なり、変更不能な文字列。
- 文字列リテラル
- テンプレートリテラル
Symbol
Object
セミコロン
- セミコロンはなくてもいいが、ないとブラウザが自動で挿入することがECMAで定義されている。詳細は以下参照。
識別子
- 関数、変数、定数等々の名前をまとめて識別子という。
- 識別子は以下の命名規則に従う。
- はじめは文字、
_
または$
。- Unicode文字おk。
- 大文字小文字は区別。
スコープ
基本
- 関数の外側でブロックの外側
- 全ファイルからアクセス可能なグローバル。
- 自動的にグローバルオブジェクトwindowのプロパティになっている。
- 関数の外側でブロックの内側
- ES6以前はブロックスコープがなくグローバルになっていた。
- ES6からは
let
を使うとブロックスコープになる。- 関数の内側
- 関数内に閉じたローカルスコープ
巻き上げ
- 変数
- ES6以前では関数の巻き上げという現象があり、以下の文は例外を起こすことなく、undefinedを返していた。
- ES6からは
ReferenceError
を返す。- 関数
- 関数宣言は巻き上げられる
- 関数式は巻き上げられない(関数式は第1級関数では変数と同じ扱いになるだろうから、当然といえば当然。)
console.log(kannkyo + 1); // undefined var kannkyo = 123;関数
引数
デフォルト引数
function add(a, b=1) { return a + b; } add(2) //3残余引数
function add(...a) { var result = 0; a.map(x => result += x) return result; } add(1, 2, 3, 4) //10関数定義
関数宣言
function add(a, b) { return a + b; } add(1, 2) //3関数式
var add = function (a, b) { return a + b; } (1, 2) //3アロー関数
var add = (a, b) => a + b; add(1,2)クロージャ
クロージャはデザインパターンの1つ。カリー化に用いられる。
function method(n){ var number = n; function closure(power){ return number ** power; } return closure; } var result = method(2)(10); // 1024 = 2 ** 10即時実行関数式
即時実行関数式は、IIFE (Immediately Invoked Function Expression)とも呼ばれるデザインパターンの1つ。
(function (){ console.log('do IIFE'); })(); console.log('finish');void演算子を用いて以下のようにも書くことができる。
void function (){ console.log('do IIFE'); }(); console.log('finish');予約語(ECMA公式、命令)
const
- 配列の中身とオブジェクトのプロパティは定数化できない。
continue
Javaとおなじ。
debugger
delete
while, do...while
Javaとおなじ。
for, for...in, for...of
- for
- Javaとおなじ。
- for...in
- オブジェクトのプロパティ名でループできる。
- for...of
- オブジェクトのプロパティ値でループできる。
var object = [10, 20, 30]; object.p = 'aiueo'; for (var i=0; i<=object.length; i++) { console.log(object[i]);// 10 20 30 undefined } for (var property_name in object) { console.log(property_name);// 0 1 2 p } for (var property_value of object) { console.log(property_value);// 10 20 30 }function
if...else
Javaとおなじ。
instanceof
new
return
switch...case...break...default
Javaとおなじ。
this
throw
try...catch...finally
Javaとおなじ。
typeof
var
void
with
予約語(ECMA公式、リテラル)
null
true
false
予約語(将来実装、strictでの利用不可)
class
enum
export
extends
import
super
予約語(将来実装、strictでの利用可)
implements
interface
let
package
private
protected
public
static
yield
演算子
代入演算子
単なる代入とは別に以下の代入演算子と代入方法がある。
- 複合代入演算子(
+=
等)
- ほぼJava等と同じだが、符号なし右シフト代入
>>>>=
が特殊。- 分割代入
- ほぼPythonと同じ。
var [first, ,end] = ['最初', '真ん中','最後'];
的な代入ができる。比較演算子
概ねJavaと同じだが、以下だけ独特。
- 等価
==
- 自動的に型を変換した上で比較する。
0 == '0'
や0 == false
は真となるが、1 == true
は偽となる。ややこしい。- オブジェクトの比較は全く同じメモリを指す場合だけ真となる。要はポインタ比較。
- 不等価
!=
- 等価の逆。
- 厳密等価
===
- 等しくかつ型が一致する場合に真。それ以外で偽となる。
- 厳密不等価
!==
- 厳密等価の逆。
算術演算子
概ねJavaと同じだが、以下だけ独特。
- 単項プラス
+
- 数値でないものに単項プラスをつけると数値にされる。
+null
は0になり、+undefined
はNaN
になり、+true
は1となる。ややこしい。ビット演算子
概ねC言語と同じだが、すべての被演算子は演算前に32ビット整数に変換されてしまうことに注意が必要。
ビット論理演算子
ビット論理演算子には以下の4つがあり、C言語とほぼ同じ。
- 論理積
- 論理和
- 排他的論理和
- 否定ビットシフト演算子
- 左シフト
a << b
- c言語の左シフト相当。
- 符号維持右シフト
a >> b
- 右シフトした上でMSBの値はシフト前と同じにする。1なら1。
- ゼロ埋め右シフト
a >>> b
- c言語の右シフト相当。
論理演算子
以下のようにC言語とほぼ同じだが、文字列はtrue扱いされるので要注意。またC言語と同様に短絡評価される。
- 論理和
a && b
のうちa
がfalseならa
を、それ以外ではb
を返す。- 論理積
a || b
のうちa
がtrueならa
を、それ以外ではb
を返す。- 否定
文字列演算子
条件(三項)演算子
カンマ演算子
「JIS X 3010 プログラミング言語C」のコンマ演算子相当。
単項演算子
- delete
- varで定義したものはdeleteできない。
- typeof
- Javaとかと同じ。
- void
- Javaとは異なり、型名ではなく演算子名。何を突っ込んでもundefinedが返ってくる演算子。
関係演算子
- in
- instanceof
- JavaScriptはプロトタイプベースの言語であり、Javaのインスタンスは存在しない。インスタンスかどうかというより、同じprototypeを持っているかどうかをチェックするだけである。
式
基本式
- this
- グループ化演算子
( )
左辺式
Javaとほぼ同じ。
- new
- super
- 展開演算子
...
そのた
undefined
NaN
Infinity
null
ブラウザについて
- タブはそれぞれ独立した実行環境になっており相互に情報のやり取りはできない。(普通は)
- 投稿日:2020-02-22T23:07:48+09:00
javascript DOM関連
DOM
ブラウザがHTMLを読み込むと内部的にDOMというデータ構造が作られ、ページが描画される。
javascriptが操作するのはHTMLソースではなくDOMである。
DOMはdocumentオブジェクトから扱うことができる。要素の操作
document.querySelector('h1').textContent = 'changed'; document.getElementById('targer').textContent = 'changed'; document.querySelectorAll('p').textContent = 'changed'; document.querySelectorAll('p')[1].textContent = 'changed'; // 何番めかを指定できる document.querySelectorAll('p').forEach((p,index) => { package.textContent = `${index}番目のpです`; });イベントを設定する
addEventListener(イベントの種類,関数)
document.querySelector('button').addEventListener('click', () => { document.getElementById('target').textContent = 'Changed!'; });classList
クラス名に関するあれこれ。
const target = document.getElementById('target'); target.classList.add('クラス名'); target.classList.remove('クラス名'); target.classList.contains('クラス名'); //真偽値が返る target.classList.toggle('クラス名');カスタムデータ属性
※これはHTMLの機能
javascriptで値を書き換えたいときなどに、値をHTMLの方に記述しておくことで見やすいコードとなる。HTML<h1 id="target" data-transition="changed">更新前</h1>JSconst target = document.getElementById('target'); target.textContent = target.dataset.transition;要素の追加
要素を作って、DOMに追加する。
- createElement()
- appendChild()const item2 = document.createElement('li'); //liタグを作る item2.textContent = 'item 2'; //中身のテキストを作る const ul = document.querySelector('ul'); //親DOMを取得 ul.appendChild(item2);//末尾の子要素として追加要素の複製・挿入をする
- cloneNode()
- insertBefore()
document.querySelector('button').addEventListener('click', () => { const item0 = document.querySelectorAll('li')[0]; //複製対象の要素取得 const copy = item0.cloneNode(true); //複製する const ul = document.querySelector('ul'); // 親ノード取得 const item2 = document.querySelectorAll('li')[2]; //複製箇所取得 ul.insertBefore(copy, item2); //複製処理 });要素の削除
- remove()
- removeChild():一部の古いブラウザで使用不可
document.querySelector('button').addEventListener('click', () => { const item1 = document.querySelectorAll('li')[1]; // item1.remove(); // 親Node.removeChild(削除するNode) document.querySelector('ul').removeChild(item1); });input要素の操作
{ document.querySelector('button').addEventListener('click', () => { const li = document.createElement('li'); const text = document.querySelector('input'); li.textContent = text.value; //value値を取得 document.querySelector('ul').appendChild(li); //要素を追加 text.value = ''; //入力欄を空にする text.focus(); //フォーカスを当てる }); }イベントオブジェクト
addEventListenerのコールバック関数に引数を渡すと、ブラウザがイベントに関する情報を渡してくれる。慣習的にeとする。
{ document.querySelector('button').addEventListener('dblclick', () => { console.log('Double Clicked!'); }); //マウスの位置を取得する document.addEventListener('mousemove', e => { console.log(e.clientX, e.clientY); }); //押されたキーボードを取得する document.addEventListener('keydown', e => { console.log(e.key); }); }フォーム関連のイベント
- focus:フォーカスが当たってるとき
- blur:フォーカスが外れてるとき
- input:内容が更新されたとき
- change:更新が確定したとき
- submit:フォームが送信されたとき
フォームタグの中でボタンを配置しておくとクリックした時にsubmitイベントが発生する。
submitイベントが発生するとページ遷移が発生し、フォームのデータがサーバに送られる。
このデフォルトの挙動をキャンセルするにはpreventDefault()を呼び出す。{ document.querySelector('form').addEventListener('submit', e => { e.preventDefault(); console.log('submit'); });イベントの伝搬
イベントは親ノードに伝搬するため、addEventListenerを対象の親ノードに設定しても上手く動作する。
e.targetで実際にクリックされた対象のノードが取得できる
e.currentTargetでイベントを設定したノードが取得できる。document.querySelector('ul').addEventListener('click', e => { if (e.target.nodeName === 'LI') { e.target.classList.toggle('done'); } });
- 投稿日:2020-02-22T23:07:48+09:00
javascript DOM関連要素の追加
DOM
ブラウザがHTMLを読み込むと内部的にDOMというデータ構造が作られ、ページが描画される。
javascriptが操作するのはHTMLソースではなくDOMである。
DOMはdocumentオブジェクトから扱うことができる。要素の操作
document.querySelector('h1').textContent = 'changed'; document.getElementById('targer').textContent = 'changed'; document.querySelectorAll('p').textContent = 'changed'; document.querySelectorAll('p')[1].textContent = 'changed'; // 何番めかを指定できる document.querySelectorAll('p').forEach((p,index) => { package.textContent = `${index}番目のpです`; });イベントを設定する
addEventListener(イベントの種類,関数)
document.querySelector('button').addEventListener('click', () => { document.getElementById('target').textContent = 'Changed!'; });classList
クラス名に関するあれこれ。
const target = document.getElementById('target'); target.classList.add('クラス名'); target.classList.remove('クラス名'); target.classList.contains('クラス名'); //真偽値が返る target.classList.toggle('クラス名');カスタムデータ属性
※これはHTMLの機能
javascriptで値を書き換えたいときなどに、値をHTMLの方に記述しておくことで見やすいコードとなる。HTML<h1 id="target" data-transition="changed">更新前</h1>JSconst target = document.getElementById('target'); target.textContent = target.dataset.transition;要素の追加
要素を作って、DOMに追加する。
- createElement()
- appendChild()
const item2 = document.createElement('li'); //liタグを作る item2.textContent = 'item 2'; //中身のテキストを作る const ul = document.querySelector('ul'); //親DOMを取得 ul.appendChild(item2);//末尾の子要素として追加要素の複製・挿入をする
- cloneNode()
- insertBefore()
document.querySelector('button').addEventListener('click', () => { const item0 = document.querySelectorAll('li')[0]; //複製対象の要素取得 const copy = item0.cloneNode(true); //複製する const ul = document.querySelector('ul'); // 親ノード取得 const item2 = document.querySelectorAll('li')[2]; //複製箇所取得 ul.insertBefore(copy, item2); //複製処理 });要素の削除
- remove()
- removeChild():一部の古いブラウザで使用不可
document.querySelector('button').addEventListener('click', () => { const item1 = document.querySelectorAll('li')[1]; // item1.remove(); // 親Node.removeChild(削除するNode) document.querySelector('ul').removeChild(item1); });input要素の操作
{ document.querySelector('button').addEventListener('click', () => { const li = document.createElement('li'); const text = document.querySelector('input'); li.textContent = text.value; //value値を取得 document.querySelector('ul').appendChild(li); //要素を追加 text.value = ''; //入力欄を空にする text.focus(); //フォーカスを当てる }); }イベントオブジェクト
addEventListenerのコールバック関数に引数を渡すと、ブラウザがイベントに関する情報を渡してくれる。慣習的にeとする。
{ document.querySelector('button').addEventListener('dblclick', () => { console.log('Double Clicked!'); }); //マウスの位置を取得する document.addEventListener('mousemove', e => { console.log(e.clientX, e.clientY); }); //押されたキーボードを取得する document.addEventListener('keydown', e => { console.log(e.key); }); }フォーム関連のイベント
- focus:フォーカスが当たってるとき
- blur:フォーカスが外れてるとき
- input:内容が更新されたとき
- change:更新が確定したとき
- submit:フォームが送信されたとき
フォームタグの中でボタンを配置しておくとクリックした時にsubmitイベントが発生する。
submitイベントが発生するとページ遷移が発生し、フォームのデータがサーバに送られる。
このデフォルトの挙動をキャンセルするにはpreventDefault()を呼び出す。{ document.querySelector('form').addEventListener('submit', e => { e.preventDefault(); console.log('submit'); });イベントの伝搬
イベントは親ノードに伝搬するため、addEventListenerを対象の親ノードに設定しても上手く動作する。
e.targetで実際にクリックされた対象のノードが取得できる
e.currentTargetでイベントを設定したノードが取得できる。document.querySelector('ul').addEventListener('click', e => { if (e.target.nodeName === 'LI') { e.target.classList.toggle('done'); } });
- 投稿日:2020-02-22T23:06:54+09:00
javascript メソッド関連
配列
const array = [10,20,30]
- 要素の追加
- unshift():先頭に追加
- push():末尾に追加
- 要素の削除(1つずつしか削除できない)
- shift():先頭から削除
- pop();末尾から削除
- 配列の操作
- splice(変化が開始する位置,削除数)
- splice(変化が開始する位置,削除数,追加する要素)
- 参照渡し
- 配列とオブジェクトは参照渡しである。
- スプレッド構文を使うと値渡しとなる。
スプレッド構文
配列を展開してくれる。
const otherScores = [10, 11]; const scores = [6, 7, 8, 9, ...otherScores]; // console.log(scores); function sum(a, b) { console.log(a + b); } sum(...otherScores); // sum(10, 11); と同じ意味分割代入 レスト構文 値の交換
//分割代入 const [a, b, c, d] = scores; //レスト構文 const [a, b, ...others] = scores; //値の交換 let x = 10; let y = 20; [x, y] = [y, x];forEach
- 配列の要素数を気にする必要が無い
- オブジェクトには使えない
- breakやcontinueは使えない
- 3つの引数を与えられる。
- 第一引数:配列データの値
- 第二引数:配列のインデックス番号
- 第三引数:現在処理している配列
const scores = [10, 20, 30, 40]; scores.forEach((score, index) => { console.log(`Score ${index}: ${score}`); }); //Score 0: 10... //Score 1: 20...map
- 返り値:処理後の配列
const prices = [180, 190, 200]; const updatedPrices = prices.map((price) => { return price + 20; }); console.log(updatedPrices); //[200,210,220]filter
- 返り値:フィルター後の配列
- trueの値だけが残る
const numbers = [1, 4, 7, 8, 10]; const evenNumbers = numbers.filter(number => { return number % 2 === 0; }); console.log(evenNumbers); //[4,8,10]オブジェクト
- []ではなく{}を使う。
- 最後のカンマはあってもなくてもエラーにならない。
- 要素の削除にはdeleteを使う
// const point = {x: 100, y: 180}; const point = { x: 100, y: 180, }; point.x = 120; console.log(point.x); //120 console.log(point.['y']); //180 point.z = 90; delete point.y; console.log(point); //{x :120,z:90}
- スプレッド構文 分割代入 レスト構文も使用可
- 分割代入ではオブジェクトのキーと同じ定数名を使えば、そのキーの値が代入される。
const others = { r: 4, color: 'red', }; const point = { x: 100, y: 180, ...others, }; const {x, r, ...others} = point; console.log(x); //100 console.log(r); //4 console.log(others); // {y:180,color:red}文字列,配列の操作
- length:長さ取得
- substring(開始位置,終了位置):一部の文字列を取得
- join(区切り文字):配列を区切り文字でつないで文字列として返す
- split(区切り文字):文字列を区切り文字で分割して配列として返す
数値の操作
let score = 7.33333; Math.floor(score) // 7 小数点切り捨て Math.ceil(score) // 8 小数点切り上げ Math.round(score) // 7 四捨五入 score.toFixed(3) // 7.333 小数点3桁まで表示 Math.random() // 0以上1未満のランダムな数値を生成日時
- Dateクラスを使う
- 引数を使う場合は、年と月は必須。
const d = new Date(); d.getFullYear(); //2020 d.getMonth; //0-11 (Jan:0,Feb:1) d.getDate(); // 1-31 d.getDay(); //0-6 (Sun:0,Mon:1) d.getHours(); //0-23 d.getMinutes(); //0-59 d.getSeconds(); //0-59 d.getMilliseconds(); 0-999 1000ミリ秒=1秒 const d = new Date(2019, 10); // 2019/11/01 00:00:00 d.setHours(10, 20, 30); // 2019/11/01 10:20:30 d.setDate(31); // 2019/12/01 10:20:30 d.setDate(d.getDate() + 3); // 2019/12/04 10:20:30タイマー機能
- setInterval(関数名,ミリ秒)
- ミリ秒ごとに関数を実行する。
- 関数を引数に設定するときは()は不要。
setInterval(func)
- 返り値はインターバルを一意に識別するインターバルID
- clearInterval(インターバルID)
- 処理を止める。
- setTimeout(関数,ミリ秒)
- ミリ秒後に一回だけ実行する
- 返り値は一意に識別するタイムアウトID
let i = 0; const id = setInterval(() => { const d = new Date(); console.log(d); i++; if (i > 3) { clearInterval(id); } }, 1000);setIntervalとsetTimeoutの違い
- 前者はインターバルの時間を超える処理であってもインターバルの時間間隔で実行されるため処理が重なることがあり、システムに負荷がかかる場合がある
- 後者は処理が終わってからを起点に次の処理になるため、負荷がかからない。
クラス
- 静的メソッド内でthisを使うことはできない
- クラス内で使われるthisはそのクラスから作られるインスタンスである。
- 静的メソッドはインスタンスを作らずに呼び出せるためthisを使うことができない。
- 子クラスのconstructorでthisを使うにはsuper()を記述
- 親クラスのメソッドを使うにはsuperを頭につける(
super.関数名();
)
- 投稿日:2020-02-22T23:04:51+09:00
javascript超基礎
型
型の名前 種類 特徴 プリミティブ型 文字列,数値,null,真偽値,undefined 値渡し オブジェクト型 配列,オブジェクト 参照渡し 変数と定数
- 最近のプログラミングでは、なるべくconstを使いつつ、必要なときにletを使う。
- (varは古い)
- 使える文字は英数字、$、_のみ。
- (ハイフンは使えない)
- 数値から始められない。
- JavaScriptでは大文字と小文字は区別される。
- 予約語は使えない。
- constで定義しても配列なら上書きできる。
値の再代入
let price = 500; // price = price + 100; price += 100; // 600 // price = price * 2; price *= 2; // 1200 // price = price + 1; // price += 1; price++; // 1201 // price -= 1; price--; // 1200JavaScriptデータ型
- 文字列(String)
- 数値(Number)
- Undefined
- Null
- 真偽値(Boolean)
- オブジェクト(Object)
※nullをtypeof関数でチェックするとobjectと認識されるのはJavaScriptの有名なバグ
数値からなる文字列
- 「+」は文字列の連結のための演算子となる
- 足し算にしたい場合はparseInt()で数値に変換する。
console.log('5' * 3); //15 console.log('5' - '3'); //2 console.log('5' + 3); //53 console.log(parseInt('5', 10) + 3); //8 console.log(parseInt('hello', 10)); //NaN真偽値
false:0,null,undefined,'',false
true:上記以外テンプレートリテラル
変数の値を文字列中に埋め込む機能。他の言語ではよく見られる機能。
for (let i = 0; i <= 10; i++) { console.log(`hello ${i}`); }関数
仮引数にはletやconstなどの宣言は不要。
function showAd(message = 'Ad') { // 仮引数 console.log('----------'); console.log(`--- ${message} ---`); console.log('----------'); }関数宣言と関数式
関数式は関数を定数や変数に値として代入するため、最後に;が必要。
関数式で渡す関数には名前がないため、無名関数とよく呼ばれる。//関数宣言 function 関数名(仮引数,仮引数) { 処理; return 返り値; } //呼び出し 関数名(実引数,実引数); //関数式 const 定数名 = function(仮引数,仮引数) { 処理; return 返り値; }; //呼び出し 定数名(実引数,実引数);
- 投稿日:2020-02-22T22:56:24+09:00
Reactカスタムフック入門 ! (ドラッグでDOMを動かすカスタムフックを作る)
カスタムフック入門 !
この記事の内容
前半 : Hooksの紹介
後半 : 以下のような、ドラッグでDOMを動かすカスタムフックの解説
Hooks!!
Hooksとは?
Hooksとは、Reactに最近追加されたAPI群です。
Hooksを使えば、関数型コンポーネントでも従来のクラス型コンポーネントとほぼ同等(筆者は困ったことないです)の処理ができるようになります。
というか、より簡潔にコードが書けるしめちゃくちゃ使いやすいので、正直もうクラス型には戻れないです。
Good bye, クラス型コンポーネント。クラス型は勉強しなくていい?
クラス型も勉強した方がいいです。
なぜなら、既存のコードの大多数はクラス型で書かれているから。
他人のコードを参考にしたり、ソースコードを読む時に必要になるでしょう。Hooksのメリットは「いい感じの疎結合さ」
Hooksを使った関数型コンポーネントの利点は本当にたくさんあります。
粒度を気にせずに雑多に少し列挙すると
- 渡ってくるpropsを明示できる
- thisに束縛されない(bindを使う必要がない)
- this.state.hogeって書かなくていい(hogeだけ)
- カスタムフックを作ることで、ロジックをコンポーネントから分離できる
- テストが書きやすい
- 状態と状態のバインディングが簡単にできる
これらをまとめると、Hooksのメリットは「いい感じの疎結合さ」だと思います。
疎結合なので変数やイベントがthisに束縛されませんし、ロジックを分離でき、テストもしやすい。
また、単に疎結合が進むだけでなく、ロジックなどはまとめられたり、状態と状態のバインディングがしやすくなったりと、扱いやすい塊にいい感じに切り分けられます。Hooksの例
useStateがHooksの代表例です。
クラス型のthis.stateに替わるものです。
以下をご覧ください。hooksState.jsimport { useState } from "react" const component=()=>{ const [ hoge, setHoge ] = useState("初期値") }useStateの引数が初期値で、返り値の一つ目が状態変数(クラス型で言うthis.state.hoge)、二つ目がそのセッター関数です。
この返り値の名前は自分で決められますが、セッターはset{大文字から始めた状態変数名}にすることが多いです。コードを見ると、なんとなくコンポーネントから分離している感じがあると思います。
useEffectがすごい
もう一つご紹介。
HooksAPIの1つにuseEffectという関数があります。
副作用を扱うAPIなんですが、これが本当に便利。例えば、あるコンポーネントがAとBっていう状態変数をもっていた時に、Aが変わった時にBも変えたいってときありますよね。
そういうとき、クラス型だと、Aを変える処理の後に毎回Bを変える処理を書く必要がある。
AやBの値で分岐があったらそれも書く必要がある。それを、useEffectだと一箇所に書くだけでいいのです。
effect.jsimport { useEffect } from "react" useEffect(()=>{ // Bに関する処理 },[A])こんな感じで、useEffectの
第一引数に行いたい処理を、
第二引数には、変更をその処理のトリガーにする変数を
配列で入れます(複数指定可能)。Reactでは状態変数変化に伴うリレンダリングによって状態とグラフィックのバインディングを実現しています。
HooksのuseEffectを使えば、状態と状態のバインディング(状態Aが変われば状態Bも変わる)も簡単に実装できるのです。ライフサイクルの代替とだけ思われがちですが、本質は状態と状態のバインディングだと思います。
カスタムフックって何?
さて、Hooksの良さも伝わってきたところで、カスタムフックの説明に入ります。
カスタムフックとは、Hooksを使ったユーザーオリジナルの新しい関数を指します。
ただの関数といえばそれまでですが、HooksAPIを使うことで、差分レンダリングや先述の状態-状態バインディングなどを利用することができます。DOMをドラッグで動かせるカスタムフックを作る!
さて、ではDOMをドラッグで動かせるようなカスタムフックを作りたいと思います。
クラス型だと、クラスとロジックが密結合して再利用性に乏しい実装になってしまいがちですが、Hooksなら分離できます!このカスタムフックを構成するHooks
使うHooksは、先述のuseState, useEffectです。
このカスタムフックの設計
- まず、対象となるDOM上でMouseDownされた時に、その時のマウス座標(初期マウス座標)を保存します。
- 次に、MouseMoveされた時、そのときのマウスがどれくらい先ほど保存した初期マウス座標からズレているかを計算します。
- そのベクトルを、DOMにstyleのtransform3dに入れて渡してあげます
- MouseUpされたら、初期マウス座標をリセット
少し簡略化して描いたので、後のコードを見て実装を確認してください。
コード
まず完成コードをお見せします。
まずは、objectのカスタムフック(コードは後述)を適用するところから。object.js// ごめんなさいカスタムフック外で新しいHooks、useRef使います import React, { useRef } from 'react'; import useMoveObject from "./useMoveObject" function App() { // 動かしたいDOMに参照を張る currentには最新のDOMが入る 初期値はnullにしてある const boxRef = useRef(null) const mouseEventAndStyle = useMoveObject(boxRef.current) return ( <div> <div {...mouseEventAndStyle}> <div style={{backgroundColor:"red", width:30, height:30}} ref={boxRef}> </div> </div> </div> ); } export default App;useObjectMoveがカスタムフックで、その返り値(eventTriggerAndStyle)をobject.jsで(仮想)DOMの中に展開しています(useRefを使ってboxRef.currentにはobjectのDOMを入れています)。
後述のuseObjectMove.jsを見てもらえればわかる通り、useObjectMoveは返り値として、onMouseDownとonMouseUp、そしてstyleをkeyに持つオブジェクトを返しています。
このオブジェクトをDOMの中で展開しているので、DOMにそれらをインラインで書き込んでいるのと同じです。
例えば、下記のsample1.jsとsample2.jsは結果としてDOMに同じ処理をしています。sample1.jsconst returnObject = { onMouseDown={hoge} onMouseUp={hogege} style={hogegege} } <div {...returnObject}> </div>sample2.js<div onMouseDown={hoge} onMouseUp={hogege} style={hogegege}> </div>続いて、ここで使われているカスタムフックuseObjectMoveはこんな感じ。
useMoveObject.jsimport { useState, useEffect } from "react" function useMoveObject(object) { // オブジェクトをオリジナルの座標から動かすベクトルを表す変数 XとYをkey、動かすピクセル数をvalueに取る const [ objectMoveVector, setObjectMoveVector ] = useState( {X: 0, Y: 0} ) // ドラッグし始めた(MouseDownした)時のマウス座標 MouseDown時に保存し、MouseUp時にnullを入れてリセット const [ mouseMoveStartCoordinates, setMouseMoveStartCoordinates ] = useState( null ) useEffect(()=>{ if( !object )return // DOMのマウント前はobjectがnullなので if( !mouseMoveStartCoordinates ) return // ドラッグし始めてないので // ドラッグし始めたならMouseMoveをイベントリスナーに登録し、onMouseMoveでobjectを動かす用のベクトルを計算する document.addEventListener('mousemove', onMouseMove) // MouseUpしたらドラッグ開始座標をリセット かつ MouseMoveのイベントリスナーも解除 document.addEventListener('mouseup', ()=>{ setMouseMoveStartCoordinates( null ) document.removeEventListener('mousemove', onMouseMove) }) }, [ mouseMoveStartCoordinates ]) const onMouseMove=(e)=>{ // ドラッグ開始地点から現在のマウスまでのベクトルを計算し、最後にドラッグ終了した時のobjectが動いたベクトルに加算 // イベントリスナーに登録したこの関数はクロージャなので、objectMoveVectorは、この中では常に最後にドラッグ終了した時のもの let vector = { X : e.clientX - mouseMoveStartCoordinates.X + objectMoveVector.X, Y : e.clientY - mouseMoveStartCoordinates.Y + objectMoveVector.Y } setObjectMoveVector(vector) } return { // MouseDownでドラッグ開始時の座標を保存 onMouseDown : (e)=>setMouseMoveStartCoordinates( { X : e.clientX, Y : e.clientY } ), // MouseUp時にマウス位置をリセット onMouseUp : ()=>setMouseMoveStartCoordinates(null), // 動かすべきベクトルをstyleとして返す style: { transform:`translate3d(${objectMoveVector.X}px, ${objectMoveVector.Y}px, 0)` } } } export default useMoveObjectDOMが欲しいのは、マウスイベントと、その結果である自身を動かすためのstyleであって、ドラッグ開始地点の座標などは不必要です。
そういった媒介となるだけの変数をカスタムフック内部に隠蔽できるので、コードがきれいになり、再利用性も高まります。
クラス型だと、こういった媒介となる変数もコンポーネントに持たせる必要がありました。ポイント
ポイントは、先述の返り値の展開の他だと、クロージャの部分ですね。
イベントリスナーで登録した関数はクロージャになっていて、中で使う変数の値は、イベントリスナーの登録をした時の値です。
たとえその変数が更新されていようと、古い値が参照されます。
なので、イベントリスナを使う時は基本useRefを使って最新の値を参照します。しかし今回は、マウスをドラッグし始めた座標を、クロージャの中で固定にしたかったので、そのままにしました。
まとめ
と言うわけで、Hooksでロジックを分離できるしそもそも書きやすいし最高!っていう記事でした!
ちなみに、クラスコンポーネントとHooksを使った関数コンポーネントは一緒に使うことができます(コンポーネント指向的に当然と言えば当然ですが)。
既存のコードも少しずつ移行できるのがいいですね。
- 投稿日:2020-02-22T20:08:41+09:00
<input type="file">をControlled的に使う
<input type="file">
はReactから直接Controlledにはできませんが、用途によってはそれに近い使い方ができます。フォーム要素の役割
以前にも書きましたが、HTMLのフォーム要素にはいくつかの役割があります。
- ユーザーからの値の入力
設定した状態の保存(Controlledにする場合は無効化される)- 状態の表示
- フォーム送信時に値を伝える
このうち、「フォーム送信時に値を伝える」機能は、フォーム送信に頼らずAjaxを投げるような場面では不要となります1。
<input type="file">
の場合そして、ファイル入力の場合は「状態の表示」も不要となることが多いです。デフォルトの
<input type="file">
のデザインは、ボタンとファイル名だけのそっけないものでカスタマイズも効かず、「デフォルトの要素を完全に殺す or 透明化させるなどして、自前で描画する」ということもよく行われるような代物です。そして、ふつうの
type="text"
のような場合であれば、すでに入力された値から再編集させるためにも元の値の表示は必要ですが、type="file"
では「すでにセットされたファイルから編集する」ような機能は通常存在せず、ファイルがあろうがなかろうが既存のファイルを選択してアップロードさせるだけですので、この意味合いでももとの値をセットさせる意味は薄いです。ということで、
<input type="file">
の場合は「新規登録の機能さえあればそれでいい」という場面もじゅうぶん考えられます。新規登録専用のものを作ってみる
新規登録以外の機能性が不要な場合、
onChange
のタイミングでフォームをリセットすれば可能そうですが、1つ注意点があります。function FileAppendInput({onChange, ...rest}) { // リセット用のカウンター const [counter, setCounter] = React.useState(0); const handleChange = React.useCallback((e) => { onChange(e); // キーを変えて表示をリセット setCounter(current => current + 1); }, []); return <input {...rest} type="file" key={counter} onChange={handleChange} /> }バグか仕様かは不明なのですが、
value = ''
でリセットすると、Chromeで「onChange
のタイミングで回収したFileList
の中身が書き換わる」という現象が発生してしまったので、それを避けるためにkey
の書き換えでフォームをリセットしています。
files
を書き戻したい場合フォーム要素へのファイルの再セットが必要な用途の場合、
useEffect
でファイルの変化を監視して、変わったときにref
経由でfiles
へセットすれば問題なく実行できそうですが、ここにもまた問題点があります。files
へのセットに必要なFileList
をどう用意するかです。前の投稿にまとめましたが、
File
からFileList
を作成することが、iOSなど一部の環境では実現不可能なのです。このような環境で<input type="file">
への書き戻しが必要な場合、最初にセットされたFileList
そのものを引き回すより他に手段がなくなってしまいます。
ファイルについても、
FormData
にFile
を追加して送信するという形で対応可能です。 ↩
- 投稿日:2020-02-22T19:47:07+09:00
Firebase を利用したプッシュ通知の実装
はじめに
仕事で解決したいことがあり、Web Push と Service Worker(以下 SW)の利用を検討しようかなと思っています
調べると Firebase Cloud Messaging(以下 FCM)を使うと簡単に実装できそうだったので、サンプルアプリを作成してみようと思います今回実装したコードは以下です
https://github.com/kurosame/glossaryFCM の設定(管理画面)
以下から初期設定を行う
管理画面上での設定は以上で、その後実装する上で必要な情報は「プロジェクトの設定」で確認できます
Firebase の初期設定
以下の情報は「プロジェクトの設定」で見れます
隠してますけど、これらの情報は公開してもセキュアです
ただし、Firestore などのリソースにアクセスできてしまうと思うので、使っている場合は適切なルールを設定しておく必要はあります@/firebase/config.tsconst config = { apiKey: '...', authDomain: '...', databaseURL: '...', projectId: '...', storageBucket: '...', messagingSenderId: '...', appId: '...' } export default config初期化後、messaging を export しておきます
@/firebase/index.tsimport firebase from 'firebase/app' import 'firebase/messaging' import config from '@/firebase/config' const firebaseApp = firebase.initializeApp(config) export const messaging = firebaseApp.messaging()FCM の設定(TS)
ここからは、以下の公式ドキュメントに書かれている内容を見ながら実装します
https://firebase.google.com/docs/cloud-messaging/js/client?authuser=0今回はトークンを管理しないので、onTokenRefresh コールバック関数の実装は省略してます
また、VAPID も使いません
cURL で FCM のサーバーキーを Authorization リクエストヘッダーに含めて FCM サーバーに POST して動作確認します通知のタイプに「通知メッセージ」と「データメッセージ」の 2 種類がありますが、今回は「通知メッセージ」を使います
通知メッセージ
- アプリがフォアグラウンドでの通知は自前で表示させる実装が必要
- アプリがバックグラウンドでの通知は SDK を使用して自動表示される
データメッセージ
- フォアグラウンド、バックグラウンドに限らず通知は自前で表示させる実装が必要
動作確認は Chrome のみで行っています
プッシュ通知許可のダイアログ表示
以下の関数を実行するとブラウザ上にダイアログが出ます
@/utils/messaging.tsexport default function initialize(): void { Notification.requestPermission().then(p => { console.info(p) }) }パラメーターの
p(permission)
はダイアログに対して行ったアクションのコールバックです
型定義を見るとtype NotificationPermission = "default" | "denied" | "granted";
となっており、この 3 つのいずれかの文字列が入っています
granted
許可した場合denied
ブロックした場合default
ダイアログの選択を無視した場合(ブラウザをリロードなど)default の場合のみダイアログが表示される仕様となっています
SW のデプロイ
通知の許可を得られたら、次はトークンの取得を行います
しかし、その前に SW を設定する必要があります
FCM の getToken 関数内で以下の SW を登録している処理があるからです
navigator.serviceWorker.registe(DEFAULT_SW_PATH, { scope: ...})
DEFAULT_SW_PATH の値は
/firebase-messaging-sw.js
です
よって、ドキュメントルートにfirebase-messaging-sw.js
ファイルを置く必要がありますローカルで動作確認中であれば、dist 等のパブリックディレクトリに置けばオッケーです
ただ実用性を考えると、アプリは webpack を使ってバンドルしているので、webpack で SW をいい感じに処理してデプロイしてくれるやつがほしいです
SW の後々の拡張性を考えて、Workbox を使うことにしました
npm i -D workbox-webpack-plugin
webpack.config.jsconst WorkboxPlugin = require('workbox-webpack-plugin') module.exports = () => ({ plugins: [new WorkboxPlugin.GenerateSW({ swDest: 'messaging-sw.js' })] })@/utils/messaging.tsimport { messaging } from '@/firebase/index' export default async function initialize(): Promise<void> { // ここから if ('serviceWorker' in navigator) { await navigator.serviceWorker .register('/messaging-sw.js') .then(reg => messaging.useServiceWorker(reg)) .catch(err => console.error(err)) } // ここまで追加 Notification.requestPermission().then(p => { console.info(p) }) }トークンの取得
通知を許可すると currentPermission が granted となり、トークンが取得できるようになります
currentPermission が denied の状態でトークンを取得するとエラーをスローします@/utils/messaging.tsimport { messaging } from '@/firebase/index' export default async function initialize(): Promise<void> { ... Notification.requestPermission().then(p => { // ここから if (p === 'granted') { messaging .getToken() .then(t => console.info(t)) .catch(err => console.error(err)) } // ここまで追加 }) }実行するとコンソールにトークンが出力されます
生成されたトークンはブラウザの IndexedDB に保存され、再利用されます
IndexedDB からトークンオブジェクトを削除すると getToken 時に再度新しいトークンを生成します動作確認
プッシュ通知が来るか確認
アプリがバックグラウンド時のみ動作します
フォアグラウンド時に通知を機能させるには onMessage イベントハンドラーの実装が必要になります(後述)curl -X POST \ -H "Authorization: key=${サーバーキー}" \ -H Content-Type:"application/json" \ -d '{ "notification": { "title":"test", "body":"testtest" }, "to": "${上記で取得したトークン}" }' \ https://fcm.googleapis.com/fcm/sendサーバーキーは管理画面の「プロジェクトの設定」で見れます
また、管理画面上からでも通知を作成して送れます
もし通知が来なかった場合に確認すること
- アプリがフォアグラウンドになっている
- OS のシステム環境設定で Chrome の通知を ON にしているか
もっと使えるプッシュ通知にする
現状だとあまり使い物にならないので、最低限以下は実装してみようと思います
- アプリがフォアグラウンド時でもプッシュ通知を送る
- プッシュ通知にクリックイベントを追加する
- 複数のデバイスにプッシュ通知を送る
アプリがフォアグラウンド時でもプッシュ通知を送る
以下のような自前の SW を使う必要があるので、用意します
@/firebase/messaging-sw.jsimportScripts('https://www.gstatic.com/firebasejs/6.2.4/firebase-app.js') importScripts('https://www.gstatic.com/firebasejs/6.2.4/firebase-messaging.js') firebase.initializeApp({ messagingSenderId: '...' }) const messaging = firebase.messaging()messagingSenderId は記事の上の方で
@/firebase/config.ts
に書いたやつと同じですwebpack の WorkboxPlugin も修正します
webpack.config.jsmodule.exports = () => ({ plugins: [ new WorkboxPlugin.InjectManifest({ swSrc: path.join(__dirname, 'src', 'firebase', 'messaging-sw.js'), swDest: 'messaging-sw.js' }) ] })アプリがフォアグラウンド時にプッシュ通知を受信すると onMessage コールバック関数が呼ばれるので、その中で通知を表示する処理を行います
@/utils/messaging.tsimport { messaging } from '@/firebase/index' export default async function initialize(): Promise<void> { ... // ここから messaging.onMessage( (payload: { notification: { title: string; body: string } }) => navigator.serviceWorker.ready .then(reg => reg.showNotification(`${payload.notification.title}(foreground)`, { body: payload.notification.body }) ) .catch(err => console.error(err)) ) // ここまで追加 }
serviceWorker.ready
を使うことで SW の activate を待って処理できますcURL で通知を POST するとアプリがフォアグラウンド時でもプッシュ通知が表示されると思います
アプリがバックグラウンド時に POST した場合は、変わらず SDK によって自動生成された通知を表示します(onMessage は呼ばれません)通知にクリックイベントを追加する
リクエストに click_action を追加します
curl -X POST \ -H "Authorization: key=${サーバーキー}" \ -H Content-Type:"application/json" \ -d '{ "notification": { "title":"test", "body":"testtest", "click_action":"${HTTPSのURL}" }, "to": "${上記で取得したトークン}" }' \ https://fcm.googleapis.com/fcm/sendアプリがバックグラウンド時の場合は、SDK を使用して、これだけでクリックイベントが発火します
アプリがフォアグラウンド時の場合は、SW に notificationclick イベントハンドラーを実装して対応します
@/utils/messaging.tsimport { messaging } from '@/firebase/index' export default async function initialize(): Promise<void> { ... messaging.onMessage( (payload: { notification: { title: string body: string click_action: string // これを追加 } }) => navigator.serviceWorker.ready .then(reg => reg.showNotification(`${payload.notification.title}(foreground)`, { body: payload.notification.body, data: payload.notification.click_action // これを追加 }) ) .catch(err => console.error(err)) ) }@/firebase/messaging-sw.jsimportScripts('https://www.gstatic.com/firebasejs/6.2.4/firebase-app.js') importScripts('https://www.gstatic.com/firebasejs/6.2.4/firebase-messaging.js') firebase.initializeApp({ messagingSenderId: '...' }) const messaging = firebase.messaging() // ここから self.addEventListener('notificationclick', e => { e.notification.close() e.waitUntil(clients.openWindow(e.notification.data)) }) // ここまで追加複数のデバイスにプッシュ通知を送る
トークンをトピックに紐付けることで実現可能です
トピックには複数のトークンを紐付けることができます
そして、トピックを指定してプッシュ通知を送り、複数デバイスへの通知を実現できますトピックへのトークン追加はリクエストヘッダーにサーバーキーを含むため、サーバー側で処理します
今回はサーバーを用意してないので、cURL を使って追加しますcurl -X POST \ -H "Authorization: key=${サーバーキー}" \ -H Content-Type:"application/json" \ https://iid.googleapis.com/iid/v1/${トークン}/rel/topics/${トピック名}Firebase プロジェクトに存在しないトピックを指定した場合は、そのトピックを新規作成します
トークンが subscribe しているトピックは以下で確認できます
curl -X POST \ -H "Authorization: key=${サーバーキー}" \ https://iid.googleapis.com/iid/info/${トークン}?details=true後は、
to
にトークンではなく、トピックを指定して POST すれば、通知が届くはずですcurl -X POST \ -H "Authorization: key=${サーバーキー}" \ -H Content-Type:"application/json" \ -d '{ "notification": { "title":"test", "body":"testtest", "click_action":"${HTTPSのURL}" }, "to": "/topics/${トピック名}" }' \ https://fcm.googleapis.com/fcm/sendサーバーでトークンをトピックに追加する場合
実際にサーバーで処理する場合は、Firebase Admin SDK が使えます
今回はサーバーを用意してないので試してませんが、たとえば Node.js であれば以下のような実装になりますimport admin from 'firebase-admin' admin.initializeApp({ credential: admin.credential.applicationDefault(), databaseURL: 'https://<DATABASE_NAME>.firebaseio.com' }) admin.messaging().subscribeToTopic(registrationTokens, topic)
- 参考
https://firebase.google.com/docs/admin/setup?hl=ja#initialize_the_sdk
https://firebase.google.com/docs/cloud-messaging/js/send-multiple#subscribe_the_client_app_to_a_topicプッシュ通知の本番導入にあたり検討したいこと
自分は PC でウェブサイトを見ようとして、いきなり以下のダイアログを出されると反射的にブロックしてしまいます
ブロックする理由は以下です
- 通知の目的が不明
- ダイアログの位置が邪魔
- (そもそも通知されるのが嫌な気持ちも少しある)
通知が嫌な場合は無理ですが、通知の目的と位置の問題は工夫できると思います
たとえば、Snackbar を活用して
- ウェブサイトを閲覧する上で邪魔でない位置に Snackbar を出す
- Snackbar に通知の目的の記載と同意ボタン的なのを設置
- 同意ボタンを押したユーザーのみ通知許可ダイアログを出す
↑ 位置はサイトを閲覧する上で邪魔にならず、でも許可までの導線をアピールできる場所が良いです
そもそもユーザーすべてが通知を許可するのは不可能なので、プッシュ通知を許可しないとアプリのメインの機能が使えないという状態は避けるべきですね
あくまでプッシュ通知はプラス α で利用できる便利機能という位置付けだと思います
- 投稿日:2020-02-22T17:47:39+09:00
【jQuery】要素内の文字列に含まれる数値でリストをソートする【js】
経緯
やんごとなき理由でhtml側をいじれなかったため、
jsでどうにかこうにか入れ替える必要がありました。
以下奮闘記です。もっといい書き方があればコメント下さい。泣いて喜びます。
今回の要件
- お店の開店時間が早い順にソートしたい
- 開店時間が同じ場合でも、閉店時間による並べ替えはしない
- 時間は24時間表記、30分刻みとする
- 営業時間は必ず
HH:mm ~ HH:mm
のフォーマットで表記される- すべての店の営業時間は10:00 ~ 5:00の範囲内に収まる(5時オープンの店はない)
デモ
デモはこちら。
開店時間を変更しても、並び替わってくれます。https://codepen.io/kotottt/pen/rNVWpVa
HTML
hoge.html<ul class="shop_lists"> <li> <div class="shop">HOGE食堂</div> <div class="time">12:00 ~ 18:00</div> </li> <li> <div class="shop">FUGA KITCHEN</div> <div class="time">10:00 ~ 20:00</div> </li> <li> <div class="shop">BAR NULL</div> <div class="time">1:00 ~ 4:00</div> </li> </ul>javascript
hoge.js$('.shop_lists').html( $('.shop_lists > li').sort(function(a,b){ var str1 = $(a).find('.time').html(); var str2 = $(b).find('.time').html(); var num1 = str1.substr( 0, 4 ).split(':'); var num2 = str2.substr( 0, 4 ).split(':'); if(num1[0] < 5){ num1[0] = Number(num1[0]) + 24; num1[0] += Number(num1[1]) / 100; } if(num2[0] < 5){ num2[0] = Number(num2[0]) + 24; num2[0] += Number(num2[1]) / 100; } return num1[0] - num2[0]; }));生意気にも解説
リストの入れ替え
$('.shop_lists > li').sort(function(a,b){
sort
メソッドで数値を複数回比較、比較した値を基に、
liタグの並べ替えを行います。
比較方法については次から。基準となる数値を含む文字列を抽出
var str1 = $(a).find('.time').html(); var str2 = $(b).find('.time').html();
a
とb
それぞれに、リストがセットされて行くので、
その中から、time
クラスを持つブロックを指定し、
htmlメソッドで中身(今回だと12:00 ~ 18:00
)を抽出します。ただ、これだと
~
と閉店時間が含まれてしまうため、
次に開店時間のみ抽出していきます。数値の抽出と比較の下準備
var num1 = str1.substr( 0, 4 ).split(':');まず、セットされた文字列から先頭の4文字を指定
substr( 0, 4 )
します。
12:00 ~ 18:00
だと12:0
になりますね。※今回は時間が30分刻みで、1分単位での比較はしなくて良いので切り捨てています。
次に、文字列を
:
を基準に、分割し配列化します。
上の例だと、num1 = ['12', '0']
となります。では、この配列の値を基準に比較する処理に入ります。
比較方法
return num1[0] - num2[0];順番が前後しますが、結論から言うと、
配列の最初の値num[0]
を比較します。この比較結果を基にリストを並べ替えていくわけですね。(完全に理解)
問題点
ただこれだけだと、以下の問題が発生してしまいます。
- リスト分単位での比較が出来ない
- 0時以降オープンのお店が、10時オープンのお店より先に表示されてしまう
そのため今回は、
- 分単位部分の
num[1]
を少数点以下の数値に変換し、num[0]
に加算。- 0時〜5時の場合は、
num[0]
に24を足す(24時〜28時に変換)これらの処理を挟むことにしました。
問題点を解決する処理
if(num1[0] < 6){ num1[0] = Number(num1[0]) + 24; num1[0] += Number(num1[1]) / 100; //結果(1時半オープンの場合): num1[0] = 25.3 }この部分ですね。
暗黙の型変換でそのまま24を足したり100で割ることもできるみたいですが、
丁寧に書きましょうということで、Numberオブジェクトで型変換の後に計算。これで、先程の問題は解決します。
あとは先程の比較式に値が渡るので、比較の後、並べ替え完了です!!!
思うところ
aとbで同じ処理してるので、うまいことまとめられないかなーというのが正直な所。
ループ回したりしてカッチョイイコードを書きたいものの、
正直わかりやすいし2回くらいならいっか()という惰性です。あと、時間フォーマットの変更に強くないので、
その場しのぎ感は否めないかと。自分のコードを呪う未来が見えるような、見えないような....。
間違い等あれば、ご指摘下さい。
- 投稿日:2020-02-22T17:47:39+09:00
【jQuery】要素内の文字列に含まれる数値でリストをソートする【javascript】
経緯
やんごとなき理由でhtml側をいじれなかったため、
jsでどうにかこうにか入れ替える必要がありました。
以下奮闘記です。もっといい書き方があればコメント下さい。泣いて喜びます。
今回の要件
- お店の開店時間が早い順にソートしたい
- 開店時間が同じ場合でも、閉店時間による並べ替えはしない
- 時間は24時間表記、30分刻みとする
- 営業時間は必ず
HH:mm ~ HH:mm
のフォーマットで表記される- すべての店の営業時間は10:00 ~ 5:00の範囲内に収まる(5時オープンの店はない)
デモ
デモはこちら。
開店時間を変更しても、並び替わってくれます。https://codepen.io/kotottt/pen/rNVWpVa
HTML
hoge.html<ul class="shop_lists"> <li> <div class="shop">HOGE食堂</div> <div class="time">12:00 ~ 18:00</div> </li> <li> <div class="shop">FUGA KITCHEN</div> <div class="time">10:00 ~ 20:00</div> </li> <li> <div class="shop">BAR NULL</div> <div class="time">1:00 ~ 4:00</div> </li> </ul>javascript
hoge.js$('.shop_lists').html( $('.shop_lists > li').sort(function(a,b){ var str1 = $(a).find('.time').html(); var str2 = $(b).find('.time').html(); var num1 = str1.substr( 0, 4 ).split(':'); var num2 = str2.substr( 0, 4 ).split(':'); if(num1[0] < 5){ num1[0] = Number(num1[0]) + 24; num1[0] += Number(num1[1]) / 100; } if(num2[0] < 5){ num2[0] = Number(num2[0]) + 24; num2[0] += Number(num2[1]) / 100; } return num1[0] - num2[0]; }));生意気にも解説
リストの入れ替え
$('.shop_lists > li').sort(function(a,b){
sort
メソッドで数値を複数回比較、比較した値を基に、
liタグの並べ替えを行います。
比較方法については次から。基準となる数値を含む文字列を抽出
var str1 = $(a).find('.time').html(); var str2 = $(b).find('.time').html();
a
とb
それぞれに、リストがセットされて行くので、
その中から、time
クラスを持つブロックを指定し、
htmlメソッドで中身(今回だと12:00 ~ 18:00
)を抽出します。ただ、これだと
~
と閉店時間が含まれてしまうため、
次に開店時間のみ抽出していきます。数値の抽出と比較の下準備
var num1 = str1.substr( 0, 4 ).split(':');まず、セットされた文字列から先頭の4文字を指定
substr( 0, 4 )
します。
12:00 ~ 18:00
だと12:0
になりますね。※今回は時間が30分刻みで、1分単位での比較はしなくて良いので切り捨てています。
次に、文字列を
:
を基準に、分割し配列化します。
上の例だと、num1 = ['12', '0']
となります。では、この配列の値を基準に比較する処理に入ります。
比較方法
return num1[0] - num2[0];順番が前後しますが、結論から言うと、
配列の最初の値num[0]
を比較します。この比較結果を基にリストを並べ替えていくわけですね。(完全に理解)
問題点
ただこれだけだと、以下の問題が発生してしまいます。
- リスト分単位での比較が出来ない
- 0時〜5時オープンのお店が、10時オープンのお店より先に表示されてしまう
そのため今回は、
- 分単位部分の
num[1]
を少数点以下の数値に変換し、num[0]
に加算。- 0時〜5時の場合は、
num[0]
に24を足す(24時〜28時に変換)これらの処理を挟むことにしました。
問題点を解決する処理
if(num1[0] < 6){ num1[0] = Number(num1[0]) + 24; num1[0] += Number(num1[1]) / 100; //結果(1時半オープンの場合): num1[0] = 25.3 }この部分ですね。
暗黙の型変換でそのまま24を足したり100で割ることもできるみたいですが、
丁寧に書きましょうということで、Numberオブジェクトで型変換の後に計算。これで、先程の問題は解決します。
あとは先程の比較式に値が渡るので、比較の後、並べ替え完了です!!!
思うところ
aとbで同じ処理してるので、うまいことまとめられないかなーというのが正直な所。
ループ回したりしてカッチョイイコードを書きたいものの、
正直わかりやすいし2回くらいならいっか()という惰性です。あと、時間フォーマットの変更に強くないので、
その場しのぎ感は否めないかと。自分のコードを呪う未来が見えるような、見えないような....。
間違い等あれば、ご指摘下さい。
- 投稿日:2020-02-22T17:04:13+09:00
Vuetify2系のグリッドシステムの属性について
はじめに
以前Vuetifyのグリッドシステムがよく分からなかったので調査した記事を作成したが、Vuetifyのメジャーバージョンアップ(1.5→2.x)により内容が古くなってしまった。
バージョンアップ自体は結構前でもう古いバージョンのVuetifyは使われなさそうだし、色々属性名も変わったりしたようなので、調査のために再度まとめた。確認のために作成したコード
https://nulltemp.github.io/vuetify2-test/
https://github.com/nulltemp/vuetify2-test基本的な変更点
https://vuetifyjs.com/ja/components/grids
v-container
はそのままだが、v-layout
がv-row
に、v-flex
がv-col
に変わっている。各属性について
v-containerの属性
fluid
See the Pen qBdZRRm by nulltemp (@nulltemp) on CodePen.
要素の最大幅に絡む設定。元の説明だとビューポートの最大幅を削除するとか書いてある。倍率変えたりCodePenのリンク直接見たりすると効果が分かる。
実はいまだに使い道をよく分かっていない。v-rowの属性
align
See the Pen vuetify2 align by nulltemp (@nulltemp) on CodePen.
start
,center
,end
,baseline
,stretch
の5つがあり、各要素の位置の基準をどこに置くかが変わる。stretch
は2系で新しく追加されたもので、高さがなんか引き延ばされる。
詳細はこの辺りを参照。
また、align-<xx>
という属性もあり、画面サイズによってalign
の値を変化させることが出来る。一番下は一見align="center"
だが、align-xl="stretch"
を指定しているため、大画面で見るとstretch
を指定したときと同じ振る舞いになる。align-content
See the Pen vuetify2 align-content by nulltemp (@nulltemp) on CodePen.
start
,center
,end
,space-between
,space-around
,stretch
の6つがあり、各要素が複数行に分かれたときの配置を決定する。stretch
は2系で新しく追加されたもので、高さがなんか引き延ばされる。
詳細はこの辺りを参照。
また、align-content-<xx>
という属性もあり、画面サイズによってalign-content
の値を変化させることが出来る。一番下は一見align-content="center"
だが、align-content-xl="stretch"
を指定しているため、大画面で見るとstretch
を指定したときと同じ振る舞いになる。dense
See the Pen vuetify2 dense by nulltemp (@nulltemp) on CodePen.
多分1系にはなかった属性。分かり辛いが恐らくpaddingに絡む設定で、
true
にするとpaddingが小さくなる。justify
See the Pen vuetify2 justify by nulltemp (@nulltemp) on CodePen.
start
,center
,end
,space-between
,space-around
の5つがあり、各要素の配置に絡む設定になる。
詳細はこの辺りを参照。
また、justify-<xx>
という属性もあり、画面サイズによってjustify
の値を変化させることが出来る。一番下は一見justify="center"
だが、justify-xl="space-between"
を指定しているため、大画面で見るとspace-between
を指定したときと同じ振る舞いになる。no-gutters
See the Pen KKpzqzL by nulltemp (@nulltemp) on CodePen.
多分1系にはなかった属性。
dense
と同じくpaddingに絡む設定のようで、こちらはtrue
にするとpaddingが0になるらしい。v-colの属性
align-self
See the Pen vuetify2 align-self by nulltemp (@nulltemp) on CodePen.
align
の値を上書きする属性。上記はv-row
のalign
にstart
を指定しているが、最後のv-col
だけalign-self="end"
を指定しているため、ほかと振る舞いが異なっている。cols
See the Pen JjdbOBd by nulltemp (@nulltemp) on CodePen.
1系だと
(size)(1-12)
(xs2
とか)で指定していた各グリッドのサイズだが、2系だとcols="5"
等のやり方で指定するようになった。また、値にはauto
という値が指定できるようになり、どうも内部の値によって幅が可変になるらしい。
例として以前のxs12
は今回だとcols="12"
になり、その他sm12
やxl12
はそれぞれsm="12"
やxl="12"
になる。offset
See the Pen vuetify2 offset by nulltemp (@nulltemp) on CodePen.
各グリッド間のオフセットを指定することが出来る。
また、offset-<xx>
という属性もあり、画面サイズによってoffset
の値を変化させることが出来る。一番下は一見offset="1"
だが、offset-xl="5"
を指定しているため、大画面で見るとoffset="5"
を指定したときと同じ振る舞いになる。order
See the Pen vuetify2 order by nulltemp (@nulltemp) on CodePen.
各グリッドの並び順を指定できる。詳細はこの辺りを参照。
値は-1~12およびfirst
,last
が使えるらしいが、-1だけちょっとうまくいかなかった。
また、order-<xx>
という属性もあり、画面サイズによってorder
の値を変化させることが出来る。一番下の緑のグリッドは一見order="1"
だが、order-xl="last"
を指定しているため、大画面で見るとorder="last"
を指定したときと同じ振る舞いになる。その他
1系では要素をinlineにするかどうか等を指定する
d-{type}
や要素の改行?に関するwrap
、要素の並び方を指定するcolumn
等の属性があったが、これらはクラスとして指定する形になったらしい(未確認)。
https://vuetifyjs.com/ja/styles/flex/また、各要素間のpaddingを指定する
grid-list-{xs through xl}
という属性があったが、こちらは{property}{direction}-{size}
という形式の新しい属性で対応するようになったらしい新しい属性かと思ったら前からあったやつだった。ただ完全に同一の属性が存在するかどうか分からなかったので、やはりこの属性で対応することになりそう(未確認)。細かい説明は以下に書いてある。
https://vuetifyjs.com/ja/styles/spacing/
- 投稿日:2020-02-22T17:00:35+09:00
【React】CSSフレームワーク (ライブラリ)
概要
フロントエンドエンジニアとして見た目にこだわりたいですよね!
気になったCSSフレームワークや有名どころの物を一覧にしたので、ぜひ使ってみてください。Semantic UI React
MATERIAL-UI
Ant Design
Blueprint
React Suite
Sancho UI
Polaris
KendoReact (有料)
- 投稿日:2020-02-22T16:43:01+09:00
気になっているReact (JavaScript) のライブラリ
概要
ライブラリの調査を普段やっていて、気になったものや、今話題になっているライブラリを一覧にしました。
気になっているレベルのライブラリがほとんどなので、もし使用したことのある方がいればぜひコメント欄で使用感を教えてください!フォーム
React Hook Form
スター数
- 7,155 (2020/02/22)
特徴・気になった理由
- ドキュメントが日本語対応している
- ドキュメントの見た目がかわいいw
- 去年の年末ごろからかなりホットになっている
- hooksベース
- Formik,ReduxFormと比べてかなり軽量、無駄なState更新を行わないためパフォーマンスが高い
使ってみた感想
useFormsを使用することで、コンポーネントに記述するフォームのState管理の処理が3分の1ほどに減らすことができたと思います。
用意されているPropsがFormik,ReduxFormと変わらないため、ほぼ使用感で高パフォーマンスのフォームを作成することができると思いました。アニメーション
Popmotion
- 他にもUtilに特化したライブラリなどがありますが、今回はReactのために用意されたFlamer Motionを載せておきます!
Framer Motion
スター数
- 4,8k (2020/02/22)
特徴・気になった理由
- hooksベース
- サンプルが豊富
- ドキュメントの見た目がかわいいw
React Simple Animate
スター数
- 1,270 (2020/02/22)
特徴・気になった理由
- アニメーションを使用したい時に、記述が少なくすぐに実装できるため
react-animated-css
スター数
- 8,802 (2020/02/22)
特徴・気になった理由
- Animate.cssのReact用ライブラリ
使ってみた感想
めちゃめちゃ簡単にアニメーションできる!!!
- 投稿日:2020-02-22T15:36:20+09:00
Laravel/PHP で(会員登録・ログイン必要なし)画像共有SNSアプリ作りました
Laravel/PHP で(会員登録・ログイン必要なし)画像共有SNSアプリ作りました
はじめに
はじめまして
GAOGAOゲートというプログラミング合宿に参加しています。daijuです。
勉強の一環として、Laravel/PHP/JavaScriptで、非同期の画像共有SNSアプリをデプロイしました。
下記サービスURLです。詳細は後述しますが、下のURLにアクセスするだけで画像投稿できます。1枚だけでもよろしければ投稿お願いします!
https://newtreap.herokuapp.com/top?access_url=1525e4df64119a38Gname=SAMPLE
なぜ作ったのか?
もともと、昔の友達と会ったときに思い出の写真を探し出すのが難しいという不満を持っていました。
例えば、ラインだとチャットメインなのでいらない写真も混ざっていて目的の写真が見つからない!といったことありませんか?そこで、イベント毎や仲の良い人たちでグループを立てて、画像のみ投稿して思い出を残せるアプリが作りたい(ラインの画像版みたいな感じ)と思ったのがきっかけです。
しかし、
・作成していく中で会員登録・ログインめんどくさくね?
・知らない人が開発したwebアプリに個人情報預けるのこわくね?
・結局ラインのアルバム・Googleフォト使えば良くね?
となったわけです。
ということで軌道修正して登録一切必要なしの画像共有SNSアプリを作成しました!
グループ作成時に発行されるURLを共有さえすれば画像を投稿できます。簡単です。5秒です。ソースコードは以下にあります。プルリク受け付けています。未熟な部分多いので是非お願いします。機能はもちろん、デザインも!
https://github.com/daiju81/treap/branches使用技術
- Laravel6
- PHP 7.3.11
- JavaScript/ajax
バックエンド(一部抜粋)
public function add(Request $request) { $image = new Image(); $image->post_id = $post->id; $image_path[] = $image_name[$i]->getRealPath(); $image_file_name[] = $image_name[$i]->getClientOriginalName(); Cloudder::upload($image_path[$i], $image_file_name[$i]); $publicId[$i] = Cloudder::getPublicId(); $logoUrl = Cloudder::show($publicId[$i], [ 'width' => 400, 'height' => 400 ]); $logoUrl = str_replace('http', 'https', $logoUrl); $image->name = $logoUrl; $image->save(); } $group_posts_images[] = Image::where('post_id', $post->id)->get(); return redirect(route('top', [ 'access_url' => $access_url, ])); } }ここが核となる画像共有のプログラムですが、requestで画像情報を受け取りCloudinaryに保存しています。
フロントエンド(一部抜粋)
<div class="card-header"></div> <div class="card-body chat-card"> <div id="comment-data"></div> </div>for (var i = 0; i < data.comments.length; i++) { for(var a = 0; a < data.comments[i].length; a++) { var html = ` <div class="media comment-visible" style="width: 100%;"> <div class="media-body comment-body"> <div class="row"> <span class="comment-body-time" id="created_at">${data.comments[i][a].created_at}</span> </div> <img style="max-width: 90%;" src="${data.comments[i][a].name}" class="comment-body-content" id="comment"> </div> </div> $("#comment-data").prepend(html); } }jsで5秒ごとに情報を受け取り更新しています。jsの知識はまだ疎いので説明が全然できません。すみません。
使い方
グループ作成し、発行されたURLをLINE等のSNSで共有するだけ!
これだけで画像を投稿できます。
まずはサンプルグループを作成したのでそこで画像投稿を試してみてください!以下URLから入れます!
https://newtreap.herokuapp.com/top?access_url=1525e4df64119a38Gname=SAMPLE
これからの展望
これからさらにアプリをブラッシュアップしていきます!
現在ハッシュタグをつけたTwitter投稿がイベント・フェス等の公式サイトや会場ビジョンで流れることがありますがこれに代わるサービスとして運用していきたいです。
プルリク受け付けているので以下から是非!
Branches · daiju81/treap · GitHubまとめ
最初想定していたコンセプトとは少しずれましたが、ログインを削ることで少しでもユーザーが感じる壁は取り除けたかなと思っています。
これで終わりではなくどんどん改善していきます。
URL共有するだけで画像を投稿できるのでぜひ1枚だけでも投稿していただけると嬉しいです。参考サイト(こちらを参考に作成しました。ありがとうございます)
- 投稿日:2020-02-22T15:11:06+09:00
我々は結局、React Hooksをどう扱えば良いのか
React Hooksの目新しさも落ち着いてきて、様々な現場で当然のように見るようになってきました。
私がここ1年使ってきてHooksについて思うことを、まとめていきます。React Hooksによって何が変わったか
React Hooksについて、公式には以下のようにあります。
フック (hook) は React 16.8 で追加された新機能です。state などの React の機能を、クラスを書かずに使えるようになります。
これだけ理解すると、
Class ComponentをFunctional Componentに書き換えられるだけでしょ?
ちょっとモダンな書き方になるだけで、本質は何も変わってないんでしょ?と思ってしまいがちです。
Toggle.tsxclass Toggle extends React.Component { constructor(props) { super(props); this.state = { isOpen: true }; } handleClick() { this.setState(state => ({ isOpen: !state.isOpen })); } render() { return ( <button onClick={ this.handleClick }> { this.state.isOpen ? 'ON' : 'OFF' } </button> ); } }↓
Toggle.tsxconst Toggle = () => { const [isOpen, setIsOpen] = useState<boolean>(true); const handleClick = () => setIsOpen(!isOpen); return ( <button onClick={ handleClick }> {isOpen ? 'ON' : 'OFF'} </button> ) }パッと見すっきりしてるし、なんとなく良い書き方っぽい。
だからただHooks使えばなんとなくモダンな感じがする。しかしHooksがそんなに浅いものだったら、ここまで話題にはなりません。
React Hooksの本質はそこではなく、Custom Hooksにあるのです。Custom Hooks
Custom Hooksとは、簡単に言えば「Hooksを使用した関数」です。
それだけだと物凄いシンプルな話なんですが、見た目以上に強力な概念です。useToggle.tsexport const useToggle = (initialValue: false) => { const [isOpen, setIsOpen] = useState<boolean>(initialValue); return { isOpen, open: useCallback(() => setIsOpen(true), []), close: useCallback(() => setIsOpen(false), []), toggle: useCallback(() => setIsOpen(!isOpen), [isOpen]) }; };状態とそれに対する操作のスコープを制限することができる
Class内には、そのコンポーネントの持つ全ての状態が列挙されます。
バケツリレー的な書き方が推奨されることが多い関係上、そのRootComponentにはあらゆる状態が集まりがちでした。class UserShowContainer extends Component { constructor(props: Props) { super(props); this.state = { isLoading: true, isError: false, isLogouting: false, user: null, item: null, isItemDialogOpen: false, isDeleteDialogOpen: false, ... }; ... }これらの状態には、お互いに関係性のないもの多くあります。
しかしこれらは「そのComponentに属する」だけで全て同じスコープとして扱われます。
そのため可読性が落ち、バグの温床になることも多々ありました。たとえばAPIのエラー判定を
isError
で行っていたとします。async fetch() { try { this.setState({ isLoading: true }); await fetchUser(); this.setState({ isLoading: false, isError: false }); } catch (e) { this.setState({ isLoading: false, isError: true }); } }追加の実装でこんなコードが書き足されたとします。
async fetchItem() { try { this.setState({ isLoading: true }); await fetchItem(); this.setState({ isLoading: false, isError: false }); } catch (e) { this.setState({ isLoading: false, isError: true }); } }お互いに同じ
isLoading
とisError
を操作してしようとしまって、たとえばエラーのはずが処理の順番でエラー扱いではなくなってしまったという不具合につながります。
もちろんこれだけ見ると「こんなコード書くほうが悪い」って話ですが、Componentが肥大化していくとこういう問題を防ぐのは難しくなります。Redux等も書き込み自由なグローバル変数のように扱われがちで、こうした「状態に対する意図しない操作」を防ぐことが困難でした。
一方Custom Hooksを使えば、状態とそれに対する操作のスコープを制限することができます。
const useFetchUser = () => { const [isLoading, setIsLoading] = useState<boolean>(false); const [isError, setIsError] = useState<boolean>(false); const [user, setUser] = setState<User | null>(null); const fetch = async () => { try { setIsLoading(true); setUser(await fetchUser()); setIsError(false); } catch { setIsError(true); } finally { setIsLoading(false); } }); useEffect(() => { fetch() }, []); return { user, isLoading, isError } }この書き方において、
isLoading
、user
といった状態が外部から操作される危険性は全くありません!ただ
user
,isLoading
,isError
が正しく返却されることのみ担保されれば良いので、テスタビリティも向上します。
また同じ挙動が担保されるのであれば、内部の実装の変更は自由です。後からReduxで書き換えることもできますし、事情があって状態の持ち方を変える場合も柔軟に対処可能です。
たとえば次の例ではisOpen
をboolean
からnumber
に変えていますが、影響範囲はCustom Hooksのスコープ内のみに閉じ込められています。export const useToggleBool = () => { const [isOpen, setIsOpen] = useState<boolean>(false); return { isOpen, open: () => setIsOpen(true), close: () => setIsOpen(false) }; }; export const useToggleNumber = () => { const [isOpen, setIsOpen] = useState<number>(0); return { isOpen: isOpen === 1, open: () => setIsOpen(1), close: () => setIsOpen(0) }; };またStateに対して許可する操作のみを関数化できるため、意図しない値が入ることを防ぐことが可能です。
例えば上記のuseToggleNumber
において、isOpen
に0
か1
以外の値が入ることを想定しなくても良いのです。状態とそれに対する操作を使い回すことができる
Custom Hooksを定義することで、状態と、その状態に対する操作を使いまわすことができます。
HomeComponent.tsxconst HomeComponent = () => { const userDialog = useToggle(); const itemDialog = useToggle(); return ( <> <UserCard onClick={userDialog.open} /> <Button onClick={itemDialog.toggle} /> {userDetailDialog.isOpen && <UserDialog onClose={userDialog.close} />} {itemDialog.isOpen && ItemDialog />} </> ) }もちろんClass Componentにも、高階コンポーネントなどロジックの再利用を行う手段はありました。
が、どうしても複雑になりがちでした。気軽に再利用ができるようになったことで、たとえば以下のような「HeadlessなUIライブラリ」も生まれきています。
ex) https://github.com/tannerlinsley/react-table/blob/master/docs/concepts.md
今までは再利用性の高いComponentを持つことが生産性向上につながり、企業は独自のComponent Libraryを作るなどしてこれを図ってきました。
今後はいかに使い回しの効くHooksを持っているかも、重要になってくるでしょう。状態をComponentから切り離すことができる
Class Componentでは、「UIを作り上げるためのロジック」や「状態を変化させるためのロジック」がクラス内に混合しがちでした。
そのためReduxなどのFlux Architectureが普及し、ComponentではUIの生成のみに注力する書き方が主流となりました。しかしHooksにより、状態をComponentから切り離すことが可能となりました。
そのため状態の分離のために、Reduxに頼る必要がなくなったのです。特にReduxはSingletonな1つのStateをProjectで共有するため、一部の状態のみを独立して扱うことが困難でした。
他の状態からも切り離し可能になったことで、フロントエンド設計の柔軟性は大幅に向上しました。React Hooksで解き放たれた状態なにで縛るか
さて、React Hooksにより「状態」はComponentから時なはたれ、自由となりました。
しかしルールのない自由は、ただの混沌です。Custom Hooksも、どの粒度で、どういう単位で設計すれば良いのか、利用者に委ねられています。
Custom HooksからCustom Hooksを呼び出すこともできるため、その設計の自由度は計り知れません。では、どのようなルールでCustom Hooksを設計すれば良いのでしょうか?
クラス定義っぽい?
まずCustom Hooksを書いてて思ったのが、クラス定義っぽい!ってことです。
const toggleA = useToggle(); // const toggleA = new Toggle();っぽい const toggleB = useToggle(); // const toggleB = new Toggle();っぽい if (toggleA.isOpen && toggleB.isOpen) { toggleA.close(); }getterとsetterを内部に隠蔽し、利用者に必要な操作のみを提供するのはカプセル化っぽい!
「じゃあ試しにhooksをドメインモデル単位にして、モデルに対して可能な操作をそこで許可したらどうだろう?」と、試してみました。const useUser = (userId: number) => { ... return { destroy: () => ..., fetch: () => ..., isLoading }; }確かに使ってる分にはそれっぽい。
user.destroy
だのuser.isLoading
だの、使ってる分にもわかりやすい。
雑に便利に使える感じはRailsっぽい。ただ1つのhooksが担保する責務が広すぎました!
クラス定義はただのデータの型なのに対し、HooksはComponentに紐づく状態や、場合によってはAPIアクセエスも含みます。
気軽に使いまわせるものではなく、どうしても利用側の状況に合わせちょっとした差異が生じてきます。
結局のところそうしたモデルに対する操作の定義はClassに任せ、HooksはそのClassを扱う程度に止めるのが良いと感じました。クラス定義と同一視するには、さすがにHooksの持つ意味合いは広すぎました。
Component始点でHooksを定義する
この反省を踏まえて考え直すに、hooksはあくまでComponentの持つ状態の延長線上です。
hooks設計の視点としてComponentが始点となるのは避けられないと感じました。ということで、まずはComponentの持つ状態を整理します。
このコンポーネントの持つ状態は何か? - APIの読み込み状態 - APIの取得の結果エラーだったか - 検索フォームに撃ち込まれた文字 - ダイアログの開閉状態その上で、互いに関係性のある状態をグルーピングし、その最小単位をCustom Hooksにしていきます。
const useFetchUser = () => { const [user, setUser] = useState<User>(); const [isLoading, setIsLoading] = useState<boolean>(false); const [isError, setIsError] = useState<boolean>(false); ... } export const useInput = () => { const [value, setValue] = useState<string>(''); return { value, onChange: (e: React.FormEvent<HTMLInputElement>) => setValue(e.currentTarget.value) }; };用途からではなく、本質的な意味単位で状態設計を行う
このとき利用者側の基準で状態設計すると、1つの状態の持つ意味合いが広くなりがちです。
たとえば初期化中はローディングを出したいと思ってisLoading
という状態設計すると下記のようになります。const [isLoading, setIsLoading] = useState<boolean>(false); setIsLoading(true); await Promise.all([fetchUser(), fetchCompany(), fetchItems()]); setIsLoading(false); }しかしこれだと1つのCustom Hooksの持つ意味が広くなりすぎ使い回しが効きづらくなります。
また後に「ユーザがロード中の時だけユーザのアイコンを変えたい!」となったとき、変更が大変です。
fetchUser
のisLoading
とfetchCompany
のisLoading
は本質的に互いに関係がない値です。
より本質的な意味単位で状態設計し、そこから各UIの事情に合わせ値を計算したほうが良いでしょう。const fetchUser = useFetchUser(); const fetchCompany = useFetchCompany(); const fetchItems = useFetchItems(); const isLoading = fetchUser.isLoading || fetchCompany.isLoading || fetchItems.isLoading;Custom Hooks内で共通する処理は、さらに抽象化する
各Custom Hooksで共通するような処理は、より汎用的なCustom Hooksで抽象化していきます。
Componentの事情に近いCustom Hooksから、より抽象化の高いCustom Hooksを呼び出すような構造となります。export const useFetchUser = () => { const [user, setUser] = useEffect<User | null>(null); const fetch = useAsync(async () => setUser(await userApi.fetch())); .... } export const useFetchCompany = () => { const [company, setCompany] = useEffect<Company | null>(null); const fetch = useAsync(async () => setCompany(await companyApi.fetch())); ... } export const useAsync = (callback) => { const [isLoading, setIsLoading] = useEffect<boolean>(false); const [isError, setIsError] = useEffect<boolean>(false); ... }課題
ただこの考え方でも、まだしっくりこない点があります。
たとえば編集画面でuseFetchUser
のuser
を編集したいときです。
useFetchUser
の中にsetUserName
などを生やすと、このCustom Hooksの責務がどんどん広くなっていきます。また
setUser
を返すようにすると、どんどんガバガバになっていき、Custom Hooksのメリットは薄れてしまいます。パラメータや戻り値でなんとかしていこうとすると、運用が続くにあたりCustom Hooksに渡すパラメータは増え、戻り値もどんどん増えていきます。
この辺りは適度にバランス感を取りつつ、継続的なリファクタリングを行う必要があるでしょう。
おわりに
というわけで確信めいた結論は出ていないものの、現時点での考えを記事にまとめました。
実際のHooksの例としては、下記Repsoitoryが非常に参考になるので、最後に紹介だけしておきます!
https://github.com/streamich/react-use
- 投稿日:2020-02-22T14:10:47+09:00
独学でエンジニアを目指してからの3か月間の学習の振り返り
はじめに
初めまして YKと申します
自己紹介
現在25歳でIT機器関連のテクニカルサポートの仕事をしております。
2019年11月頃に、PG関連に興味を持ち最初はHTMLから入り最終的にはRubyやrailsなどのサーバーサイドの方まで学習しました。
今回は振り返りで、どのように何を学習してきたかまとめます。なにを学習してきたか?(時系列順)
11月5日~12月5日
・Progate 「HTML & CSS」「Javasript」 全コース
最初は王道のProgateさんにて学習を開始しました。
意識した事:このころはWEBコーダーを目指していたので基本的なWEBサイトの構造をおおまかに理解して先にどんどん進めることを意識して学習しました。
・ドットインストールにて「Bootstarap入門」
フレームワークの存在をしりメジャーなBootstarapを学習しようと開始
意識した事:やはりとりあえず手を動かして先に進めることを意識
・youtebeにて「Bootstarap tutorial」の模写
youtubeにアップされている外人さんの解説付きのチュートリアルを2つ模写
LPの基本的な書き方やページ内での移動のさせ方などを理解できた。
参考:https://www.youtube.com/watch?v=gqOEoUR5RHgここまでで大体一ヶ月かかりました。
学習手順は有名な良サイト東京フリーランスさんのデイトラ1stを参考にしました。
https://tokyofreelance.jp/30daystrial-coding-2nd/WEBコーディングに関しては模写できるレベルになるまでとりあえず先に進めてしまうのが効率いいと感じました。
12月6日
・自分はどのようなエンジニアになりたいか考える
東京フリーランスさんのデイトラ1stが終わり、今後の自分について1日思考しました。(デイトラ2nd以降はフロントエンドエンジニアになるための学習になっていく為)
この時自分はサーバーサイドの言語を使って便利なアプリを作ってみたいとも思っており、難易度は上がってしまうが挑戦してみようと方向転換しバックエンドエンジニアになるための学習をはじめることにしました。12月7日~12月18日
・Progate「Ruby」「rails」全コース
まずはProgateにて学習開始、railsコースは2周しました。
本格的なプログラミングに初めて触れ難しさに驚愕意識した事:railsコースの2周目は自分のローカル環境にて実装しました。
Progate上の用意されエディターとは違いエラーなども多く苦戦しましたが良い学習になりました。・Udemyにて「railsコース」
動画教材にてRuby,railsの基礎的な部分、基本的なCRUDなどを学習
dockerやcloud9などを利用する環境構築の手引きがありがたかった印象意識した事:dockerなどを使用しての環境構築の部分を記事にしてアウトプットして残しておくなど工夫しました。初学者には難易度高い環境構築の部分は動画教材を参考にしたほうが効率が良いと思います。
参考:フルスタックエンジニアが教える 即戦力Railsエンジニア養成講座
https://www.udemy.com/course/rails-kj/
はじめての Ruby on Rails入門-RubyとRailsを基礎から学びWebアプリケーションをネットに公開しよう
https://www.udemy.com/course/the-ultimate-ruby-on-rails-bootcamp/・rails tutorial開始
railsの学習といえばコレ!というほど有名な教材
意識した事:文字の教材ということもあり難易度が急激に上がった為、1周目は雰囲気を掴むため通しで読みきり2周目に自分のローカルにて学習しました。結果的にわからなくて先にどんどん進めていくのが完走目指すには最適と感じました。
12月19日~1月19日
・ポートフォリオ製作
いろんな方の動画や記事にてある程度学習したらすぐにポートフォリオ製作に移ったほうが良いと発言されいます。自分もそれに習い早めに作成を開始しました。
結果から言うと早めに初めて本当に良かった思います自分で考えてコードを書く、わからないことを調べて実装する これが思った以上に頭に残りますしどんな学習よりも効率よいと実感しました。
僕が作成したポートフォリオアプリは自動コーディネートアプリというものでその日の気温から最適な洋服を選んで表示してくれるアプリです。
昔から欲しかったものを自分で作り上げられたことに素直にうれしいです。意識した事:学習してきた機能に関しては問題なく実装できたのですがそれ以外の部分に関しては実装方法調べて自分のアプリ用にカスタマイズして作成していきました。
具体的に
・多対多の関係を利用したタグ機能の実装
・外部APIを利用して気象情報を取得し反映させる
・条件分岐を使用して多数のコーディネートを反映させる
・洋服のカテゴリー別ユーザーページの実装 などなど
別の記事にてポートフォリオについては詳しく書こうと思っています。参考までにポートフォリオサイトのgithubとURLを載せておきます
「ポートフォリオサイト Ocean」
https://oceanmorningggg.herokuapp.com/
github
https://github.com/MKprojects39/Ocean1月20日~1月31日
・Git Githubの復習 CicleCiの実装
一人で開発していましたが作業用ブランチを使用してやってみたりはしていたが、理解が足りないと感じたので新しいtestappを作成し疑似的に共同開発の練習をしたりして学習。
CicleCiも試験的に導入してみました。とりあえず自動テストが走るように組み込むことを目標にしました。・Udemy 「Vue.jsコース」
自分のプロフィールサイトがあると便利なのでVue.jsを使用して作成したいと思い学習開始しました。
最近javascriptに力を入れてる企業さんも多いそうなのでどんなものか知りたかったのもあります。意識した事:17時間のコースでボリュームがかなりあったので手を動かすとこ、見て理解に徹するとこのメリハリを意識し効率よく最後まで完走させました。
参考:超Vue JS 2 入門 完全パック - もう他の教材は買わなくてOK! (Vue Router, Vuex含む) https://www.udemy.com/course/vue-js-complete-guide/
この方の動画は声がハキハキしており眠くならずに完走できました。話も分かりやすいのでおすすめですよ!(長時間の動画学習は眠くなるものが多いのでw)
2月1日~2月20日
・Vue.jsを使用してのプロフィールサイトの製作
Udemyにての学習が終わりすぐにプロフィールサイトの作成を開始しました。
意識した事:今回はシンプルに見やすいサイトにしたかったので凝った細工はあまりしてません。
Vue.jsとfirebaseを利用して最速でSPAを作ることを意識して実装。
今後はfirebaseも深堀してVueXの恩恵を受けれるアプリを作成したいです参考:My profile
https://my-profile-yk.firebaseapp.com/2月21日~
・転職活動を開始
現在、転職活動をしております
都内でコードレビュー文化のあるWEB系企業に入りたいので頑張ります!読んでいただきありがとうございました。
- 投稿日:2020-02-22T14:10:47+09:00
独学でエンジニアを目指してからの三ヶ月間の学習振り返り
はじめに
初めまして YKと申します
自己紹介
現在25歳でIT機器関連のテクニカルサポートの仕事をしております。
2019年11月頃に、PG関連に興味を持ち最初はHTMLから入り最終的にはRubyやrailsなどのサーバーサイドの方まで学習しました。
今回は振り返りで、どのように何を学習してきたかまとめます。なにを学習してきたか?(時系列順)
11月5日~12月5日
・Progate 「HTML & CSS」「Javasript」 全コース
最初は王道のProgateさんにて学習を開始しました。
意識した事:このころはWEBコーダーを目指していたので基本的なWEBサイトの構造をおおまかに理解して先にどんどん進めることを意識して学習しました。
・ドットインストールにて「Bootstarap入門」
フレームワークの存在をしりメジャーなBootstarapを学習しようと開始
意識した事:やはりとりあえず手を動かして先に進めることを意識
・youtebeにて「Bootstarap tutorial」の模写
youtubeにアップされている外人さんの解説付きのチュートリアルを2つ模写
LPの基本的な書き方やページ内での移動のさせ方などを理解できた。
参考:https://www.youtube.com/watch?v=gqOEoUR5RHgここまでで大体一ヶ月かかりました。
学習手順は有名な良サイト東京フリーランスさんのデイトラ1stを参考にしました。
https://tokyofreelance.jp/30daystrial-coding-2nd/WEBコーディングに関しては模写できるレベルになるまでとりあえず先に進めてしまうのが効率いいと感じました。
12月6日
・自分はどのようなエンジニアになりたいか考える
東京フリーランスさんのデイトラ1stが終わり、今後の自分について1日思考しました。(デイトラ2nd以降はフロントエンドエンジニアになるための学習になっていく為)
この時自分はサーバーサイドの言語を使って便利なアプリを作ってみたいとも思っており、難易度は上がってしまうが挑戦してみようと方向転換しバックエンドエンジニアになるための学習をはじめることにしました。12月7日~12月18日
・Progate「Ruby」「rails」全コース
まずはProgateにて学習開始、railsコースは2周しました。
本格的なプログラミングに初めて触れ難しさに驚愕意識した事:railsコースの2周目は自分のローカル環境にて実装しました。
Progate上の用意されエディターとは違いエラーなども多く苦戦しましたが良い学習になりました。・Udemyにて「railsコース」
動画教材にてRuby,railsの基礎的な部分、基本的なCRUDなどを学習
dockerやcloud9などを利用する環境構築の手引きがありがたかった印象意識した事:dockerなどを使用しての環境構築の部分を記事にしてアウトプットして残しておくなど工夫しました。初学者には難易度高い環境構築の部分は動画教材を参考にしたほうが効率が良いと思います。
参考:フルスタックエンジニアが教える 即戦力Railsエンジニア養成講座
https://www.udemy.com/course/rails-kj/
はじめての Ruby on Rails入門-RubyとRailsを基礎から学びWebアプリケーションをネットに公開しよう
https://www.udemy.com/course/the-ultimate-ruby-on-rails-bootcamp/・rails tutorial開始
railsの学習といえばコレ!というほど有名な教材
意識した事:文字の教材ということもあり難易度が急激に上がった為、1周目は雰囲気を掴むため通しで読みきり2周目に自分のローカルにて学習しました。結果的にわからなくて先にどんどん進めていくのが完走目指すには最適と感じました。
12月19日~1月19日
・ポートフォリオ製作
いろんな方の動画や記事にてある程度学習したらすぐにポートフォリオ製作に移ったほうが良いと発言されいます。自分もそれに習い早めに作成を開始しました。
結果から言うと早めに初めて本当に良かった思います自分で考えてコードを書く、わからないことを調べて実装する これが思った以上に頭に残りますしどんな学習よりも効率よいと実感しました。
僕が作成したポートフォリオアプリは自動コーディネートアプリというものでその日の気温から最適な洋服を選んで表示してくれるアプリです。
昔から欲しかったものを自分で作り上げられたことに素直にうれしいです。意識した事:学習してきた機能に関しては問題なく実装できたのですがそれ以外の部分に関しては実装方法調べて自分のアプリ用にカスタマイズして作成していきました。
具体的に
・多対多の関係を利用したタグ機能の実装
・外部APIを利用して気象情報を取得し反映させる
・条件分岐を使用して多数のコーディネートを反映させる
・洋服のカテゴリー別ユーザーページの実装 などなど
別の記事にてポートフォリオについては詳しく書こうと思っています。参考までにポートフォリオサイトのgithubとURLを載せておきます
「ポートフォリオサイト Ocean」
https://oceanmorningggg.herokuapp.com/
github
https://github.com/MKprojects39/Ocean1月20日~1月31日
・Git Githubの復習 CicleCiの実装
一人で開発していましたが作業用ブランチを使用してやってみたりはしていたが、理解が足りないと感じたので新しいtestappを作成し疑似的に共同開発の練習をしたりして学習。
CicleCiも試験的に導入してみました。とりあえず自動テストが走るように組み込むことを目標にしました。・Udemy 「Vue.jsコース」
自分のプロフィールサイトがあると便利なのでVue.jsを使用して作成したいと思い学習開始しました。
最近javascriptに力を入れてる企業さんも多いそうなのでどんなものか知りたかったのもあります。意識した事:17時間のコースでボリュームがかなりあったので手を動かすとこ、見て理解に徹するとこのメリハリを意識し効率よく最後まで完走させました。
参考:超Vue JS 2 入門 完全パック - もう他の教材は買わなくてOK! (Vue Router, Vuex含む) https://www.udemy.com/course/vue-js-complete-guide/
この方の動画は声がハキハキしており眠くならずに完走できました。話も分かりやすいのでおすすめですよ!(長時間の動画学習は眠くなるものが多いのでw)
2月1日~2月20日
・Vue.jsを使用してのプロフィールサイトの製作
Udemyにての学習が終わりすぐにプロフィールサイトの作成を開始しました。
意識した事:今回はシンプルに見やすいサイトにしたかったので凝った細工はあまりしてません。
Vue.jsとfirebaseを利用して最速でSPAを作ることを意識して実装。
今後はfirebaseも深堀してVueXの恩恵を受けれるアプリを作成したいです参考:My profile
https://my-profile-yk.firebaseapp.com/2月21日~
・転職活動を開始
現在、転職活動をしております
都内でコードレビュー文化のあるWEB系企業に入りたいので頑張ります!読んでいただきありがとうございました。
- 投稿日:2020-02-22T14:10:47+09:00
エ
はじめに
初めまして YKと申します
自己紹介
現在25歳でIT機器関連のテクニカルサポートの仕事をしております。
2019年11月頃に、PG関連に興味を持ち最初はHTMLから入り最終的にはRubyやrailsなどのサーバーサイドの方まで学習しました。
今回は振り返りで、どのように何を学習してきたかまとめます。なにを学習してきたか?(時系列順)
11月5日~12月5日
・Progate 「HTML & CSS」「Javasript」 全コース
最初は王道のProgateさんにて学習を開始しました。
意識した事:このころはWEBコーダーを目指していたので基本的なWEBサイトの構造をおおまかに理解して先にどんどん進めることを意識して学習しました。
・ドットインストールにて「Bootstarap入門」
フレームワークの存在をしりメジャーなBootstarapを学習しようと開始
意識した事:やはりとりあえず手を動かして先に進めることを意識
・youtebeにて「Bootstarap tutorial」の模写
youtubeにアップされている外人さんの解説付きのチュートリアルを2つ模写
LPの基本的な書き方やページ内での移動のさせ方などを理解できた。
参考:https://www.youtube.com/watch?v=gqOEoUR5RHgここまでで大体一ヶ月かかりました。
学習手順は有名な良サイト東京フリーランスさんのデイトラ1stを参考にしました。
https://tokyofreelance.jp/30daystrial-coding-2nd/WEBコーディングに関しては模写できるレベルになるまでとりあえず先に進めてしまうのが効率いいと感じました。
12月6日
・自分はどのようなエンジニアになりたいか考える
東京フリーランスさんのデイトラ1stが終わり、今後の自分について1日思考しました。(デイトラ2nd以降はフロントエンドエンジニアになるための学習になっていく為)
この時自分はサーバーサイドの言語を使って便利なアプリを作ってみたいとも思っており、難易度は上がってしまうが挑戦してみようと方向転換しバックエンドエンジニアになるための学習をはじめることにしました。12月7日~12月18日
・Progate「Ruby」「rails」全コース
まずはProgateにて学習開始、railsコースは2周しました。
本格的なプログラミングに初めて触れ難しさに驚愕意識した事:railsコースの2周目は自分のローカル環境にて実装しました。
Progate上の用意されエディターとは違いエラーなども多く苦戦しましたが良い学習になりました。・Udemyにて「railsコース」
動画教材にてRuby,railsの基礎的な部分、基本的なCRUDなどを学習
dockerやcloud9などを利用する環境構築の手引きがありがたかった印象意識した事:dockerなどを使用しての環境構築の部分を記事にしてアウトプットして残しておくなど工夫しました。初学者には難易度高い環境構築の部分は動画教材を参考にしたほうが効率が良いと思います。
参考:フルスタックエンジニアが教える 即戦力Railsエンジニア養成講座
https://www.udemy.com/course/rails-kj/
はじめての Ruby on Rails入門-RubyとRailsを基礎から学びWebアプリケーションをネットに公開しよう
https://www.udemy.com/course/the-ultimate-ruby-on-rails-bootcamp/・rails tutorial開始
railsの学習といえばコレ!というほど有名な教材
意識した事:文字の教材ということもあり難易度が急激に上がった為、1周目は雰囲気を掴むため通しで読みきり2周目に自分のローカルにて学習しました。結果的にわからなくて先にどんどん進めていくのが完走目指すには最適と感じました。
12月19日~1月19日
・ポートフォリオ製作
いろんな方の動画や記事にてある程度学習したらすぐにポートフォリオ製作に移ったほうが良いと発言されいます。自分もそれに習い早めに作成を開始しました。
結果から言うと早めに初めて本当に良かった思います。自分で考えてコードを書く、わからないことを調べて実装する これが思った以上に頭に残りますしどんな学習よりも効率よいと実感しました。
- 投稿日:2020-02-22T11:56:53+09:00
puppeteerでThis relative module was not found: ~ ws ~ WebSocketTransport.js
環境
・windows10
・vue-cli3
・electron-builder現象
electronでpuppeteerを使おうとした。
npm run electron:serveしたら、This dependency was not found: * ws in ./node_modules/puppeteer/lib/WebSocketTransport.js To install it, you can run: npm install --save ws対応
To install it, you can run: npm install --save wsとあるので言われたとおりにコマンドを打つ。
で、再度build。
しかし、同じエラーが…。解決策
node_modules/puppeteer/lib/WebSocketTransport.jsをいじる。
const WebSocket = require('ws');を
const WebSocket = require('../node_modules/ws');に修正する。(wsをnpm installした後に)
備考
issueはこちら
https://github.com/puppeteer/puppeteer/issues/3487自分の環境ではこの方法で助かったが、リンク先は割と紛糾しているみたい?
この分野(electron+αの開発)は日本語の参考記事も少ないので、じぶんみたいなぺーぺーだとわりと簡単なところで結構つまづく。
- 投稿日:2020-02-22T05:23:53+09:00
webサービスを運営してみた(2020/2/22)
はじめに
アルバイトの勤怠管理を無料でできるサービスTimestampを個人で運営しています。
フルタイムで仕事をしつつ個人のサービス運営で売上を上げられるのかの実験だったり、技術向上が目的だったりします。
ここでは運営や開発に関する記録を残していきます。
2020年2月に始めたばかりでサービスとしてはまだまだな状態です!ユーザー数
googleアナリティクスのグラフです。
ユーザ数が先週より伸びてますね。嘘です0人になってますね?
アカウント登録者数は2人でアクティブなアカウントは0です。雑記
コードは自動生成されたものなんです
バックエンドはNodeとExpress、フロントエンドはReactとMateriau-ui、データベースはMysqlを使用しているのですが、これらは自動生成されたコードを利用してます。コード自動作成サービスScaffoldHubで作成しました。ReactやVueなどの言語を選んだり、データベースのテーブル設計もできます。自動生成されたコードのデモを動かすところまでは無料で楽しかったので、触ってみてはどうでしょうか。コードをダウンロードしようとすると有料です。
Timestampのコードの90%ぐらいは自動生成されたコードですね。開発予定
しばらくは機能開発に時間がかかりそうです。作業時間を確保できてるしモチベーションも保ててはいるのですが、
それでもなかなか進まなくてもどかしい。
そのあとはSEO対策、英語翻訳を外注、LPのデザインを外注しようかなと考えているのですが、
外注できるかは料金次第ですね。。
- 投稿日:2020-02-22T04:52:57+09:00
初心者によるプログラミング学習ログ 246日目
100日チャレンジの246日目
twitterの100日チャレンジ#タグ、#100DaysOfCode実施中です。
すでに100日超えましたが、継続。
100日チャレンジは、ぱぺまぺの中ではプログラミングに限らず継続学習のために使っています。
246日目は、
おはようございます
— ぱぺまぺ@webエンジニアを目指したい社畜 (@yudapinokio) February 21, 2020
246日目
・udemyで、webサイト模写
・udemyで、バナー作成#早起きチャレンジ#駆け出しエンジニアと繋がりたい#100DaysOfCode#webデザイン
- 投稿日:2020-02-22T04:16:33+09:00
Rails+jQuery イベントの発火により部分テンプレートを更新させる方法
はじめに
インクリメンタルサーチについて書いた記事ではajaxとjsonの組み合わせを使ってビューを変化させていましたが、今回はjsonを使わずRailsの部分テンプレートを更新させる方法でビューを変更する方法について自分なりにまとめていきます。
環境
Ruby 2.5.1
Rails 6.0.2
haml、jQueryを使っていきます。やりたいこと
form_withのsubmitを使わず、jsのイベント発火(clickやchangeなど)により非同期通信で部分テンプレートを更新させたいです。
jsonを活用して更新する方法もありますが、ここでは部分テンプレートを活用して、js内の記述を簡略化させていきます。まずはsubmitを使った時の更新
post_controller.rbclass PostController < ApplicationController def index @posts = Post.all end def create @post = Post.create(message: params[:message]) @posts = Post.all render partial: "post", collection: @posts end endindex.html.haml%h2 Posts = form_with model: @post do |f| = f.text_field :message = f.submit "Post", id: :submit .posts = render partial: "post", collection: @posts_post.html.haml= post.messageまずは比較として、jsを使わず更新してみます。
結果
部分テンプレートのみの更新ではなく、ページ全体を部分テンプレートに変更されてしまいました。部分テンプレートのみ更新させる
post_controller_rbclass PostController < ApplicationController def index @posts = Post.all end def create @post = Post.create(message: params[:message]) @posts = Post.all # partial: "post", collection: @posts <- 削除 end endcreate.js.haml(新規作成)$('.posts').html("#{j(render partial: "post", collection: @posts)}");結果
本題 submitでの送信ではなく、jsのイベントで部分テンプレートの更新を行う
post.js$(function() { $('#submit').on('click', function(event) { event.preventDefault(); var input = $('#message').val(); console.log(input) $.ajax({ type: "POST", url: "/post", data: {message: input} }) .done(function(response) { $('.posts').html(response); }) }) })post_controller.rbclass PostController < ApplicationController def index @posts = Post.all end def create @post = Post.create(message: params[:message]) @posts = Post.all render partial: "post", collection: @posts <- 復元 end endサーバーから返されるデータの形式を指定しないことで、標準的なHTMLで記述された部分テンプレートの情報が返されてきているようです。
結果
jsで更新させる部分にajax通信で返されたデータを置換させることで部分テンプレートの更新が実現できました。活用できそうな場面
マウスオーバーと組み合わせてマウスの位置によってビューを更新させたり、一時的に処理を停止させたりといったjsとの組み合わせで活用できそうです。
- 投稿日:2020-02-22T02:54:10+09:00
JSの先読み後読み
- 投稿日:2020-02-22T02:54:10+09:00
JSの正規表現の先読み後読み
- 投稿日:2020-02-22T00:11:10+09:00
form部品を試作しました。
label要素を用いたチェックエリアの装飾。
選択が行われたことを判定し、送信ボタンを押せるようにする。