- 投稿日:2019-07-01T23:40:38+09:00
jQueryの$.each()メソッド
jQueryの$.each()メソッド
jQueryの$.each()メソッドを使用すると、
配列やハッシュに対して繰り返し処理を行うことができる。第一引数には繰り返し処理を行いたい配列、
第二引数には取り出した要素に対して行いたい処理を設定する。例
前提として、変数inputにはユーザーが入力した値がに代入される
var fruits = ['apple', 'grape', 'orange']; $.each(fruits, function(i, fruit) { if (input === fruit) { $("#result").text(input); return false; } });※第二引数で指定した処理の引数
第一引数…配列の要素番号(インデックス)
第二引数…配列の要素ユーザーが入力した値と配列に格納されている値を一つ一つ比較する。
一致した場合、$("#result").text(input)を実行し、
"return false"で処理から抜ける。
- 投稿日:2019-07-01T23:16:26+09:00
【Express】trailing slash(URL末尾のスラッシュ)なしのURLにリダイレクトさせる方法
こんにちは、プログラミングスクールのレビューサイト「スクールレポート」を運営しているアカネヤ(@ToshioAkaneya)です。
【Express】trailing slash(URL末尾のスラッシュ)なしのURLにリダイレクトさせる方法
以下のmiddlewareを使いましょう。
app.use((req, res, next) => { if (req.path.substr(-1) === '/' && req.path.length > 1) { const query = req.url.slice(req.path.length) res.redirect(301, req.path.slice(0, -1) + query) } else { next() } })参考: https://stackoverflow.com/questions/13442377/redirect-all-trailing-slashes-globally-in-expre
はてなブックマーク・Pocketはこちらから
- 投稿日:2019-07-01T23:02:42+09:00
redux-form (3) - Field-Level Validation Example
redux-form (1) - Simple Form Example
redux-form (2) - Synchronous Validation Example
redux-form (3) - Field-Level Validation Example
ReactでForm componentを作るときに、とても便利なredux-formの説明です。
redux-form はReact form componentをRedux storeにconnectするためのものです。使い方は簡単で、提供されたreducerを使います。actionを明示的に指定する必要もありません。具体的には、次の3つのキー要素が使われます。
- formReducer : Redux actionによって伝えられた Form の更新を、Redux stateに反映させるreducer。
- reduxForm() : HOCでform componentをwrapし、ユーザ入力をRedux actionにbindします。また親で指定されたonSubmit を handleSubmitとして渡します。
- <Field /> : wrapped form componentで使われるcomponentで、input componentをRedux-form に組み込んでくれます。
3番目の<Field />については以下に説明します。
Field について
Field componentは、個別のinputをどのようにRedux storeにconnectすべきかを示すものです。以下の3点が重要です。
- name propが必要とされます。例 'firstName'
- component propが必要とされます。次の3パターンがあります。1. normal component / 2. stateless function / 3. DOM input string (input, select, or textarea)
- その他の全てのpropは、component propの指定で生成された要素に渡されます。(*1)
3番目に関連して、<Field />がcomponent propのcomponentに渡すpropは次の3つに分かれます。
- input object
- meta object
- custom props (*1で述べたprops)
Field-Level Validation Example
redux-form (2) - Synchronous Validation Exampleでは、Redux state 全体のvaluesを入力として、errorのobjectを返す、validate関数でのチェックの方法を見ました。1個のvalidate関数で全Formのvalidateを行いました。
今回は、各Fieldを個別に validate する方法を紹介します。各Field毎に、validate propとしてvalidate関数を指定します。validate関数はFieldのvalueを入力にとり、value が valid であればundefineを返し、value が invalid であればエラーメッセージ(文字列)を返します。 小さく再利用可能なvalidate関数を作ることで、同じようなコードを何回も書くことを避けることができるメリットがあります。
Field-Level Validation Example - Getting Started With redux-form
以下のソースコードを見てください。
最初に小さなvalidate関数を多数定義しています。
それをField componentの validate prop に指定します。
例えばrequiredは複数のField componentで指定されています。一度定義すれば、再利用可能なので同じコードを繰り返す必要がなくなります。src/FieldLevelValidationForm.jsimport React from 'react' import { Field, reduxForm } from 'redux-form' const required = value => (value || typeof value === 'number' ? undefined : 'Required') const maxLength = max => value => value && value.length > max ? `Must be ${max} characters or less` : undefined const maxLength15 = maxLength(15) export const minLength = min => value => value && value.length < min ? `Must be ${min} characters or more` : undefined export const minLength2 = minLength(2) const number = value => value && isNaN(Number(value)) ? 'Must be a number' : undefined const minValue = min => value => value && value < min ? `Must be at least ${min}` : undefined const minValue13 = minValue(13) const email = value => value && !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value) ? 'Invalid email address' : undefined const tooYoung = value => value && value < 13 ? 'You do not meet the minimum age requirement!' : undefined const aol = value => value && /.+@aol\.com/.test(value) ? 'Really? You still use AOL for your email?' : undefined const alphaNumeric = value => value && /[^a-zA-Z0-9 ]/i.test(value) ? 'Only alphanumeric characters' : undefined export const phoneNumber = value => value && !/^(0|[1-9][0-9]{9})$/i.test(value) ? 'Invalid phone number, must be 10 digits' : undefined const renderField = ({ input, label, type, meta: { touched, error, warning } }) => ( <div> <label>{label}</label> <div> <input {...input} placeholder={label} type={type} /> {touched && ((error && <span>{error}</span>) || (warning && <span>{warning}</span>))} </div> </div> ) const FieldLevelValidationForm = props => { const { handleSubmit, pristine, reset, submitting } = props return ( <form onSubmit={handleSubmit}> <Field name="username" type="text" component={renderField} label="Username" validate={[required, maxLength15, minLength2]} warn={alphaNumeric} /> <Field name="email" type="email" component={renderField} label="Email" validate={email} warn={aol} /> <Field name="age" type="number" component={renderField} label="Age" validate={[required, number, minValue13]} warn={tooYoung} /> <Field name="phone" type="number" component={renderField} label="Phone number" validate={[required, phoneNumber]} /> <div> <button type="submit" disabled={submitting}> Submit </button> <button type="button" disabled={pristine || submitting} onClick={reset}> Clear Values </button> </div> </form> ) } export default reduxForm({ form: 'fieldLevelValidation' // a unique identifier for this form })(FieldLevelValidationForm)以下は、オリジナルなものを最小化したindex.jsです。
src/index.jsimport React from 'react' import ReactDOM from 'react-dom' import { Provider } from 'react-redux' import { createStore, combineReducers } from 'redux' import { reducer as reduxFormReducer } from 'redux-form' const dest = document.getElementById('content') const reducer = combineReducers({ form: reduxFormReducer // mounted under "form" }) const store = createStore(reducer) const showResults = values => new Promise(resolve => { setTimeout(() => { // simulate server latency window.alert(`You submitted:\n\n${JSON.stringify(values, null, 2)}`) resolve() }, 500) }) let render = () => { const FieldLevelValidationForm = require('./FieldLevelValidationForm').default ReactDOM.hydrate( <Provider store={store}> <h2>Form</h2> <FieldLevelValidationForm onSubmit={showResults} /> </Provider>, dest ) } render()実行画面
エラー表示
今回は以上です。
- 投稿日:2019-07-01T22:16:09+09:00
select2でタグの文字列を取得する
はじめに
select2でvalueではなく、text(タグの文字列)を取得する方法を調べたのでメモっておく
環境
- Windows10
- Edge
- select2 4.0.6-rc.1
方法
select2('data')でタグのオブジェクト配列が取得できるため、それに対し"text"属性を取得すればよい。
例えば0番目のタグの文字列は、以下で取得できる。$('.js-example-basic-multiple').select2('data')[0].text;参考
How to get Selected Text from select2 when using
https://stackoverflow.com/questions/19814601/how-to-get-selected-text-from-select2-when-using-input
- 投稿日:2019-07-01T21:56:49+09:00
DjangoでサクッとWebサイトを作る Part1
DjangoでサクッとWebページを作ってみました。
初心者向けに超基礎的な部分だけ解説します。セットアップ
既にご存知かと思いますが、DjangoはPythonで開発されています。したがってまずはPythonをインストールする必要があります。
本記事執筆辞典ではPythonは3.7がおすすめです。もしPython2.xがインストール済の場合はアップグレードしましょう。インストールが正しく行われているか確認するには以下のコードを実行してみてください。
$ python3 --version Python 3.6.1Djangoのインストール
projects/requirements.txt
ファイル中に以下のテキストを追加します。Django~=2.0.6
次に、以下のコードを実行してインストールします。
pip install -r requirements.txt以下のようになりましたでしょうか。
(myvenv) ~$ pip install -r requirements.txt Collecting Django~=2.0.6 (from -r requirements.txt (line 1)) Downloading Django-2.0.6-py3-none-any.whl (7.1MB) Installing collected packages: Django Successfully installed Django-2.0.6インストールはこれで完了です。
次回は具体的な開発の解説に入っていこうと思います。最終的にはこんな感じになります
LeadingTech更新はゆっくりになりますが頑張りますので気長にお待ちください!
- 投稿日:2019-07-01T20:42:34+09:00
51歳からのプログラミング 備忘 Laravelとjqueryでリアルチャットモドキ
やっと、チャット擬きが作れたよ。
僕レベルじゃチャットも作れないのかと、何度も諦めそうになったけれど、楽しいから、もがてみた。子供のおもちゃのようなコードなんだろうけれど、何となく動いてくれたので、とりあえず。
(二重投稿防止とか、リロード関連の処理とか、そういうのはやってません。)
DB
テーブル:Chat
カラム 内容 comment 投稿コメントを記録 created_at フィールド作成日時を記録 流れ?
1.サイトインの時に日時を取得
2.SSEで、1もしくは3 の日時以後のログを出力
3.ajaxで画面遷移せずにコメント登録して、ログを出力し、登録日時を取得各日付は更新日時として$_SESSION['CURRENT']で管理
controller
controller// サイトIN時に、日時をSESSIONに更新日時として記録 public function index(){ session_start(); $_SESSION['CURRENT'] = date('Y-m-d H:m:s'); return view('index'); } // 更新日時以降のコメントがあれば、クライアントに送信し // 更新日時を最新のコメント日時に更新 public function sse(){ session_start(); $comment = Chat::all()->toArray(); header('Content-Type:text/event-stream'); foreach($comment as $value){ if($value['created_at'] > $_SESSION['CURRENT'] ){ echo 'data:'.$value['comment']; echo "\n\n"; flush(); } } $current = Chat::orderBy('created_at','desc')->first()->toArray(); if($current['created_at'] > $_SESSION['CURRENT']){ $_SESSION['CURRENT'] = $current['created_at']; } } // ajaxで送信されてきたコメントを登録し、更新日時を更新 public function ajax(Request $request){ session_start(); $comment['comment'] = $request->comment; if(isset($comment['comment'])){ $chat = new Chat; $chat->fill($comment)->save(); } $comment = Chat::orderBy('created_at','desc')->first()->toArray(); $_SESSION['CURRENT'] = $comment['created_at']; return response($comment); }クライアント側
index.blade.php<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <script> $(function(){ var commentInp = $('input[name="comment"]'); // sse var source = new EventSource('sse'); source.onmessage = function(chat){ $('div[name="comment"]').prepend('<p>'+chat.data); } // ajax $('form').submit(function(){ comment = commentInp.val(); $.ajax({ type : 'post', url : 'ajax', data :{'comment':comment,_token:'{{csrf_token()}}'}, }).then( function(chat){ $('div[name="comment"]').prepend('<p>'+chat['comment']);}, function(){}, ); return false; }); }); </script> <form> <input type="text" name="comment"/> <button>send</button> </form> <div name="comment"></div>今の僕には、このコードが限界。
websocketとか、プロトコルを操る人に羨望!
僕には無理かなー。ITに関係ない一言
子供とね、試作チャットで遊んでたんだけど、リアルタイムに発言が反映されないので、使いづらいって言われたのです。3か月前のことなんだけれど。
んで、リアルタイムに発言を表示できるようにしようと試みたんだ。でも、こんな時間がかかるとはね、僕の脳は優秀ではないようですな。
- 投稿日:2019-07-01T19:59:51+09:00
【AtomPub API】Livedoorブログの投稿記事を取得する
はじめに
趣味でLivedoorブログをやっていて毎日投稿を心がけている。
しかしながら、なかなか毎日の投稿数が安定しない。
個人的には毎日の投稿数とかが可視化されているとモチベーションが上がるので、勉強も兼ねてやってみようと思うプライベート端末で実装するため、あまり複雑なコーディングはしたくない。よって一番馴染みがあるNode.jsを使って簡単なスクリプトを組む。
やることは以下一点
- 記事作成日が昨日の日付のものを抽出する
※スプレッドシートへの書き込みはひとまず置いといて、Livedoorブログから投稿記事の取得を試みる
※APIの細かい使用は公式を参照。使用モジュール
- wise
- request
- xml2json
ソース
livedoor.jslet wsse = require('wsse'); let request = require('request'); let parser = require('xml2json'); var token = new wsse.UsernameToken({ username: '【UID】', password: '【API_KEY】' }); // 記事を取得 var options = { url: 'https://livedoor.blogcms.jp/atompub/【ブログID】/article', method: 'GET', headers: { 'Authorization': 'WSSE profile="UsernameToken"', 'X-WSSE': token.getWSSEHeader({nonceBase64:true}) } }; // リクエスト実行 request(options, function(error, response, body) { // XML形式で返ってくるのでJSONにパース var json = parser.toJson(body); let obj = JSON.parse(json); // 昨日の日付のエントリ let toDaysEntry = []; // from(昨日の日付の00:00:00) let from = new Date(new Date().setHours(0, 0, 0, 0)); from.setDate(from.getDate() - 1); from.setHours(from.getHours() + 9); // +09:00 // to(今日の日付の00:00:00) let to = new Date(new Date().setHours(0, 0, 0, 0)); to.setHours(to.getHours() +9); // +09:00 // エントリはfeed.entryに格納されている if(obj.feed && obj.feed.entry){ for(let i = 0 ; i < obj.feed.entry.length; i++){ // エントリ let e = obj.feed.entry[i]; // 最終更新日をDate型に変換 let eDate = new Date(e.updated); // 昨日の日付のエントリだけ抽出 if(eDate > from && eDate < to){ toDaysEntry.push(e); } } console.log(toDaysEntry.length); } });感想
今回はおそらく一番オーソドックスな投稿記事の取得だけだったが、APIから投稿することも可能らしい。
なんにせよこれで件数は取得できたので、次回はスプレッドシートへの書き込みをやってみようと思う。
- 投稿日:2019-07-01T19:39:58+09:00
ブラウザから利用できるメディアデバイスを取得する
MediaDevices.enumerateDevices()
で取得します。if(!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) { // mediaDevicesをサポートしていないブラウザや、HTTPS接続ではサポートしていないブラウザの場合 console.error('not support'); } else { navigator.mediaDevices.enumerateDevices() .then(function(devices) { // 取得成功 devices.forEach(function(device) { console.log(device); }); }) .catch(function(err) { // 取得失敗 console.error(err); }); }
MediaDevices.enumerateDevices()
はMediaDeviceInfo
の配列を返します。
MediaDeviceInfo
の中身は以下の通りです。
プロパティ名 内容 手元のMacbookで取れた値 deviceId デバイスを一意に表す識別子 1e07db9ca237ce1...(省略) groupId デバイスのグループ識別子。同じ物理デバイスに所属する2つのデバイスは同じ値になる 1096f640cae1194...(省略) kind デバイスの種類。
'videoinput'、'audioinput'、'audiooutput'のいずれかaudioinput label デバイス名を表すラベル 内蔵マイク (Built-in) 取得した
deviceId
は メディアデバイスを使用するためのgetUserMedia
や、音声出力先を切り替えるsetSinkId
の引数に指定できます。
- 投稿日:2019-07-01T19:39:58+09:00
ブラウザから利用できるメディアデバイスの情報を取得する
MediaDevices.enumerateDevices()
で取得します。if(!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) { // mediaDevicesをサポートしていないブラウザや、HTTPS接続ではサポートしていないブラウザの場合 console.error('not support'); } else { navigator.mediaDevices.enumerateDevices() .then(function(devices) { // 取得成功 devices.forEach(function(device) { console.log(device); }); }) .catch(function(err) { // 取得失敗 console.error(err); }); }
MediaDevices.enumerateDevices()
はMediaDeviceInfo
の配列を返します。
MediaDeviceInfo
の中身は以下の通りです。
プロパティ名 内容 例(手元のMacbookで取れた値) deviceId デバイスを一意に表す識別子 1e07db9ca237ce1...(省略) groupId デバイスのグループ識別子。同じ物理デバイスに所属する2つのデバイスは同じ値になる 1096f640cae1194...(省略) kind デバイスの種類。
'videoinput'、'audioinput'、'audiooutput'のいずれかaudioinput label デバイス名を表すラベル 内蔵マイク (Built-in) 取得した
deviceId
は メディアデバイスを使用するためのgetUserMedia
や、音声出力先を切り替えるsetSinkId
の引数に指定できます。
- 投稿日:2019-07-01T19:13:15+09:00
ひとまずのsassコンパイル用gulpfile.js設定
gulp導入しました。
引き続きgulpです。gulpfile.js設定
gulp4だと3系の記事を参照していたらエラー吐きまくったので、バージョンは気をつけましょう。
追加したモジュール
- gulp-sass
- gulp-plumber
- gulp-notify
それぞれインストールしてください。
sassコンパイル用
npm install gulp-sass --save-dev
エラー時に監視を止めないnpm install gulp-plumber --save-dev
エラーコードのポップアップ表示npm install gulp-notify --save-dev
それらを反映した設定ファイルgulpfile.jsvar gulp = require("gulp"); var sass = require("gulp-sass"); var plumber = require('gulp-plumber'); var notify = require('gulp-notify'); //sassコンパイル gulp.task("sass",function(done){ gulp.src("sass/*.scss") .pipe(plumber({errorHandler: notify.onError('<%= error.message %>')})) .pipe(sass({outputStyle: 'expanded'})) .pipe(gulp.dest("./css")); done(); }); //動作設定 gulp.task("default", gulp.series('sass', function(done){ gulp.watch("sass/*.scss", gulp.series('sass')); done(); }));
これを使用するプロジェクトフォルダに配置。
該当のフォルダにcdで移動後、gulpで開始します。$ gulp終了するときは「ctrl + c」です。
さらにbrowser-sync加える
ファイルを更新すると同時にブラウザも更新してくれます。
複数のブラウザを開いている状態で、ページ遷移、スクロールなどを同期してくれます。npm install browser-sync --save-devgulpfile.jsvar gulp = require("gulp"); var sass = require("gulp-sass"); var plumber = require('gulp-plumber'); var notify = require('gulp-notify'); var browserSync =require('browser-sync'); //sass gulp.task("sass",function(done){ gulp.src("sass/*.scss") .pipe(plumber({errorHandler: notify.onError('<%= error.message %>')})) .pipe(sass({outputStyle: 'expanded'})) .pipe(gulp.dest("./css")); done(); }); //ブラウザ同期 gulp.task('browser-sync', function(done) { browserSync({ proxy: "http://hogehoge" //MAMPなどで設定したバーチャルホスト }); done(); }); //ブラウザリロード gulp.task('bs-reload', function (done) { browserSync.reload(); done(); }); //動作設定 gulp.task('default', gulp.series('browser-sync', function (done) { gulp.watch("sass/*.scss", gulp.series('sass')); gulp.watch("./*.html", gulp.series('bs-reload')); gulp.watch("./**/*.html", gulp.series('bs-reload')); gulp.watch("./css/*.css", gulp.series('bs-reload')); gulp.watch("./js/*.js", gulp.series('bs-reload')); done(); }));MAMPで設定しなくてもbrowser-syncでローカル環境は作ってくれますが、PHP環境などは対応していないので、やはり動的サイトではMAMPを入れたほうが良さそうです。
MAMPについてはこちら
MAMPのインストール&初期設定+αをしてみる完全な静的サイトだったら、browser-syncの部分を下記に書き換えるとMAMPなしでローカル環境を作ってくれます。
gulpfile.js//ブラウザ同期 gulp.task('browser-sync', function(done) { browserSync({ server: { baseDir: "/", index: "index.html" } }); done(); });公式サイトのオプション一覧
- 投稿日:2019-07-01T17:48:16+09:00
javascriptの特性について簡単にまとめてみた
javascriptってどういうもの?
javascriptは「ブラウザを操作」するプログラミング言語です。javascriptで何かプログラムを書けば、ブラウザはその命令通りに処理してくれます。
ブラウザの仕組み
ブラウザの役割はWEBページを表示させることです。WEBページはHTMLとCSS、数点の画像で作られてます。HTMLとCSSには重要な特徴があります。それは、一度ブラウザに読み込まれたら変化しないです。例えば、次のページに行こうとしない限り、基本的にブラウザ上に同じ情報を表示し続けます。
・HTML → ページのコンテンツを表示させるもの
・CSS → HTMLにスタイル情報を提供してレイアウトやデザインを決めるものウィンドウ幅に合わせて伸縮するレイアウトやサイト、画面サイズに合わせてレイアウトを大きく変えるレスポンシブWEBデザインで作られているWEBページがありますが、レスポンシブもHTMLとCSSが初めに読み込まれた時から変わることはないです。HTMLとCSSは不変で静止したデータと言えます。
しかし、javascriptを使うことによって、これらの静止したデータであるHTMLとCSSを、その場でリアルタイムに書き換えることができます。加えて一部のコンテンツを入れ替えたり、画像のスライドショーのような動きをつけたりすることができます。
- 投稿日:2019-07-01T17:48:16+09:00
javascriptの特徴について簡単にまとめてみた
javascriptってどういうもの?
javascriptは「ブラウザを操作」するプログラミング言語です。javascriptで何かプログラムを書けば、ブラウザはその命令通りに処理してくれます。
ブラウザを操作するってどういうこと?
ブラウザの役割はWEBページを表示させることです。WEBページはHTMLとCSS、数点の画像で作られてます。HTMLとCSSには重要な特徴があります。それは、一度ブラウザに読み込まれたら変化しないです。例えば、次のページに行こうとしない限り、基本的にブラウザ上に同じ情報を表示し続けます。
・HTML → ページのコンテンツを表示させるもの
・CSS → HTMLにスタイル情報を提供してレイアウトやデザインを決めるものウィンドウ幅に合わせて伸縮するレイアウトやサイト、画面サイズに合わせてレイアウトを大きく変えるレスポンシブWEBデザインで作られているWEBページがありますが、レスポンシブもHTMLとCSSが初めに読み込まれた時から変わることはないです。HTMLとCSSは不変で静止したデータと言えます。
しかし、javascriptを使うことによって、これらの静止したデータであるHTMLとCSSを、その場でリアルタイムに書き換えることができます。加えて一部のコンテンツを入れ替えたり、画像のスライドショーのような動きをつけたりすることができます。
具体的な「書き換え」の例
HTMLを書き換える方法ですが、大きく4つに分けられてます。
1.タグに囲まれたテキストを書き換える
//idから取得したオブジェクトを日付データに書き換えてます document.getElementById("choice").textContent = new Date(); console.log(document.getElementById("choice").textContent);2.要素を追加・削除する
//li要素を追加 var todo = ['デザイン','データ','申し込み','牛乳を買う']; todo.push('歯医者にいく'); for(var i = 0; i < todo.length; i++){ var li = document.createElement('li'); li.textContent = todo[i]; document.getElementById('list').appendChild('li'); }3.タグの属性の値を変える
function changePic(){ var aaa=document.getElementById("picdiv"); aaa.innerHTML="<img src='girls2.jpg'>" }4.CSSの値を変化させる
上記4つのパターンでHTMLとCSSが書き換えられると、変更された内容がブラウザに即時反映されます。しかも、画面が描き替わるのは変更があった箇所だけで、ページ全体の読み込みは発生せずに待ち時間もありません。これにより単なるWEBページではない、より動きのあるWEBページを作ることができます。
- 投稿日:2019-07-01T17:34:01+09:00
Vuex の使い方を勉強してみた
はじめに
Qiita 初投稿です
Vuex について勉強した際のメモをまとめました。
まだWeb開発初心者のため、単語や言い回しなどおかしなところがあるかもしれません。間違った記載がありましたらご教授いただけると嬉しいです!
Vuex とは
Vue を用いたアプリケーションの開発では、コンポーネント間でのデータのやりとりが頻繁に発生する。
コンポーネント間でデータの整合性を保つためには 各コンポーネントで値渡しの処理を記述する必要があり、ソースの可読性とデバッグ効率が低下しやすい。Vuex は、Vueアプリケーションにおけるデータの状態管理を一元化して開発効率を上げることを目的としたライブラリである。
Vue アプリケーションで扱うデータセットを Store と呼ばれる領域で一元管理することで、各コンポーネントは Store にアクセスすれば常に共通の値を参照することができるようになる。
また、Store のデータに対する操作を予め定義しておけるので、予期しない操作の防止や保守性・可読性の向上が見込める。Vuex / Store の定義
Vuex を Vue アプリケーションで使用する際は以下のように宣言し、Store を定義する。
store/index.js"use strict" import { Vue } from "vue" import { Vuex } from "vuex" Vue.use(Vuex); // Storeを生成 const store new Vuex.Store({ state: { ... }, getters: { ... }, mutations: { ... }, actions: { ... } }); export default store;main.jsimport Vue from "vue" import store from "store" // Vueインスタンスの定義時に、Store情報を組み込む new Vue({ el: '#app', store, render: h => h(App) });
store
の宣言でstate
,getters
,mutations
,actions
という項目があるが、これらは Store が保持するデータ項目や、Store 上のデータを外部(コンポーネント等)から操作するための関数を定義する項目である。
state
,getters
,mutations
,actions
Store の作成時に定義できる項目は下記の4つである。
項目名 概要 state Store で管理するデータ項目の定義 getters state 内のデータの状態から算出される値(≒算出プロパティ) mutations state のデータを直接操作するための関数(非同期処理は定義不可) actions mutations の操作を各コンポーネントから呼び出すために使用する関数(非同期処理を定義可) ↓ 定義のイメージ
const store new Vuex.Store({ state: { ... }, getters: { ... }, mutations: { ... }, actions: { ... } });それぞれの項目は用途によって使い分けされるので、順番に説明する。
state
( Store で管理するデータ項目の定義 )Vuex の Store で管理するデータ項目を定義する。
ここに定義したデータは Vueアプリケーション内の各コンポーネントから適宜取得・更新することができる。state 定義の例
store/index.js// Store 定義 const store = new Vuex.Store({ state: { count: 0 }, // ... });次のように定義することもできる。
store/index.jsconst state = { count: 0 }; // Store 定義 const store = new Vuex.Store({ state, // ... });コンポーネントから state の値を使用
this.$store.state
の値をcomputed
で監視する。components/Counter.vue<template> <div>{{ count }}</div> </template> <script> export default { name: "Counter", computed: { count () { return this.$store.state.count } } }; </script>
mapState
ヘルパーを使用した方が簡潔に書ける。store/index.js// Store 定義 const store = new Vuex.Store({ state: { count1: 0, count2: 0 }, // ... });components/Counter.vue<template> <div> <div>{{ count1 }}</div> <div>{{ count2 }}</div> </div> </template> <script> import { mapState } from "vuex" export default { name: "Counter", computed: { ...mapState([ "count1", // 注意)プロパティ名は ' または " でくくる必要がある "count2" ]) } }; </script>state データの更新・削除について
state
は 直接更新・削除を行なってはいけない 。基本的に Store 内のデータ操作は、後述する mutations に定義する。
getters
( state 内のデータの状態から算出される値(≒算出プロパティ))
getters
ではstate
のデータに対する算出プロパティを定義し、各コンポーネントで利用できる。例えば、TODOリストの未完了のデータ数を取得する関数
doneTodoCount
が、以下のように定義されるとする。components/TodoList.vue<template> <!-- ... --> </template> <script> export default { name: "TodoList", computed: { doneTodoCount () { return this.$store.state.todos.filter(todo => todo.done).length } } }; </script>上記の書き方で目的は果たせるが、他のコンポーネントでこの関数を利用したい場合にはこの関数をコピーするか、共通処理として外部モジュールに切り出してインポートする必要がある。
getters
を使用することで、Store 経由で共通の算出プロパティとして使用できるようになる。getters の定義
getters
に定義する関数は第1引数にstate
をもち、ここから Store のデータにアクセスできる。store/index.js// Store 定義 const store = new Vuex.Store({ state: { todos: [ { id: 1, label: '...', done: true }, { id: 2, label: '...', done: false } ] }, getters: { // 第1引数に state をもつ doneTodoCount: (state) => { return state.todos.filter(todo => todo.done).length } } // ... });コンポーネントから getters の関数を使用
state
と同様の形でthis.$store.getters
に含まれるゲッター関数をcomputed
で監視する。components/TodoList.vue<template> <!-- ... --> </template> <script> export default { name: "TodoList", computed: { doneTodoCount () { return this.$store.getters.doneTodoCount } } }; </script>
mapGetters
ヘルパー関数によって参照することもできる。components/TodoList.vue<template> <!-- ... --> </template> <script> import { mapGetters } from "vuex" export default { name: "TodoList", computed: { ...mapGetters([ "doneTodoCount" ]) } }; </script>※getters では同期的な処理のみを記述する。Ajax等の非同期処理を実行したい場合は、後述する actions で定義する。
mutations
( state のデータを直接操作する関数 )記述中
actions
( mutations の操作 + 非同期処理する関数 )記述中
モジュール分割による store の切り分け
Store は以下のようにモジュール分割して定義することもできる。
store/moduleA.jsconst moduleA = { state: { ... }, getters: { ... }, mutations: { ... }, actions: { ... } }; export default moduleA;store/moduleB.jsconst moduleB = { state: { ... }, getters: { ... }, mutations: { ... }, actions: { ... } }; export default moduleB;store/index.jsimport moduleA from "moduleA.js" import moduleB from "moduleB.js" const store = new Vuex.Store({ modules: { A: moduleA, B: moduleB } });モジュール分割することで Store が肥大化することを防ぎ、またカテゴリ等によって Store を分けて管理できる。
デフォルトでは各モジュールで宣言したgetters
,mutations
,actions
はグローバル名前空間に登録されるため、複数のモジュールが同じミューテーション/アクションタイプに反応することになる。名前空間をモジュール単位で登録したい場合は、モジュールの宣言時に
namespaced = true
を設定する。store/moduleA.jsconst moduleA = { namespaced = true, state: { ... }, getters: { ... }, mutations: { ... }, actions: { ... } };モジュール分割した Store をコンポーネントから参照する
以下のようなモジュール分割された Store を定義し、コンポーネントから参照してみる。
store/moduleA.jsconst moduleA = { namespaced = true, state: { result: undefined }, mutations: { setResult(state, data) { state.result = data }, clearResult({ commit }) { state.result = undefined } }, actions: { setResult({ commit }, data) { commit("setResult", data) }, clearResult({ commit }) { commit("clearResult") } } }; export default moduleA;store/moduleB.jsconst moduleB = { // moduleA と同じ内容 }; export default moduleB;
moduleA
、moduleB
はそれぞれが
- データ項目
result
- データ操作用のアクション
setResult
、clearResult
を持つ。
以下のコンポーネントでは、
moduleA
、moduleB
の データ項目result
に対して表示・更新・クリアができる。components/Sample.vue<template> <div> <div>{{ result_A }}</div> <div>{{ result_B }}</div> <input type="text" v-model="input_A"> <button @click="_set_A">更新</button> <button @click="clearResult_A">クリア</button> <input type="text" v-model="input_B"> <button @click="_set_B">更新</button> <button @click="clearResult_B">クリア</button> </div> </template> <script> import { mapState, mapActions } from "vuex" export default { name: "Sample", data: { input_A: "", input_B: "" }, computed: { // 第1引数に名前空間(moduleA, moduleB)を指定し、 // それぞれの "state.result" を別名で取得 ...mapState("moduleA", { result_A: state => state.result }), ...mapState("moduleB", { result_B: state => state.result }), }, methods: { // 第1引数に名前空間(moduleA, moduleB)を指定し、 // それぞれのアクションを別名で取得 ..mapActions("moduleA", [ setResult_A: "setResult", clearResult_A: "clearResult" ]), ..mapActions("moduleB", [ setResult_B: "setResult", clearResult_B: "clearResult" ]), // ↓ setResult に引数を渡すために定義 // Aのボタン押下処理 _set_A: function () { this.setResult_A(this.input_A); }, // Bのボタン押下処理 _set_B: function () { this.setResult_B(this.input_B); } } }; </script>上記の
..mapActions("moduleA", [ ... ])
のように、ヘルパー関数の第1引数に名前空間(moduleA, moduleB)を指定することで、指定したモジュールの store を操作できる。まとめ
Vuex を使うと状態管理がだいぶ楽になることが分かりました。
また、データの扱いがある程度ルール化されているので、初心者にはありがたいです。
最後までご覧いただきありがとうございました!
- 投稿日:2019-07-01T17:29:19+09:00
webpack4でsassを別ファイルとして出力させたいのにエラーがでてしまった
概要
sassをjsにバンドルさせるのではなく、外部ファイルとして
<link rel="stylesheet" href="style.css">
な感じでhtmlで読み込ませるようにしたいなぁと思いました。
src
でsassを入力し、dist
なりビルド後のファイルとしてstyle.css
出力するかたちにしたかったのですが、どうにも下記のエラーが出てしまいコンパイルが止まってしまうのです。問題のエラー文
(node:7683) DeprecationWarning: Tapable.plugin is deprecated. Use new API on `.hooks` instead /Users/[プロジェクトパス]/node_modules/webpack/lib/Chunk.js:849 throw new Error( ^ Error: Chunk.entrypoints: Use Chunks.groupsIterable and filter by instanceof Entrypoint instead at Chunk.get (/Users/[プロジェクトパス]/node_modules/webpack/lib/Chunk.js:849:9) at /Users/[プロジェクトパス]/node_modules/extract-text-webpack-plugin/dist/index.js:176:48 at Array.forEach (<anonymous>) at /Users/[プロジェクトパス]/node_modules/extract-text-webpack-plugin/dist/index.js:171:18 at AsyncSeriesHook.eval [as callAsync] (eval at create (/Users/[プロジェクトパス]/node_modules/tapable/lib/HookCodeFactory.js:32:10), <anonymous>:7:1) at AsyncSeriesHook.lazyCompileHook (/Users/[プロジェクトパス]/node_modules/tapable/lib/Hook.js:154:20) at Compilation.seal (/Users/[プロジェクトパス]/node_modules/webpack/lib/Compilation.js:1244:27) at hooks.make.callAsync.err (/Users/[プロジェクトパス]/node_modules/webpack/lib/Compiler.js:624:17) at _err0 (eval at create (/Users/[プロジェクトパス]/node_modules/tapable/lib/HookCodeFactory.js:32:10), <anonymous>:11:1) at _addModuleChain (/Users/[プロジェクトパス]/node_modules/webpack/lib/Compilation.js:1095:12) at processModuleDependencies.err (/Users/[プロジェクトパス]/node_modules/webpack/lib/Compilation.js:1007:9) at process._tickCallback (internal/process/next_tick.js:61:11)環境
"dependencies": { "axios": "^0.18.0", "node-sass": "^4.11.0", "sass-loader": "^7.1.0", "vue": "^2.5.22" }, "devDependencies": { "@babel/core": "^7.2.2", "@babel/preset-env": "^7.3.1", "autoprefixer": "^9.4.7", "babel-loader": "^8.0.5", "css-loader": "^2.1.0", "eslint": "^5.13.0", "eslint-loader": "^2.1.1", "extract-text-webpack-plugin": "^4.0.0-beta.0", "postcss-loader": "^3.0.0", "vue-loader": "^15.6.2", "vue-router": "^3.0.2", "vue-template-compiler": "^2.5.22", "webpack": "^4.29.0", "webpack-cli": "^3.2.1", "webpack-dev-server": "^3.1.14" }解決策
https://github.com/webpack-contrib/extract-text-webpack-plugin/issues/760
ドンピシャなものがgithubのissueに上がっていました。メモ
こちら、数ヶ月前の下書きで、具体的な解決方法を忘れてしまいました;;
上記githubには
Solution: npm i -D extract-text-webpack-plugin@next
と書いてあるので、これを参考にしたと思うのですが
またwebpack使用時に思い出したら確実な結果を追記したいと思います。とりあえず、苦労して見つけた解決策のような気がしたので備忘録として残しておきます。
- 投稿日:2019-07-01T17:07:33+09:00
gulpインストールと一括管理用のフォルダ構成
gulp導入しました。
とりあえず、インストール用のメモ。
導入自体はNodeとnpm入れてれば簡単にできます。グローバルインストールやらローカルインストールやらなんのこっちゃって感じだったのですが、
(グローバルにインストールしたら、ローカルはいらないのかと思ったら、そうでもないようで...)
管理も面倒だし、ひとまずはローカルだけでいいやという結論です。事前準備
- Node.js
- npm (Node.jsのパッケージ管理)
フォルダ構成の準備
ローカルインストールだと、フォルダごと、プロジェクトごとにgulpをいれなきゃいけないのが面倒だとおもうので、gulpはひとつで、gulpfile.js(gulpの設定ファイル)のみ、プロジェクトごとの管理にします。
準備段階の構成例
/親フォルダ/
└ /プロジェクト1/
└ index.html
└ /プロジェクト2/
└ index.html
これを準備してください。最終的な構成目標
/親フォルダ/
└ /プロジェクト1/
└ index.html
└ gulpfile.js (gulpの設定ファイル)
└ node_modules (node_modulesのエイリアス)
└ /プロジェクト2/
└ index.html
└ gulpfile.js (gulpの設定ファイル)
└ node_modules (node_modulesのエイリアス)
└ /node_modules/ (モジュール管理フォルダ)
└ package.json (Node.jsのpackage管理ファイル)package.json作成
まずは
cd
で親フォルダまで移動して、下記のコマンドを入れます。$ npm init色々聞かれますが、全てenter(return)で大丈夫です。
これでpackage.jsonが作成されます。gulpのインストール
ローカルインストール(
--save-dev
、--save
)$ npm install gulp --save-dev
--save
でインストールすると、webサイトに使用するデータと見なされるので、
--save-dev
で、開発用のデータですよ、と分類してあげてるみたいです。ちなみにグローバルインストール
$ npm install -g gulpこれで/node_modules/が生成されます。
この中にモジュールがインストールされます。/node_modules/のエイリアス作成
いわゆるショートカットです。右クリックから作成して、それぞれのプロジェクト内に配置します。
このとき、名前は全て「node_modules」にしてください。エイリアスを作成すると、きちんと参照先のnode_modulesを読んでくれるので、
gulpの管理がひとつで済みます。現在のフォルダ構成
/親フォルダ/
└ /プロジェクト1/
└ index.html
└ node_modules (node_modulesのエイリアス)
└ /プロジェクト2/
└ index.html
└ node_modules (node_modulesのエイリアス)
└ /node_modules/ (モジュール管理フォルダ)
└ package.json (Node.jsのpackage管理ファイル)gulpfile.jsの作成
それぞれのプロジェクトに合わせて、設定ファイルを作ります。
/親フォルダ/
└ /プロジェクト1/
└ index.html
└ gulpfile.js (gulpの設定ファイル) ←これです
└ node_modules (node_modulesのエイリアス)
└ /プロジェクト2/
└ index.html
└ gulpfile.js (gulpの設定ファイル)
└ node_modules (node_modulesのエイリアス)
└ /node_modules/ (モジュール管理フォルダ)
└ package.json (Node.jsのpackage管理ファイル)gulpfile.jsについては、それについてだけの記事がいいので、ちょっと分けます。
gulpfile.jsの記事書きました。
- 投稿日:2019-07-01T17:03:53+09:00
#babylonjs で意図的にフレームレートを落とす
グラフィカルなデバッグを行う際、数フレームレベルの動作を検証する必要があったりします。
通常の
engine.runRenderLoop
だけでは window.requestAnimationFrame が呼ばれ、モニタのリフレッシュレートに最適化された速度で再生されてしまいます。Unity では Time.timeScale を利用してスローモーションにすることが出来ますが、 babylon.js にそのような API は存在しません。
そこで、 v4 で実装された
engine.customAnimationFrameRequester: Nullable<ICustomAnimationFrameRequester>
を上書きすることで、フレームレートを落とすことが出来ます。https://doc.babylonjs.com/api/interfaces/babylon.icustomanimationframerequester
interface ICustomAnimationFrameRequester { renderFunction?: Function; requestAnimationFrame: Function; requestID?: number; }これは元々は WebXR のために作られた API です。
WebVR では、
window.requestAnimationFrame
の代わりに VRDisplay.requestAnimationFrame を用いる必要があります。これは外部 HMD とメインモニタとでリフレッシュレートが異なる可能性があるため、アクティブになっている VRDisplay がある場合はそちらのリフレッシュレートを優先して処理します。※今後置き換わる WebXR Spec では、 XRSession.requestAnimationFrame を利用することになる予定です。
今回はこちらを使ってフレームレートを落とします。
const targetFPS = 30; engine.customAnimationFrameRequester = { requestAnimationFrame: (func) => { setTimeout(func, Math.round(1000 / targetFPS)); }, };単純に、
1000 / targetFPS
ミリ秒遅延して関数を呼ぶようにするだけです。これは今回フレームの処理時間を考慮していないため、実際には 30 FPS 未満になってしまいますが、window.requestAnimationFrame
が実装されていなかった場合のフォールバックがsetTimeout(func, 16)
なのでまあいいとしましょう。この方法を応用すれば、「ボタンをクリックしたら次のフレームを動かす」という動作も可能です。
let progressFrame = false; document.getElementById('progress-frame').addEventListener('click', () => { progressFrame = true; }); const customRequestAnimationFrame = (func) => { setTimeout(() => { if (progressFrame) { func(); progressFrame = false; return; } customRequestAnimationFrame(func); }, 1); }; engine.customAnimationFrameRequester = { requestAnimationFrame: customRequestAnimationFrame, };※残念ながらフレームを巻き戻すことは今の所出来ないようです。
- 投稿日:2019-07-01T16:41:58+09:00
【備忘録】React & TypeScript & Webpack4 & Babel7 & dev-server の最小構成ボイラープレートの作成
WebpackとBabelの復習の題材として、最近流行りのReact & TypeScript で最小構成のボイラープレートを作成したので、作成手順と解説を残しておく。
環境
Nodeとnpmは以下の通り。
Node version : 10.15.2
npm version : 6.9.0使用するnpmモジュール
今回使用するモジュールは以下の通り。(使用するモジュールは全てlatest)
npmモジュール名 バージョン 説明 react 16.8.6 - react-dom 16.8.6 - webpack 4.35.0 モジュールバンドラ本体 webpack-cli 3.3.5 webpackをコマンドで実行できるようにする webpack-dev-server 3.7.2 webpackでの開発サーバーの立ち上げ用 @babel/core 7.4.5 トランスパイラ本体 @babel/preset-env 7.4.5 トランスパイラ本体 @babel/preset-react 7.0.0 React用 @babel/preset-typescript 7.3.3 TypeScript用 babel-loader 8.0.6 webpackで使用するできるようにする @types/react 16.8.22 React用の型定義モジュール @types/react-dom 16.8.4 ReactDOM用の型定義モジュール プロジェクト基盤の作成
各種ディレクトリとファイルを作成する。
以下の構造を持つプロジェクト基盤を作成する。. ├── src │ └── index.tsx ├── index.html ├── package.json ├── .babelrc └── webpack.config.jspackage.jsonファイルに設定追加とnpmモジュールの追加
使用するnpmモジュールをpackage.jsonに設定する。
ついでにscriptsにwebpack-dev-server起動用コマンドと、ビルド用コマンドを設定する。{ "name": "react-ts-webpack", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "start": "webpack-dev-server --open", "build": "webpack" }, "keywords": [], "dependencies": { "@babel/core": "^7.4.5", "@babel/preset-env": "^7.4.5", "@babel/preset-react": "^7.0.0", "@babel/preset-typescript": "^7.3.3", "@types/react": "^16.8.22", "@types/react-dom": "^16.8.4", "babel-loader": "^8.0.6", "react": "^16.8.6", "react-dom": "^16.8.6", "webpack": "^4.35.0", "webpack-cli": "^3.3.5" }, "devDependencies": { "webpack-dev-server": "^3.7.2" } }package.json作成後、以下のコマンドを実行してnpmモジュールをインストールする。
$ npm install.babelrcファイルに設定追加
.babelrcファイルにトランスパイラの設定を追加する。
ES6~をES5に変換する @babel/preset-env と、今回対象となる React、TypeScript を用のプリセットを設定する。{ // プリセットを使用して、ES6 ~ を ES5 に変換 // 今回は React と TypeScript が対象のため、専用のプリセットも追加 "presets": ["@babel/preset-env", "@babel/react", "@babel/typescript"] }webpack.config.jsファイルに設定追加
webpack.config.jsファイルに各種設定を追加する。
const path = require('path'); const rules = [{ // 対象とする拡張子を指定 test: /\.tsx?/, // 対象から外すディレクトリを指定 exclude: /node_modules/, // babelを使用する loader: 'babel-loader', }]; module.exports = { // ブラウザ環境で使用するためwebをtargetとする target: 'web', // モード値を production に設定すると最適化された状態で、 // development に設定するとソースマップ有効でJSファイルが出力される mode: 'development', // 起点となるTSXファイル(エントリーポイント) entry: './src/index.tsx', // ビルド後の出力先設定 output: { // 出力先パス path: path.resolve(__dirname, 'build'), // ファイル名 filename: 'bundle.js', }, module: { // ビルド時に使用するルール(上で設定)を設定 rules }, resolve: { // 対象とする拡張子を指定 extensions: ['.ts', '.tsx', '.js'] }, // webpack-dev-serverの設定 devServer: { // 起点となるパス contentBase: './', // ポート番号 port: 5000, }, };ここまでの設定で使用する準備は完了。
動作確認
動作を確認するためにindex.htmlとindex.tsxの中身を実装する。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>TypeScript App</title> </head> <body> <div id="app-root"></div> <script src="bundle.js"></script> </body> </html>import React from 'react'; import ReactDOM from 'react-dom'; function App(): JSX.Element { const sum = (a: number, b: number): number => a + b; return ( <div> <h1>React & TypeScript!</h1> <p>Test: {sum(15, 15)} </p> </div> ); } export default App; const root = document.getElementById('app-root'); ReactDOM.render(<App />, root);実装が完了したら、以下のコマンドを実行して開発用サーバー起動することを確認する。
$ npm start > react-ts-webpack@1.0.0 start /Users/kento/Programing/VScodeProjects/TypeScriptWithModernReact/react-simple-todo-list > webpack-dev-server --open ℹ 「wds」: Project is running at http://localhost:5000/ ℹ 「wds」: webpack output is served from / ℹ 「wds」: Content not from webpack is served from ./ ℹ 「wdm」: wait until bundle finished: / ℹ 「wdm」: Hash: 530304f8ddadb1675c93 Version: webpack 4.35.0 Time: 1623ms Built at: 2019-07-01 16:29:25 Asset Size Chunks Chunk Names bundle.js 1.23 MiB main [emitted] main Entrypoint main = bundle.js [0] multi (webpack)-dev-server/client?http://localhost:5000 ./src/index.tsx 40 bytes {main} [built] [./node_modules/ansi-html/index.js] 4.16 KiB {main} [built] [./node_modules/html-entities/index.js] 231 bytes {main} [built] [./node_modules/react-dom/index.js] 1.33 KiB {main} [built] [./node_modules/react/index.js] 190 bytes {main} [built] [./node_modules/webpack-dev-server/client/index.js?http://localhost:5000] (webpack)-dev-server/client?http://localhost:5000 4.29 KiB {main} [built] [./node_modules/webpack-dev-server/client/overlay.js] (webpack)-dev-server/client/overlay.js 3.51 KiB {main} [built] [./node_modules/webpack-dev-server/client/socket.js] (webpack)-dev-server/client/socket.js 1.53 KiB {main} [built] [./node_modules/webpack-dev-server/client/utils/createSocketUrl.js] (webpack)-dev-server/client/utils/createSocketUrl.js 2.77 KiB {main} [built] [./node_modules/webpack-dev-server/client/utils/log.js] (webpack)-dev-server/client/utils/log.js 964 bytes {main} [built] [./node_modules/webpack-dev-server/client/utils/reloadApp.js] (webpack)-dev-server/client/utils/reloadApp.js 1.63 KiB {main} [built] [./node_modules/webpack-dev-server/client/utils/sendMessage.js] (webpack)-dev-server/client/utils/sendMessage.js 402 bytes {main} [built] [./node_modules/webpack-dev-server/node_modules/strip-ansi/index.js] (webpack)-dev-server/node_modules/strip-ansi/index.js 161 bytes {main} [built] [./node_modules/webpack/hot sync ^\.\/log$] (webpack)/hot sync nonrecursive ^\.\/log$ 170 bytes {main} [built] [./src/index.tsx] 420 bytes {main} [built] + 29 hidden modules ℹ 「wdm」: Compiled successfully.次は、ビルドが実行できるかの確認をする。以下のコマンドを実行する。
$ npm run build > react-ts-webpack@1.0.0 build /Users/kento/Programing/VScodeProjects/TypeScriptWithModernReact/react-simple-todo-list > webpack Hash: 7061f3fb5f989d0481ae Version: webpack 4.35.0 Time: 942ms Built at: 2019-07-01 16:27:49 Asset Size Chunks Chunk Names bundle.js 907 KiB main [emitted] main Entrypoint main = bundle.js [./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 472 bytes {main} [built] [./src/index.tsx] 420 bytes {main} [built] + 11 hidden modules実行後、プロジェクト内を確認すると、build ディレクトリが作成されていることが確認できる。
また、作成されたディレクトリ内にbundle.jsというファイルが作成される。
以上で確認完了。
作成したボイラープレートは こちら
- 投稿日:2019-07-01T15:53:57+09:00
jsでのDOM操作(初学で詰まったとこ)
jsでのDOM操作(初学で詰まったポイント)
今日からjavascriptを学習し始めました。C系しか触ったことないので
混乱しています。?DOM操作つまづきポイント:
・querySelectorで複数要素を取れるか??
・データ属性も取れるのか??
・querySelectorは該当したものが複数ある場合どうしているか??
・独自のセレクターも取れるのか??
・getElementsByClassNameは常に複数なのか??
・子要素をつけたり外したりはどうするのか?????????????????????????????????
querySelectorで複数要素を取れるのか??
⇨取れません。
querySelectorの指定方法で複数要素をとりたいときはquerySelectorAllを使うようです。var elements = document.querySelectorAll('.iruka');これでirukaクラスが全て取れます。
データ属性も取れるのか??
⇨取れました。
データ属性・・・ユーザごとが自分で作れる独自のHTMLの属性。<div data-animal="iruka">?</div>data-animalなんて属性は聞いたことがありません。これがデータ属性らしいです。接頭辞にdata-をつけます。
データ属性は以下の記法で取得できます。var element = document.querySelector('[data-animal="iruka"]');querySelectorは該当したものが複数ある場合どうしているか??
例えば,
<div class="iruka">バンドウイルカ</div> <div class="iruka">アマゾンカワイルカ</div>というHTMLだった場合に、
var element = document.querySelector('.iruka');としたとき、elementの中はバンドウイルカなのでしょうか?アマゾンカワイルカなのでしょうか?
正解は、querySelectorは最初の要素(=上の方に書かれた要素)を取得するので
バンドウイルカになります。独自のセレクターも取れるのか??
→普通に取れます。
<skin>キュッキュ</skin>こんな独自のセレクターが現れた場合はこうです。
var element = document.querySelector('skin');独自セレクターも普通のセレクターと同じ扱いです。
getElementsByClassNameは常に複数なのか??
→いつだって複数です。なので注意が必要な場合があります。
<div class="alone">?</div>このようにaloneクラスがドキュメント全体で一個しかない場合でも、複数扱いになります。よって
var element = document.getElementsByClassName('alone'); element.style.display = 'none';としても、イルカは消えません。
消すには、element[0]と要素指定してあげるか、querySelector('.alone')でとってあげましょう。タグをつけたり外したりはどうするのか??
忘れそうなのでメモ。
element.appendChild(追加する子要素); element.removeChild(除去する子要素);今日は以上です。
ちなみにイルカも好きですが、シャチの方が好きなので、シャチの絵文字が出ないかな〜
といつも思ってます。?
- 投稿日:2019-07-01T15:42:32+09:00
[勉強用] React hooksで非同期処理を書く (ステップ5)
はじめに
前ステップから続き、勉強用のuseFetchを書いていきます。今回のテーマはキャンセル処理です。
課題
URLを変更すると、クリーンアップ処理が走り、新しいデータ取得が走りますが、ブラウザが処理をしている前のデータ取得の処理を止めることができたわけではありません。例えば、長いデータを転送中だったり、サーバからの応答を待っていたり、サーバの名前解決をしていたりする場合は、データ取得の処理は継続しています。fetchはpromiseベースのAPIですが、AbortControllerを使うことで処理をキャンセル(abort)することができます。
ステップ5: キャンセル処理
const useFetch = url => { const [result, setResult] = useState({}); useEffect(() => { let cleanedUp = false; const abortController = new AbortController(); const fetchData = async () => { try { const response = await fetch(url, { signal: abortController.signal }); if (!response.ok) throw new Error(`status: ${response.status}`); const data = await response.json(); if (!cleanedUp) { setResult({ data }); } } catch (error) { if (!cleanedUp) { setResult({ error }); } } }; setResult({ loading: true }); fetchData(); const cleanup = () => { cleanedUp = true; abortController.abort(); setResult({}); }; return cleanup; }, [url]); return result; };念のため、cleanupでresultも初期化するようにしました。(一瞬以前のresultで描画されるを防ぐため)
動作確認
実際に動くコードはこちらです。codesandbox
キャンセルの動作はUIで直接は分かりませんので、Chrome DevToolsでNetworkタブを確認してください。このように、ローディング中にURLを変更すると、canceledとなっていることを確認できると思います。
おわりに
本コードは勉強用ですので、そのままでは使わないでください。(ちゃんとした実装はこちら)
さらなる課題と解決は次のステップへ。
- 投稿日:2019-07-01T14:54:08+09:00
Vuexのモジュールを使用してストアを管理する
Vuex で状態管理を行っているアプリケーションでは、基本的にストアは1つだと思います。
アプリケーションが大きくなるにつれストアで管理する必要がある状態は多くなった場合、沢山のステートやミューテーションなどを一つのオブジェクトで管理する事になってしまうでしょう。
そういった事態を防ぐ為に、Vuex にはモジュールオプションが用意されています。
これに加えて、個人的にモジュールオプションを使用する事のメリットとして、管理するステートに紐づくオプションをまとめてグループ化できる事だと感じています。私がVuexのモジュールオプションについて調べた時、分割したモジュールのステートへのアクセスや更新を行う方法に混乱して、なかなか理解が進みませんでした。
個人的な振り返りも含めて、簡単な「TODOアプリ」の作成を通して Vuex のモジュールの使用について記事にします。この記事では、以下の環境で作業を進めていきます。
- node -> 10.15.3
- npm -> 6.4.1
- @vue/cli -> 3.8.4
プロジェクトの作成
まずは、適当なディレクトリでプロジェクトを作成します。
vue create vue-todo-appVuexを使用する為、
Manually select features
を選択し、その後Vuexを選びます。? Please pick a preset: default (babel, eslint) ❯ Manually select features? Check the features needed for your project: ◉ Babel ◯ TypeScript ◯ Progressive Web App (PWA) Support ◯ Router ❯◉ Vuex ◯ CSS Pre-processors ◉ Linter / Formatter ◯ Unit Testing ◯ E2E Testingその他、いくつかの質問が表示されるので回答していきます。
今回は以下のように選択を進めました。? Pick a linter / formatter config: (Use arrow keys) ❯ ESLint with error prevention only ESLint + Airbnb config ESLint + Standard config ESLint + Prettier? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i > to invert selection) ❯◉ Lint on save ◯ Lint and fix on commit? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? (Use arro w keys) ❯ In dedicated config files In package.json? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In dedica ted config files ? Save this as a preset for future projects? (y/N) Nインストールが完了したら、開発環境を実行します。
cd vue-todo-app yarn serve
続いて、
src/App.vue
を編集します。src/App.vue<template> <div id="app"> <h1>Vuex module option demo</h1> </div> </template> <script> export default { name: 'app' } </script> <style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: left; color: #2c3e50; margin-top: 60px; } </style>ストアの作成
@vue/cli でプロジェクトを作成した場合は、
src/store.js
にストアを定義したファイルが配置されています。
モジュールオプションを使用する際に ES Modules を使用する為にディレクトリの構成を変更していきます。mkdir src/store mkdir src/store/modules mv src/store.js src/store/store.js続いて、タスクを管理するモジュールとして
tasks.js
と、担当者を管理するモジュールとしてpersons.js
をsrc/store/modules/配下
に作成します。touch src/store/modules/tasks.js src/store/modules/persons.js
最後に
src/main.js
でストアの読み込み先を変更します。src/main.jsimport Vue from 'vue' import App from './App.vue' import store from './store/store' Vue.config.productionTip = false new Vue({ store, render: h => h(App) }).$mount('#app')モジュールにステートを定義をして読み込む
src/store/modules/tasks.jsexport default { state: { tasks: [ {id: 1, name: 'sample task 1', status: true, personId: 1}, {id: 2, name: 'sample task 1', status: false, personId: 2}, ], }, }src/store/modules/persons.jsexport default { state: { persons: [ {id: 1, name: '一郎'}, {id: 2, name: '次郎'} ] } }modulesのオブジェクトの中でインポートしたモジュールを読み込みます、
src/store/store.jsimport Vue from 'vue' import Vuex from 'vuex' import tasks from './modules/tasks' import persons from './modules/persons' Vue.use(Vuex) export default new Vuex.Store({ modules: { tasks, persons } })ストアに登録したモジュールのステートをコンポーネント側で読み込む
モジュールとして分割した場合でもステートを読み込む方法は変わりません。
コンポーネント側で読み込んだステートはモジュールの名前が付いたオブジェクトにラップされた状態となります。
これによってステートは、モジュール毎にスコープを分離する事ができます。
tasks
というモジュールをmapState
で読み込んだ場合は、コンポーネント側では以下のようなオブジェクトとして保持しています。{ // ~~~ 省略 ~~~~ tasks: { tasks: [ {id: 1, name: 'sample task 1', status: true, personId: 1}, {id: 2, name: 'sample task 2', status: false, personId: 2}, ] }, // ~~~ 省略 ~~~~ }この点を踏まえた上で、
src/App.vue
を編集します。
src/App.vue
で読み込んだステートを TaskList コンポーネントに props として渡します。src/App.vue<template> <div id="app"> <h1>Vuex module option demo</h1> <task-list :taskList="tasks.tasks" :personList="persons.persons" /> </div> </template> <script> import Vuex from 'vuex' import TaskList from '@/components/TaskList' export default { name: 'app', components: { TaskList, }, computed: { ...Vuex.mapState(['tasks', 'persons']), } } </script> <style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: left; color: #2c3e50; margin-top: 60px; } </style>
src/components/配下
に TaskList コンポーネントを作成します。src/components/TaskList.vue<template> <ul class="task-list"> <li v-for="task in taskList" :key="task.id"> <label> <input type="checkbox" :checked="task.status"> <span>{{task.name}}</span> <span> / </span> <span>担当者: {{getPersonName(task.personId)}}</span> </label> </li> </ul> </template> <script> export default { name: 'TaskList', props: { taskList: { type: Array, default: () => [], }, personList: { type: Array, default: () => [], }, }, methods: { getPersonName (id) { const person = this.personList.find((person) => { return person.id === id }) return person ? person.name : '未設定' }, }, } </script> <style scoped> .task-list { list-style: none; padding-left: 0; } </style>これで、ストアにモジュールとして登録したステートを使用する事ができました。
ステートの状態を更新する
チェックボックスの状態が変更されたタイミングでタスクのステートを変更できるようにします。
まずは、src/store/modules/tasks.js
にミューテーションとアクションを定義します。src/store/modules/tasks.jsexport default { state: { tasks: [ {id: 1, name: 'sample task 1', status: true, personId: 1}, {id: 2, name: 'sample task 2', status: false, personId: 2}, ], }, mutations: { changeCheckStatus (state, {id, checked}) { const tasks = state.tasks.slice() const task = tasks.find((task) => { return task.id === id }) task.status = checked state.tasks = tasks }, }, actions: { changeCheckStatus ({commit}, payload) { commit('changeCheckStatus', payload) }, }, }次に、
src/App.vue
側で先程定義したアクションを読み込みます。src/App.vue<template> <div id="app"> <h1>Vuex module option demo</h1> <task-list :taskList="tasks.tasks" :personList="persons.persons" @changeCheckStatus="changeCheckStatus" /> </div> </template> <script> import Vuex from 'vuex' import TaskList from '@/components/TaskList' export default { name: 'app', components: { TaskList, }, computed: { ...Vuex.mapState(['tasks', 'persons']), }, methods: { ...Vuex.mapActions(['changeCheckStatus']), }, } </script> <style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: left; color: #2c3e50; margin-top: 60px; } </style>最後に TaskList コンポーネント側から、input タグで change イベントが発生するタイミングで
$emit()
を実行します。src/components/TaskList.vue<template> <ul class="task-list"> <li v-for="task in taskList" :key="task.id"> <label> <input type="checkbox" :checked="task.status" @change="handleCheck($event, task.id)"> <span>{{task.name}}</span> <span> / </span> <span>担当者: {{getPersonName(task.personId)}}</span> </label> </li> </ul> </template> <script> export default { name: 'TaskList', props: { taskList: { type: Array, default: () => [], }, personList: { type: Array, default: () => [], }, }, methods: { getPersonName (id) { const person = this.personList.find((person) => { return person.id === id }) return person ? person.name : '未設定' }, handleCheck (e, id) { this.$emit('changeCheckStatus', { id: id, checked: e.currentTarget.checked, }) }, }, } </script> <style scoped> .task-list { list-style: none; padding-left: 0; } </style>モジュールとして登録されているミューテーション・アクション・ゲッターは、モジュールを使用しない場合と同じようにコンポーネント側での読み込み・使用が可能です。
しかし、モジュールを分けていても同じスコープ上に各ミューテーション・アクション・ゲッターが登録される為、モジュール間で使用している名前が競合する場合があります。試しに tasks モジュールと persons モジュールに test というミューテーションを定義してみます。
src/store/modules/tasks.jsexport default { state: { tasks: [ {id: 1, name: 'sample task 1', status: true, personId: 1}, {id: 2, name: 'sample task 2', status: false, personId: 2}, ], }, mutations: { changeCheckStatus (state, {id, checked}) { const tasks = state.tasks.slice() const task = tasks.find((task) => { return task.id === id }) task.status = checked state.tasks = tasks }, test () { window.alert('task のアラート') } }, actions: { changeCheckStatus ({commit}, payload) { commit('changeCheckStatus', payload) } }, }src/store/modules/tasks.jsexport default { state: { persons: [ {id: 1, name: '一郎'}, {id: 2, name: '次郎'}, ], }, mutations: { test () { window.alert('person のアラート') }, } }TaskList コンポーネントの各チェックボックスにおいてチェンジイベントが発生した時に、先程登録したミューテーション test をコミットしてみます。
src/App.vue<template> <div id="app"> <h1>Vuex module option demo</h1> <task-list :taskList="tasks.tasks" :personList="persons.persons" @changeCheckStatus="test" /> </div> </template> <script> import Vuex from 'vuex' import TaskList from '@/components/TaskList' export default { name: 'app', components: { TaskList, }, computed: { ...Vuex.mapState(['tasks', 'persons']), }, methods: { ...Vuex.mapActions(['changeCheckStatus']), test () { this.$store.commit('test') }, }, } </script> <style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: left; color: #2c3e50; margin-top: 60px; } </style>モジュール間において同じ名前で登録されたミューテーションは、コミットで呼び出される時に一致する名前の処理がすべて実行されます。
(アクションも同様の挙動・ゲッターはエラーが発生します。)名前空間の指定
モジュールとして分割するだけでは、ゲッター・ミューテーション・アクションも同一のスコープ上に登録されてしまいます。それらを、モジュール内に閉じ込める場合は
namespaced
オプションにtrue
を渡して名前空間を指定します。src/store/modules/tasks.jsexport default { namespaced: true, // 名前空間の指定 state: { tasks: [ {id: 1, name: 'sample task 1', status: true, personId: 1}, {id: 2, name: 'sample task 2', status: false, personId: 2}, ], }, mutations: { changeCheckStatus (state, {id, checked}) { const tasks = state.tasks.slice() const task = tasks.find((task) => { return task.id === id }) task.status = checked state.tasks = tasks }, test () { window.alert('task のアラート') } }, actions: { changeCheckStatus ({commit}, payload) { commit('changeCheckStatus', payload) } }, }src/store/modules/tasks.jsexport default { namespaced: true, // 名前空間の指定 state: { persons: [ {id: 1, name: '一郎'}, {id: 2, name: '次郎'}, ], }, mutations: { test () { window.alert('person のアラート') }, } }名前空間を指定した事によって、モジュールのミューテーションへのアクセス方法が若干変更になります。名前空間が有効な場合は、接頭辞にモジュール名を指定します。
src/App.vue<template> <div id="app"> <h1>Vuex module option demo</h1> <task-list :taskList="tasks.tasks" :personList="persons.persons" @changeCheckStatus="test" /> </div> </template> <script> import Vuex from 'vuex' import TaskList from '@/components/TaskList' export default { name: 'app', components: { TaskList, }, computed: { ...Vuex.mapState(['tasks', 'persons']), }, methods: { ...Vuex.mapActions(['changeCheckStatus']), test () { this.$store.commit('tasks/test') // 接頭辞としてモジュール名を指定して`/`で繋ぎます。 }, }, } </script> <style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: left; color: #2c3e50; margin-top: 60px; } </style>それでは、各モジュールの名前空間が有効な状態でタスクの新規登録機能を作成していきます。
task モジュールにミューテーションとアクションを定義します。src/store/modules/tasks.jsexport default { namespaced: true, state: { tasks: [ {id: 1, name: 'sample task 1', status: true, personId: 1}, {id: 2, name: 'sample task 2', status: false, personId: 2}, ], }, mutations: { changeCheckStatus (state, {id, checked}) { const tasks = state.tasks.slice() const task = tasks.find((task) => { return task.id === id }) task.status = checked state.tasks = tasks }, addTask (state, {name, personId}) { state.tasks = [ ...state.tasks, { id: new Date().getTime(), name: name, status: false, personId: personId, }, ] }, }, actions: { changeCheckStatus ({commit}, payload) { commit('changeCheckStatus', payload) }, addTask ({commit}, payload) { commit('addTask', payload) }, }, }
src/App.vue
にフォームを設置します。src/App.vue<template> <div id="app"> <h1>Vuex module option demo</h1> <form @submit.prevent="addTask(newTask)"> <table> <tr> <th>タスク名</th> <td><input type="text" placeholder="taskName" v-model="newTask.name"></td> </tr> <tr> <th>担当者</th> <td> <select v-model.number="newTask.personId"> <option value="0">未選択</option> <option v-for="person in persons" :value="person.id" :key="person.id" > {{person.name}} </option> </select> </td> </tr> </table> <button type="submit">タスク追加</button> </form> <hr> <task-list :taskList="tasks" :personList="persons" @changeCheckStatus="changeCheckStatus" /> </div> </template> <script> import Vuex from 'vuex' import TaskList from '@/components/TaskList' export default { name: 'app', components: { TaskList, }, data() { return { newTask: { name: name, personId: 0, } } }, computed: { // ヘルパー関数でモジュール名を指定してステートの読み込み ...Vuex.mapState('tasks', ['tasks']), ...Vuex.mapState('persons', ['persons']), }, methods: { // ヘルパー関数でモジュール名を指定してアクションの読み込み ...Vuex.mapActions('tasks', ['changeCheckStatus', 'addTask']), }, } </script> <style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: left; color: #2c3e50; margin-top: 60px; } </style>まとめ
- モジュールとして分割する事でコードの見通しがよくなる
- モジュールとして分割する事でモジュール間のステートのスコープを分ける事ができる
- モジュールとして分割 + 名前空間を指定する事でゲッター・ミューテーション・アクションもモジュール内に閉じ込める事ができる
名前空間の指定をしない場合は、コミットやディスパッチで指定した名前の処理がすべてのモジュールに対して実行される為、どのモジュールにどのような処理が定義されているか把握している必要がありそうです。しかし、チームで開発している場合や規模の大きなアプリケーションだと全て把握する事は難しいので、基本的にモジュールを分ける場合は名前空間を指定した方が良いかなと思いました。
- 投稿日:2019-07-01T14:26:28+09:00
ESLintのバージョンあげたらhasOwnPropertyで怒られるようになった。
ESLintのバージョンを6にあげたらいろいろな箇所でエラーになるように...
object.hasOwnProperty(key)で怒られてました。
検索すると説明がありました。
「no-prototype-builtins」でひっかかってたんですね。
Examples of incorrect code for this rule: ダメな書き方
判定するときには下のようにしてたんですが、それでもダメなんですかねぇ?
if (obj && obj.hasOwnProperty(key)) { // something }Examples of correct code for this rule: 良い書き方
ESLintのサイトに説明があったのでこのように変更しました。
if (obj && Object.prototype.hasOwnProperty.call(obj, "key")) { // something }長い...
関数にしたほうがいいかも?
const isObjectHaveProperty = (obj,key) => { return !!(obj) && Object.prototype.hasOwnProperty.call(obj, key); }みたいな?(関数名が長いし、名前の英語はこれでいいのか??)
- 投稿日:2019-07-01T11:11:44+09:00
JavaScriptからjQueryへの第一歩
この記事でやること・やらないことなど
- 基本的に初心者向けの内容です。
- JavaScriptは少しかじったけどjQueryはそこまで…みたいな方を想定しています。
- JavaScriptの構文をさらっとおさらいしてからjQueryの導入手順、簡単なDOM操作をやります。
- 詳細な説明はあまりしません。ざっくりいきます。
- jQueryのバージョンは3系を使用します。
- npm/yarnなどのパッケージマネージャーは使用しません。
- jQuery UI/プラグインの導入に関する詳細な手順は紹介しません。
- Ajaxはやりません。
jQueryオワコンとか言ってはいけないJavaScriptとは
- プログラミング言語
- Webブラウザ上で動作する
- Webで快適なUIを構築したいのであればほぼ必須スキル
jQueryとは
- JavaScriptのライブラリ
- 使用することでJavaScriptを短い記述量で書ける
- (JavaScriptだけでもなんとかなることはなる)
速習JavaScript 基本構文
// 変数 var hello = "Hello World"; console.log(hello); // 変数(再宣言不可) let hello2 = "Hello JavaScript"; // let hello2 = "Hello"; // エラー発生 // hello2 = "Hello"; // 実行可能 console.log(hello2); // 定数(再代入不可) const hello3 = "Hello jQuery"; // hello3 = "Hello"; // エラー console.log(hello3); // 関数宣言 function showMessage(message) { console.log(message); } // 定義した関数を実行 showMessage("executed"); // if文 var isTrue = true; if(isTrue) { console.log("OK"); } else { console.log("NG"); } // for文 for(var i = 0; i < 10; i++) { console.log(i + "番目"); }速習JavaScript DOM
① 要素を取得し、その中のテキストを表示する(onclickで呼ぶパターン)
<script> // 関数定義 function button1Pushed() { // 要素取得 var sample1 = document.getElementById("sample1"); // 表示 alert(sample1.textContent); } </script> <h1 id="sample1">Hello JavaScript</h1> <!-- scriptタグ内で定義されている関数をクリック時に呼ぶ --> <button onclick="button1Pushed()">Push</button>② クリック時にtargetに要素を追加する(addEventListenerを使うパターン)
<script> window.onload = function() { // ページが読み込まれたらこの中身が実行される // ボタン取得 var button = document.getElementsByTagName("button")[0]; // クリックイベントを付与する button.addEventListener("click", function() { // idがtargetの要素を取得 var target = document.getElementById("target"); // div要素を作成し、Hogeというテキストをセット var child = document.createElement("div"); child.innerText = "Hoge"; // targetの中に作成したdivを追加 target.appendChild(child); }); } </script> <button>Push</button> <div id="target"></div>jQuery
- document.getElement~やdocument.createElementでやっていたことを$でできる
- CSSセレクタと同じような使い方で要素を取得できる
導入方法
方法① CDNを使う
- 簡単
- インターネットにつながってないと使えない
以下にアクセス
https://code.jquery.com/jQuery Core 3.4.1のminifiedをクリック
(どれでも良いですが、slim系のを選ぶと純粋なjQueryの機能に絞ったものになるので最後に紹介するjQueryUIなどが使えません)出てきたscriptタグをコピーして適用したいHTMLのheadタグ内に記述する
以下記述例
<!DOCTYPE html> <html lang="jp"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Sample</title> <!-- jQuery読み込み部分 --> <script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script> <script> $(function() { $("body").append($("<div>jQueryが使えるようになりました。</div>")) }) </script> </head> <body> </body> </html>方法② ローカルにダウンロードして使う
以下にアクセス
https://jquery.com/Download jQueryをクリック
Download the compressed, production jQuery 3.4.1を右クリックし、名前を付けて保存する
適用したいHTMLのheadタグ内にscriptタグを記述し、src属性でダウンロードしたファイルを読み込む。
(以下の例ではhtmlファイルと同じ階層にjsフォルダを作成し、そこにjqueryのファイルを配置しています)以下記述例
<!DOCTYPE html> <html lang="jp"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Sample</title> <!-- jQuery読み込み部分 --> <script src="js/jquery-3.4.1.min.js"></script> <script> $(function() { $("body").append($("<div>jQueryが使えるようになりました。</div>")) }) </script> </head> <body> </body> </html>jQueryの記述例
headタグ等省略してます
① 要素を取得し、その中のテキストを表示する
<script> function button1Pushed() { // idがsample1の要素のテキストを取得して、アラートに出す alert($("#sample1").text()); } // buttonタグのonclickを消して以下で書く方が方が良いといわれることもある // $(function () { // $("button").on("click", function() { // alert($("#sample1").text()); // }); // }) </script> <h1 id="sample1">Hello JavaScript</h1> <!-- scriptタグ内で定義されている関数をクリック時に呼ぶ --> <button onclick="button1Pushed()">Push</button>② クリック時にtargetに要素を追加する
<script> $(function() { $("button").on("click", function() { // idがtargetの要素にdivタグを作成して追加 $("#target").append($("<div>Hoge</div>")); }); }); </script> <button>Push</button> <div id="target"></div>③ 削除も増やしてみた
<script> $(function() { $("#add").on("click", function() { $("#target").append($("<div>Hoge</div>")); }); $("#remove").on("click", function() { $("#target").empty(); }); }); </script> <button id="add">ADD BUTTON</button><button id="remove">REMOVE BUTTON</button> <div id="target"></div>セレクタ
基本的にはCSSのセレクタと同じように扱える。
サンプル
https://jsfiddle.net/45cq2nk7/1/イベントハンドラ
選択した要素(jQueryオブジェクト)のonメソッドを使用することでイベントと処理を結び付けられる。
サンプル
https://jsfiddle.net/tv1n76ak/よく使うメソッド
メソッド名 説明 val() 引数なしの場合、inputタグなどで入力されている値を取得する。
また、引数を設定すると引数の値をinputタグのvalueにセットする。text() 引数なしの場合、タグ内のテキストを取得する。
また、引数を設定すると引数の値をタグ内にセットする。css() 引数が1つの場合、引数に設定したスタイルの値を取得する。引数が2つの場合、引数に設定したスタイルを適用する。 attr() 引数が1つの場合、引数に設定したプロパティの値を取得する。
また、引数が2つの場合、引数に設定したプロパティを設定する。append() 引数にjQueryオブジェクトを指定することで、タグ内に子要素に引数の要素を追加する。 remove() そのタグを削除する。 empty() そのタグの中身を削除する。 サンプル
https://jsfiddle.net/ftv97mkj/その他
jQuery UIを使う
jQuery UIを使うと、すでに用意された部品を使用できます。
こちらもCDNで導入する方法とローカルにダウンロードして使う方法があります。
https://jqueryui.com/datepickerなどが有名です。
サンプル
https://jsfiddle.net/j4fa6vo8/プラグインを使う
jQuery UI以外にも様々な開発者が作成したプラグインを導入することもできます。
導入方法は各プラグインによるので、それぞれの公式ページを参照するなどしてください。LIGHTBOX(個人的になんとなく好きなプラグイン)
https://lokeshdhakar.com/projects/lightbox2/最後に
今回は初学者向けにボリュームを絞った形になりましたが、
他にもできることがたくさんあるので調べて使ってみてください。
https://api.jquery.com/
- 投稿日:2019-07-01T09:07:26+09:00
# Node.jsとTypeScriptによる高速かつ軽量なWebシステム構築1 ~ 地獄の門前 ~
Node.jsとTypeScriptによる高速かつ軽量なWebシステム構築1 ~ 地獄の門前 ~
1.はじめに
ツリー型情報掲載システム(Node.js+TypeScript版)
ソースコードNode.jsとTypeScriptで上記のものを作ったので、このあたりの開発に関する話をしていきます。今回は導入部分です。
2.Node.jsによる完全包囲
Node.jsはJavaScriptを好きな場所で走らせることが出来るフレームワークとして、様々な場所で使われている。
- VSCodeのようなデスクトップアプリとそのプラグイン
- 開発ツールと制御用スクリプト
- バックエンドサービス
- 色々なツール類
VSCodeにはかなりお世話になっている。初期バージョンの頃はあんまり使えないという認識だったのだが、その後の進化は凄まじいものがあった。Node.jsを使えばそんなデスクトップアプリから、ちょっとしたコマンドまで色々なものが開発可能だ。もちろんWebアプリを作るときのバックエンドのプログラムを作ることも出来る。そして、そういったプログラムを作るときの開発環境も一通りNode.jsで作られている。既にNode.jsからは逃げられない世界が構築されているのだ。
Node.jsで何に使うのと聞かれたら、「作れるものが多すぎて、一概には答えられない」という、質問した人間が不満にしか思わないような回答になってしまうのである。
3.異世界転移のJavaScriptと自らを縛るTypeScript
JavaScriptはクセの強い言語だ。書きたい処理を書くのは簡単なのだが、実は仕様が複雑で、なんだかよく分からないけれど動いているから大丈夫なのだろうというプログラムを書いてしまいがちである。コールバックされたときに今持っているthisが何のインスタンスを示すのか、外側のブロックから拾ってきた変数のデータがいつのものなのか、気を抜いてうっかりしていると命を取られかねない。
受け取った変数のパラメータの取り方は本当に正しいのか、オブジェクトの中身がどうなっているのか、うろ覚えのプロパティ名前が本当に合っているのか、実は一瞬たりとも気を休められない。しかし本当に恐ろしいのは、実は間違っているのに何事も無かったかのように動くことにある。好き勝手に色々書いていたら、いつの間にかそこが異世界と化しているのだ。
この危険を回避するにはJavaScript単体の使用をやめ、TypeScriptで自らを拘束する以外に方法が無い。自らの意思で手かせを付け、足に鉄球と鎖を巻き付けるのだ。身動きがとれない苦しみと引き換えに、突然の異世界転移は防ぐことが出来る。異世界で無双するのは「なろう」だけで十分である。
4.PHPからNode.jsへの移植作業とその理由
以前はバックエンドをPHPで作っていたのだが、それをNode.jsに移植することにした。理由は単純で、現行のほとんどの処理をTypeScript化したかったからだ。TypeScriptならバックエンドとフロントエンドを同じ言語で書くことが出来る。それによってデータ構造の記述が両方で使い回せるのだ。これは非常に大きなアドバンテージである。二度手間を防げる上に、移植に伴うミスも発生しなくなる。これでデータのやりとりがぐっと楽になるのだ。
ただしPHPからNode.jsへの移行には重大な注意点がある。速度はPHPの方が圧倒的に速いことだ。初期化処理とか色々込みのPHPプログラムが、準備万端で待ち受けしているNode.jsのプログラムと互角以上にやり合うのだ。色々と実験を繰り返しみたが、PHP7系統の速度がおかしいぐらいに速い。
ということでNode.jsでプログラムを組む場合は、速度的な優位を期待してはいけない。細かい処理でPHPと互角、重い処理ほどPHPの方が優位性を増していく。ただし大量のアクセスをさばく際のメモリ消費量に関してはNode.jsの方が優れているので、貧弱なリソース下で動かすなら考慮に入れるべきだろう。
5.Node.jsの特徴
向いてない作業
Node.jsの特徴は、細かい処理を大量に捌くことだ。たとえば受付のオペレータのように、用件を聞いたら対象の部署へ内線を回すという作業などが該当する。間違っても重い荷物を持たせてはいけない。Node.jsは荷物を持った瞬間に腰痛を発症し、手痛い損害賠償を請求されることになるだろう。
重い処理とはなんなのか。画像加工とか機械学習とか動画編集とかはもちろんこれに該当する。そういうWebサービスを作るなら、別の言語で作ったプロセスにいったん投げた方が幸せになれる。
そしてもっと一般的に行われている中でやってはいけない仕事、それはDOMを組み合わせてHTMLデータを作成する作業だ。ぶっちゃけこれをやらせるなら、PHPとかに処理を任せた方がよい。その方がよっぽど高速だ。
HTMLデータを生成する作業を主体とするのなら、はっきりいってNode.jsという選択は愚策といってよい。踵を返して他の言語の門を叩いて欲しい。Node.jsで開発しても、面倒なだけでちっとも作業は効率化しない。
向いてる作業
Node.jsに向いている作業は最初に書いた通り、受付オペレータである。Webシステムならば、クライアントからの要求を聞き、それをDBに投げ、結果が返って来たらクライアントにデータを送り返すのだ。できるだけ内容に関知しないことが理想だ。しかしこれだけは最低限処理しないといけないというのがある。クライアントが誰なのか、問い合わせが正当なものなのかの確認だ。つまり向いている作業とは、WebAPIを構築する部分である。
ではHTMLは誰が作るのという疑問を抱くかもしれない。初期ページ以外はフロントエンド側で動的に生成すれば良いのだ。バックエンド側は生成に必要なデータを返してやるだけで良い。場合によってはサーバサイドレンダリングが必要なこともあるかもしれない。しかしそれを主体とするシステムに導入するのは考え直した方が良いだろう。
まとめ
- 軽量の処理ならNode.js
- Webサービスを作る上で重い処理ほどPHPの方が速くなるし、さらに重い処理はネイティブ言語系に投げた方が良い
- 大量アクセス時の省メモリの動作はNode.jsが有利
- 金をかけても問題なく、GB単位でメモリが用意できる環境なら、省メモリがあまりアドバンテージにならない
- 本気の同時一万アクセスとかは、普通に組んでたら無理なので夢は見ないこと
6.非同期地獄と入門と門前
Node.jsはシングルスレッドかつ非同期を前提に動作する。非同期は必要なデータが返ってくるまでラグのある処理をタスクプールに積んでいく。ファイルの入出力からDBアクセスまで、その場で欲しいデータをことごとくその場でもらうことが出来ない。データ受け取り後に必要となる処理を切り分けて、ひたすらプールにため込んでいくのだ。これが非同期地獄というやつだ。ただしこの非同期地獄はPromise/async/awaitによって、一応は抜け出すことが出来る。ただし非同期処理にPromiseを返してこないライブラリを扱う場合は、そのまま使うかPromise化するかという面倒な作業が待っている。
この非同期処理に慣れることや、自分が必要とするライブラリのPromise化が終わったら、ようやく開発の門の前に立った状態となる。そう、門前に立っただけである。つまり、まだ入門すらしていないのだ。
ちなみにシングルスレッドだと、複数CPUがあっても、そのリソースを活用できないという認識はしなくて大丈夫だ。プロセスを増やせばリソースは使い切れるし、それを簡単に行う仕組みも用意されている。どのみちスクリプト系の言語はマルチスレッド対応言語でも、変数などの排他制御の問題で性能が頭打ちになる。そうなると結局マルチプロセスしか選択肢が残らない。マルチスレッドで性能を追求したければ、ネイティブ系の言語か、Javaを使った方が幸せになれるだろう。
まとめ
- 非同期処理は、使用するライブラリを全てPromise化してからが始まり
- とにかく慣れるまでが辛い
- シングルスレッドでもマルチプロセスにすればCPU資源を生かすことは出来る
- 非同期処理の同期をとらずに気軽に大量のループで回すと不幸が訪れる
- ガチで性能を追求するなら別の言語へ
7.TypeScriptと流星
非同期処理による問題も解決し、いよいよ入門を果たしても、所詮は門を通り抜けたに過ぎない。そこからTypeScriptという地獄が始まる。フロントエンドとバッグエンドを同じ言語で書けるという利点を生かすため、以前に別言語で作った資産を移植する作業をしなければならない。つまり、以前作った資産が多い人間ほど、重い荷物を背負うのだ。
私の場合、ブラウザ上でウインドウシステムを実現するというフロントエンドフレームワークという資産があった。これを移植しなければならないのだ。荷物があまりに重すぎたので、単純移植を諦め、ほとんどの部分を作り直した。
フロントエンドで書いていたJavaScriptのTypeScriptへの移植は、同じ系統の言語だからすぐに出来るだろうとか安易に考えてはいけない。コンパイルした瞬間、流星のごときエラーメッセージによって、自分の甘さをとことん教えられるのだ。ここで必要なのは、エラーの数に心を折られない精神力だ。
こんなに型でカタカタしなくてもいいだろうにと思って、一つ一つ修正していくと、実は馬鹿をやっていた記述をいくつも発見することになる。そう、ここでたまたま動いていたコードを見つけることになるのだ。さらに段階的にstrictを有効にして、チェックを厳しくしていく必要がある。最終的にeslintを導入してanyすら禁止すれば、ようやく入門を完了した状態となる。anyが一個でも残っていたら、入門から脱したとは言えない。
PHPからTypeScriptの移植は文法が似ているおかげもあって、非同期への対処が終わっていれば、意外にあっさり出来る。JavaScriptで食らったペガ○ス流星拳に比べれば衝撃の度合いは低い。
まとめ
- JavaScriptから移行するのは、それなりの覚悟が必要
- TypeScriptの型の仕様を本気で突き詰めると、実はシャレにならないほど複雑
- TypeScriptの言語仕様はこうしている間にも増え続け、気がつくと背後に知らないキーワードが立っている
- anyを自分のソースコードから完全に除去しないかぎり、素のJavaScriptの呪縛からは抜け出せない
- エラー流星拳に対して、身を守るためにクロスを纏うことは出来ないが苦労はするだろう
というかクロスって肝心な場所を守ってない気がする8.必要となる知識
今回の開発で必要となった知識をざっと挙げてみたいと思う。
- HTML/CSS
- JavaScriptの文法と特性
- ブラウザでJavaScriptを動かすためのDOMを操作や、イベント
- Ajaxや人を殺さない方のJSON
- TypeScriptの文法と特性
- Node.jsの基本的な使い方とランタイムライブラリ
- WebPackなどの開発ツールやプラグイン、必要なpolyfill
- npmで呼び出す、大海の中に投げ出された中から掴み取るモジュール
- DBとSQLとそれを操作するモジュール
なんだかんだでWebPackが鬼門だ。情報はたくさんあるのだが、内容が新旧入り交じり、プラグインも混沌としている。自分が必要としているものがなんなのか、最適解を見つけるまで試行錯誤することになるだろう。この辺りの話もこの後の記事でしていきたい。
9.次回の予定
- 投稿日:2019-07-01T02:34:49+09:00
JavaScriptの"仕様にまだ導入されていない新機能"紹介
概要
ECMAScript の仕様は TC39 という組織によって決められているが、この記事ではまだ ECMAScript の仕様に導入されていないが TC39 が proposal (提案)として認めている新機能(ESNext とも呼ばれる)をいくつか紹介する。各見出しは該当の GitHub ページにリンクされている。
今すぐこれらの機能を使いたい場合は babel の plugin を使用するのがいいだろう。また、ブラウザの中でも特に Google Chrome は新機能の実装に積極的であり、仕様に導入される前の proposal を早めに Chrome に実装することがよくある。これについて最新情報を知りたい場合は @ChromiumDev を見ればよいだろう。
proposal ごとに stage (「提案されただけの段階」から「仕様に導入された段階」までのどれくらいの進捗度かを表す)が割り振られているが、今後変わっていくものであるためここでは書かない。stage ごとの一覧は以下のページで見られる。
Optional Chaining
ネストされたオブジェクトの深いプロパティに安全にアクセスしたいとき、一段階深く行くたびに
undefined
やnull
であるかどうかチェックする必要がある(なぜならundefined
やnull
のプロパティにアクセスしようとするとエラーが発生する)が、optional chaining ではその必要がなくなる。Swift の同名の operator や C# の null-conditional operator に類似している。名前の由来はおそらくその Swift のものであり、
Optional
型の値からOptional
型の値を得る operator であるため chain (連ねて使用すること)できるからだと思われる。例えば
o.a.b.c
にアクセスしたいとき、以下のように、まずo
が nullish (undefined
かnull
)であるかチェックし、次にo.a
が nullish であるかチェックし...と冗長なコードになってしまう。Note:
x == null
はx
が nullish のときのみtrue
になることに注意。const o_a_b_c = (o == null || o.a == null || o.a.b == null) ? undefined : o.a.b.coptional chaining により次のように簡潔に書ける。
const o_a_b_c = o?.a?.b?.ckey を expression で指定するプロパティアクセスや関数呼び出しもできる。
o?.[propName] // o == null ? undefined : o[propName] f?.(arg) // f == null ? undefined : f(arg)Nullish Coalescing
何らかの値が
null
かundefined
である場合は代わりに何か自分の指定したデフォルト値を使用したいということがある。nullish coalescing はこれを簡潔に書くためのシンタックスである。nullish coalescing と呼ばれる理由は、左に nullish な値があると右の値と融合(coalescing)させられて結果一つの値となるからである。Before
// o.a が null か undefined なら代わりに 1 を使用する const o_a = o.a == null ? 1 : o.aAfter
const o_a = o.a ?? 1先述の optional chaining と組み合わせると、JSON形式のレスポンスから何らかの値を取り出したいときに便利だろう。
const posts = response?.data?.user?.[0]?.posts ?? []Note: 同様の目的でよく使われる
||
は、左の値が falsy (Boolean に変換するとfalse
になる値) であれば右の値が使われてしまうという点で??
とは異なる。const o = { a: 0, b: false, c: '' } o.a || 10 // 10 o.b || 10 // 10 o.c || 10 // 10 o.a ?? 10 // 0 o.b ?? 10 // false o.c ?? 10 // ''Promise.allSettled & Promise.any
どちらも複数の promise を受け取る関数だが、
Promise.allSettled
はすべての promise が settle (resolve または reject すること)した時点で resolve し、Promise.any
はいずれかの promise が resolve した時点で resolve し、すべて reject した時点で reject する。既存のPromise.all
やPromise.race
と比較すると以下のようになる。
Promise.allSettled
Promise.any
Promise.all
Promise.race
resolve するタイミング すべての promise が settle いずれかの promise が resolve すべての promise が resolve いずれかの promise が resolve resolve の値 * そのまま resolve 値の配列 そのまま reject するタイミング なし すべての promise が reject いずれかの promise が reject いずれかの promise が reject reject の値 - reject 値の配列 そのまま そのまま (settle = resolve か reject すること)
*
Promise.allSettled
の resolve 値は、各 promise の最終的な状態(resolve/reject とその値)を表すオブジェクトの配列となる。const p1 = Promise.resolve(1) const p2 = Promise.reject(">_<") Promise.allSettled([p1, p2]).then(console.log) // [ // { status: 'fulfilled', value: 1 }, // { status: 'rejected', reason: '>_<' } // ]do Expression
do { ... }
ブロック内で最後に実行された statement (文)の値がdo
ブロック全体の値となる。これにより、一つの値を求めるいくつかの statement を一つの expression にまとめることができ、(途中で使った)変数のスコープを狭く抑えることもできる。Haskell の do notation に相当し、Scala などでは通常のブロック
{ ... }
が同じ機能を持つ。const x = 5 const result = do { // result === 2 if (x > 10) { 1 } else if (x > 0) { 2 } else { 3 } } const a = do { // a === 5 const b = 3 const c = 4 const a_squared = b * b + c * c Math.sqrt(a_squared) }Pipeline Operator
ある値
x
に functionf1
を適用し、その返り値にf2
を適用し...と繰り返したいとき、fn(...(f2(f1(x)))...)
と書けば、function が右から左に並び、括弧も多くなるので可読性が低下する。pipeline operator|>
により以下のように簡潔に書けるようになる。F# や OCaml に同じ operator がすでに存在し、bash の pipe
|
も似た機能を持っている。pipeline と呼ばれるのは、function はものを入れると反対から何かが出てくるパイプのようなものとみなして、function をつなげてより大きな function、すなわちパイプラインを構成するというイメージがあるからである。const result = x |> f1 |> f2 |> f3 // result === f3(f2(f1(x)))Partial Application
function に対して引数を渡して実行することを apply (名詞: application) というが、functional programming (関数型プログラミング)の界隈では複数の引数を取る function に対して引数を partial (部分的)に与えて新たな function を得ることを parital application という。この proposal はこの機能の JavaScript 版である。
function sum(a, b, c) { return a + b + c } const sumWith3 = sum(?, 3, ?) console.log(sumWith3(1, 2)) // 6 const sumWith3And5 = sumWith3(5, ?) console.log(sumWith3And5(8)) // 16先述の pipeline operator を使用するときに便利である。
const result = x |> f1 |> f2(?, 3) |> f3(4, ?, 5) // f3(4, f2(f1(x), 3), 5) と等価Note:
this
を内部で使用している function について、わざわざbind
する必要はない。obj.f(?)(3)
はobj.f(3)
と等価である。Class Fields
クラスのフィールド(他のオブジェクト指向言語でいうところのインスタンス変数)を Java などと同じように
constructor
の外で宣言できるようになる。Before
class C { constructor() { this.c = 0 } }After (完全に等価なコードではない)
class C { c = 0 // class field }Note: 上2つのシンタックスには機能的な違いがある。実は後者は内部的には
Object.defineProperty()
によって data property1 として定義される。これがどのような違いを生むかと言うと、例えば次のように accessor property1 をもったクラスを継承するときに、新しいシンタックスだとそのプロパティが data property へと上書きされてしまう。class C { // accessor property get a() { return 10 } set a(value) { console.log("setter called") } } // Beforeの方 class D extends C { constructor() { super() this.a = 1 } } const d = new D() // "setter called"が出力される d.a = 2 // ここでも出力される // Afterの方 class E extends C { a = 1 } const e = new E() // "setter called"が出力されない e.a = 2 // ここでも出力されないPrivate Fields
先述の class field について、
#
を先頭につけることで外からアクセスできない private な field となる。Java や C# における private instance variable に相当する。class Counter { #c = 0 // private field get count() { return this.#c } increment() { this.#c++ } } const c = new Counter() console.log(c.count) // 0 c.increment() console.log(c.count) // 1 console.log(c.#c) // SyntaxError子クラスから親クラスの private field にはアクセスできない。
class C { #a = 0 } class D extends C { printa() { console.log(this.#a) // Syntax Error } }ただし、同じクラスの他のインスタンスの private field にはアクセスできる。
class C { #a = 0 constructor(a) { this.#a = a } printa() { console.log(this.#a) } add(other) { return new C(this.#a + other.#a) } } const c1 = new C(1) const c2 = new C(2) const c3 = c1.add(c2) c3.printa() // 3Private Methods & Getter/Setters
先述の private field と同様に method や getter/setter にも
#
をつけることで private にできる。class C { get #a() { return 1 } set #a(value) { } #method() { } }Static Fields & Methods
static
をつけることで (public) field、private field、private method を static (インスタンスではなくクラスのプロパティ)にできる。Java や C# における static variable/method に相当する。public static method はすでに ECMAScript 2015 で導入されている。class A { static publicStaticVar = 4 static #privateStaticVar = 4 static #privateStaticMethod() { return 4 } }
data property は
{ prop: 1 }
のようにして作られる通常のプロパティであるのに対し、accessor property とは getter と setter によって定義されたプロパティである。 ↩
- 投稿日:2019-07-01T01:20:40+09:00
はじめてのChrome拡張機能 ページをポップアップさせる
はじめに
シングルディスプレイだと複窓をする機会がよくあります。時には3窓、4窓することもしばしば。そんな時に思うことがあります。
ブックマークバーとURLバー、邪魔だな......。
ということで、2クリックでポップアップウィンドウ化する拡張機能を作りました。
https://chrome.google.com/webstore/detail/popup-browser/mkcacjdndeohfnkioobenjpoifkecnjf
準備
そもそもjavascriptの知識が十分になかったので1からやり直した。
js-primer:ECMAScript 2018時代のJavaScript入門書Chrome拡張機能については、色々読んだのですが結局公式のドキュメントが一番わかりやすかったです。
https://developer.chrome.com/extensions設計
とにかく簡単にポップアップさせたかったので、コンテキストメニュー(右クリックで開くやつ)から動作させるようにします。こんな感じ。
超絶シンプルな機能なのでコードは短くできそうです。
コードを書く
manifest.json
はこのようになりました。permissions
にはタブを操作するための"tabs"
とメニューをいじるためのcontextMenus
を記述する必要があります。manifest.json{ "name": "popup browser", "description" : "Pop up browser", "version": "1.0.2", "manifest_version": 2, "permissions": ["contextMenus", "tabs"], "browser_action" :{ "default_icon": "p.png" }, "background": { "scripts": ["background.js"] }, "icons": { "16": "p.png"} }参考:https://developer.chrome.com/extensions/manifest
公式で用意されている Chrome APIs を利用して、中身を書いていきます。
background.js// コンテキストメニューに要素を追加する chrome.contextMenus.create({ title: "ポップアウトさせる", id: "popout", contexts: ["all"], onclick: () => { // 現在開いているタブのURLを変数 tab に格納する chrome.tabs.getSelected(tab => { // 現タブを閉じて、 chrome.tabs.remove(tab.id); // 新たにポップアップウィンドウでURLを開く chrome.windows.create({ url: tab.url, type: 'popup', // Youtubeで丁度よく窓化できるサイズ width: 650, height: 450, focused: true }); }); } });それぞれ用いた API のドキュメントです。
https://developer.chrome.com/apps/contextMenus
https://developer.chrome.com/extensions/tabs
https://developer.chrome.com/extensions/windows非同期処理を扱うコードを1から書くのは初めてなのでどこかおかしいかもしれない。
が、まあ動くので大丈夫!(本当?)おわりに
実際に使っているのですが、普通に便利で良いです。色々と作っていきたいですね。
- 投稿日:2019-07-01T00:48:39+09:00
JavaScriptでGETリクエストを送る
はじめに
以前の投稿でJSON受け取って整形する処理はわかったのでGETリクエストを送って、JSONを受け取ることがJavaScriptだけでできないか勉強中。
XMLHttpRequestでできないか挑戦
index.html<body> <form onSubmit="zipcode();">郵便番号: <input type="text" name="postcode" id="postcode"> <input type="submit" value="送信"> <input type="reset" value="リセット"> </form> <textarea rows="100" cols="100" id="output" readonly></textarea> <script type="text/javascript"> var URL = 'http://zipcloud.ibsnet.co.jp/api/search?zipcode='; var target = document.getElementById("output").value; function zipcode(){ var postcode = document.getElementById('postcode').value; <!-- URLにpostcodeの値(入力値)を加える --> URL = URL+postcode; var request = new XMLHttpRequest(); <!-- URLにGETリクエストを投げる --> request.open( "GET", URL, true ); request.send(null); <!-- readyStateが変わる度に呼び出される --> request.onreadystatechange = function(){ if(request.readyState == 4){ if(request.status == 200){ <!-- レスポンスが返ってきたらテキストエリアに代入する --> target = request.responseText; } } } } </script> </body>上記では動きそうで動かない。
ので原因を調べつつAjaxでやってみる。
- 投稿日:2019-07-01T00:42:19+09:00
【脱jQuery】AjaxをFetch APIで実現する
ナウいAjaxのやり方として、Fetch APIのテストソースを書きました。
呼び先のAPIは天気を取得するOpenWeatherMapを使用しています。snippet.jsvar API_KEY = 'APIキーを設定'; var lon = '141.355539'; var lat = '43.067885'; var URL = `http://api.openweathermap.org/data/2.5/forecast?lat=${lat}&lon=${lon}&cnt=1&units=metric&APPID=${API_KEY}`; (async ()=>{ try{ var res = await fetch(URL); var data = await res.json(); console.log(JSON.stringify(data)); } catch(err){ console.log(err); } })();
- 投稿日:2019-07-01T00:17:47+09:00
gRPC-Web Hello World Guideをやってみた
こちらのガイド【gRPC-Web Hello World Guide】を試してみた備忘録となります。
この記事にあるコードもほぼ参考サイトを流用させていただいております。
間違いなどありましたら指摘お願いします。1. 試した環境
- macOS Mojave
- Docker version 18.09.2, build 6247962
- protoc : libprotoc 3.7.1
- protoc-gen-grpc-web-1.0.4
- node : v10.14.1
- Google Chrome : 75.0.3770.100 (Official Build) (64-bit)
2. Protocol Buffersの定義ファイルを作成
gRPCのserviceを定義。
あとでこの.proto
ファイルから.js
ファイルを生成します。helloworld.protosyntax = "proto3"; package helloworld; service Greeter { rpc SayHello (HelloRequest) returns (HelloReply); } message HelloRequest { string name = 1; } message HelloReply { string message = 1; }3. gRPCサービスの実装
nodeで作成
server.jsconst PROTO_PATH = __dirname + '/helloworld.proto'; const grpc = require('grpc'); const protoLoader = require('@grpc/proto-loader'); const packageDefinition = protoLoader.loadSync( PROTO_PATH, {keepCase: true, longs: String, enums: String, defaults: true, oneofs: true }); const protoDescriptor = grpc.loadPackageDefinition(packageDefinition); const helloworld = protoDescriptor.helloworld; function doSayHello(call, callback) { callback(null, { message: 'Hello! ' + call.request.name }); } function getServer() { const server = new grpc.Server(); server.addService(helloworld.Greeter.service, { sayHello: doSayHello, }); return server; } if (require.main === module) { const server = getServer(); server.bind('0.0.0.0:9090', grpc.ServerCredentials.createInsecure()); server.start(); } exports.getServer = getServer;4. プロキシの設定
Envoy Proxyを使います。
envoy.yamladmin: access_log_path: /tmp/admin_access.log address: socket_address: { address: 0.0.0.0, port_value: 9901 } static_resources: listeners: - name: listener_0 address: socket_address: { address: 0.0.0.0, port_value: 8080 } filter_chains: - filters: - name: envoy.http_connection_manager config: codec_type: auto stat_prefix: ingress_http route_config: name: local_route virtual_hosts: - name: local_service domains: ["*"] routes: - match: { prefix: "/" } route: cluster: greeter_service max_grpc_timeout: 0s cors: allow_origin: - "*" allow_methods: GET, PUT, DELETE, POST, OPTIONS allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout max_age: "1728000" expose_headers: custom-header-1,grpc-status,grpc-message enabled: true http_filters: - name: envoy.grpc_web - name: envoy.cors - name: envoy.router clusters: - name: greeter_service connect_timeout: 0.25s type: logical_dns http2_protocol_options: {} lb_policy: round_robin # win/mac hosts: Use address: host.docker.internal instead of address: localhost in the line below hosts: [{ socket_address: { address: host.docker.internal, port_value: 9090 }}]envoy.DockerfileFROM envoyproxy/envoy:latest COPY ./envoy.yaml /etc/envoy/envoy.yaml CMD /usr/local/bin/envoy -c /etc/envoy/envoy.yaml5. クライアントのコードを作成
5-1.
client.js
client.jsconst {HelloRequest, HelloReply} = require('./helloworld_pb.js'); const {GreeterClient} = require('./helloworld_grpc_web_pb.js'); const client = new GreeterClient('http://localhost:8080'); const request = new HelloRequest(); request.setName('World'); client.sayHello(request, {}, (err, response) => { console.log(response.getMessage()); });
helloworld_pb.js
とhelloworld_grpc_web_pb.js
はhelloworld.proto
から自動生成されます。5-2.
package.json
package.json{ "name": "grpc-web-simple-example", "version": "0.1.0", "description": "gRPC-Web simple example", "devDependencies": { "@grpc/proto-loader": "^0.3.0", "google-protobuf": "^3.6.1", "grpc": "^1.15.0", "grpc-web": "^1.0.0", "webpack": "^4.16.5", "webpack-cli": "^3.1.0" } }5-3.
index.html
index.html<!DOCTYPE html> <meta charset="UTF-8"> <title>gRPC-Web Example</title> <script src="dist/main.js"></script>6.
protoc
から.js
ファイルを生成する
.proto
ファイルの定義からjsファイルを作成するためには、protoc
とprotoc-gen-grpc-web
が必要6-1.
protoc
をインストールこちらを参考にインストールしました
http://google.github.io/proto-lens/installing-protoc.htmlmacで試したのでbrewでインストールしました。
bash$ brew install protobuf $ which protoc #インストールされたか確認 /usr/local/bin/protoc6-2.
protoc-gen-grpc-web
をダウンロードこちらのリンクからmac用の
protoc-gen-grpc-web-1.0.4-darwin-x86_64
をダウンロードさせていただきました。https://github.com/grpc/grpc-web/releases
ダウンロードしたらPATHの通っているフォルダに移動させる
bash$ sudo mv ~/Downloads/protoc-gen-grpc-web-1.0.4-darwin-x86_64 \ /usr/local/bin/protoc-gen-grpc-web chmod +x /usr/local/bin/protoc-gen-grpc-webこれで
protoc
とprotoc-gen-grpc-web
が使える状態になったので、以下のコマンドでjsファイルを生成するbash$ protoc -I=. helloworld.proto \ --js_out=import_style=commonjs:. \ --grpc-web_out=import_style=commonjs,mode=grpcwebtext:.
helloworld.proto
からhelloworld_pb.js
とhelloworld_grpc_web_pb.js
が自動生成されたことを確認しました。bash$ ls -l helloworld* -rw-r--r-- 1 kengookumura staff 1084 Jun 30 15:19 helloworld.proto -rw-r--r-- 1 kengookumura staff 6645 Jun 30 16:45 helloworld_grpc_web_pb.js -rw-r--r-- 1 kengookumura staff 14533 Jun 30 16:45 helloworld_pb.js7. jsファイルをwebpackでバンドルする
bashyarn yarn webpack client.js --mode development #または、 npm i npx webpack client.js --mode development
dist/main.js
が作られていればOK8. サンプルを実行
ここまでで実行の準備ができましたので、実際に動かしてみます。
server.js
実行bashnode server.js #ポート9090でリッスンします
Envoyプロキシを実行
bashdocker build -t helloworld/envoy -f ./envoy.Dockerfile . docker run -d -p 8080:8080 helloworld/envoy
index.html
を表示するためのwebサーバーをなんでもいいので起動するbash#nodeで簡易サーバ yarn add -D node-static yarn static -p 8081 #phpで簡易サーバ php -S 0.0.0.0:8081 #pythonで簡易サーバ python3 -m http.server 8081
Hello! World
とコンソールに出ていたら成功しています。
Readmeの手順にそって行いましたが、参考にさせていただいたコードの方では、
Hello! World
の出力部分以外の処理もあり、https://github.com/grpc/grpc-web/blob/master/net/grpc/gateway/examples/helloworld/server.js
https://github.com/grpc/grpc-web/blob/master/net/grpc/gateway/examples/helloworld/client.js
Hey! World0
〜Hey! World4
の部分で繰り返し処理のサンプル。
Got error, ...
の部分でエラー処理のサンプルのようですので、こちも参考にすると良いと思いました。
gRPC-Webのchrome拡張も試してみた
こちらを試してみました
【gRPC-Web Developer Tools - Chrome Web Store】
githubはこちら
【SafetyCulture/grpc-web-devtools】
client.jsconst {HelloRequest, HelloReply} = require('./helloworld_pb.js'); const {GreeterClient} = require('./helloworld_grpc_web_pb.js'); const enableDevTools = window.__GRPCWEB_DEVTOOLS__ || (() => {}); const client = new GreeterClient('http://localhost:8080'); enableDevTools([ client, ]); const request = new HelloRequest(); request.setName('World'); client.sayHello(request, {}, (err, response) => { console.log(response.getMessage()); });client.js// ここの部分を追加、更新しています const enableDevTools = window.__GRPCWEB_DEVTOOLS__ || (() => {}); const client = new GreeterClient('http://localhost:8080'); enableDevTools([ client, ]);このように、
helloworld.proto
で定義した内容に対して、値がわかりやすいように表示してくれました。
最後まで読んでいただいてありがとうございました。