- 投稿日:2020-02-04T23:34:39+09:00
Twitterの投稿日時をURLからJavaScriptで求める。
tl;dr
- 年に3度くらい、ツイートの投稿日時ってどこを見ればよいんだっけ、と悩むため忘備録として記事にする
- URLからJavaScriptを使って求めるものとする
はじめに
- Twitterは閲覧環境によって米西海岸の時刻で表示されるなどして、実際の投稿日時を勘違いする問題が稀によくあった。※ 既に改善されていたらすみません。
- 情報リークやデマなどのイベントを追っかけるのに、一刻も早く正しい時系列で事態を把握したいのに、えーっと、、となってしまうのがイヤ。
- 以前はhtmlタグのなかにepochが埋め込まれていたこともあったように思うが今は無さげ。こういう事があるためHTMLは当てにしたくない。
- Twitterはだいぶ前から、Snowflakeという時刻情報を組み込んだIDを使っており、今回はこれを利用する。
やりかた
- ブラウザ(筆者はChrome v79で確認)で、任意のツイートページを開く。
- Developer Consoleを開き、下記のJavaScriptを実行すると、ツイート投稿日時が日本時間で表示される。
alert(new Date(Number((BigInt(window.document.URL.split("/")[5])>>22n) + 1288834974657n)).toLocaleString('ja-JP'))説明
- Snowflakeでは64bit整数のうち、timestampで42 bit、worker-idで10 bit、sequenceで12 bitをアサインしている。
- URLからツイートのIDを取り出す( URLを
/でスプリットして5番目の数値)。- timestampを22bitシフトして取り出したのち、オフセット値である
1288834974657を加算して、日本語ロケールの時刻として表示する。- よく使う人はBookmarkletにすれば便利だと思います。
補足
- なおIEやEdgeといったBigInt(ES2020)に対応していないブラウザでは使えない。
- Snowflake採用以前の、大昔のツイートは今回の方法は使えない。
- Snowflakeのオープンな開発は大分前から止まっており、IDの仕様は既に変わっている可能性もあるが、観測できる範囲では時刻情報は正確そう。
- 投稿日:2020-02-04T23:03:38+09:00
ブラウザごとの URL 解決の挙動の違い
初投稿です。
最近 Chrome にしか対応していないウェブサイトを改修する機会があった。適当に Babe って polyfi ったが、IE11 に完全対応するのは厳しそうで、それは諦めたが、 Edge44 は初期状態でちょいバグるくらいの感じだったので対応を試みた。URL の解決関係でハマったのでメモしておく。趣味人なので間違っていたらご教授ください。
URL コンストラクタの挙動が違う
JavaScript では
new URL(url, [base])の構文でベース URL を指定して URL オブジェクトを作ることができるが、url に空文字列を入れた場合の href の値が Chrome と Edge44 で微妙に違う。main.jsconst myURL = new URL("", "https://example.com/index.html") // Chrome だと myURL.href === "https://example.com/index.html" // Edge だと myURL.href === "https://example.com/" const myURL = new URL("", "https://example.com/css") // Chrome だと myURL.href === "https://example.com/css" // Edge だと myURL.href === "https://example.com/" const myURL = new URL("", "https://example.com/") // base がスラッシュで終わる場合は(偶然)返り値が一致する // ともに myURL.href === "https://example.com/" const myURL = new URL("..", "https://example.com/index.html") // url に空でない文字を入れると挙動は一致する // ともに myURL.href === "https://example.com/" const myURL = new URL("https://example.com/index.html") // ともに myURL.href === "https://example.com/index.html" const myURL = new URL("https://example.com/") // ともに myURL.href === "https://example.com/"「ベースURL」の意味からして、最初の例では Edge のように
https://example.com/を返してほしいところだが、Chrome ではそうはならない1。まるで空文字列を無視して(つまり base のつもりで入れたものを url として解釈して)いるようなふるまいを見せる。new URL("", "https://example.com/index.html") new URL("https://example.com/index.html") // Chrome では同じ内容の URL オブジェクトができるChrome と空文字列の省略
そういえば経験的に、Chrome は空文字列を勝手に無いものと扱うような雰囲気がある。たとえば以下のように適当な DOM オブジェクトを作るとする。
const input = document.createElement("input"); input.setAttribute("type", "text"); input.setAttribute("value", data[i] || ""); input.required = flag===1 ? true : false;データが存在しなければ
valueの属性値に空文字列が入るわけだが、これを HTML に起こすとブラウザ間でけっこう差が出る。Chrome では=""ごと削除され、属性名だけがタグ内に残るが、Edge44 (EdgeHTML) では空文字列の存在がきちんと残される。ちなみに Firefox は属性値省略絶対殺すマンになる。最近出た Edge79 (Chromium) では Chrome と同じように表示されるので、レンダリングエンジンの問題だろう。<!-- Chrome, and Edge79 (Chromium) --> <input type="text" value required> <!-- IE11, Edge44 (EdgeHTML), and Firefox --> <input type="text" value="" required="">HTML の属性名・属性値を省略できるか問題はややこしい、これ以上の追及はやめておくが、ともかくこのような具合で Chrome では URL コンストラクタの第一変数が抹消されてしまい、挙動がバグったのだと思われる。本来 URL() コンストラクタの第一引数に空文字列が入ることは想定されていない? TypeError が出ても仕方がない場面だが、url も base もそれっぽい USVString なので一応動いてしまう。
ほかのオブジェクト型
気になったので開発者ツールのコンソールでいろいろ試してみたが、コンストラクタに空文字列を与えても無視されない。たとえば以下のペアは異なる結果が返ってくる。
new Date("", "2") // Thu Mar 01 1900 00:00:00 GMT+0900 (日本標準時) new Date("2") // Thu Feb 01 2001 00:00:00 GMT+0900 (日本標準時) new RegExp("", "g") // /(?:)/g new RegExp("g") // /g/なお Edge44 はまた違うふるまいをする。Chromium のお気持ち察し能力が高い。
new Date("2") // [date] Invalid Date:うーん、結局ほんとうの原因が何かはよくわからないままだ。WHATWG の仕様書とか読めば解決するのかもしれないけど、読み方わからないのでいったんここまで。
2020-02-05: Edge44 (EdgeHTML) をちょっと訂正。
つまりもともとの実装は、Chrome の不適切な挙動をもとに実装されていたということになる。 ↩
- 投稿日:2020-02-04T22:50:07+09:00
Fn Projectを使ってPythonで書いたfunctionを動かしてみる
概要
このエントリでは、OSSのFaaSサーバである「Fn Project」を使い、Pythonの関数を動かすパターンを扱います。
下図のようなPythonによる掛け算の関数をFnのサーバにデプロイして動かします。
想定読者
- Fn ProjectでのJavaScriptプログラムまだ自分で動かしていない方
- 「Fn Projectを使ってJavaScriptで書いたfunctionをNodeで動かしてみる」のPython版に興味がある方
準備
Fn Projectを動かすまでのところは、別エントリ「OCIのMicro InstanceでCentOSにFn Projectのサーバをインストールしてみる」を参照ください。
このエントリでは、筆者はOCIのMicro Instanceの上で作業しています。
Pythonでfunctionを動かす
作業の基本的な流れは、Introduction to Fn with Pythonに書いてあるものに従っています。
Fn上のアプリ
別のエントリ「Fn Projectを使ってJavaScriptで書いたfunctionをNodeで動かしてみる」で、「fn create app」で、fn上に「calc-fn-app」アプリを作ってあるものにFunctionを追加する形をとります。
登録
/multiplyという位置にPythonで関数を一つ作るため、「fn init」を実行します。
$ fn init --runtime python --trigger http multiply Creating function at: ./multiply Function boilerplate generated. func.yaml created.下記のような3つのファイルができています。
$ ls func.py func.yaml requirements.txtコードを変更
下記のような、入力値のleftとrightを足して返すような関数に書き換えます。
import io import json from decimal import * from fdk import response def handler(ctx, data: io.BytesIO=None): value = Decimal('NaN') try: body = json.loads(data.getvalue()) left = Decimal(body.get('left')) right = Decimal(body.get('right')) value = left * right except (Exception, ValueError) as ex: print(str(ex)) return response.Response( ctx, response_data=json.dumps( {"result": str(value)}), headers={"Content-Type": "application/json"} )アプリデプロイします。「-w」で作業ディレクトリを指定して、登録しています。
fn --verbose deploy --app calc-fn-app --local -w /home/opc/calc-fn-app/multiply初回実行時には、以下のような流れとなるようです。
$ fn --verbose deploy --app calc-fn-app --local -w /home/opc/calc-fn-app/multiply Deploying multiply to app: calc-fn-app Bumped to version 0.0.2 Building image fndemouser/multiply:0.0.2 FN_REGISTRY: fndemouser Current Context: default Sending build context to Docker daemon 6.144kB Step 1/12 : FROM fnproject/python:3.6-dev as build-stage 3.6-dev: Pulling from fnproject/python 80369df48736: Pull complete aaba0609d543: Pull complete a97b990f94a5: Pull complete af4a941e5376: Pull complete 709c35256bb6: Pull complete 3c3deb8445b4: Pull complete Digest: sha256:8ac8c28a68fd0442b9ddcdf6a41f30230482d72d1024cafca06c9f1ac0bd821c Status: Downloaded newer image for fnproject/python:3.6-dev ---> c5dbe9a0175b Step 2/12 : WORKDIR /function ---> Running in f7aaa0e58f7a Removing intermediate container f7aaa0e58f7a ---> 697f68e69e7c Step 3/12 : ADD requirements.txt /function/ ---> c4472f73a275 Step 4/12 : RUN pip3 install --target /python/ --no-cache --no-cache-dir -r requirements.txt && rm -fr ~/.cache/pip /tmp* requirements.txt func.yaml Dockerfile .venv ---> Running in 4f758b74080b Collecting fdk Downloading https://files.pythonhosted.org/packages/1d/b8/41b81bf76766f7e810627728647a8076626070a6e1d01a18a8ed16bd3d3f/fdk-0.1.12-py3-none-any.whl (46kB) Collecting iso8601==0.1.12 Downloading https://files.pythonhosted.org/packages/ef/57/7162609dab394d38bbc7077b7ba0a6f10fb09d8b7701ea56fa1edc0c4345/iso8601-0.1.12-py2.py3-none-any.whl Collecting httptools>=0.0.10 Downloading https://files.pythonhosted.org/packages/1b/03/215969db11abe8741e9c266a4cbe803a372bd86dd35fa0084c4df6d4bd00/httptools-0.0.13.tar.gz (104kB) Collecting pbr!=2.1.0,>=2.0.0 Downloading https://files.pythonhosted.org/packages/7a/db/a968fd7beb9fe06901c1841cb25c9ccb666ca1b9a19b114d1bbedf1126fc/pbr-5.4.4-py2.py3-none-any.whl (110kB) Collecting pytest==4.0.1 Downloading https://files.pythonhosted.org/packages/81/27/d4302e4e00497448081120f65029696070806bc8e649b83f644de006d710/pytest-4.0.1-py2.py3-none-any.whl (217kB) Collecting pytest-asyncio==0.9.0 Downloading https://files.pythonhosted.org/packages/33/7f/2ed9f460872ebcc62d30afad167673ca10df36ff56a6f6df2f1d3671adc8/pytest_asyncio-0.9.0-py3-none-any.whl Collecting py>=1.5.0 Downloading https://files.pythonhosted.org/packages/99/8d/21e1767c009211a62a8e3067280bfce76e89c9f876180308515942304d2d/py-1.8.1-py2.py3-none-any.whl (83kB) Collecting pluggy>=0.7 Downloading https://files.pythonhosted.org/packages/a0/28/85c7aa31b80d150b772fbe4a229487bc6644da9ccb7e427dd8cc60cb8a62/pluggy-0.13.1-py2.py3-none-any.whl Collecting attrs>=17.4.0 Downloading https://files.pythonhosted.org/packages/a2/db/4313ab3be961f7a763066401fb77f7748373b6094076ae2bda2806988af6/attrs-19.3.0-py2.py3-none-any.whl Collecting setuptools Downloading https://files.pythonhosted.org/packages/a7/c5/6c1acea1b4ea88b86b03280f3fde1efa04fefecd4e7d2af13e602661cde4/setuptools-45.1.0-py3-none-any.whl (583kB) Collecting more-itertools>=4.0.0 Downloading https://files.pythonhosted.org/packages/72/96/4297306cc270eef1e3461da034a3bebe7c84eff052326b130824e98fc3fb/more_itertools-8.2.0-py3-none-any.whl (43kB) Collecting six>=1.10.0 Downloading https://files.pythonhosted.org/packages/65/eb/1f97cb97bfc2390a276969c6fae16075da282f5058082d4cb10c6c5c1dba/six-1.14.0-py2.py3-none-any.whl Collecting atomicwrites>=1.0 Downloading https://files.pythonhosted.org/packages/52/90/6155aa926f43f2b2a22b01be7241be3bfd1ceaf7d0b3267213e8127d41f4/atomicwrites-1.3.0-py2.py3-none-any.whl Collecting importlib-metadata>=0.12; python_version < "3.8" Downloading https://files.pythonhosted.org/packages/8b/03/a00d504808808912751e64ccf414be53c29cad620e3de2421135fcae3025/importlib_metadata-1.5.0-py2.py3-none-any.whl Collecting zipp>=0.5 Downloading https://files.pythonhosted.org/packages/be/69/4ac28bf238f287f1677f41392e24d2c4ffafcf11648c23824f5f62ef6ccb/zipp-2.1.0-py3-none-any.whl Building wheels for collected packages: httptools Building wheel for httptools (setup.py): started Building wheel for httptools (setup.py): finished with status 'done' Created wheel for httptools: filename=httptools-0.0.13-cp36-cp36m-linux_x86_64.whl size=217310 sha256=51eb19168c0639662416a8ad83b9df7d7ceb8eb14a58ce3bec5ffe3ab3b07a7f Stored in directory: /tmp/pip-ephem-wheel-cache-pwxrmfa5/wheels/e8/3e/2e/013f99b42efc25cf3589730cf380738e46b1e5edaf2f78d525 Successfully built httptools Installing collected packages: iso8601, httptools, pbr, py, zipp, importlib-metadata, pluggy, attrs, setuptools, more-itertools, six, atomicwrites, pytest, pytest-asyncio, fdk Successfully installed atomicwrites-1.3.0 attrs-19.3.0 fdk-0.1.12 httptools-0.0.13 importlib-metadata-1.5.0 iso8601-0.1.12 more-itertools-8.2.0 pbr-5.4.4 pluggy-0.13.1 py-1.8.1 pytest-4.0.1 pytest-asyncio-0.9.0 setuptools-45.1.0 six-1.14.0 zipp-2.1.0 WARNING: You are using pip version 19.3.1; however, version 20.0.2 is available. You should consider upgrading via the 'pip install --upgrade pip' command. Removing intermediate container 4f758b74080b ---> 5246c18d4e6f Step 5/12 : ADD . /function/ ---> c6181bb33bd1 Step 6/12 : RUN rm -fr /function/.pip_cache ---> Running in 695fe059e692 Removing intermediate container 695fe059e692 ---> 5ea69eaafc3a Step 7/12 : FROM fnproject/python:3.6 3.6: Pulling from fnproject/python 80369df48736: Already exists aaba0609d543: Already exists a97b990f94a5: Already exists af4a941e5376: Already exists 709c35256bb6: Already exists 671870542c6c: Pull complete 936a6f40830a: Pull complete Digest: sha256:3b438ba11405bba0f6e1e8d8819c9b8be38249da7253491f3cf9f7e5ed6c0ec6 Status: Downloaded newer image for fnproject/python:3.6 ---> e8e10863d7cd Step 8/12 : WORKDIR /function ---> Running in ddbc794f4bd9 Removing intermediate container ddbc794f4bd9 ---> 4e3bed62a7f5 Step 9/12 : COPY --from=build-stage /python /python ---> b1c7d4fd7598 Step 10/12 : COPY --from=build-stage /function /function ---> 87446e069bd2 Step 11/12 : ENV PYTHONPATH=/function:/python ---> Running in af075fbcae2c Removing intermediate container af075fbcae2c ---> 98a9f515e7e0 Step 12/12 : ENTRYPOINT ["/python/bin/fdk", "/function/func.py", "handler"] ---> Running in 555033ab1e55 Removing intermediate container 555033ab1e55 ---> 9942a3d98858 Successfully built 9942a3d98858 Successfully tagged fndemouser/multiply:0.0.2 Updating function multiply using image fndemouser/multiply:0.0.2... Successfully created function: multiply with fndemouser/multiply:0.0.2 Successfully created trigger: multiply Trigger Endpoint: http://127.0.0.1:18080/t/calc-fn-app/multiplyfnのコマンドで確認します。(別エントリでplus,minusを作ってあります)
$ fn list fn calc-fn-app NAME IMAGE ID multiply fndemouser/multiply:0.0.2 01E086F2D6NG8G00GZJ000001E plus fndemouser/plus:0.0.2 01E0393FDCNG8G00GZJ000000E subtract fndemouser/subtract:0.0.3 01E05KAKGENG8G00GZJ0000013Dockerのコマンドで確認すると、対応するコンテナのイメージができていることがわかります。
$ docker images | grep multiply fndemouser/multiply 0.0.2 9942a3d98858 About a minute ago 175MB実行
登録時の末尾にあったURLにcurlでアクセスしてみます。
$ curl -d '{"left":"2", "right":"4"}' http://127.0.0.1:18080/t/calc-fn-app/multiply {"result": "8"}2*4の結果である8が返ってきています。
おわりに
このエントリでは、Fn projectを使ってPythonで書いたfunctionを動かしてみることを扱いました。
このエントリで使用したコードは、https://github.com/hrkt/calc-fn-app/releases/tag/0.0.3のタグに格納してあります。
補足:外部ライブラリ
このエントリでは、外部ライブラリを使いませんでしたが、requirements.txt中に記載することで、pipを利用できます。
補足:このエントリを書くにあたり
このエントリを書くにあたり、OCIのマイクロインスタンス上に下記の状況を作り、ノートPCからクラウド側にsshでリモートでつないで作業しました。
- sshの先にscreenで複数枚のウインドウ上げておいて作業して
- VS CodeをWEBで動かして「cdr/code-serverとOCIのAlways FreeのMicroインスタンスでVS Codeを動かしてみる」
- Fn projectのサーバもそこで動かして
- コードはGitHubに
- 投稿日:2020-02-04T22:36:58+09:00
EffectiveなNext.jsを目指して(Routing)
はじめに
Reactを用いてSSR(Server Side Rendering)が可能なフレームワークであるNext.js.簡単にWebアプリケーションを作成することができるが,柔軟性もあり,より効率的に書けるようになれば手軽に高品質なアプリが作れると感じて勉強を始めました.日本語のドキュメントもあまりないので,Next.jsを活用する人たちの役に立てればなと思います.
今回は,Routingについてまとめていきます.サンプルコードとデモページを参考にしながら読んでいただければ,よりわかりやすいかと思います.
- サンプルコード - https://github.com/syakoo/next-routing-test
- デモページ - https://next-routing-test.sakoooo1001.now.sh/
環境
- Next.js - 9.2.1
§1.CSRのルーティングの手段
サンプルコードの
src/pages/ch1にこの章のコードがあります.CSRのルーティングの手段として3つの例を紹介します.
- Link
- useRouter
- withRouter
それぞれの例について,サンプルコードを見ていきましょう.
Ex1.Link
src/pages/ch1/index.js(抜粋)import Link from 'next/link' // ===================================== // 方法1.Link const Ex1 = () => { return ( <> <Link href='/ch1'> <a>§1.CSRでのルーティング</a> </Link> </> ) }普段使っている
<a>タグだとサーバーにリクエストが投げられるので,nextで提供されているLinkというJSXコンポーネントを使います.Ex2.useRouter
src/pages/ch1/index.js(抜粋)import { useRouter, withRouter } from 'next/router' // ===================================== // 方法2.useRouter const Ex2 = () => { const router = useRouter() return ( <> <button onClick={() => router.push('/ch1')}> §1.CSRでのルーティング(default) </button> <button onClick={() => router.push('/ch1', '/ch1', { shallow: true })}> §1.CSRでのルーティング(shallow: true) </button> </> ) }
<Link>タグだとクライアントからのクリックアクションからしかルーティングができない為,router.push('/ch1')とメソッドでCSRでのルーティングができるようになります.注目してほしいのは,
router.push()の第三引数のshallowというパラメータです.デフォルトではfalseとなっていますが,これをtrueにすることでgetInitialProps()を省いたページルーティングが可能になります.
デモページでブラウザのコンソールを表示した状態でボタンをクリックすると,実際にgetInitialProps()が呼び出されていないことが確認できます.
Ex3.withRouter
src/pages/ch1/index.js(抜粋)import { useRouter, withRouter } from 'next/router' // ===================================== // 方法3.withRouter const Comp = ({ router }) => { return ( <> <button onClick={() => router.push('/ch1')}> §1.CSRでのルーティング(default) </button> <button onClick={() => router.push('/ch1', '/ch1', { shallow: true })}> §1.CSRでのルーティング(shallow: true) </button> </> ) } const Ex3 = withRouter(Comp)Ex2と同様に
routerのメソッドでルーティングを制御しています.このrouterの取得の方法で好みの方を選択するといいと思います.§2.動的ルーティングでは[XXXX].jsシンタックスを使え
Next.jsのルーティングは
pages/以下のフォルダとJSファイルがそのままパスとして配置されます.そのため,ブログのidやユーザー名などの動的なルーティングを処理するのがやや面倒で,具体的にはqueryとして値を渡すことでやりくりしています.少し脱線しますが,このqueryを受け取る方法は,getInitialPropsとnext/routerを使う方法があります.
具体的には以下のようになります.
- getInitialProps()
Component.getInitialProps = async ({ query }) => { return { query } }
- next/router
const router = useRouter() const { query } = router
getInitialProps()を導入すると,nextはビルド時にjsへと変換し,getInitialProps()がなければhtmlへビルドする仕様になっています.なので,特に理由がなければnext/routerのやり方を採用することをオススメします.さて,本題に戻ります.[XXXX].jsシンタックスとは,pages以下のファイル名を
[適当な名前].jsにすることで,そのページの任意の名前に割り当てることができます.詳しい説明は,公式のホームページ(NEXT.js - Dynamic Routes)等を参考にしてください.今回は,[XXXX].jsシンタックスを採用することで,他のやり方とどのような違いがあるかについて説明します.比較したコードは以下の2パターンになります.
Ex1.hrefにqueryを追加する方法.
src/pages/ch2/index.js(抜粋)import Link from 'next/link' // ===================================== // 方法1.hrefにqueryを追加する方法. const Ex1 = () => { return ( <ul> <li> <Link href={{ pathname: '/ch2/ex1', query: { user: 'hoge' } }} as='/ch2/ex1/hoge' > <a>/ch2/ex1/hoge</a> </Link> </li> <li> <Link href={{ pathname: '/ch2/ex1', query: { user: 'foo' } }} as='/ch2/ex1/foo' > <a>/ch2/ex1/foo</a> </Link> </li> </ul> ) }
Linkのあたりがごちゃごちゃしてます.pages/ch2/ex1/index.jsに{query: {user: "hoge"}}を/ch2/ex1/hogeというパスとして渡しています.Ex2.[XXXX].jsシンタックスを使用.
src/pages/ch2/index.js(抜粋)import Link from 'next/link' // ===================================== // 方法2.[XXXX].jsシンタックスを使用. const Ex2 = () => { return ( <ul> <li> <Link href='/ch2/ex2/[user]' as='/ch2/ex2/hoge'> <a>/ch2/ex2/hoge</a> </Link> </li> <li> <Link href='/ch2/ex2/[user]' as='/ch2/ex2/foo'> <a>/ch2/ex2/foo</a> </Link> </li> </ul> ) }Ex1.と比べるとシンプルです.
pages/ch2/ex2/[user].jsに/ch2/ex2/hogeというパスとして渡しています.動作確認
動作を確認します.
https://next-routing-test.sakoooo1001.now.sh/ch2
を実際に動かすとわかりやすいと思います."hrefにqueryを追加する方法"と"[XXXX].jsシンタックスを使用"の
/ch2/ex1/hogeと/ch2/ex2/hogeをクリックしてみます.
どちらもqueryを取得できていて,同じ画面が表示されると思います.じゃーコードの量が減るだけだと思いきや,両方のページでこのページをリロードしてみます.すると...
Ex1.のみが404エラーが返されます.理由は,このch2/ex1/hogeはクライアント側で無理やり付けたパス名であり,リロードでNextのサーバーにこのパスが渡されたときに対応するファイルが存在しないからです.一方,[XXXX].jsシンタックスを使用すると,サーバーでもNextが自動で割り当ててくれます.よって,動的なルーティングには
[XXXX].jsを使いましょう.Ex3. [...XXXX].jsシンタックス
これは,
[XXXX].jsシンタックスの拡張版みたいな感じで,そのページ以下の任意のパスを割り当てて,配列としてqueryを受け取ることができます.動作確認は上のリンクからできます.§3.asを忘れてはいけない.
一番伝えたかった事になります.§1.でCSRでのルーティング,§2.で[XXXX].jsシンタックスについて説明していきました,この
[XXXX].jsシンタックスについて,サンプルコードをもう一度見てみます.src/pages/ch2/index.js(抜粋)import Link from 'next/link' // ===================================== // 方法2.[XXXX].jsシンタックスを使用. const Ex2 = () => { return ( <ul> <li> <Link href='/ch2/ex2/[user]' as='/ch2/ex2/hoge'> <a>/ch2/ex2/hoge</a> </Link> </li> <li> <Link href='/ch2/ex2/[user]' as='/ch2/ex2/foo'> <a>/ch2/ex2/foo</a> </Link> </li> </ul> ) }また,
一方,
[XXXX].jsシンタックスを使用すると,サーバーでもNextが自動で割り当ててくれます.と説明しています.つまり,
/ch2/ex2/hogeとurlを打てばNextが自動でpages/ch2/ex2/[XXXX].jsに割り当ててくれるわけです.。。。ん?
じゃ,Linkタグにas要らなくない?と考えてLinkタグを以下のように修正してみますBad<Link href='/ch2/ex2/hoge'> <a>/ch2/ex2/hoge</a> </Link>このようにして,直接
/ch2/ex2/hogeを指定してもnextがpages/ch2/ex2/[XXXX].jsに割り当ててくれるので,大丈夫だろうって魂胆です.実際に確認したサンプルコードを見ていきます.Ex1. asを消してみた.
src/pages/ch3/index.js(抜粋)import Link from 'next/link' // ===================================== // 方法1.asを消してみた. const Ex1 = () => { return ( <ul> <li> <Link href='/ch3/ex1/hoge'> <a>/ch3/ex1/hoge</a> </Link> </li> <li> <Link href='/ch3/ex1/foo'> <a>/ch3/ex1/foo</a> </Link> </li> </ul> ) }Ex2. asを残した.
src/pages/ch3/index.js(抜粋)import Link from 'next/link' // ===================================== // 方法2.asを残した. const Ex2 = () => { return ( <ul> <li> <Link href='/ch3/ex2/[user]' as='/ch3/ex2/hoge'> <a>/ch3/ex2/hoge</a> </Link> </li> <li> <Link href='/ch3/ex2/[user]' as='/ch3/ex2/foo'> <a>/ch3/ex2/foo</a> </Link> </li> </ul> ) }動作確認
動作を確認します.
https://next-routing-test.sakoooo1001.now.sh/ch3
を実際に動かすとわかりやすいと思います.ブラウザのコンソールを表示してみます.
いきなりエラーが表示されています.nextのLinkタグは,デフォルトでprefetchがtrueになっており,バックグラウンドでリンクのページを事前に取得してくれます.その際に,ch3/ex1/hoge.jsがないよと言っています.また,両方ともリンクをクリックしてみます.すると,
コンソールを見ると,asありでは表示されているChapter3/Ex2のgetInitialPropsだよ: 1というログが,asなしでは観測することができません.ではどこに表示されているのかというと,サーバーのコンソールで表示されています.整理すると,[XXXX].jsシンタックスはサーバーのみで自動で割り当てられます.つまり,asを消して,hrefに動的パスを入力してしまうと,リクエストがサーバーに行きサーバーでパスの割り当て(かつSSR)をして
ch3/ex1/hoge.jsを返すことでクライアントで表示できるようになっています.これにより,同じ動作をするのにパフォーマンスに差が出てしまいます.結論としては,
- hrefにはフォルダ
pages/のパスを書くと認識しておくと間違いはないかと思います.
まとめ
Next.jsのRoutingについて見ていきました.次は他の機能について効率化を目指して勉強していきたいと思います.
- 投稿日:2020-02-04T21:38:46+09:00
【おっちゃんがエンジニアになるまで】@3ヶ月目〜色んな人に助けられる〜(継続編)
はじめに
こんにちは!ばーんです。
30歳、独学、業界未経験、既婚者、働きながら
のおっちゃんが具体的にどうやってエンジニアになれるのか?
を(ほぼ)リアルタイムで振り返り共有させてもらいます。自身の思考整理がメインですが、これから目指す方の指標に少しでもなれれば幸いです。
数字振り返り
1ヶ月目 Total 76h(1日平均2.5h)
2ヶ月目 Total 91h(1日平均3h)
3ヶ月目 Total 102h(1日平均3.3h)Day61-70 38h
Day71-80 31h
Day81-90 33h1月末ぐらいに「今月はちょっとサボってもたかなぁ...」と思ってたんですが、蓋をあけてビックリ。
足りないぐらいに感じてたので...
体が慣れて来たのかなぁ?と前向きに捉えておきます!!Day61-70:メンター襲来。期待値てんこ盛り
東京で活躍しているメンターのエンジニアが、関西に来てくれて教えてくれるという神イベントが発生しました!ありがたい!正直お金払ってでも依頼したいですね
この日程で取り組んだのは「ペアプロ、API、JS(fizzbuzz、ズンドコ)、Git、ソースツリー」
盛り沢山ですね…それぞれ解説していきます。ペアプロ
もう間違いなくやってよかったな と思えるものでした。ペアプロとは「ペアプログラミング」の略です。
名前から大体想像つくと思いますが、1つのプログラミングを2人で作成していきます。
特にレベル差がある人同士でやると効果が高いそうです。始めたばかりの人が経験者に教えてもらえるなど。
相手の思考を聴けるタイミングはそうそうないので(どう考えて、どのような手順で進めていくか?)今回はモーダル表示の仕方やCSSの命名規則などを教えてもらいました!記事はこちら
API
アプリケーションとプラグラミングを繋ぐものです。Qiitaやgithubにログインする際に「Twitterと連携(認証)しますか?」みたいな表記が出てくることがあると思いますが、あれもAPIの1種です。
自分が作成したのはこちらです↓
グーグルスプレッドシートのAPI連携できた!
— ばーん@転職準備中 (@baan_nasebanaru) January 4, 2020
・シートに記事のタイトル、日時、本文打ち込む
・その内容がHTMLで表示される
・簡易なjQuery使用
今月中にはもう一つ作る&Qiitaに作成方法まとめるはします。
公開したですが、認証キーとか使ったのでその辺の安全確認できれば公開します☺️ pic.twitter.com/HyjB59Md4wこれはGoogle spreadsheetにデータを打ち込むと、WebSiteに反映されるようにしてます!
他にもTwitterのAPIなんかも有名ですね。※僕は教えてもらいながらやったので、正直初学者のタイミングでやるべきかどうかは不明です
簡単なものを作ってみて把握ぐらいでいいのかなーと思ってます。JS(fizzbuzz、ズンドコ)
JSはProgateでやった程度の僕がチャレンジしました。一度つよつよメンターに教えてもらって、月末にチャレンジしました!
JSの理解が深まるのでとてもオススメです!(fizzbuzz→ズンドコの順)Git、ソースツリー
こちらも聞かないと理解し難い内容ですね。ソースツリーは初めて使って感動しました!
覚えるまでが大変ですが、覚えるとgithubにあげるのすごく簡単ですよ〜そして、この後目標設定(ロードマップ)とポートフォリオを再度作成始めます
この時の目標は「デザインとJS学習を深めていって2月からサイト制作の営業を始める!」といった感じでした
この週に感じたのは「オフライン学習の大切さ」ですね。
調べたら殆どのことは分かる現代ですが、やはり会って教えてもらえると段違いに学習が進みます!なので、この後は積極的に勉強会の予約を取りました!
Day71-80:デザイン学習開始
主にポートフォリオの修正がメインでした。ただ、そこでSVGに出会ってしまってガッツリはまっちゃいました!笑
SVGの記事はこちらもう、この時は無我夢中でした。次の日が仕事とか知らん!楽しい!と思ってゴリゴリ進めてましたね。普通に外が明るくなるぐらいまでしてたりしてました。
社会人として、とかはおいといて楽しい!は大切ですね。継続という点で見てもあとは、メンターに見てもらいながらポートフォリオサイト第2弾が完成しました!(こちら)
この時に感じたのはデザインやらないと…でした。今回時間がかかったのはデザインが殆どでした。
今後どういった方向に進むにしてもデザインの基礎は最低限必要だと感じたので、次週からとりかかります↓Day81-90:デザイン学習、目標再設定
「デザイン学習、コミュニティへの参加、勉強会への参加、Qiitaでのアウトプット」を行いました。
デザイン学習
これはCocodaのDailyCocoda!で実践しました!
書籍も読みましたが、基本は実践が8割でいいと思っているので、DailyCocoda!はとても良いと思います(基本的に無料で実践できます)。
書籍は「なるほどデザイン」「ノンデザイナーズ・デザインブック」をオススメされたので随時見てます
そして、やったはずのAdobeXDの使用方法忘れていたのでchot.designさんを活用しました。
ここのサイトめちゃくちゃ見やすいのでオススメです。※ちなみに初めての成果物はこちら(3hぐらいかかった…目安1hで)
コミュニティへの参加、勉強会への参加
参加したコミュニティ
「独学フリーコミュニティ【学習積み上げ】」※現在申込締切
「わくわく会」参加者募集中!!※主催者のTwitterへのリンク勉強会
「実務にハードルを感じてる駆け出しさんたちの勉強会」参加者募集中!!※コンパスへのリンクわくわく会も、勉強会も関西が活動のメインなので、関西で駆け出している方、駆け出そうとしている方は是非ご一考下さい(わくわく会はオンラインメインなので、地域関係なし)。
この記事にコメント頂けるか、DM頂ければ対応できます〜。主催者の方に直接ご連絡でもOKですし。コミュニティは入っていると気軽に質問できるのでありがたいです!
勉強会は、実際に働かれている方の声を聞けたので大変有意義でした!
現状を知れたのが特に大きかったな〜と思っています。個人的には目標を立てる時大事なことが2つあって、
①現実的なレベル感を知ること
②できるできないは別にして、自分の理想を描くことこの両面を把握することが自己成長を進める鍵だと考えています(過去の経験から)。
①だけだと小さく纏まってしまうし、②だけだと心がついていかないことが多いかな〜と。この後メンターとも話してみて月初に立てた目標を変更しました。
ロードマップをひくことはエンジニアを目指す場合にはとても大事なので!旧「デザインとJS学習を深めていって2月からサイト制作の営業を始める!」
新「Qiitaで溜まっているアウトプットを終わらせて、ワードプレスとデザイン学習を進め案件獲得を目指す」Qiitaでのアウトプット
月初に教わったことをひたすらアウトプットしてました。こちら↓
【超簡単】【AdobeXD】テキストアニメーション(SVG)
【CSS小技集】position,before(after),display
Qiitaで書くことがアウトプットの全てではないです。Twitterに書いてもいいですし、友達に話してもいいです。
できる と 教えられる はレベルが違います。教えられるようになって始めて理解していると言えるのでまだの方はドシドシやっていきましょー^o^成果物一覧
ポートフォリオサイト第2弾
80日目ぐらい。修正込みで40hほどかかりました。【超簡単】【AdobeXD】テキストアニメーション(SVG)
【CSS小技集】position,before(after),display
記事は全て3h以上かかっています。80〜90日目に作成
補足
自分も頂いてばかりではなく何かgiveできないかな?と思い今回書き方を変更しました。
何のあてにもなりませんが「3ヶ月以上継続できる人が少ない」と言われるエンジニアの学習で、少なくとも3ヶ月直近で続いたので、その実例を見やすく書かせて頂きました。
リンクを多めにつけたこと、過去記事もなるべくみやすくなるように修正。また、駆け出したばかりの方が「1ヶ月目でこのぐらい作れればいいのか」といった簡単な比較対象になれればいいな〜と考えて成果物の欄も設けました。ご要望などありましたら、どしどしコメントください^^
さいごに
いかがでしたか?
駆け出しエンジニアの皆様に「これでいいんだ!」が伝わると幸いです^^
(なのでかなり本音で書いてます)来月も振り返りますが、来月からは(転職編)としてお届けしたいと思っています。
転職までのリアルな道のりを綴っていきますのでよろしくお願いします〜ばーん
- 投稿日:2020-02-04T21:25:15+09:00
TypeScriptをプロダクト開発に使う上でのベストプラクティスと心得
TypeScript - JavaScript that scales.
画像はTypeScript最高っていう顔です。
JavaScriptに触れ始めてから13年ほど経ちますが、TypeScriptに触れ始めたのは4年前──。キッカケはAngular 2のbeta版です。TypeScriptコミッターでも初期のバージョンからずっと動向を追いかけていたわけでもありませんが、この4年間ほぼ毎日TypeScriptをWebアプリケーション開発で触れてきたので、個人的ベストプラクティスをまとめてみます。
経験則ではありますが、公私ともに小規模中規模問わず様々なプロジェクトで通用してきたやり方であるため、多くのプロジェクトでも通用する開発手法であると思います。
おことわり
- 記述している内容は、あくまでWebアプリケーション開発における知見であり、ライブラリ開発などでは事情が異なる場合があります
- 本記事で述べている“規模”とは、開発人数/コード量/利用者数が多いことを指す曖昧な言葉であり、厳密な定義はご想像におまかせします
- 筆者はReact/Reduxを利用することが多いため、用例はそれらを用いることが多いものの、記事内容自体はそれらに依存しません
- 内容はTypeScript 3.7.5をベースとしたものです
1. トランスパイラの特性を知る/正しく選択する
TypeScriptはWebブラウザ上で動かないため、JavaScriptにトランスパイル1しなければいけません。このトランスパイルを行うツールとしてまず挙げられるのが、Microsoftが提供する tsc です。その他にも webpack や Babel でもトランスパイルは可能です。
トランスパイラ 利点 欠点 備考 tsc Microsoft製で最新バージョンに対応/全機能利用可能 Path Aliasesの未解決/旧ESへの互換性が不完全 --watchオプションで差分トランスパイルも可能webpack(ts-loader) webpackのloader/全機能利用可能 プロジェクトの肥大化で遅くなる パフォーマンス改善方法あり Babel 旧ESへの変換が優秀 型チェックを行わない/トランスパイルが高速 Re-exports問題をはらむ 同じTypeScriptという言語を利用する場合においても、トランスパイラによってTypeScript自体の機能制限がかかったり、思わぬトラブルを招く場合があります。それぞれのトランスパイラの特徴を踏まえた上で、それにより生じる問題も見ていきましょう。
1-1. tsc
TypeScriptの開発元であるMicrosoft純正のTypeScriptトランスパイラです。TypeScriptを利用する際に
typescriptパッケージをインストールする必要がありますが、それに同梱されています。公式ツールなだけあって最も早く最新バージョンのTypeScriptに対応したり、言語すべての機能を利用することができる一方で、バンドラではないためminifyやchunkの設定はできません。また、Path Aliasesの未解決や旧ESへの互換性が不完全であることが欠点として挙げられます。
tsconfig.jsonにはcompilerOptions.targetオプションで、トランスパイル後のECMAScriptバージョンを指定することができます。つまり、Babelのように後方互換性のあるコードを生成できるわけです。しかし指定したバージョンへのトランスパイルはBabelほど精度の高いものではなく、TypeScript 2.1までは Async Await をES5/ES3へトランスパイルできなかったり、執筆時時点の最新バージョンTypeScript 3.7においてはglobalThisがそのままトランスパイル後のファイルへ出力される……など、 tsc だけでプロダクト開発をすることは難しい印象があります。そのような理由から、 多くのプロジェクトでは webpack や Babel を選択することが多く、 tsc はTypeScriptの型チェックツールとして使われたり、ライブラリ開発などで用いられることが多いのが現状 です。
1-1-1. Path Aliases と tsc の未解決問題
TypeScriptのESM(ECMAScript Modules)構文では、
node_modulesなどの外部パッケージを取り込む場合を除き、インポート対象のパスを相対パスで記述しなければいけません。Reactを始めとした“コンポーネント”という小さな単位でUIを作り上げる開発手法が主流になった今、ディレクトリの階層が深くなることは多々ありますし、そのような場合に../../../../fooなどとディレクトリを巡るパスを記述するのは苦行でしかありません。そこでプロジェクト内の特定のパスに対してエイリアスを設定できる機能が Path Aliases となります。
tsconfig.jsonのcompilerOptions.pathsより設定可能です。 Path Aliases のターゲットとなるパスはcompilerOptions.baseUrlを基準とした相対パスで記述します。tsconfig.json{ "compilerOptions": { "baseUrl": "./src", "paths": { "@models/*": ["./models/*"] } } }上記のようにワイルドカード
*を用いたエイリアス設定を行うことで、プロジェクト内のどのファイルからも@modelsで/src/models内のファイルパスを参照できるようになります。なお、@を接頭辞としているのは Path Aliases であることを示す僕の個人的な習慣です。エイリアス名がパッケージ名と衝突した場合、当該パッケージをimport構文で読み込むことは難しくなるため、パッケージ名にかぶらないような命名規則にしておくと吉です。/src/components/home/header/account/navigation/list.component.tsx- import { UserModel } from "../../../../../models/user.model"; + import { UserModel } from "@models/user.model";TypeScriptの構文エラーや参照エラーにもならず、またVS Codeなどのエディタでは通常のパス指定と同様にコードジャンプも有効です。
しかし、 tsc で Path Aliases を含むコードをトランスパイルすると、エイリアス名がそのまま出力され、実行時エラーを招きます。
/src/components/home/header/account/navigation/list.component.js"use strict"; exports.__esModule = true; var user_model_1 = require("@models/user.model"); // 存在しないファイルを `require` している console.log(user_model_1.UserModel);公式トランスパイラである tsc で Path Aliases のパスが解決されないことに疑問を持つ開発者は多く、実際にこれを指摘するIssueがTypeScriptリポジトリに立てられ、Issue内の議論も荒れに荒れていますが、「パス解決は tsc が担うべき機能ではない」という理由でクローズされています。
したがって、 Path Aliases のパスを解決するには、後述する webpack などを利用するか、 tsconfig-paths のようなパッケージを利用する必要があります。
1-2. webpack(ts-loader)
TypeStrong/ts-loader: TypeScript loader for webpack
人によっては好き嫌いが分かれる webpack ですが、プロダクションレベルでWebフロントエンドアプリケーションを開発する際においては、必需品かつデファクトスタンダードと言っても過言ではないでしょう。そんな webpack ですが、TypeScriptをトランスパイルするローダも開発されており、最も代表的なものが ts-loader です。
ts-loader には
transpileOnlyオプションが設けられており、これをtrueに指定することで型チェックのスキップと引き換えに高いパフォーマンスを得ることができます。一方で webpack 4 を利用する場合、後述する Re-exports 問題を誘発することになるため、大量のWarningを抑制するための設定が必要となります。1-2-1. awesome-typescript-loader
以前の ts-loader はパフォーマンスが低く、TypeScriptのトランスパイルに時間を要する問題が存在していました。そこで awesome-typescript-loader というローダも登場しましたが、しばらくするとメンテナンスされなくなり、現在ではGitHubリポジトリがアーカイブ化されています。
今となっては ts-loader のパフォーマンスも改善され、また先述の
transpileOnlyオプションも提供されていることもあり、開発が停止した awesome-typescript-loader を利用するメリットはないでしょう。しかし、Storybookを含むいくつかのライブラリではいまだに awesome-typescript-loader の利用を推奨しているため注意が必要です。1-2-2. 型チェック方法と fork-ts-checker-webpack-plugin
先述の通り ts-loader の
transpileOnlyオプションはパフォーマンスの向上に期待できますが、型チェックが行われなくなります。VS Codeなどのエディタ上でもリアルタイムに型チェックが行われますが、やはりビルド時にも型チェックを行ってほしいもの。そこで型チェックの方法の1つとして、 tsc を利用するものが挙げられます。 tsc はそれ単体でトランスパイラではありますが、
--noEmitオプションを指定することによってファイルの生成を防ぐ──すなわち型チェックのみを実行できます。僕が携わるプロジェクトでは、npm-run-scriptsのlintコマンドにこれを割り当てています。package.json{ "scripts": { "lint": "tsc --noEmit" } }もう1つの代表的な方法としては、ランタイムでも型チェックを実行する webpack 用プラグイン fork-ts-checker-webpack-plugin が挙げられます。独立したプロセスでチェックが走るため、 ts-loader などのトランスパイル処理に影響を与えることがありません。Reactのフレームワークである Next.js のバージョン9からは、このプラグインが標準で採用されています。
1-3. Babel
Babel · The compiler for next generation JavaScript
Babelは、2018年の夏頃にリリースされたバージョン7よりTypeScriptのトランスパイルを標準でサポートしています。TypeScriptのトランスパイルを行うには、
@babel/preset-typescriptプリセットが必要となります。最新のECMAScript仕様のJavaScriptを用いるプロジェクトのほとんどは Babel を利用しているでしょうから、プリセット1つを設定に追加するだけでTypeScriptの利用を開始できるのは嬉しいポイントの1つです。一方、あくまで Babel の拡張機能の1つであるため、JavaScriptへのトランスパイル以外の処理は実行できません。
1-3-1. TypeScriptへの機能制限
Babel によるTypeScriptのトランスパイルは、一部のTypeScriptの機能が制限されます。Const Enumsはそのうちの1つで、これを利用することはもちろん、これを利用したライブラリをトランスパイルすることもできません。
BabelがトランスパイルできないConst-Enumsexport const enum Status { ^^^^^ // 'const' enums are not supported. Published, Draft, }僕が気に入っている ts-key-enum というライブラリは、TypeScriptのEnumsを利用して
event.keyの値を参照できるというものですが、バージョン3ではConst Enumsを利用するという変更が行われました。僕のプロジェクトではBabelを利用してTypeScriptをトランスパイルしているため、Const Enumsを利用している ts-key-enum のバージョン3は使えず、バージョン2を使い続ける必要があります。実際に
@babel/preset-typescriptが行うことは、TypeScriptファイルから型情報を取り除くだけです。型チェックを行わないどころか、後述する Re-exports 問題が生じます。1-3-2. インデックスファイルによるモジュール参照のスリム化
中規模以上のアプリケーションになってくると、1ファイルあたりの
import記述量やあらゆるファイルからのモジュールを参照する場面が増えてくるでしょう。そこで、とあるディレクトリ内に存在するファイルを外部から容易に参照できるよう、慣習的にindex.tsというインデックスファイルを作成し、その中でモジュールを再エクスポートする Re-exports を行うことがあります。/src/components/header/index.ts// 各コンポーネントを Re-exports export { LogoComponent } from "./logo.component"; export { NavigationComponent } from "./navigation.component";ESM(ECMAScript Modules)やCommonJSにおいてインポート対象のパスをディレクトリ名で終えた場合、 対象ディレクトリ内の
index.jsまたはindex.tsを参照するようになっているため、インデックスファイルを記述することで以下のようにスッキリとモジュールを参照できるようになります。// Before import { LogoComponent } from "./components/header/logo.component"; import { NavigationComponent } from "./components/header/navigation.component"; // After import { LogoComponent, NavigationComponent } from "./components/header";パスの記述もスッキリし、1つの
import文で複数のモジュールを取り込めるようになるため、ファイル内のモジュール参照記述量を削減できます。加えてindex.tsから Re-exports されていないファイルの参照を禁止するルールを設けることで、モジュール1つ1つの依存/影響範囲を抑えることができます。1-3-3. Re-exports 問題
webpack の
transpileOnlyオプションを有効にした場合や、Babelの@babel/preset-typescriptを利用した場合、この Re-exports が大量のWarningを吐いてしまう問題をはらんでいます。具体的には、Type AliasesやInterfacesなどの型情報を Re-exports した場合に生じます。Babelなどが行う型情報の削除のイメージ- const num: number = 123; + const num = 123; - const getUserName: string = (user: userModel.UserModel) => { + const getUserName = (user) => {これらのトランスパイルは、あくまでTypeScriptファイルから型情報を削除するだけで、型チェックも型解決も行いません。TypeScriptはJavaScriptのスーパーセットであり、型情報を削除すればJavaScriptと変わらないという言語設計があるからこそ、こういった処理だけでトランスパイルが可能なわけですね。
// /src/models/user.model.ts export type UserModel = { id: number; }; export const createUser = (json: JSON) => ({ id: json.id, }); // /src/models/index.ts export { UserModel, createUser } from "./user.model";上記のような場合を考えてみましょう。
user.model.tsではType Aliasesを用いてモデルを定義しており、同一階層のindex.tsでモデルの Re-exports を行っています。// /src/models/user.model.ts /* 型情報なので削除される export type UserModel = { id: number; }; */ export const createUser = (json: JSON) => ({ id: json.id, }); // /src/models/index.ts /* UserModelは削除されているため、Re-exportできない! */ export { UserModel, createUser } from "./user.model";型情報を削除するだけの Babel などでトランスパイルをした場合、まず最初に
user.model.tsのUserModelType Aliasが削除されます。しかし Re-exportsを行うはずのindex.tsでは、既に存在しないUserModelを参照してしまうためWarningが生じてしまうわけです。これを根本的に解決するには、 Babel などが厳密な型チェックや型解決を行う必要がありますが、 Babel の役割やパフォーマンスを考えると、それを採用することはまず考えられないでしょう。
webpack-filter-warnings-plugin を利用することで、このWarningを抑制することも可能ですが、何も設定せずに回避できる唯一の策は
import * asによる全モジュールのインポートです。/src/models/index.ts// UserModelは削除されているが、明示的に Re-exports 対象を指定していないため、Warningが生じない import * as userModel from "./user.model"; export { userModel }; // `userModel.UserModel` `userModel.createUser` などで参照可能tsc による型チェックやVS Codeなどのエディタ上のチェックで、この Re-exports 問題を未然に防ぐには、
tsconfig.jsonのcompilerOptions.isolatedModulesオプションをtrueにします。これにより、型情報の Re-exports を行った時点で型エラーとして扱われるようになります。/src/models/index.ts// compilerOptions.isolatedModules: true export { UserModel, createUser } from "./user.model"; ^^^^^^^^^ /* Cannot re-export a type when the '--isolatedModules' flag is provided.ts(1205) */
結論として Babel などの型チェックを行わないツールを、TypeScriptのトランスパイラとして利用する場合は、
- 型定義の明示的な Re-exports をしない(Named Exports含む)
compilerOptions.isolatedModulesをtrueにするの2点に気をつける必要があります。
2. 型参照を正規化する
TypeScriptでは型定義を構成する型情報の一部も参照できるため、 その型定義に関連するコンテキストにおいては、できる限り正規化された型参照を行うべき です。
例えば、僕が携わるプロジェクトではモデルをType Aliasesで定義することが多いわけですが、このモデルに関する関数や値に対して型情報を与えたい場合、標準型名の代わりにモデルのプロパティ型参照を行うようにしています。
/src/models/user.model.tsexport type UserModel = { id: number; name: string; email: string; };/src/components/users/user.component.tsximport { userModel } from "@models/index"; type Props = { // `name: string;` と書かない name: userModel.UserModel["name"]; } export const UserComponent = (props: Props) => ( <div> {props.name} </div> )
import文の記述コストは掛かってしまいますが、これによる利点は2つあります。
- 型情報が変更された場合でも、影響範囲が明確になり、また型チェックの恩恵を受けられる
- 関連型へのコードジャンプが可能になる
2の利点はプロジェクトが複雑になるほど恩恵を受けられると思います。
上記のコード例では、UserComponentがUserModelに関連する、ユーザ情報を表示する関数(コンポーネント)です。ここからモデルの定義へ即座にジャンプできるようになると、ビューへの表示処理を記述する際のデータの確認が楽になります。3. Enumsを使わない
TypeScriptにはEnumsが存在しますが、 Enumsのユースケースはほとんどありません 。特に、TypeScript 2.4から追加された、値にstring型を持てるString Enumsを利用する場合は、完全にオブジェクトへ置き換えることが可能です。
enum Status { Published = "published", Draft = "draft", } // 上記String Enumsは、オブジェクトで代用可能 const status = { Published: "published", Draft: "draft", };Enumsではなく、オブジェクトを利用する理由は以下が挙げられます。
- 先述の通り Babel などではConst Enumsが利用できない
- Enums値として自由な値を扱える
- Enumsを引数に取るような関数を呼ぶ場合、当該Enumsを毎回
importで参照しなければならない- 外部入力値がEnumsに準拠した値であるかのチェックが容易
3-1. Enums値として自由な値を扱える
type Status = { published: "published", invisible: "draft" | "deleted", }; const status: Status = { published: "published", invisible: "deleted", };Enumsでは値として、数値か文字列のどちらかしか利用できませんが、オブジェクトであれば真偽値などの他の型、Type Annotationsを組み合わせればさらにUnion Typesも利用可能です。
3-2. Enumsを引数に取るような関数を呼ぶ場合、当該Enumsを毎回
importで参照しなければならないEnumsの場合// status.ts enum Status { Published, Draft, } // func.ts export const updateStatus(status: Status) = () => ...; // main.ts import { Status } from "./status"; import { updateStatus } from "./func"; updateStatus(Status.Published);Enumsを引数に取るような関数を呼ぶ場合、実引数でもEnumsからキーの指定が必要になるため、当該Enumsを参照しなければいけません。Swiftの
.Publishedのように、キー名を直接記述できる型推論が存在すれば、まだ利用しやすいのですが……。オブジェクトの場合// status.ts const status = { published: "published", draft: "draft", }; // func.ts export const updateStatus(status: keyof typeof status) () => ...; // main.ts updateStatus("published"); // ハードコーディングのように見えるが型チェックが有効 updateStatus("foo"); ^^^^^ /* Argument of type '"foo"' is not assignable to parameter of type '"published" | "draft"'.ts(2345) */オブジェクトならばオブジェクト自体を参照する必要がなく、またLiteral Typesによる型チェックも効くため型安全性は保たれます。
3-3. 外部入力値がEnumsに準拠した値であるかのチェックが容易
Enumsの場合const checkValidStatus = (value: string): value is Status => { if (value === Status.Published) return true; if (value === Status.Draft) return true; ... return false; };JSONの受け取りやユーザからの入力など、外部入力値の検証が容易なのもオブジェクトの特徴の1つです。
ループ処理でEnumsの各キーや値を連続的に参照することはできないため、とある値がEnumsの値に含まれているかどうかを判別したい場合、1つずつ一致しているか確かめる必要があります。オブジェクトの場合const checkValidStatus = (value: string): value is keyof typeof status => Object.values(status).includes(value);オブジェクトの場合はキーや値のループが可能であり、
Array.prototype.includesを用いればワンラインで検証可能です。4. TypeScript/フレームワークの標準型定義を活用する
TypeScriptも、ReactなどのUIフレームワークも、開発に役立つ標準型定義が提供されています 。自分で型を定義しても良いですが、十分にテストされ開発者間で共通の知識ともなる標準型定義を利用しない手はありません。
例えばReactの場合
@types/reactによって型定義が提供されていますが、ComponentProps型定義を利用することでコンポーネントからPropsの型を抽出することが可能です。ComponentProps型を利用しない場合// foo.comopnent.tsx export type Props = { ... }; export const FooComponent = (props: Props) => ...; // other.component.tsx /* 型名が衝突しないように別名をつけなければならない */ import { Props as FooComponentProps, FooComponent } from "./foo.component"; type Props = { ... } & Pick<FooComponentProps, "xxx">; export const OtherComponent = (props: Props) => ( ... <FooComponent xxx={props.xxx} /> ... );ComponentProps型を利用する場合// foo.comopnent.tsx type Props = { ... }; export const FooComponent = (props: Props) => ...; // other.component.tsx import { FooComponent } from "./foo.component"; type Props = { ... } & Pick<React.ComponentProps<typeof FooComponent>, "xxx">; export const OtherComponent = (props: Props) => ( ... <FooComponent xxx={props.xxx} /> ... );よくReactのプロジェクトで
export type Propsと記述されたコードを見かけますが、ComponentPropsでPropsの抽出が可能なので、覚えておくと便利です。5. InterfacesではなくType Aliasesを利用する
最近TypeScriptに触れた人にとっては違いがイマイチぱっとしないInterfacesとType Aliasesですが、 現在はInterfacesをアプリケーション上で利用することはほとんどありません 。
どちらもクラスのインターフェースとして利用できますし、型に関するエラー情報も変わりません。古いTypeScriptのバージョンではInterfacesの方が使い勝手が良かったものの、現在ではType Aliasesの方が圧倒的に使いやすいでしょう。
Type Aliasesでは Union Types|を扱うこともできますし、 Intersection Types&による型のマージは使う機会も多いと思います。以下は僕が実際に利用している、Reactとreact-reduxにおけるContainer Componentの定義例です。
user-information.component.tsx// Interfaces ではなく Type Aliases で Props を定義 type Props = { user: userModel.UserModel; updateUser: (name: userModel.UserModel["name"]) => void; updateRequestStatus: RequestStatus; }; export const UserInformationComponent = (props: Props) => ...;user-information.container.tsimport { UserInformationComponent } from "./user-information.component"; type Props = React.ComponentProps<typeof UserInformationComponent>; type StateProps = Pick<Props, "updateRequestStatus">; type DispatchProps = Pick<Props, "updateUser">; type OwnProps = Omit<Props, keyof (StateProps & DispatchProps)>; export const UserInformationContainer = connect( (state): StateProps => ({ ... }), (dispatch, props: OwnProps): DispatchProps = > ({ ... }), )(UserInformationComponent);6. TypeScript独自のモジュール形式(TSM)を利用しない
// module.ts export = 123; // main.ts import number = require("./module");結論から述べると、 上記のようなTypeScript独自のモジュール形式を使用してはいけません 。
6-1. ESMとCommonJSのDefault Exports/Imports
ライブラリやサーバサイドNode.jsならまだしも、今からWebフロントエンドアプリケーション開発を行う場合はESMを利用することがほとんどだと思います。
元来JavaScriptにはモジュールという仕組みが存在せず、歴史的経緯から現在では2つのモジュール形式──ESM(ECMAScript Modules)とCommonJSが主流となっています。モジュールシステムの先駆けともなったCommonJSはNode.jsで生まれたこともあり、サーバサイドNode.jsやライブラリなどで多く利用されていますが、その後ECMAScriptでESMが策定されたことにより、今後はESMの利用が(緩やかにではありますが)活発になっていくことでしょう。
ESMやCommonJSなどのモジュールシステム間には、互換性がありません。さらにNode.jsで当たり前のように利用されているモジュールシステムは、厳密にはCommonJSの仕様を参考にしたものであって、CommonJSではありません。
CommonJSにおいては、“エクスポート対象となる値はプロパティを持つオブジェクトである”と定義されています2。Default Exportsを行う場合、慣用的にdefaultプロパティに値をセットします。一方でNode.jsはmodule.exports自体に値を渡すことで、その値をDefault Exportsとして扱う仕様となっており、同じCommonJSを採用した(と言われている)プラットフォームでもDefault Exportsの仕方が異なるのが現状です。// Node.js独自のDefault Exports module.exports = 123; // CommonJS本来のDefault Exports(慣習) module.exports.default = 123; // Node.jsでDefault Importsをする場合、 `module.exports.default` を明示的に参照しなければならない /* `module.exports = 123` の場合 */ require("./module"); // 123 /* `module.exports.default = 123` の場合 */ require("./module"); // { default: 123 } require("./module").default; // 123一方で、今後デファクトスタンダードになるであろうESMのDefault Exportsの仕様としては、“
export default xxxでエクスポートした値はdefaultプロパティを持つオブジェクトとしてエクスポートされる”と定義されています3。また“import xxx fromでインポートされる値は、エクスポートされた値のdefaultプロパティを参照する”とも定義されています4。すなわち、Node.jsでよく用いられるmodule.exports = xxxなどのエクスポート方法は、ESMのこの仕様にも反することになるわけです。// ESMのDefault Exportsの仕様上、以下のどちらも仕様的に正しい export default 123; module.exports.default = 123; // ESMのDefault Importsの仕様上、上記のエクスポートされた値は以下の方法で読み込めるのが正しい import number from "./xxx"; // 1236-2. TypeScript Modules
Babel ではバージョン6より、Node.jsの
module.exports = xxxの書き方をしようが、ESMの仕様に沿ったDefault Exportsを利用しようが、Default Imports時にエクスポートされた値自体を参照するのか、defaultプロパティの値を参照するのかを自動判別するようになりました。
しかしTypeScriptのDefault Importsは、ESMの仕様に準拠していることから、 Babel とは異なりNode.jsのDefault Exportsに対応していません。そこで TypeScriptが用意した、独自のモジュールシステムTSM(TypeScript Modules:勝手に命名しています) の記述方法が、以下のコードとなります。// module.ts /* `module.exports = 123` と同じ */ export = 123; // main.ts import number = require("./module"); /* `default` プロパティは参照しない */この記述方法は、Node.jsのDefault Exportsを利用したライブラリが型定義を提供する際に用いるべきものであって、 アプリケーションを開発する我々が使用するべきではありません 。我々が仕様すべきはESMに沿った記述方法です。しかしながら、TSMの
export = xxxとESMには互換性がないため、もしTSMを利用したライブラリをアプリケーションで使いたい場合、ESMのインポート方法を利用できないのでしょうか。結論から述べると、 TypeScript 2.7以降であれば、TSM形式でエクスポートされたモジュールをESMでインポートすることが可能 です。TypeScript 2.7では
compilerOptions.esModuleInteropオプションが提供され、trueにすることでこれを可能にします(デフォルトはtrue)。compilerOptions.esModuleInteropがtrueの場合// とあるライブラリ.ts export = FooClass; // アプリケーション内 import FooClass from "とあるライブラリ";一部の環境ではTypeScript 1.8で追加された
compilerOptions.allowSyntheticDefaultImportsをtrueにすることで、TSMでエクスポートされた値をESMでインポートすることができるかもしれませんが、esModuleInteropオプションとは異なり、allowSyntheticDefaultImportsはトランスパイル結果に一切の影響を及ぼさず、TypeScriptのチェックレイヤを通過するだけのものでした。
esModuleInteropはESMの仕様に沿ったコードになるようトランスパイルするため出力結果に影響を与えますが、allowSyntheticDefaultImportsのように表面上でだけエラーを隠すわけではないため、ランタイムエラーを引き起こすような心配はありません。
なおesModuleInteropが有効の場合は自動的にallowSyntheticDefaultImportsが有効となります。7. パッケージは「TypeScript製 > 型定義ファイル同梱有無 >
@types提供有無」の優先順位で選ぶアプリケーションを開発する上でライブラリの選定は重要な要素の1つで、似たようなライブラリの中からどのライブラリを選定するかは、個人の些細な差はあれど、その多くはGitHubリポジトリのスター数やnpmのダウンロード数、コミュニティの活発度、ドキュメントの豊富さなどが共通することでしょう。 TypeScriptを用いた開発においては、これに加えて“TypeScript製であるかどうか”も重要な指標の1つ となります。
スター数などの他の指標が似たりよったりするライブラリが複数ある場合、僕は以下のような優先度で最終的にライブラリを選定します。
- TypeScriptで記述されたライブラリか?
- 型定義ファイルがリポジトリに同梱されているライブラリか?
@types/*で型定義が提供されているライブラリか?「どれも型定義が提供されているのならば問題ないのでは」と思う人もいるかもしれませんが、ライブラリ本体がTypeScriptで記述されていないということは、本体の型と型定義ファイルの型に乖離が存在する可能性が高くなります。
7-1. TypeScriptで記述されたライブラリか?
ライブラリ本体がTypeScriptで記述されている場合、型定義ファイルは tsc などによって自動的に生成されたものが多く、最も信頼性の高い型定義ファイルを提供してくれます。ライブラリ内のコードが
asによるType Assertionsを多用していなければ、型定義と実際のAPIに乖離が生まれることはほとんどありません。7-2. 型定義ファイルがリポジトリに同梱されているライブラリか?
本体がJavaScriptで記述されていても、型定義ファイルがリポジトリに同梱されている場合、新バージョンリリース時に型定義ファイルも一緒に更新される可能性が高くなります。ミスとして型定義と実際のAPIに乖離が生まれることがあっても、「型定義が実際のAPIに追いついていない」という可能性は低くなります。
7-3.
@types/*で型定義が提供されているライブラリか?
@types/*による型定義が提供されているライブラリの場合、型定義ファイルとライブラリ本体は別々のリポジトリに存在するため、基本的に型定義ファイルの更新が本体よりも遅れます。したがって最新のライブラリを利用する際に、型定義ファイルだけ古いAPIで記述されている……なんてこともあり得ます。さらに
@types/*は独立した1つのパッケージとして提供されているため、ライブラリ本体のバージョンと一致しないことがほとんどです。fooというパッケージの最新バージョンが1.2.0でも、@types/fooの最新バージョンが2.3.1などとかけ離れた場合もよくあり、本体のバージョンに対応した型定義ファイルのバージョンが何かを把握しなければいけません。型定義ファイルの存在しないパッケージを選ぶのはできる限り控え、また
@types/*の型定義ファイルの利用も本来ならば避けておきたいところです。
事実、React(reactパッケージ)はHooks APIという最新のAPIがバージョン16.7で提供される予定だったのに対し、提供バージョンが16.8へ先延ばしされたことによって、@types/reactの16.7では本来存在しないHooks APIの型定義を参照できてしまう事態になっていました。エディタ上や tsc による型チェックではエラーが一切発生しないのに、ランタイムではエラーが発生してしまうという悲しい事件です。ぴえん。8.
Nullable<T>型を定義するJavaScriptには、虚無値として
nullとundefinedの2つの値が存在します。この2つの値をどう使い分けるか、そもそも使い分けずにどちらかの値で統一するか、はプロジェクトによって方針が異なり、大手企業やライブラリの中でも揺れています。両者の大きな違いは、以下が挙げられると思います。
null
- 開発者が意図的に利用しなければ生まれない値
- デフォルト引数が設定されている関数へ渡しても、
nullが渡るtypeof演算子が"object"を返す負の遺産とも言える仕様が存在するundefined
- 存在しないプロパティの参照時などに勝手に返される値
- デフォルト引数が設定されている関数へ渡しても、デフォルト値が適用される
undefined自体はグローバルオブジェクトのundefinedプロパティを参照しており、ES5.0以前(Strict Mode非利用時)はグローバルオブジェクトのundefinedプロパティを書き換えられてしまう仕様だった大きく分けて
undefined統一派と使い分ける派の2つに別れますが、やはりどちらもメリット・デメリットが存在します。しかし開発する上で重要なのは、良し悪しよりもルールを決めておくこと です。僕は経験的にnullundefinedを使い分けることが多いです。// `null` `undefined` 使い分ける派 type Nullable<T> = T | undefined; // `null` `undefined` 統一派 type Nullable<T> = T | null | undefined; const getUserName = (user: Nullable<userModel.UserModel>) => ...;変数や関数の引数などで虚無値を受け入れる場合、
Nullable<T>型を定義しておくと便利 です。
TypeScriptはnull及びundefinedを除去するNonNullable<T>型を標準で提供していますが、Nullable<T>型を提供していません。この理由がまさに上述したとおりで、プロジェクトによって方針が異なるからです5。8-1. 虚無値は
undefinedで統一する派 ─ TypeScript開発チームTypeScript自体の開発チームが用意したスタイルガイドの中には、“
nullとundefinedを区別せず、虚無値はundefinedを利用しなければならない”と記述されています6。あくまでこのスタイルガイドはTypeScriptという言語を開発する際においてのガイドであり、TypeScript利用者へそれを強制/推奨するものでもありません。この点を勘違いしてIssueをたてる人が多かったせいか、
TypeScript開発チームはブチギレて、当該ドキュメントのページ最上部には、恐ろしい数の注意文言が記載されています。Coding guidelines · microsoft/TypeScript Wiki
「注意文言なし→1行だけ注意文言が記載される→画像のように大量の注意文言が記載される」という変遷を見てきた僕からすると、ちょっとした恐怖を感じます。
8-2. 虚無値は
nullundefinedで使い分ける派 ─ Facebook一方で、Reactなどのライブラリを手掛けるFacebookの開発チームは、
nullとundefinedの利用を明確に分けています。現にReactでは、DOMノードを一切レンダリングさせないコンポーネントを定義する場合は、
undefinedではなくnullを返さなければいけません。何もレンダリングさせないReactコンポーネントexport const NoRendering = () => { ... return null; // `undefined` だとエラー };9. Optional Parametersと
Nullable<T>型を使い分ける上記の
Nullable<T>型宣言に関連して、TypeScriptにはOptional Parameters?が存在します。オブジェクト型のプロパティや関数の引数に対して利用できるものです。このOptional Parametersのユースケースを誤解しているプロジェクトも少なくないのですが、 Optional Parametersは「虚無値も受け取るための構文」ではなく「値の省略を許可する構文」 です。// これらの関数はまったくコンテキストもAPIも異なるが、 // 仮引数 `b` 及び `c` の型は `string | undefined` で一致する const func1(a: string, b: string | undefined, c: string | undefined) => ...; const func2(a: string, b?: string, c?: string) => ...; // Nullable( T | undefined )は「虚無値の受け渡し」を許す func1("a", undefined, undefined); func1("a"); // エラー // Optional Parametersは「値の省略」を許す func2("a", undefined, undefined); func2("a"); // OKJavaScriptレイヤーで見ても、オブジェクトのプロパティも関数の引数も明示的に指定しなければ、値は
undefinedになるため、Optional Parametersを付けた値はT | undefined型になります。nullが入ることはありません。型安全の点から、NullableとOptional Parametersは使い分けることをオススメします。むしろ、Optional Parametersの利用をできる限り控えたほうが良いと言っても過言ではありません。引数の追加時などに、影響範囲が浮き彫りにならないからです。
Reactでコンポーネントを定義する場合は、Propsに対しOptional Parametersを利用すべきかどうかを吟味する必要があります。ReactでProps型を定義する場合の例(with-JSDoc)type Props = { /** 表示したいユーザ、存在しなければそれ用のメッセージを表示 */ account: Nullable<userModel.UserModel>; /** * 無効化するかいなか * @default false */ isDisabled?: boolean; };10.
anyでなくとも、型安全性のない型を利用しないTypeScriptはなんと言っても型安全性を担保してくれる言語であるため、TypeScript初学者向けの記事や本でも「
anyはできる限り控えるべき」と記述されていることが多く、これに異論を唱える人はいないでしょう。しかしany以外にも型安全性のない型はいくつか存在します。言語自体の解説記事ではないのでできる限り詳細は割愛したいところですが、特に初心者が引っかかりやすいのは
{}型でしょう。{}型は空オブジェクトにのみ一致する型ではなく、虚無値を除くすべての型に一致する安全性の低い型 です。TypeScriptには、記述した値そのものが型になるLiteral Typesのおかげで、とても柔軟な型定義が可能になっています。
Literal-Typestype A = "sample"; const a: A = "foo"; ^^^ // Type '"foo"' is not assignable to type '"sample"'.ts(2322)しかしオブジェクト型の定義には注意が必要です。 TypeScriptは構造的部分型を採用しています 。また JavaScriptは「ほぼすべてがオブジェクトのように振る舞う」性質を持ちます 。この2つの特性によって、オブジェクト型の挙動は、直感と反するかもしれません。
10-1. JavaScriptの「ほぼすべてがオブジェクトのように振る舞う」性質
Rubyなどとは異なり、JavaScriptはすべてがオブジェクトの言語ではありません。プリミティブ値を持つ
numberやstring型が存在します。しかしながら、これらのプリミティブ値に対して、対応する標準オブジェクトのプロパティを参照することができます。その際に、プリミティブ値は対応する標準オブジェクトへ一時的に変換され、評価終了後にプリミティブ値へ戻ります。const num1 = 123; // これは number型 のプリミティブ値 const num2 = Number(123); // これは number型 に対応する標準オブジェクト num2.toString(); // "123" -- 標準オブジェクトのプロパティ(の中の関数)を参照 num1.toString(); // "123" -- プリミティブ値→標準オブジェクトへ変換→プロパティ参照→評価後にプリミティブ値へ戻るすなわちJavaScriptは、プリミティブ値であっても対応する標準オブジェクトのプロパティを呼ぶことができるため、「オブジェクトのように振る舞う」言語です。
しかし、対応する標準オブジェクトが存在しないプリミティブ値もあります。それが虚無値であるundefinedとnullです。したがって「ほぼ」がつく、「JavaScriptは、ほぼすべてがオブジェクトのように振る舞う」と説明できるわけです。10-2. TypeScriptの構造的部分型がもたらす罠
一方でTypeScriptは構造的部分型であるため、構造が部分一致していれば同じ型であるとみなします。例えばStringオブジェクトもNumberオブジェクトも、プロトタイプに
toStringプロパティを持っているため、以下の型はどちらのオブジェクトにも一致します。オブジェクトが共通プロパティを持っていれば、同じ型としてみなすtype StringConvertible = { toString: () => void; }; const a: StringConvertible = Number(123); const b: StringConvertible = String("123");しかし先程の「一部を除くプリミティブ値はオブジェクトのように振る舞う」という性質を持っているため、上記の型はプリミティブ値にも一致することになります。
プリミティブ値であっても変換後の対応オブジェクトに対して部分型が適用されるtype StringConvertible = { toString: () => void; }; const a: StringConvertible = 123; const b: StringConvertible = "123";ここまで読めばお分かりだと思いますが、
{}型というのは、一見空オブジェクトと一致する型のように見えて、オブジェクトのように振る舞うすべての値=虚無値以外のすべての値と一致する、ほぼほぼany型と変わらない型 であるわけです。type EmptyObject = {}; // 以下はどれもエラーにならない const a: EmptyObject = 123; const b: EmptyObject = "123"; const c: EmptyObject = false; const d: EmptyObject = []; const e: EmptyObject = {}; // 以下はどれもエラーになる const f: EmptyObject = null; const g: EmptyObject = undefined;
type XXX = {}のように直接{}を型定義に利用することはないと思いますが、Generics周りでは注意が必要です。経験上、無意識にFoo<{}>などと渡している箇所が多々見られたからです。11. エディタのリネーム機能を活用する
TypeScriptはLanguage Serviceと呼ばれるサービスによって、異なるエディタでも似たようなコード解析やコードジャンプが行えるようになっています。(僕はLanguage Serviceに詳しいわけではありませんが)特にTypeScriptのLanguage Serviceはとても優秀で、バグを見たことがほとんどありません。
TypeScriptとVS CodeのようなIDEライクなエディタを利用する上で活用しておきたいのは、リネーム機能です。VS Codeの場合
F2キーでリネーム機能を呼び出せますが、利用頻度の割には遠い位置にキーマップされているため、僕はCtrl+Rに割り当てています。// オブジェクト型のプロパティをリネームすると…… export type UserModel = { - email: string; + emailAddress: string; }; // 型参照を正規化していても、リネーム対象となる type Props = { - email: userModel.UserModel["email"]; + email: userModel.UserModel["emailAddress"]; };TypeScriptにおけるリネーム機能は、リネーム対象となる変数をモジュール(ESM/CommonJS)経由で参照しているファイル内からすべてを網羅して一括リネームしてくれるため、わざわざgrepして影響箇所を調べる必要がありません。さらに、変数や関数などの値だけでなく、Type Aliasesやオブジェクト型のプロパティ名、Enumのキー名などもリネーム対象となるため、リファクタリングが非常に捗ります。
最後に
TypeScriptはJavaScriptあっての言語です。HTMLやCSSも同様ですが、JavaScriptは破壊的変更が許されない(言語設計者からすると)シビアな言語であり、それ故にバグすらも仕様として扱われます。TypeScriptの罠のほとんどはJavaScriptの罠から生まれたものであり、逆に言えばJavaScriptの特性や罠さえ理解していれば、TypeScriptの罠を理解することも容易いことでしょう。
本記事ではあくまでアプリケーション上で利用する際のTipsを述べたまでで、TypeScript自体の詳細な機能は述べていません。しかしTypeScriptの型プログラミングの柔軟性や、開発の安定性はとても素晴らしいものです。僕もまだまだ学習中の身ではありますが、JavaScriptもTypeScriptも日々進化しているため、表面上の情報だけでなく、実際にプロダクトに落とし込むことが可能な機能なのか、どのような経緯で実装された機能なのかも理解していきたいですね。
おひたしおひたし。
上級言語をバイナリへ変換することを“コンパイル”と言いますが、TypeScriptのようなメタ言語を上級言語へ変換することを“トランスパイル”と言います。しかし言い分ける必要もないため、多くの場合は“コンパイル”と表記が統一されることもあります。 ↩
ECMAScript 2015 Language Specification – ECMA-262 6th Edition ↩
ECMAScript 2015 Language Specification – ECMA-262 6th Edition ↩
Nullable types on TypeScript - aka Maybe Types from Flow · Issue #23477 · microsoft/TypeScript ↩
- 投稿日:2020-02-04T21:12:43+09:00
Google OAuth 2.0 認証を使ったGoogle Sign-Inの実装(JS編)
概要
最近はどのサイトでもSNSからのログインだったり、登録するサイトが当たり前になっています。
よく見かけるこんな感じのボタンですね。。
今回はjsだけを使って簡単にGoogle Sign-Inを実装してみます。
Googleアカウントの作成
まずはじめにGoogleアカウントを作成しましょう。
可能ならそのアプリ専用に作った方がいいと思います。Google Cloud Platformでの設定
https://console.cloud.google.com/getting-started
まずは先ほど作ったGoogleアカウントでログインしましょう。
左上の[Project]からプロジェクトを作成を選択します。
プロジェクト名はわかりやすい任意の名前をつけて、場所はとりあえずこのままで作成します。
先ほど作成したプロジェクトを選択して左のメニューから
[APIとサービス]→[認証情報]を選択[認証情報を作成]→[OAuth クライアント ID]から今回使うOAuth認証情報を作成します。
今回は[ウェブアプリケーション]を選択して[名前]を入力します。
[承認済みのJavaScript生成元]には作ったアプリのDomainを入力します。(複数選択可能)
例:http://localhost:8080[承認済みのリダイレクトURI]にはユーザーが認証された後にリダイレクトされるURLを設定します。
例:http://localhost:8080/login/callback[作成]するとクライアントIDが発行されるのでメモしといてください。
実装
では実装してみましょう。
HTML<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="google-signin-client_id"content="YOUR_CLIENT_ID.apps.googleusercontent.com"> <script src="https://apis.google.com/js/platform.js" async defer></script> </head> <body> <div class="g-signin2" data-onsuccess="onSignIn" data-theme="dark"></div> </body> </html>正直HTMLにこの記述するだけでGoogle Sign-Inの実装はできちゃいます。
[YOUR_CLIENT_ID]は先ほどメモしたクライアントIDを入力しましょう。
簡単ですよね。更にログインしたユーザーのプロフィール情報を取得するにはgetBasicProfile()メソッドを使用します。
JavaScriptfunction onSignIn(googleUser) { var profile = googleUser.getBasicProfile(); console.log('ID: ' + profile.getId()); // GoogleのID console.log('Name: ' + profile.getName()); // アカウント名 console.log('Image URL: ' + profile.getImageUrl()); // プロフィール画像 console.log('Email: ' + profile.getEmail()); // ユーザーのメールアドレス }ちなみにログアウトを追加したい場合は、こんな感じで書けます。
HTML<a href="#" onclick="signOut();">ログアウト</a> <script> function signOut() { var auth2 = gapi.auth2.getAuthInstance(); auth2.signOut().then(function () { // ログアウトした後の処理 }); } </script>まとめ
今回はJSを使ってGoogle OAuth 2.0 認証を使ったGoogle Sign-Inの実装をしました。
ほんとこれだけでログイン機能の実装はできてしまうので皆さんも今時のおしゃれサイトにする際は使ってみてください。
ただ、実際の運用に使う際はこれだけだとセキュリティーガバガバなのでサーバーサイドでIDトークン検証したりなど、工夫が必要ですね。
また別でサーバーサイドでの処理などの実装もあげるのでよかったらみてください。参考
- 投稿日:2020-02-04T21:04:35+09:00
Vuetify 2.2の新機能 Presets
2020/01/02 にリリースされた Vuetify 2.2 から追加されたPresets機能がなかなかに素敵だったので、記事にしてみました。
Presets機能とは
Googleが策定するデザインガイドライン Material Design の中で作成された、Material studies をpresetのスタイルとして用意してくれる機能です。
公式ドキュメント:Vuetify Customization/Presets
下記はリリースページに掲載されているPresets機能のサンプルです。
Material studiesとは
Material studies はレシピアプリ、ファイナンスアプリ、教育アプリなどより具体的なアプリケーションのデザイン例と共に、そのデザインの理論的根拠やコンポーネント選択について詳細に説明した、Material Designを学ぶ上で非常に有用なコンテンツとなっております。
要するに
Material studies のアプリのデザインを Vuetify でサクッと試せる機能
使い方
Preset は Vue CLI の plugin として実装されているので、Vue CLIを利用して追加します。
利用方法は vuetifyjs/vue-cli-plugins リポジトリにまとめられていますので、そちらも参照してください。Vue CLIのインストール
インストール済みの方はスキップしてください。
$ npm install -g @vue/cli # OR $ yarn global add @vue/cliPreset Rallyを試す
# プロジェクト作成 $ vue create my-app $ cd my-app # vuetifyをプロジェクトに追加 $ vue add vuetify # 任意のpresetを追加 # 今回はrallyを選択 $ vue add vuetify-preset-rally # 起動して確認 $ yarn serve起動すると、下記のような感じでスタイルが適用されているのがわかります。
色やフォントが変化しています。
いくつかコンポーネントのスタイルも変更されているようですが、それらは自分でコンポーネントを追加しないと分からないようです。
何が起こったのか?
vue add vuetify-preset-rallyによって発生した差分は下記の通りです。$ git status modified: package.json modified: public/index.html modified: src/plugins/vuetify.js modified: yarn.lock
public/index.htmlの diff は下記の通りで、fontが追加されています。public/index.htmldiff --git a/public/index.html b/public/index.html index 2545523..ae2a70f 100644 --- a/public/index.html +++ b/public/index.html @@ -8,6 +8,7 @@ <title>my-app</title> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css"> + <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto+Condensed:300,400,500,700:100,300,400,500,700,900|Eczar:400:100,300,400,500,700,900&display=swap"> </head> <body> <noscript>
src/plugins/vuetify.jsに関しては、モジュールをimportし、vuetifyへ渡す実装が追加されていました。src/plugins/vuetify.jsdiff --git a/src/plugins/vuetify.js b/src/plugins/vuetify.js index ec46adb..c14a53a 100644 --- a/src/plugins/vuetify.js +++ b/src/plugins/vuetify.js @@ -1,7 +1,9 @@ import Vue from 'vue'; import Vuetify from 'vuetify/lib'; +import { preset } from 'vue-cli-plugin-vuetify-preset-rally/preset' Vue.use(Vuetify); export default new Vuetify({ + preset, });ここで利用されている
vue-cli-plugin-vuetify-preset-rallyの実装は下記にあります。
https://github.com/vuetifyjs/vue-cli-plugins/tree/master/packages/vue-cli-plugin-vuetify-preset-rally
presetディレクトリの中の実装を見ると、index.jsによるthemeのカラーの設定や、scssとsassによるスタイルのoverrideが実装されています。
これらにより、Material studies のスタイルが Vuetify に適用される仕組みになっています。overrideしているstyleの数はそんなに多くありませんが、Releaseページでも、
features are not supportable at this time.
と書かれているので、今後に期待です。
課題
Nuxt.jsからだとどうPresetsを追加すべきかが悩ましい
個人的にVuetifyを使う場合、セットでNuxt.jsを利用する場合が多いです。Nuxt.jsは公式推奨の手順だと
yarn create nuxt-appコマンドによる初期化手順であり、Vue CLIを利用していません。
そのため、vue addによるpluginの追加に対応していないので、Presetsをどう追加するのかが悩ましいです。2020/02/05 追記
指定する機能は入っているようですが、いまいちうまく動作しないようです。
https://github.com/nuxt-community/vuetify-module/pull/247指定は下記のように
nuxt.config.js中で行うと思うのですが、エラーになります。
js:nuxt.config.js
vuetify: {
preset: 'vue-cli-plugin-vuetify-preset-rally/preset'
},
まとめ
サクッとアプリを作った際に配色で悩むことがよくあるので、このような簡単に利用できるpresetがあるのは個人的に嬉しいです。
また、 Vue CLI以外でのインストール方法に関しては今後に期待です。
- 投稿日:2020-02-04T21:04:01+09:00
初心者によるプログラミング学習ログ 229日目
100日チャレンジの229日目
twitterの100日チャレンジ#タグ、#100DaysOfCode実施中です。
すでに100日超えましたが、継続。100日チャレンジは、ぱぺまぺの中ではプログラミングに限らず継続学習のために使っています。
229日目は
おはようございます
— ぱぺまぺ@webエンジニアを目指したい社畜 (@yudapinokio) February 3, 2020
229日目
・udemyで、css+javascript講座
・webサイト部分的模写#早起きチャレンジ#駆け出しエンジニアと繋がりたい#100DaysOfCode
- 投稿日:2020-02-04T21:01:59+09:00
ツイキャス配信で閲覧人数が増えた時にデスクトップ通知する
ツイキャスの配信では右上に今いる人と累積の人数が表示されている。
配信をみているとキャス主はその数字が変化するたびにn名様いらっしゃいと律儀に言っている人が多かった。おそらく右上の数字が変化していくのを目視で確認していると思う。人が増えたら
n名様いらっしゃいと毎回言うためにはこの数字をずっとみてないといけない。
なんだか大変そうだったので人数が増えた時のみデスクトップ通知を行うためのブックマークレットを書いた。javascript:(()=> { let getFraction = () => parseInt(document.querySelector(".tw-fraction").textContent.split("/")[0]); let oldFraction = getFraction(); let newFraction; let intervalId = setInterval(() => { newFraction = getFraction(); if (newFraction > oldFraction) { new Notification(newFraction + "名様いらっしゃい"); } oldFraction = newFraction; }, 2000); console.log("Interval ID = " + intervalId); })();このコードをブックマークレットとして登録する。
実行するためには配信画面で登録したブックマークをクリックすれば良い。人数が以前取得した値よりも増えた場合に以下のように通知される。
- 投稿日:2020-02-04T20:45:31+09:00
Draft.jsで絵文字(サロゲートペア )をEntity化して削除を良い感じに扱う方法
Draft.jsで絵文字(サロゲートペア )をEntityにして良い感じに扱う方法
Draft.jsで絵文字を扱うと削除した時にいい感じに消えてくれない問題が発生します。
その解決法にたどり着くまでに地味に時間がかかったので備忘録をかねて書きます問題
例えばdraft.jsで4人家族の絵文字を消そうとすると、3人家族になったりします
????=>???この問題はdraft.jsでサロゲートペアの絵文字を扱うと発生してしまいます
これを解決する方法として、Draft.jsの Entity というものがありますEntity
メタデータを使用してテキストの範囲に注釈を付けるために使用するエンティティシステムについて説明します。
エンティティは、スタイル付きテキストを超えるレベルの豊かさをもたらします。リンク、メンション、
および埋め込みコンテンツはすべて、エンティティを使用して実装できます。といったもので、指定した文字の範囲に役割を与えたり、複数の文字列を一つの文字?として扱うことが可能です
デモ + コード
そしてデモとコードをCodeSandboxで用意しました
https://codesandbox.io/s/draft-js-emoji-to-entity-tdoe2
解説
ざっくり説明すると
初期化時、文字入力時、コピペ時、IMEの入力(コンポジットモード)が終わった時に操作した文字列の絵文字を正規表現で開始と終了のindexを取得して、EntityのIMMUTABLEというものに変換しています。こうすることで、一つの文字列として扱うことができ削除の問題を解決できます。
引数に渡している正規表現を変えることで他のEntity化の用件にも対応できると思いますもう少し詳しく書くと、最終的に変更されたContentBlockから絵文字を見つけてEntity化し、操作履歴を誤魔化すために、一度UndoしてからPushだとかいろいろしてたりします。細かく話すとDraft.jsの構造の話になって面倒なので割愛
最後に
ググってもいい感じの記事にたどりつけなかったので、誰かの役に立てるとありがたい
最後にDraft.jsを触った感想ですが、これよりいい感じのライブラリはないものか
メンテが殆どされていないのと、不具合があったり、IMEのイベントまわりがうまく制御されていないのか、ちょっと変化ことをしようとすると日本語とかでバグったりする
- 投稿日:2020-02-04T20:45:31+09:00
Draft.jsで絵文字(サロゲートペア )をEntity化して削除を良い感じにできるようにするう方法
Draft.jsで絵文字(サロゲートペア )をEntityにして良い感じに削除をできるようにする方法
Draft.jsで絵文字を扱うと削除した時にいい感じに消えてくれない問題が発生します。
その解決法にたどり着くまでに地味に時間がかかったので備忘録をかねて書きます問題
例えばdraft.jsで4人家族の絵文字を消そうとすると、3人家族になったりします
????=>???この問題はdraft.jsでサロゲートペアの絵文字を扱うと発生してしまいます
これを解決する方法として、Draft.jsの Entity というものがありますEntity
メタデータを使用してテキストの範囲に注釈を付けるために使用するエンティティシステムについて説明します。
エンティティは、スタイル付きテキストを超えるレベルの豊かさをもたらします。リンク、メンション、
および埋め込みコンテンツはすべて、エンティティを使用して実装できます。といったもので、指定した文字の範囲に役割を与えたり、複数の文字列を一つの文字?として扱うことが可能です
デモ + コード
そしてデモとコードをCodeSandboxで用意しました
https://codesandbox.io/s/draft-js-emoji-to-entity-tdoe2
解説
ざっくり説明すると
初期化時、文字入力時、コピペ時、IMEの入力(コンポジットモード)が終わった時に操作した文字列の絵文字を正規表現で開始と終了のindexを取得して、EntityのIMMUTABLEというものに変換しています。こうすることで、一つの文字列として扱うことができ削除の問題を解決できます。
引数に渡している正規表現を変えることで他のEntity化の用件にも対応できると思いますもう少し詳しく書くと、最終的に変更されたContentBlockから絵文字を見つけてEntity化し、操作履歴を誤魔化すために、一度UndoしてからPushだとかいろいろしてたりします。細かく話すとDraft.jsの構造の話になって面倒なので割愛
最後に
ググってもいい感じの記事にたどりつけなかったので、誰かの役に立てるとありがたい
最後にDraft.jsを触った感想ですが、これよりいい感じのライブラリはないものか
メンテが殆どされていないのと、不具合があったり、IMEのイベントまわりがうまく制御されていないのか、ちょっと変化ことをしようとすると日本語とかでバグったりする
- 投稿日:2020-02-04T20:23:17+09:00
TypeScript で型エラーにならないバグのうちよくハマるものの個人用メモ
概要
- 自分用メモの側面が強いのでご了承ください。
- JavaScriptの仕様のハマりやすいポイントのメモ
- TSの型チェックやESLintなどの一般的なチェックツールで弾けるものは対象外
- TypeScriptで普通に型付けしてても型エラーにならず実行時エラーになるケースなど
配列添字アクセス
map,filter,forEachメソッドあたりを使っていれば配列添え字アクセスはめったに必要にならないが、ユーティリティ関数などの実装でたまに必要になることがある。- 配列
xs: T[]の要素へ添字アクセスxs[i]すると値がundefinedになりうるが、xs[i]の型はT | undefinedではなくTに評価されているので型チェックで発見できず実行時エラーとなりうる。at: (array: T[], index: number) => T | undefinedのようなより安全な添え字アクセスをするための関数を自作するのもあり。Immutable.jsのListであれば、List<T>型のリストの要素アクセスメソッドgetの返り値はT | undefinedとなるので安全だが、ビルトイン配列オブジェクトより遅く、手軽さも損なわれる。Array.sort
- 文字列でソートされるので注意
- 数値でソートしたい場合
array.sort((a, b) => a - b)と書く必要があるが、やや面倒。sortNumeric: (array)のような関数呼び出しにしてもよいが、メソッドチェーンでそのあとに処理をつなげたいときなどに見づらくなりこれはこれで不便。pipeline operatorが実装されれば
atやsortNumeric関数が使いやすくなるはずだが、現状関数呼び出しするしか無い印象。
(今後追記予定)
- 投稿日:2020-02-04T19:56:14+09:00
【jQuery】リロードしないとプレビュー表示できない→turbolinksが原因
写真投稿アプリを作成中に、jQueryを用いてプレビュー表示機能を実装しようとした所、うまくプレビュー機能が機能せず、リロードを行うとプレビュー表示が成功するという問題に直面しました。
調べた所、初心者に多いミスらしく、私自身もJavaScriptにおいてturbolinksの概念が無かったため、解決にかなり時間を要しました。
ネット上でも同じような悩みを抱えている方が多かったため、参考になればと思い手順をまとめました。環境
ruby 2.5.1
rails 5.2.4やりたかった事
写真投稿フォーム
↓
画像を選択
↓
選択した画像がプレビュー表示される発生した問題
写真投稿フォーム
↓
画像を選択
↓
$\color{red}{\rm 選択した画像がプレビュー表示されない}$
↓
リロード
↓
画像を選択
↓
選択した画像がプレビュー表示される問題のコード
jquery$(function(){ fileField = $('#file') #選択された画像を取得し表示 fileField.on('change', fileField, function(e) { var file = e.target.files[0] var reader = new FileReader(), $preview = $("#img-field"); reader.onload = (function(file) { return function(e) { $preview.empty(); $preview.append($('<img>').attr({ src: e.target.result, width: "100%", class: "preview", title: file.name })); }; })(file); reader.readAsDataURL(file); }); });解決方法
jquery$(document).on('turbolinks:load', function() { #上記のコードをここに挟む });turbolinksによりJSが機能しない時があるとの事。
上記のように囲むことで、この問題は解決するようです。turbolinksとは
turbolinksとは、一言で言うとWebアプリケーションをすばやく表示させることができるgemのこと。
rails4以降標準で用意されている。初心者の方や、慣れている方でも忘れることが多いみたいなので、
JSが変だと思ったら、turbolinksを疑ってみましょう。
- 投稿日:2020-02-04T19:34:41+09:00
A-Frame: aframe-star-system-componentの調査
前回のA-Frameで利用できるパーティクルコンポーネント調査から、aframe-star-system-component
の使い勝手が良さそうだとわかりました。
パーティクルを使いこなせるようになる為、コンポーネントの各プロパティがどのような意味を持つのか、詳しく見ていこうと思います。color
スター粒子の色
type: String
default: #FFF'
min: -
demo - color: red色名、HEX、RGB、HSLが使えるようです。
radius
内側の星球に球の中心からの距離
type: Number
default: 300
min: 0
demo - radius: 10デフォルト値が300で結構遠くにあるので、10ぐらいまで近づけてみると、星に見えていたものがただの四角形でした。
depth
内側の球と外球の間の距離
type: Number
default: 300
min: 0
demo - depth: 0, radius: 50starSize
個々の星の大きさ
type: Number
default: 1
min: 0
demo - starSize: 1, radius: 10
demo - starSize: 2, radius: 10モジュールの説明ではプロパティ名はsizeと書かれていましたが、コードを見てみるとstarSizeでした。
count
作成された総星の数
type: Number
default: 10000
min: 0
demo - count: 100;100個程度だと、あるかないかわからないです。
texture
スプライトは、個々の星に使用する(例:PNGイメージ)
type: Asset
default: -
min: -
demo - texture: image/compass.png; radius: 20; depth: 0;jpn, png, svg, gifが使えました。
アルファチャンネルは有効ですが、画像同士が重なるとうまく抜けてないようです。
アニメーションgifを使ってみましたが、静止画になっていました
- 投稿日:2020-02-04T19:00:06+09:00
Reactのざっくり概要
この記事の目的
公式チュートリアルで三目並べを作ってもらうのが一番理解できるが、その前段階で「React」とはどのようなものなのかを知ってもらうことを目的とする。
本資料を読んだ後に、チュートリアルを両方やってもらったほうがより理解が深まる。参考資料
ドキュメント
チュートリアル
※余力があれば、以下のチュートリアルも行うことを推奨する
Getting Started with React - An Overview and Walkthrough TutorialReactのざっくり概要
Reactとは何か
Facebookが開発したWebアプリケーションのView(フロントエンド)を作成するためのJavaScriptライブラリ。
公式ページでは以下の特徴を挙げている。
- 宣言的なView
- WebアプリケーションのViewをJSX記法で明示する
- 素のDOMを直接操作しない(Reactが管理する仮想DOMを操作する)
- コンポーネントベース
- コンポーネントという単位で部品を作成する
- コンポーネントでカプセル化を行う
- 一度学習すれば、どこでも使える
- フロントエンド
- React Native(モバイルアプリ開発)
フロントエンドのざっくり歴史
- HTMLだけ ※1995年頃
- HTML + JavaScript ※1995年〜2000年頃
- JavaScriptでちょっとした動きを付けた
- HTML + Flash ※2000年〜2005年頃
- JavaScriptはセキュリティ的に危ない!
- Flashでモリモリ動きを付けた。
- だけどFlashプラグインが必要。。。
- iPhoneはFlashを排除(ここで死滅)
- HTML + JavaScript ※2005年頃
- Ajax(非同期通信によるViewの構築)の登場
- 画面描画に関する処理はクライアントサイド(ブラウザ)、複雑なビジネスロジックやデータの永続化はサーバサイド(WebAPIで機能を提供)という棲み分けが進み始めた
- ページ遷移しないWebページの登場(SPA)
- GoogleMap凄い!
- HTML + jQuery ※2005年〜2010年頃
- Ajaxをいい感じにやってくれるライブラリが登場
- 生DOM操作が辛い。。。
- HTML + JavaScriptのMVCフレームワーク(Knockout.jsなど) ※2010年〜2015年頃
- 生DOM操作の辛さを軽減する方法を模索した時期
- JavaScript内の値の変更がHTMLに反映されるまでの流れをMVCモデルで解決しようとしてた
- が、View反映は結局素のDOM操作になるので辛みはそれほど軽減されなかった
- JavaScriptの仮想DOMを扱うフレームワーク(React, Vue.js, Angularなど) ※2015年頃〜現在
- HTMLのDOMツリー構築をJavaScriptが行う
- 仮想DOMをJavaScriptで操作するので、素のDOMを操作する辛みから開放された
- 現実的なViewのコンポーネント化に関する手法が提供された
サーバサイドのざっくり歴史
- Java + Struts / Spring ※2000年頃
- サーバサイドでHTMLを構築して、ブラウザでは描画のみさせようという流れ
- サーバサイドからのレスポンスを待つので、ブラウザ側の描画待ち時間があった(UXが低い)
- その後、フロントとの棲み分けでAPIサーバに徹する使い方が主流に
- Ruby + Ruby on Rails ※2005年頃〜
- サーバサイドでHTMLを構築することは変わらず。。。
- その後、フロントとの棲み分けでAPIサーバに徹する使い方が主流に
- ASP.NET MVC5 ※2010年頃〜
- やはり、サーバサイドでHTMLを構築することは変わらず。。。
- APIサーバの機能に徹する仕組みもあるが、SpringやRailsと比べるとマイナーな存在
- Node.js ※2010年頃〜
- JavaScriptで処理をかけることが特徴。フロントとバックエンドを同じ言語で書ける!
- JavaScriptの非同期処理を生かして、サーバサイドのI/O待ちをしない(ノンブロッキングI/O)ことで高速なレスポンスを実現
- この特徴で、C10K問題を解決できる素養がある
- C10K問題とはクライアント数10000問題のこと。サーバに接続するクライアント数が1万を超えると、サーバのCPUやメモリに余裕があっても並行処理するスレッドが増えすぎてサーバの処理が滞る問題
- APIサーバとしてのポテンシャルが高いが、サーバサイドは堅牢な言語(型安全であるとか、これまでの長い実績があるとか、技術者がJavaしか書けないとか)が好まれるため、まだ普及しているとは言い難い
ReactとJavaScriptの関係
- ReactはJavaScriptをいい感じに出力するフレームワーク
- JavaScriptだけでDOM操作したり部品化するのが辛いので、Reactというフレームワークを使って楽をしようということ
- Javaに対するStrutsとかSpringの関係と似ている
- JavaScriptには言語仕様のバージョンがある
- Javaに7や6といったバージョンがあるように、JavaScriptにもバージョンがある
- EcmaScriptがJavaScriptの言語仕様であり、JavaScriptとは各メーカーのブラウザのJavaScriptエンジンが実装している実装内容である
- 各ブラウザによって対応しているEcmaScriptのバージョン(JavaScriptのバージョン)が異なるが、開発者がReactで実装するJavaScriptは最新のバージョンで記述できる(ReactはEcmaScriptのバージョンをダウンコンバートする機能がバンドルされている)
Reactを使うと何が嬉しいのか
素のJavaScript(バニラJS)やjQueryで辛かった以下の問題を解決してくれる。
素のDOMの状態を開発者側が覚えなくても良くなった(仮想DOM)
少し前までは、DOMにidやnameを付与してjQueryなどでDOMを操作していたが、DOMの状態はHTML上にしかなく、JSで書き換えられたDOMがどのような状態になっているかは開発者の脳内で想像するしかなかった。
それに対して、Reactは仮想DOMという仕組みを用いている。Reactは仮想DOM(オブジェクトのインスタンスのようなもの)を操作し、その最終結果をHTMLのDOMに反映するという流れを取る。これによって、開発者はインスタンスの状態でViewの状態を把握できるため、素のDOMを扱うストレスから開放された。仮想DOMによって再描画が高速になった
素のDOMをidなりnameなりで操作した場合、素のDOMは一部分だけ再描画する機能がないのでDOMツリーを全て再描画することになり描画速度が遅いという問題があった。
それに対して、仮想DOMは仮想DOM内で新旧の差分を取り、差分があったDOMノードのみ生DOMを再描画するので、再描画が高速になった。画面部品の部品化ができるようになり、再利用性が高まった
HTMLにはファイル分割する現実的な方法がなく(他のHTMLをiframeで読み込む程度)、複数ページで似たようなデザインがあっても各ページで同じ実装をしなければいけなかった。
素のJavaScriptも同様で、比較的新しいEcmaScript6までimportがサポートされておらず、JavaScriptのファイル分割はcommon.js等の外部ライブラリを併用しなければ基本的に不可能だった。それに対して、Reactはimportによるファイル分割をサポートしているだけでなく、コンポーネントというViewの部品単位で分割(ファイル分割やクラスとしての分割)が可能となり、ファイル分割で1ファイルのステップ数を押さえられるだけでなく、複数画面の同じ画面部品を同じコンポーネントで再利用しやすくなった。
Vue.js、Angularとの比較
現在のフロントエンド・JavaScriptフレームワークはReact, Vue.js, Angularの3つが代表であり、それぞれに以下のような特徴がある。
共通した特徴
- 仮想DOMを扱う
- コンポーネント指向である
個別の特徴
これまでの経緯
- React
- Facebookが初版を開発
- 2017年からMITライセンス(OSS)
- 現在はOSSコミュニティで維持管理
- Vue.js
- 個人(Angular開発に関わっていた中国人)が初版を開発
- MITライセンス(OSS)
- OSSコミュニティで維持管理
- Angular
- Googleが初版を開発
- MITライセンス(OSS)
- OSSコミュニティで維持管理
学習コスト
- React
- 学習コスト「中」
- 画面の一部分だけReactを適用することも可能だが、Create React Appで「ReactのWebアプリケーション」として作成するのが一般的
- Vue.js
- 学習コスト「低〜中」
- 画面の一部分にだけVue.jsを適用して動きを付けるjQuery的な使い方が可能
- ただし、仮想DOMの恩恵を受けるには、Vue cli+シングルファイルコンポーネントを用いてビルドする必要があり、その場合の難易度は「中」となる
- Angular
- 学習コスト「高」
- フルスタックフレームワークなので、Angular wayと呼ばれるアプリケーション構築フレームワークに則る必要があり、DIを使用することが前提であるなど学習コストが高め
フレームワークとしてのトレンド
日本と中国ではVue.jsの人気が上がっているが、世界的に見た場合はReactがデファクトスタンダードである。Angularは世界的に見ても人気度が下がっている。
ライフサイクルが比較的長いシステムで考えた場合、安定したバックグラウンド(Facebookの開発チームが主導)があり、中長期的なサポートが期待できるReactが望ましいと考える。
出典:Googleトレンド
- 投稿日:2020-02-04T17:08:42+09:00
【Nuxt.js】dayjs応用編:カウントダウンをしてみよう!
前置き
基礎編に続き応用編です。
2つの日時の差分を出していきます?
基礎編はこちら
【Nuxt.js】dayjs導入編:リアルタイムな日時を表示してみよう
https://qiita.com/aLiz/items/8de5727e274f1a9a1efe復習
・dayjs() =パース
・format() =表示形式の指定index.vue<p v-text="$dayjs().format('dddd、MMMM D、YYYY h:mm A')" />表示結果
index.vueTuesday、February 4、2020 1:33 PMdddd MMMM D ……長い!?笑
省略したい場合はLLLLで同じ表記に。
この辺りも基礎編で解説済みです。
https://github.com/iamkun/dayjs/blob/dev/docs/en/I18n.md・locale('ja')を指定で日本語表記
index.vue<p v-text="$dayjs().locale('ja').format('dddd、MMMM D、YYYY h:mm A')" />表示結果
index.vue2020年2月4日 火曜日 13:33❓ではこれは
※グローバルでlocale('ja')指定済みindex.vue<p v-text="$dayjs('February 4, 2020').add(1, 'year').format('YYYY')" />表示結果
index.vue2021ですね?
dayjsで2020年2月4日をパース(解析)
そこに1年追加し2021年2月4日とし
表示形式を年数字のみにしているためですね?カウントダウン
では基礎編を踏まえて
2つの日時の差分を出しましょう!
あと何ヶ月、とかあと何日!
差分はditt()を使用します。index.vue<p v-text="$dayjs('2020-02-04').diff('2020-01-01')" />結果はミリ秒で表示
index.vue2937600000ミリ秒で見てもパッとしないので
月の差分を見てみましょう〜??index.vue<p v-text="$dayjs('2020-02-04').diff('2020-01-01', 'month') + '月'" />結果は1ヶ月ですね。
diffの第二引数がformatのような役割です。index.vue1月日付
index.vue<p v-text="$dayjs('2020-02-04').diff('2020-02-01', 'day') + '日'" />結果
index.vue3日時間
index.vue<p v-text="$dayjs('2020-02-04T12:30:00').diff('2020-02-04T10:30:00', 'h') + '時間'" />結果
index.vue2時間基礎が分かれば簡単ですね?
記事が公開したときにわかる様に、
note・Twitterフォローをお願いします?
https://twitter.com/aLizlab
- 投稿日:2020-02-04T16:56:18+09:00
Jupyter notebookのショートカットをカスタマイズしてsublime text風にする
Jupyter Notebookでsublime textのショートカットを使用する方法を紹介します。
行の複製、複数選択、複数行同時編集といったsublime textでの便利な機能を使用できます。
また、ショートカットの割り当てを変更することもできます。Jupyter Notebookとは
Pythonのコードと実行結果をまとめて保存できるエディタです。
公式サイトのトップページで雰囲気をつかめます:Project Jupyter | HomeJupyter Notebookのショートカットには2種類ある:「エディタのショートカット」と「コマンド実行」のショートカット
Jupyter notebookでは、コードの塊(セル)を実行したり、セルを追加すると言った機能があります。
これらの機能のショートカットをカスタマイズする方法は、公式で詳しく説明されています: Customize keymaps — Jupyter Notebook 6.0.3 documentation以下のように、ショートカットカスタマイズ用のファイルを作成することでカスタマイズできます。
~/.jupyter/nbconfig/notebook.json{ "keys": { "command": { "bind": { "G,G,G":"jupyter-notebook:restart-kernel-and-run-all-cells" } } }, }また、GUIで直感的にショートカットを変更することもできます。
しかし、エディタでのコード編集に使用するショートカットについては、解説があまり充実していないように思います。
この記事では、エディタ・ショートカットのカスタマイズ方法を説明します。
以下の方法により、コード編集用のショートカットをカスタマイズでき、例えばsublime textのショートカットを使用できるようになります。
custom.jsでjupyter notebookでのJavaScriptの動作をカスタマイズ
jupyter notebookで読み込まれるJavaScriptファイルの中には、ユーザーがカスタマイズするのを想定したものがあります。
それがcustom.jsです。これは、デフォルトでは配置されていませんが、存在する場合には自動的に読み込まれます。
~/.jupyter/custom/custom.jsに、ファイルを作成してください。OSによって配置場所がことなるかもしれないので、import jupyter_core jupyter_core.paths.jupyter_config_dir() # この結果が、'~/.jupyter'になるはずです。 # その場合、'~/.jupyter/custom/custom.js'が読まれます。で確認してみてください。
このcustom.jsに以下のように書きます。
jupyter notebookのJavaScriptでは、モジュールの管理にrequirejsが使用されているので、その構文に整合するように書きます。.jupyter/custom/custom.jsdefine([ "custom/js_required/import-sublime-keybindings", "base/js/events" ], function(keybindings, events) { "use strict"; keybindings.bindSublimeKeymap(); });外部のファイルにて、以下のようにショートカット設定を書き、custom.jsで読み込みます。
.jupyter/custom/js_required/import-sublime-keybindings.jsdefine([ 'base/js/namespace', 'notebook/js/cell', 'codemirror/lib/codemirror', 'codemirror/keymap/sublime' ], function(IPython, cell, CodeMirror) { "use strict"; var bindSublimeKeymap = function() { var map = CodeMirror.keyMap.sublime; var notebook = IPython.notebook; if (!notebook) return; // 既存のショートカットを無効化する関数 var deleteIfExist = function(strCommand) { if (map[strCommand]) { delete map[strCommand]; } }; // ここでキーマップをsublime textに設定 cell.Cell.options_default.cm_config.keyMap = 'sublime'; // Cmd-Enterのショートカットを無効化する deleteIfExist("Cmd-Enter"); // sublime textでデフォルトで用意されていないショートカットを自分で用意する map["Shift-Cmd-D"] = "deleteLine"; map["Cmd-D"] = "duplicateLine"; map["Alt-W"] = "wrapLines"; map["Cmd-B"] = "selectNextOccurrence"; map["Shift-Cmd-M"] = "selectBetweenBrackets"; map["Alt-Up"] = "swapLineUp"; map["ALt-Down"] = "swapLineDown"; // 以上で新規セルにショートカットが設定される。 // 既存セルにも適用するために以下を実行 var cells = IPython.notebook.get_cells(); var numCells = cells.length; for (var c = 0; c < numCells; c++) { cells[c].code_mirror.setOption('keyMap', 'sublime'); } }; return { bindSublimeKeymap: bindSublimeKeymap }; });これでJupyter notebookを再起動すれば、ショートカットが適用されるはずです。
- 投稿日:2020-02-04T16:32:17+09:00
ContentEditableでpreventDefault可能なショートカットキー一覧(Max OS編)
はじめに
本記事はMac OS上での動作を想定しています。Windows 10はこちら
ブラウザごとに色々なショートカットがありますが、これをキャンセルし、独自のショートカットを追加したい時があります。
特にJavascriptでリッチテキストエディタを作る場合を想定し、
contentEditable="true"なelementのイベント発火においてpreventDefault()でキャンセルできるか否かを、実際に試してリストアップしたので共有します。概略
- OSやブラウザ本体の制御に近いものほどpreventDefaultできない。
- 想像以上にショートカットの種類があった。
- Mac OSの場合はkeydownだけでなくbeforeinputでしかキャンセルできない物がある(参考:過去の投稿記事)。
蛇足:keydownでpreventDefaultするとElectronのacceleratorも発火しなくなります。一方で、preventDefaultしなかった場合はブラウザのデフォルト動作とacceleratorの発火が共に起こり、これはこれで制御が難しい。
環境
- OS: Mac OS 10.14.6
- Safari 13.0.4
- Chrome 79.0.3945.117
- Firefox 72.0.1
表のみかた
キーボードでキー入力した場合にブラウザの行うデフォルト動作と、keydownイベント、および、beforeinputイベントにおいてpreventDefault()の有効/無効をyes or noで表に示します。preventDefault()でキャンセルできる場合はyes、キャンセルできない場合はnoとしました。先に書いてある方がkeydownイベント、後に書いてある方がbeforeinputイベントです。動作はSafariでの動作を基本とし、それと異なる動作をするブラウザではその旨を併記しました。"なし"と書いてあるところは特にデフォルト動作が無いことを意味します。
ただし、Firefoxではkeydownのみが調査対象です。これはFirefoxではbeforeinputを使う必要が無いからです(参考:過去の投稿記事)
単独キー入力(without Modifier key)
key 動作 Safari Chrome Firefox A-Z,1-9 文字入力 no/yes yes/yes yes 1文字分の長さの記号(括弧やカンマなど) 文字入力 no/yes yes/yes yes delete 前方文字を消す yes/yes yes/yes yes backspace 後方文字を消す yes/yes yes/yes yes Enter 改行する(段落を区切る) yes/yes yes/yes yes Arrow カーソル移動 yes/no yes/no yes Commandと同時に押した場合
key 動作 Safari Chrome Firefox A 全選択 yes/no yes/no yes B なし yes/yes ブックマークを開く/yes C 選択領域をCopy yes/no yes/no yes D ブックマーク追加 yes/no yes/no yes F page内を検索 yes/no yes/no yes G 検索中に次の候補へカーソル移動 yes/no yes/no なし H ブラウザを最小化 no/no ブラウザを閉じる/yes/no yes I メールアカウントのウィンドウをだす/yes/no 選択領域をitalicにする/yes/yes ページ情報のwindowを表示/yes J なし なし ブラウジングライブラリー/yes K URL windowで検索(Eと同じ) なし なし yes L URL バーへカーソル移動 no/no なし yes M ゴミ箱の横に最小化 no/no yes/no yes N 新しいwindowsでブラウザを開く no/no no/no no O ファイルを開く no/no yes/no yes P ページの印刷 yes/no yes/no yes/no Q ブラウザを閉じる no/no no/no no R ページの再読み込み no/no yes/no yes S ページ(html)の保存 no/no yes/no yes T 新しいタブを開く no/no no/no no U 選択領域にunderlineを引く なし なし ソースを開く/yes V Paste no/yes yes/yes yes W ブラウザ(タブ)を閉じる no/no no/no no X 切り取り no/no yes/no(コピーのみされる) yes Y 履歴を表示 no/no yes/no なし Z Undo no/yes yes/yes yes 1-9 タブブラウザの切り替え no/no yes/no yes 0 なし - ページを縮小 yes/no yes/no yes = なしページを拡大 なし なし yes ; 次の英単語へ移動 /yes/no yes/no ページを拡大/yes + ページを拡大 yes/no yes/no yes ^ なし @ なし [履歴に合わせてページ遷移/yes/no なし yes ]履歴に合わせてページ遷移/yes/no なし yes : 辞書登録? yes/no yes/no なし / なし 「ステータスバーはオンです」の表示/yes/no なし なし , ブラウザの設定画面を開く no/no yes/no no . なし space spotlite検索 no/no no/no no delete なし backspace 現在位置から行の先頭まで削除 yes/no yes/yes yes enter なし RightArrow 前方へ単語単位で移動 行の末尾へ移動/yes/no yes/no yes LeftArrow 後方へ単語単位で移動 行の先頭へ移動/yes/no yes/no yes UpArrow 文章の先頭へ移動 /yes/no yes/no yes DownArrow 文章の末尾へ移動 /yes/no yes/no yes Command+Shiftと同時に押した場合
(Shift無しの場合と動作が異なるものだけ記載)
key 動作 Safari Chrome Firefox A なし なし アドオンマネージャー/yes B お気に入りリンクのバーを出す yes/no yes/no ブラウジングライブラリーを開く/yes C デバッグwindowを表示 yes/no yes/no yes D 時計が動く(謎) yes/no ブックマーク編集/yes/no yes H なし ホーム画面へ移動/yes/no 履歴の検索/yes J なし ダウンロード履歴/yes/no デバッグコンソールを別windowで開く/yes L リーディングリスト yes/no なし なし M なし ユーザーログイン/yes/no なし N プライベートブラウズで新しいwindowを開く yes/no no/no 新しいwindowでブラウザを開く/yes Q OSからログアウト no/no no/no no R ページ全体の表示の切り替え yes/no yes/no yes S なし なし スクリーンショートカット/yes T なし ダウンロード履歴から検索/no/no アドオンマネージャー/yes X なし なし 全て右寄せにする/yes Z Redo yes/yes yes/yes yes Arrow,Home,End,Pageup,Pagedown 選択しながらフォーカス移動 yes/no yes/no yes Command+Optionと同時に押した場合
(option無しと動作が同じものは省略)
key 動作 Safari Chrome Firefox A デバッグwindowを開く yes/no なし なし B ブックマーク yes/no yes/no なし C デバッグwindowを表示 yes/no yes/no yes D 下のMac特有のアプリのバーが消える no/no no/no no F URLwindowでお気に入り選択 yes/no URLwindowにカーソルが移動/yes/no URIwindowで検索/yes I デバッグwindowを表示 yes/no yes/no yes J デバッグwindowを表示 なし yes/no なし K デバッグwindowを表示 なし なし yes M なし なし モバイル機器表示エミュレータ/yes U なし なし ソースの表示/yes Z デバッグwindowを表示 なし なし yes 1-9 お気に入り?おすすめ?の1-9番のページへ移動 yes/no なし なし 最後に
全部のキーを実際に押して試しました。beforeinputがある分、windowsでの検証の倍時間が掛かった。開発においてはbeforeinputとkeydownの使い分けが必要で、Mac OS、特にSafari対応はほんとキツイです。
間違いに気づいたらごしていき頂ければ幸いです。
- 投稿日:2020-02-04T16:26:48+09:00
ContentEditableでpreventDefault可能なショートカットキー一覧(Windows編)
はじめに
本記事はWindows上での動作を想定しています。Mac OSはこちら
ブラウザごとに色々なショートカットがありますが、これをキャンセルし、独自のショートカットを追加したい時があります。
特にJavascriptでリッチテキストエディタを作る場合を想定し、
contentEditable="true"なelementのイベント発火においてpreventDefault()でキャンセルできるか否かを、実際に試してリストアップしたので共有します。概略
- OSやブラウザ本体の制御に近いものほどpreventDefaultできない。
- 想像以上にショートカットの種類があった。
- Windowsなら基本はkeydownでpreventDefaultする(Mac OSは事情が異なる)。
蛇足:keydownでpreventDefaultするとElectronのacceleratorも発火しなくなります。一方で、preventDefaultしなかった場合はブラウザのデフォルト動作とacceleratorの発火が共に起こり、これはこれで制御が難しい。
環境
- OS: Windows 10 (1903)
- Chrome 79.0.3945.130
- Edge (Legacy) 44.18362.449.0
- Firefox 72.0.2
表のみかた
キーボードでキー入力した場合にブラウザの行うデフォルト動作と、keydownイベントにおいてpreventDefault()の有効/無効をyes or noで表に示します。preventDefault()でキャンセルできる場合はyes、キャンセルできない場合はnoとしました。動作はChromeでの動作を基本とし、それと異なる動作をするブラウザではその旨を併記しました。
単独キー入力(without Modifier key)
key 動作 Chrome Edge(Legacy) Firefox A-Z,1-9 文字入力 yes yes yes 1文字分の長さの記号(括弧やカンマなど) 文字入力 yes yes yes delete 前方文字を消す yes yes yes backspace 後方文字を消す yes yes yes Enter 改行する(段落を区切る) yes yes yes insert インサートmodeに変更 yes yes なし Alt メニュー等へフォーカスを移す yes yes no Home 表示上の行の先頭へ移動 yes yes yes End 表示上の行の末尾へ移動 yes yes yes Page Up 表示上の一画面上へ移動 yes yes yes Page Down 表示上の一画面下へ移動 yes yes yes 右クリックキー 右クリックメニューを呼ぶ no no no Ctrlと同時に押した場合
key 動作 Chrome Edge(Legacy) Firefox A 全選択 yes yes yes B Boldにする yes yes ブックマークを開く/yes C 選択領域をCopy yes yes yes D ブックマーク追加 yes yes yes E URL windowで検索 yes なし yes F page内を検索 yes yes yes G 検索中に次の候補へカーソル移動(検索中でない場合は、前回の検索を再開) yes なし yes H ブラウザ履歴のページへ yes yes yes I 選択領域をitalicにする yes yes ブックマークを検索/yes J ダウンロード履歴 yes yes 新しいタブを開く/yes K URL windowで検索(Eと同じ) yes yes yes L URL バーへカーソル移動 yes yes yes M なし yes リーディングリストを開く/yes ミュート/yes N 新しいwindowsでブラウザを開く no yes no O ファイルを開く yes URLバーバーへカーソル移動/yes yes P ページの印刷 yes yes yes Q なし R ページの再読み込み yes yes yes S ページ(html)の保存 yes yes yes T 新しいタブを開く no yes プライベートモードで新しいタブを開く/no U 選択領域にunderlineを引く yes yes ソースを開く/yes V Paste yes yes yes W ブラウザ(タブ)を閉じる no yes no X 切り取り yes yes yes Y なし Redo/yes Redo/yes Z Undo yes yes yes 1-9 タブブラウザの切り替え yes yes yes 0 なし - ページを縮小 yes yes yes = ページを縮小 yes なし yes ; ページを拡大 yes yes yes + ページを拡大 yes なし なし ^ なし \ なし @ なし [なし ]なし : なし / なし \ なし , なし . なし space なし delete 前方を単語単位で削除 yes yes yes backspace 後方を単語単位で削除 yes yes yes enter なし 改行/yes なし RightArrow 前方へ単語単位で移動 yes yes yes LeftArrow 後方へ単語単位で移動 yes yes yes UpArrow なし 行の先頭へ移動 DownArrow なし 行の末尾へ移動 Pageup タブ切り替え no 表示されている範囲の先頭へ移動/yes no Pagedown なしタブ切り替え no 表示されている範囲の末尾へ移動/yes no Home 文章の先頭へ移動 yes yes yes End 文章の末尾へ移動 yes yes yes Ctrl+Shiftと同時に押した場合
key 動作 Chrome Edge(Legacy) Firefox A なし なし アドオンマネージャーを開く/yes B ブラウザのメニューを出す yes なし ブラウジングライブラリーを開く/yes G 検索 yes 音声読み上げ なし H なし なし ブラウジングライブラリーを開く/yes I デバッグツールを開く yes なし yes J デバッグツールを開く yes yes 別windowでデバックツールを開く/yes K 無し yes 同じページを新しいタブで開く/yes デバッグツールを開く/yes L なし ページを再読み込み/yes なし M ユーザーログイン関連 yes 手書き入力モード/yes モバイルデバイスのエミュレート表示/yes N シークレットモードでブラウザを開く no なし なし O ブックマーク操作 yes yes なし P 印刷 yes yes 新しいwindowでブラウザを開く/no Q なし なし ブラウザ(全ページ)を閉じるブラウザを閉じる/no R なし なし ページの再読み込み/no S なし なし スクリーンショットを取る/yes T ブックマーク操作 no 新しいタブで同じページを開く/yes 設定画面へ移動/yes V pasteしつつデバッグウィンドウで操作されたDOMを可視化 yes なし paste/yes W ブラウザ(全ページ)を閉じる no yes no Z Redo yes なし yes Backspace なし 後方の単語の削除/yes なし Delete 閲覧履歴の削除 yes yes なし Enter なし 改行/yes なし Arrow,Home,End,Pageup,Pagedown 選択しながらフォーカス移動 yes yes yes さいごに
全てのキーを押して試しました。眩暈が。。。しかし、Mac OSはもっときつかった。
ミスや漏れがあったらご指摘いただけたら嬉しいです。
- 投稿日:2020-02-04T15:43:38+09:00
JavaScriptで文字が全て半角数字かどうか判定する
JavaScriptで渡した文字が全て半角数字かどうか判定する方法です。
e1xport default class AsciiNumberTester { static containsOnlyHalfWidthNumber(str) { return /^[0-9]+$/.test(str); } }import AsciiNumberTester from 'path/to/class_file'; AsciiNumberTester.containsOnlyHalfWidthNumber(this.value)-----------以下解説----------------
[0-9]+は 0~9のどれかが1回以上繰り返し- 正規表現においては(ざっくり解説だと)
^は先頭、$は行の終わり.test()は正規表現と与えられた文字列が一致するかチェック
- 投稿日:2020-02-04T15:36:53+09:00
Docker 環境で nodemon が watch してくれない問題と対処方法
Web アプリの開発環境を Docker で作っていたところ、nodemon がうまくファイル変更を検出してくれなくて困ったので、そのときのメモ。
マウントしたボリュームだと nodemon が watch してくれない問題
問題を再現するために、以下の3つのファイルを用意する。
ポイントとしては、ホストのディレクトリをコンテナ内にマウントしてそれをコンテナ内の nodemon が watch しているということ。
DockerfileFROM node:12 ADD . /app WORKDIR /app CMD ["node", "."]index.jsconst http = require('http') http.createServer((req, res) => { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end('ok') }).listen(80)docker-compose.ymlversion: '3' services: app: build: . ports: - 8080:80 volumes: - .:/app command: npx nodemon
docker-compose upでコンテナを起動し、Web ブラウザからhttp://localhost:8080/にアクセスするとokと表示されるはず。この開発用コンテナは nodemon から node プロセスを起動しているので、
index.jsが編集された場合には自動で restart してほしい。しかしこの場合、いくら
index.jsを編集しても nodemon はその変更を検出してくれない。Chokidar が Polling するように設定する
nodemon はファイル監視のために内部で Chokidar を利用しており、そのドキュメントにはこのように書いてある。
usePolling (default: false). Whether to use fs.watchFile (backed by polling), or fs.watch. If polling leads to high CPU utilization, consider setting this to false. It is typically necessary to set this to true to successfully watch files over a network, and it may be necessary to successfully watch files in other non-standard situations. Setting to true explicitly on MacOS overrides the useFsEvents default. You may also set the CHOKIDAR_USEPOLLING env variable to true (1) or false (0) in order to override this option.
ネットワーク経由でファイルを監視する場合にはポーリングするように設定する必要があるらしい。
docker-compose.yml+ environment: + CHOKIDAR_USEPOLLING: 1というわけで環境変数
CHOKIDAR_USEPOLLING=1を設定した上で再試行したところ、無事にファイル変更を検出してくれるようになった。めでたしめでたし。
- 投稿日:2020-02-04T14:19:10+09:00
最新版『 Laravel DB.com 』コード書かずにCRUD作成~利用方法まとめ~
「 Laravel DB.com 」 、利用方法まとめ
2020年2月2日リニューアルしたので、使用方法をまとめたいと思います。
Laravel DB.com ってなに?
「LaravelDB.com」は、ER図(テーブルの設計)を作成することで、”Migration” OR ”CRUD(テストデータ)”のコードまで生成できるサービス(ツール)です。
確認したLaravel動作環境
- 開発環境: Laravel5.5 ~ 6.x / PHP7.2.1 / MySQL5.6.38
- 確認環境: Mac: Chromeブラウザ
メリット
- インストール不要
- テーブル設計をクラウドに保存可能 (他アプリの設計に使い回せる!)
- Migration自動生成
- CRUD自動生成
- テストデータ(Faker)自動生成
- Validation自動生成(テーブル設計時のカラム型を参照して作成してる。※Enum型は自分でCODE修正必要かも)
- Webサービスだから、どのPCでも同じデータを活用して作成できる
さあ、 LaravelDB.com はじめて行きましょう!
操作方法(Youtubu)
— LaravelDB.com (@ErdLaravel)の操作方法【 CRUD: 新しいプロジェクトを用意 】
最低限Laravelがインストールした状態を準備します!
1. この時点ではまだLOGINは作らないように!
2. .env と MySQLのDB準備はしておきましょう。
【 LaravelDB.comへ ログイン 】
Googleアカウントのみログイン可能です。(画面右上 OR ページ下部の中央)
LaravelDB.com
【 LaravelDB.comの POINT!! 】
ER図を作成していきますが重要事項があります!
<<重要>>
主キーのAutoIncrementは どのテーブルも『 id 』 と固定すること!!
LaravelのEloquentModel を便利に使うためには、どのテーブルも 『 id 』 と固定するのが吉です。
理由は「 モデル名::find($id); 」と便利に使う場合は 主キーのAutoIncrement 『 id 』 名にしておかないと動作しないからです( Laravelを勉強してる人はなんとなく知ってることでしょうか )。
※ laravelDB.comのCRUDでも生成されるファイルは「 ○○○::find($id); 」を使ってるためそ!
※ laravelに最初から入っているテーブル「user」「password_resets」は作成しないこと(migrate時に上書きしてしまいます)
※慣れれば簡単ですが、少しだけ慣れる時間は必要ですね。
【 CRUDを生成してみる! 】
- まずは、ER図を簡単に作ってみましょう!! その後に右メニュー「 ER図のLoad/Save 」をクリックすると以下画面が表示されます。
![]()
- 外部キーを沢山つけてる場合は、Migrationの順番が重要なので試す場合は「OFF」がオススメです。
- 「 Laravel( CRUD BUILD) 」 ボタンをクリックでファイルが生成されます!!
【 ダウンロードファイルを確認しましょう 】
【移行方法】
Macの場合: 「Optionキー + ドラッグ&ドロップ」 → 「結合」でフォルダの上書きではなく、結合になります。
Winの場合:「ドラッグ&ドロップ」で結合できます。ダウンロードした各フォルダを上記の方法で移動しましょう!
【 LaravelのHOMEページを表示しましょう 】
私の環境では「http://localhost/ 」
【 「 コマンド 」 を3つ順番に打ちましょう! 】
<<重要>>
DBや.env設定など最低限の設定を完了しておくこと!!php artisan migrate composer update //テストデータいらない場合は打たなくてもOK php artisan db:seed以上3つです。
【 完成: ブラウザで確認しましょう! 】
http://localhost/ (URLは開発環境によって異なります) で確認しましょう!
あなたが作ったテーブルのCRUD画面へ遷移できますよ!!作成されたindex/show/editの画面
基本的な処理が最初から出来てます!捗りそうですね!
Validationがスゴイ! → テーブル設計に合わせて生成
例でざっくりテーブルをER図で書いてみました。
以下"t_gsusers"テーブルを中心に見ていきます。
生成されたコントローラーのcreate(),edit()にはこういったvalidationが挿入されます。
TGsusersController$this->validate($request, [ "name" => "required|max:128", //string('name',128) "lid" => "required|max:128", //string('lid',128) "lpw" => "required|max:128", //string('lpw',128) "m_department_id" => "required|integer", //integer('m_department_id') "m_position_id" => "required|integer", //integer('m_position_id') "m_prefectures_id" => "required|integer", //integer('m_prefectures_id') "m_kanri_id" => "required|integer", //integer('m_kanri_id') "m_lifeflg_id" => "required|integer", //integer('m_lifeflg_id') "m_test_id" => "required|integer", //integer('m_test_id') ]);{{old('name')}} → 入力項目を補完
validationで未入力等ではじかれた場合、入力した文字を消さずに表示します。
date,datetime型 → 入力フォー厶type="date"
- 画面表示側もtype='date'
- validation側もdateチェックが入ります。
ER図外部キー設定→JOIN自動生成 QueryBuilderをController側にコメントで用意
イチからQueryBuilderを使用して、JOINを書くのは大変です。ベースになる部分は生成してくれます。使用する場合はコメントになってるのでコメントを外し、上記に書かれてるコードのEloquentModelの1行をコメントして使います。
※機械的に作成してるため、より良い記述に変更は必要になります。動作した後はコードを見ると良いでしょう
- Routing
- Controller
- Views
- Model 後は、自分で変更をいれていくだけですね。
日本発 Laravelツール
日本から海外でも使われるプロダクトになることを願っております。
Twitter: LaravelDB.com
以上
- 投稿日:2020-02-04T14:19:10+09:00
最新版『 Laravel DB.com 』コード書かずに超スピード開発~(DEMO動画あり)~
Laravel DB.com ってなに?
「LaravelDB.com」は、ER図(テーブルの設計)を作成することで、”Migration” OR ”CRUD(テストデータ)”のコードまで生成できるサービス(ツール)です。
DEMO動画(操作方法)
【YouTubeリンク>> https://t.co/jGVarMoXse 】
— LaravelDB.com (@ErdLaravel)の操作方法
「 Laravel DB.com 」 、利用方法まとめ
2020年2月2日リニューアルしたので、使用方法をまとめたいと思います。
注意)以前に見たことがある人は「PWA」でChcheAPIが効いてて古い画面のままかもしれません。
その場合には、[開発者ツール]→[Application]→[Clear Storage]でキャッシュを削除してください。確認したLaravel動作環境
- 開発環境: Laravel5.5 ~ 6.x / PHP7.2.1 / MySQL5.6.38
- 確認環境: Mac: Chromeブラウザ
メリット
- インストール不要
- テーブル設計をクラウドに保存可能 (他アプリの設計に使い回せる!)
- Migration自動生成
- CRUD自動生成
- テストデータ(Faker)自動生成
- Validation自動生成(テーブル設計時のカラム型を参照して作成してる。※Enum型は自分でCODE修正必要かも)
- Webサービスだから、どのPCでも同じデータを活用して作成できる
さあ、 LaravelDB.com はじめて行きましょう!
【 CRUD: 新しいプロジェクトを用意 】
最低限Laravelがインストールした状態を準備します!
1. この時点ではまだLOGINは作らないように!
2. .env と MySQLのDB準備はしておきましょう。
【 LaravelDB.comへ ログイン 】
Googleアカウントのみログイン可能です。(画面右上 OR ページ下部の中央)
LaravelDB.com
【 LaravelDB.comの POINT!! 】
ER図を作成していきますが重要事項があります!
<<重要>>
主キーのAutoIncrementは どのテーブルも『 id 』 と固定すること!!
LaravelのEloquentModel を便利に使うためには、どのテーブルも 『 id 』 と固定するのが吉です。
理由は「 モデル名::find($id); 」と便利に使う場合は 主キーのAutoIncrement 『 id 』 名にしておかないと動作しないからです( Laravelを勉強してる人はなんとなく知ってることでしょうか )。
※ laravelDB.comのCRUDでも生成されるファイルは「 ○○○::find($id); 」を使ってるためそ!
※ laravelに最初から入っているテーブル「user」「password_resets」は作成しないこと(migrate時に上書きしてしまいます)
※慣れれば簡単ですが、少しだけ慣れる時間は必要ですね。
【 CRUDを生成してみる! 】
- まずは、ER図を簡単に作ってみましょう!! その後に右メニュー「 ER図のLoad/Save 」をクリックすると以下画面が表示されます。
![]()
- 外部キーを沢山つけてる場合は、Migrationの順番が重要なので試す場合は「OFF」がオススメです。
- 「 Laravel( CRUD BUILD) 」 ボタンをクリックでファイルが生成されます!!
【 ダウンロードファイルを確認しましょう 】
【移行方法】
Macの場合: 「Optionキー + ドラッグ&ドロップ」 → 「結合」でフォルダの上書きではなく、結合になります。
Winの場合:「ドラッグ&ドロップ」で結合できます。ダウンロードした各フォルダを上記の方法で移動しましょう!
【 LaravelのHOMEページを表示しましょう 】
私の環境では「http://localhost/ 」
【 「 コマンド 」 を3つ順番に打ちましょう! 】
<<重要>>
DBや.env設定など最低限の設定を完了しておくこと!!php artisan migrate composer update //テストデータいらない場合は打たなくてもOK php artisan db:seed以上3つです。
【 完成: ブラウザで確認しましょう! 】
http://localhost/ (URLは開発環境によって異なります) で確認しましょう!
あなたが作ったテーブルのCRUD画面へ遷移できますよ!!作成されたindex/show/editの画面
基本的な処理が最初から出来てます!捗りそうですね!
Validationがスゴイ! → テーブル設計に合わせて生成
例でざっくりテーブルをER図で書いてみました。
以下"t_gsusers"テーブルを中心に見ていきます。
生成されたコントローラーのcreate(),edit()にはこういったvalidationが挿入されます。
TGsusersController$this->validate($request, [ "name" => "required|max:128", //string('name',128) "lid" => "required|max:128", //string('lid',128) "lpw" => "required|max:128", //string('lpw',128) "m_department_id" => "required|integer", //integer('m_department_id') "m_position_id" => "required|integer", //integer('m_position_id') "m_prefectures_id" => "required|integer", //integer('m_prefectures_id') "m_kanri_id" => "required|integer", //integer('m_kanri_id') "m_lifeflg_id" => "required|integer", //integer('m_lifeflg_id') "m_test_id" => "required|integer", //integer('m_test_id') ]);{{old('name')}} → 入力項目を補完
validationで未入力等ではじかれた場合、入力した文字を消さずに表示します。
date,datetime型 → 入力フォー厶type="date"
- 画面表示側もtype='date'
- validation側もdateチェックが入ります。
ER図外部キー設定→JOIN自動生成 QueryBuilderをController側にコメントで用意
イチからQueryBuilderを使用して、JOINを書くのは大変です。ベースになる部分は生成してくれます。使用する場合はコメントになってるのでコメントを外し、上記に書かれてるコードのEloquentModelの1行をコメントして使います。
※機械的に作成してるため、より良い記述に変更は必要になります。動作した後はコードを見ると良いでしょう
- Routing
- Controller
- Views
- Model 後は、自分で変更をいれていくだけですね。
日本発 Laravelツール
日本から海外でも使われるプロダクトになることを願っております。
Twitter: LaravelDB.com
以上
- 投稿日:2020-02-04T14:19:10+09:00
コード書かずに超スピード開発~(DEMO動画あり)~最新版『 Laravel DB.com 』
Laravel DB.com ってなに?
「LaravelDB.com」は、ER図(テーブルの設計)を作成することで、”Migration” OR ”CRUD(テストデータ)”のコードまで生成できるサービス(ツール)です。
DEMO動画(操作方法)
【YouTubeリンク>> https://t.co/jGVarMoXse 】
— LaravelDB.com (@ErdLaravel)の操作方法
「 Laravel DB.com 」 、利用方法まとめ
2020年2月2日リニューアルしたので、使用方法をまとめたいと思います。
注意)以前に見たことがある人は「PWA」でChcheAPIが効いてて古い画面のままかもしれません。
その場合には、[開発者ツール]→[Application]→[Clear Storage]でキャッシュを削除してください。確認したLaravel動作環境
- 開発環境: Laravel5.5 ~ 6.x / PHP7.2.1 / MySQL5.6.38
- 確認環境: Mac: Chromeブラウザ
メリット
- インストール不要
- テーブル設計をクラウドに保存可能 (他アプリの設計に使い回せる!)
- Migration自動生成
- CRUD自動生成
- テストデータ(Faker)自動生成
- Validation自動生成(テーブル設計時のカラム型を参照して作成してる。※Enum型は自分でCODE修正必要かも)
- Webサービスだから、どのPCでも同じデータを活用して作成できる
さあ、 LaravelDB.com はじめて行きましょう!
【 CRUD: 新しいプロジェクトを用意 】
最低限Laravelがインストールした状態を準備します!
1. この時点ではまだLOGINは作らないように!
2. .env と MySQLのDB準備はしておきましょう。
【 LaravelDB.comへ ログイン 】
Googleアカウントのみログイン可能です。(画面右上 OR ページ下部の中央)
LaravelDB.com
【 LaravelDB.comの POINT!! 】
ER図を作成していきますが重要事項があります!
<<重要>>
主キーのAutoIncrementは どのテーブルも『 id 』 と固定すること!!
LaravelのEloquentModel を便利に使うためには、どのテーブルも 『 id 』 と固定するのが吉です。
理由は「 モデル名::find($id); 」と便利に使う場合は 主キーのAutoIncrement 『 id 』 名にしておかないと動作しないからです( Laravelを勉強してる人はなんとなく知ってることでしょうか )。
※ laravelDB.comのCRUDでも生成されるファイルは「 ○○○::find($id); 」を使ってるためそ!
※ laravelに最初から入っているテーブル「user」「password_resets」は作成しないこと(migrate時に上書きしてしまいます)
※慣れれば簡単ですが、少しだけ慣れる時間は必要ですね。
【 CRUDを生成してみる! 】
- まずは、ER図を簡単に作ってみましょう!! その後に右メニュー「 ER図のLoad/Save 」をクリックすると以下画面が表示されます。
![]()
- 外部キーを沢山つけてる場合は、Migrationの順番が重要なので試す場合は「OFF」がオススメです。
- 「 Laravel( CRUD BUILD) 」 ボタンをクリックでファイルが生成されます!!
【 ダウンロードファイルを確認しましょう 】
【移行方法】
Macの場合: 「Optionキー + ドラッグ&ドロップ」 → 「結合」でフォルダの上書きではなく、結合になります。
Winの場合:「ドラッグ&ドロップ」で結合できます。ダウンロードした各フォルダを上記の方法で移動しましょう!
【 LaravelのHOMEページを表示しましょう 】
私の環境では「http://localhost/ 」
【 「 コマンド 」 を3つ順番に打ちましょう! 】
<<重要>>
DBや.env設定など最低限の設定を完了しておくこと!!php artisan migrate composer update //テストデータいらない場合は打たなくてもOK php artisan db:seed以上3つです。
【 完成: ブラウザで確認しましょう! 】
http://localhost/ (URLは開発環境によって異なります) で確認しましょう!
あなたが作ったテーブルのCRUD画面へ遷移できますよ!!作成されたindex/show/editの画面
基本的な処理が最初から出来てます!捗りそうですね!
Validationがスゴイ! → テーブル設計に合わせて生成
例でざっくりテーブルをER図で書いてみました。
以下"t_gsusers"テーブルを中心に見ていきます。
生成されたコントローラーのcreate(),edit()にはこういったvalidationが挿入されます。
TGsusersController$this->validate($request, [ "name" => "required|max:128", //string('name',128) "lid" => "required|max:128", //string('lid',128) "lpw" => "required|max:128", //string('lpw',128) "m_department_id" => "required|integer", //integer('m_department_id') "m_position_id" => "required|integer", //integer('m_position_id') "m_prefectures_id" => "required|integer", //integer('m_prefectures_id') "m_kanri_id" => "required|integer", //integer('m_kanri_id') "m_lifeflg_id" => "required|integer", //integer('m_lifeflg_id') "m_test_id" => "required|integer", //integer('m_test_id') ]);{{old('name')}} → 入力項目を補完
validationで未入力等ではじかれた場合、入力した文字を消さずに表示します。
date,datetime型 → 入力フォー厶type="date"
- 画面表示側もtype='date'
- validation側もdateチェックが入ります。
ER図外部キー設定→JOIN自動生成 QueryBuilderをController側にコメントで用意
イチからQueryBuilderを使用して、JOINを書くのは大変です。ベースになる部分は生成してくれます。使用する場合はコメントになってるのでコメントを外し、上記に書かれてるコードのEloquentModelの1行をコメントして使います。
※機械的に作成してるため、より良い記述に変更は必要になります。動作した後はコードを見ると良いでしょう
- Routing
- Controller
- Views
- Model 後は、自分で変更をいれていくだけですね。
日本発 Laravelツール
日本から海外でも使われるプロダクトになることを願っております。
Twitter: LaravelDB.com
以上
- 投稿日:2020-02-04T14:19:10+09:00
『 Laravel DB.com 』コード書かずにCRUD作成【最新版 2020/02/01 〜利用方法まとめ〜】
「 Laravel DB.com 」 、利用方法まとめ
2020年2月1日リニューアルしたので、使用方法をまとめたいと思います。
Laravel DB.com ってなに?
「LaravelDB.com」は、ER図(テーブルの設計)を作成することで、”Migration” OR ”CRUD(テストデータ)”のコードまで生成できるサービス(ツール)です。
確認したLaravel動作環境
- 開発環境: Laravel5.5 ~ 6.x / PHP7.2.1 / MySQL5.6.38
- 確認環境: Mac: Chromeブラウザ
メリット
- インストール不要
- テーブル設計をクラウドに保存可能 (他アプリの設計に使い回せる!)
- Migration自動生成
- CRUD自動生成
- テストデータ(Faker)自動生成
- Validation自動生成(テーブル設計時のカラム型を参照して作成してる。※Enum型は自分でCODE修正必要かも)
- Webサービスだから、どのPCでも同じデータを活用して作成できる
さあ、 LaravelDB.com はじめて行きましょう!
操作方法(Youtubu)
— LaravelDB.com (@ErdLaravel)の操作方法【 CRUD: 新しいプロジェクトを用意 】
最低限Laravelがインストールした状態を準備します!
1. この時点ではまだLOGINは作らないように!
2. .env と MySQLのDB準備はしておきましょう。
【 LaravelDB.comへ ログイン 】
Googleアカウントのみログイン可能です。(画面右上 OR ページ下部の中央)
LaravelDB.com
【 LaravelDB.comの POINT!! 】
ER図を作成していきますが重要事項があります!
<<重要>>
主キーのAutoIncrementは どのテーブルも『 id 』 と固定すること!!
LaravelのEloquentModel を便利に使うためには、どのテーブルも 『 id 』 と固定するのが吉です。
理由は「 モデル名::find($id); 」と便利に使う場合は 主キーのAutoIncrement 『 id 』 名にしておかないと動作しないからです( Laravelを勉強してる人はなんとなく知ってることでしょうか )。
※ laravelDB.comのCRUDでも生成されるファイルは「 ○○○::find($id); 」を使ってるためそ!
※ laravelに最初から入っているテーブル「user」「password_resets」は作成しないこと(migrate時に上書きしてしまいます)
※慣れれば簡単ですが、少しだけ慣れる時間は必要ですね。
【 CRUDを生成してみる! 】
- まずは、ER図を簡単に作ってみましょう!! その後に右メニュー「 ER図のLoad/Save 」をクリックすると以下画面が表示されます。
![]()
- 外部キーを沢山つけてる場合は、Migrationの順番が重要なので試す場合は「OFF」がオススメです。
- 「 Laravel( CRUD BUILD) 」 ボタンをクリックでファイルが生成されます!!
【 ダウンロードファイルを確認しましょう 】
【移行方法】
Macの場合: 「Optionキー + ドラッグ&ドロップ」 → 「結合」でフォルダの上書きではなく、結合になります。
Winの場合:「ドラッグ&ドロップ」で結合できます。ダウンロードした各フォルダを上記の方法で移動しましょう!
【 LaravelのHOMEページを表示しましょう 】
私の環境では「http://localhost/ 」
【 「 コマンド 」 を3つ順番に打ちましょう! 】
<<重要>>
DBや.env設定など最低限の設定を完了しておくこと!!php artisan migrate composer update //テストデータいらない場合は打たなくてもOK php artisan db:seed以上3つです。
【 完成: ブラウザで確認しましょう! 】
http://localhost/ (URLは開発環境によって異なります) で確認しましょう!
あなたが作ったテーブルのCRUD画面へ遷移できますよ!!作成されたindex/show/editの画面
基本的な処理が最初から出来てます!捗りそうですね!
Validationがスゴイ! → テーブル設計に合わせて生成
例でざっくりテーブルをER図で書いてみました。
以下"t_gsusers"テーブルを中心に見ていきます。
生成されたコントローラーのcreate(),edit()にはこういったvalidationが挿入されます。
TGsusersController$this->validate($request, [ "name" => "required|max:128", //string('name',128) "lid" => "required|max:128", //string('lid',128) "lpw" => "required|max:128", //string('lpw',128) "m_department_id" => "required|integer", //integer('m_department_id') "m_position_id" => "required|integer", //integer('m_position_id') "m_prefectures_id" => "required|integer", //integer('m_prefectures_id') "m_kanri_id" => "required|integer", //integer('m_kanri_id') "m_lifeflg_id" => "required|integer", //integer('m_lifeflg_id') "m_test_id" => "required|integer", //integer('m_test_id') ]);{{old('name')}} → 入力項目を補完
validationで未入力等ではじかれた場合、入力した文字を消さずに表示します。
date,datetime型 → 入力フォー厶type="date"
- 画面表示側もtype='date'
- validation側もdateチェックが入ります。
ER図外部キー設定→JOIN自動生成 QueryBuilderをController側にコメントで用意
イチからQueryBuilderを使用して、JOINを書くのは大変です。ベースになる部分は生成してくれます。使用する場合はコメントになってるのでコメントを外し、上記に書かれてるコードのEloquentModelの1行をコメントして使います。
※機械的に作成してるため、より良い記述に変更は必要になります。動作した後はコードを見ると良いでしょう
- Routing
- Controller
- Views
- Model 後は、自分で変更をいれていくだけですね。
日本発 Laravelツール
日本から海外でも使われるプロダクトになることを願っております。
Twitter: LaravelDB.com
以上
- 投稿日:2020-02-04T13:47:01+09:00
フロントエンド言語とJavaScript の役割
フロントエンド言語とは主にユーザーが目にする部分で主にhtml,css,JavaScriptから成り立っている。
「JavaScript」とは、動的なWebページを作成する事のできるプログラミング言語です。通常はブラウザー上で実行されます。JavaScriptを使うと、ユーザーのアクションに応じたコンテンツの表示の他、ブラウザー上で表示される地図やグラフィックアニメーションなども表示する事ができます。
「JavaScript」を使ってできる事はたくさんあります。開発当初のJavaScriptはクライアントサイドで実行され動的Webページを作成していましたが、現在ではサーバーサイドで動作する「Node.js」の他、Adobe Acrobat、Yahoo! ウィジェットエンジン、組み込みコンピューターなど様々な所で使われています。
JavaScriptを使うとWebページで表示するHTMLコンテンツを変更する事ができます。サンプルのボタンをクリックすると表示されているテキストが変わります。
JJavaScriptでHTMLコードそのものを変更する事ができます。
CSSで指定する、文字の大きさや色なども変更する事ができます。ボタンをクリックするとテキストの色が変わります。
JavaScriptでHTMLコンテンツを表示・非表示にする事ができます。
具体的には以下のようなことができます。
ポップアップウィンドウ
Ajax
サイトの利便性アップ
サイトのブラッシュアップ
カウントダウンタイマーが作れる
ソートや検索機能を作れる
- 投稿日:2020-02-04T12:12:34+09:00
【個人開発】GIF生成アプリを作ってアメリカのProduct Huntに投稿した話
背景
こんにちは。
キャリアチェンジをしてエンジニアとして就職するためにバンクーバーの専門学校に通っているMarieと申します。
現在、実績なしで海外就職という難易度高めの問題に立ち向かっています。
もうすぐ就職しなくてはならないのですが、なんせ実績がないので、ポートフォリオに作品を載せて、求人サイトでポチポチ応募するだけでは十分なアピールにはなりません。
だったら、もっと自分から動かなくては!!と思い、作ったアプリをアメリカのProduct Huntに投稿してみました!というキラキラ(と見せかけてとても泥臭い)お話です。Product Huntってなに
Product Huntは、シリコンバレーで生まれたサービスで、世界中のユーザーが新サービスを投稿したり、探したりできるサービスです。
投稿されたサービスに対して、ユーザーが票を入れる(UPVOTEする)仕組みがあり、デイリーランキングとして集計されるのも魅力の1つです。
見ているだけで楽しいので、まだ知らない方は覗いてみると良いと思います。何を作ったか
MsgifというGIF生成アプリを作りました。 どんなサービスなのかは画像を見てもらった方が早いと思うのですが、タイピングした文字がGIFアニメーションに変換されるというものです。
どんな風に作ったか
技術としてはReact.js、ライブラリはstateを管理するためにRedux、HTMLをCanvasに変換するためにhtml2canvas、Canvasで撮った画像をGIFに変換するためにjsgifを使っています。
仕組みとしては、テキストエリアに文字をタイプする度にキャプチャーして画像に変換し、それを配列に放り込んで、文字を打ち終わったタイミング(CreateGIFというボタンが押されたタイミング)でそれらを1つのGIFファイルにまとめて出力しています。
もしコードに興味がある方は、GitHubに公開していますので、こちらをご覧ください。
投稿してみてどうだったか
実は、このエントリーを書いている時点で、Productのデイリーランキングの集計真っ最中です!
1日でランキングがリセットされてしまうため、前日の0時前後にパソコンの前に座って、「行けー!!!」と力強くエンターキーを打刻。
投下した直後から反応は悪くなかったのですが、なんせ無名で実績もゼロなので、放っておいて票が集まるわけがありません。Twitter、Facebook、LinkedIn、WhatsApp、LINE…いろんなところでペコペコしながら清き一票をお願いして、現時点でUpvotes:140、ランキング10位の状態です。
個人開発をしていて思うのは、必要なスキルがめちゃくちゃ多くて割と無理ゲーだということです。
コーディングができるだけでは十分ではなく、どうやったら人に見てもらえるか考えるマーケティングの力も要りますし、サービスを知ってもらう営業努力もかかせません。今日1日だけで色んなところにペコペコしすぎて、とても疲れ…充実していました。
まだ集計中なので、需要がありそうであれば後日結果もご報告できたらなと思います。結局宣伝かい
はい...すみません!
いろんなところでペコペコしておいて、このままQiitaでペコペコしないわけにはいきません。
自分としては1年前にはReactなんて触ったこともなかったので、正直サービスをリリースできて感慨深いものがあります。
ユーザーの皆様にはそんなことは関係ないのは百も承知ですが、
もしサービスが面白いと思っていただけたら、1票を入れてUpvoteをしていただけると大変励みになります!!
(Upvoteするにはログインが必要なのでお手数だとは思うのですが…何卒よろしくお願いします?♀️
)▼投票はこちらから
https://www.producthunt.com/posts/msgif長くなりましたが、自分で考えたサービスが世の中の人に使われるというのは本当に嬉しいことです。
多くの人がこのサービスを面白がってくれたら幸いです!
- 投稿日:2020-02-04T11:53:55+09:00
【JavaScript】ツイートボタンのクリック時にイベント計測などをする
Twitter 公式のツイートボタンを使っているとき、ボタンは iframe で実装されているため、通常の click イベントは取れません1。ボタンがクリックされたときになにかしたい場合(例: Google Analyticsのイベント計測など)は、Twitter が提供する widgets.js というスクリプトを使います。
TL;DR
最終形は以下のような感じになります。
<body> <!-- ... --> <!-- ツイートボタン(公式から雛形コードをコピペしてボタンを設置したいところに貼る) --> <a class="twitter-share-button" href="https://twitter.com/intent/tweet"> Tweet</a> <!-- ... --> <!-- 以下のコードはどこに貼ってもよいが、bodyの閉じタグの前などが無難 --> <!-- widgets.jsの読み込み(公式から雛形コードをコピペ) --> <script>window.twttr = (function(d, s, id) { var js, fjs = d.getElementsByTagName(s)[0], t = window.twttr || {}; if (d.getElementById(id)) return t; js = d.createElement(s); js.id = id; js.src = "https://platform.twitter.com/widgets.js"; fjs.parentNode.insertBefore(js, fjs); t._e = []; t.ready = function(f) { t._e.push(f); }; return t; }(document, "script", "twitter-wjs"));</script> <!-- widgets.jsを使っていろいろする(自分で書く部分) --> <!-- 必ず上記の雛形コードの後に書く --> <script> twttr.ready(function (twttr) { twttr.events.bind('click', function () { // ここにツイートボタンが押されたときにしたい処理を書く // 例: Google Analyticsのイベント計測 ga('send', 'event', 'Tweet Button', 'click') }) }) </script> </body>解説
0. ツイートボタンの設置
ツイートボタンそのものの設置の詳細はここでは触れません。以下のドキュメントなどを参考にしてください。
https://developer.twitter.com/en/docs/twitter-for-websites/tweet-button/overview1. widgets.jsを読み込む
ツイートボタンなどを自分の JavaScript からごにょごにょするには、Twitter が提供する widgets.js という JavaScript を読み込む必要があります。
以下のページから雛形コードをコピーして、ページの HTML に貼り付けてください。場所はどこでもよいですが、
<body>の閉じタグの前あたりがよいでしょう。
https://developer.twitter.com/en/docs/twitter-for-websites/javascript-api/guides/set-up-twitter-for-websites2. widgets.jsを使ってイベントリスナを設定する
上記の雛形を埋め込むと、それ以降の行で
twttrというオブジェクトが使えるようになります(雛形より上にかかれているコードでtwttrを使うと未定義エラーになります)。twttrでできることを網羅的に知りたい場合は、以下のドキュメントを参照してください。
https://developer.twitter.com/en/docs/twitter-for-websites/javascript-api/overview今回は
twttr.events.bindを使って、clickのイベントリスナ(イベントが発生した時に実行したい処理)を設定します。ただ、イベントリスナの設定は、
twttrの初期化が終わってからでないとできません。そこで、twttr.readyを使って、「twttrの初期化が終わった時に」イベントリスナを登録するように包みます。twttr.ready(function (twttr) { // ここに twttr の初期化が完了したときにやりたい処理を書く // 今回はclickのイベントリスナを登録する twttr.events.bind('click', function () { // ここにツイートボタンが押されたときにしたい処理を書く // 概ねなんでも可能 }) })これでツイートボタンをクリックした時になにかできるようになります
厳密には、別オリジンのフレーム内容へのアクセスとなるためです。同一ドメインのフレームであれば、このような制約は基本的にありません。 ↩
- 投稿日:2020-02-04T11:49:27+09:00
WebStormでJestする方法
概要
JavaScript開発用のIDE、WebStorm上でJestする方法について書きます
やりかた
WebStormを起動して、Jestのテストケースを含むプロジェクトを開く
Jestのサンプルプロジェクトとして以下を使った
https://github.com/riversun/es6_babel7_jestプロジェクトを開く
プロジェクトディレクトリを開くとこんな感じ。
/testディレクトリ以下に、Jestをつかったテストケース(hello.test.js)が入ってるのでそれを開く。実は、この状態でもうWebStormでJestを実行可能なのだが、より便利にするために
Jest用のコード補完ライブラリを追加するJest用のコード補完ライブラリをWebStormに追加する
コード補完ライブラリを追加していく。
File->Settingsを開き、
Languages & Frameworks->JavaScript>Librariesを開いて、右にある
ボタンをクリック。
すると、ライブラリ一覧画面がでてくるので、jestを選択して、
をクリックしてダウンロードする。
こんな風に、Jestの構文部分がハイライトされるようになる。
WebStorm上からテスト起動
ファイルやエディタ上で右クリックコンテクストメニューから
するとテストが実行される。とても簡単。
実行結果の確認
まとめ
JestをWebStorm上で実行する方法について説明しました。
npm testでコマンドラインからやる方法もアリですが、IDEに統合されていると、GUI上で確認したり、その後のデバッグへのつながりなどやはり便利に感じました。






























































