20190701のJavaScriptに関する記事は30件です。

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"で処理から抜ける。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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はこちらから

はてなブックマークに追加
Pocketに追加

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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します。また親で指定されたonSubmithandleSubmitとして渡します。 
  • <Field /> : wrapped form componentで使われるcomponentで、input componentをRedux-form に組み込んでくれます。

3番目の<Field />については以下に説明します。

Field について

Redux Form/API/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.js
import 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.js
import 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()

実行画面

エラー表示

image.png

今回は以上です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DjangoでサクッとWebサイトを作る Part1

DjangoでサクッとWebページを作ってみました。
初心者向けに超基礎的な部分だけ解説します。

セットアップ

既にご存知かと思いますが、DjangoはPythonで開発されています。したがってまずはPythonをインストールする必要があります。
本記事執筆辞典ではPythonは3.7がおすすめです。もしPython2.xがインストール済の場合はアップグレードしましょう。

インストールが正しく行われているか確認するには以下のコードを実行してみてください。

$ python3 --version
Python 3.6.1

Djangoのインストール

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

更新はゆっくりになりますが頑張りますので気長にお待ちください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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か月前のことなんだけれど。

んで、リアルタイムに発言を表示できるようにしようと試みたんだ。でも、こんな時間がかかるとはね、僕の脳は優秀ではないようですな。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AtomPub API】Livedoorブログの投稿記事を取得する

はじめに

趣味でLivedoorブログをやっていて毎日投稿を心がけている。
しかしながら、なかなか毎日の投稿数が安定しない。
個人的には毎日の投稿数とかが可視化されているとモチベーションが上がるので、勉強も兼ねてやってみようと思う

プライベート端末で実装するため、あまり複雑なコーディングはしたくない。よって一番馴染みがあるNode.jsを使って簡単なスクリプトを組む。
やることは以下一点

  • 記事作成日が昨日の日付のものを抽出する

※スプレッドシートへの書き込みはひとまず置いといて、Livedoorブログから投稿記事の取得を試みる
※APIの細かい使用は公式を参照。

使用モジュール

  • wise
  • request
  • xml2json

ソース

livedoor.js
let 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から投稿することも可能らしい。
なんにせよこれで件数は取得できたので、次回はスプレッドシートへの書き込みをやってみようと思う。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ブラウザから利用できるメディアデバイスを取得する

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の引数に指定できます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ブラウザから利用できるメディアデバイスの情報を取得する

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の引数に指定できます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ひとまずの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.js
var 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-dev
gulpfile.js
var 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();
});

公式サイトのオプション一覧

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

演算子について

演算子とは

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

javascriptの特性について簡単にまとめてみた

javascriptってどういうもの?

javascriptは「ブラウザを操作」するプログラミング言語です。javascriptで何かプログラムを書けば、ブラウザはその命令通りに処理してくれます。

ブラウザの仕組み

ブラウザの役割はWEBページを表示させることです。WEBページはHTMLとCSS、数点の画像で作られてます。HTMLとCSSには重要な特徴があります。それは、一度ブラウザに読み込まれたら変化しないです。例えば、次のページに行こうとしない限り、基本的にブラウザ上に同じ情報を表示し続けます。

・HTML → ページのコンテンツを表示させるもの
・CSS → HTMLにスタイル情報を提供してレイアウトやデザインを決めるもの

ウィンドウ幅に合わせて伸縮するレイアウトやサイト、画面サイズに合わせてレイアウトを大きく変えるレスポンシブWEBデザインで作られているWEBページがありますが、レスポンシブもHTMLとCSSが初めに読み込まれた時から変わることはないです。HTMLとCSSは不変で静止したデータと言えます。

しかし、javascriptを使うことによって、これらの静止したデータであるHTMLとCSSを、その場でリアルタイムに書き換えることができます。加えて一部のコンテンツを入れ替えたり、画像のスライドショーのような動きをつけたりすることができます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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ページを作ることができます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vuex の使い方を勉強してみた

はじめに

Qiita 初投稿です:relaxed:

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.js
import 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.js
const 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.js
const moduleA = {
  state: { ... },
  getters: { ... },
  mutations: { ... },
  actions: { ... }
};

export default moduleA;
store/moduleB.js
const moduleB = {
  state: { ... },
  getters: { ... },
  mutations: { ... },
  actions: { ... }
};

export default moduleB;
store/index.js
import 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.js
const moduleA = {
  namespaced = true,

  state: { ... },
  getters: { ... },
  mutations: { ... },
  actions: { ... }
};

モジュール分割した Store をコンポーネントから参照する

以下のようなモジュール分割された Store を定義し、コンポーネントから参照してみる。

store/moduleA.js
const 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.js
const moduleB = {
  // moduleA と同じ内容
};

export default moduleB;

moduleAmoduleB はそれぞれが

  • データ項目 result
  • データ操作用のアクション setResultclearResult

を持つ。

以下のコンポーネントでは、 moduleAmoduleB の データ項目 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 を使うと状態管理がだいぶ楽になることが分かりました。
また、データの扱いがある程度ルール化されているので、初心者にはありがたいです。
最後までご覧いただきありがとうございました!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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使用時に思い出したら確実な結果を追記したいと思います。

とりあえず、苦労して見つけた解決策のような気がしたので備忘録として残しておきます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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の記事書きました。

ひとまずのsassコンパイル用gulpfile.js設定

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

#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,
    };

※残念ながらフレームを巻き戻すことは今の所出来ないようです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【備忘録】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.js

package.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.

起動後、localhost:5000 にアクセスする。
スクリーンショット 2019-07-01 16.26.03.png

次は、ビルドが実行できるかの確認をする。以下のコマンドを実行する。

$ 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-01 16.32.16.png

以上で確認完了。

作成したボイラープレートは こちら

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript関連

Javascript

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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(除去する子要素);

今日は以上です。
ちなみにイルカも好きですが、シャチの方が好きなので、シャチの絵文字が出ないかな〜
といつも思ってます。?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[勉強用] 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タブを確認してください。

image.png

このように、ローディング中にURLを変更すると、canceledとなっていることを確認できると思います。

おわりに

本コードは勉強用ですので、そのままでは使わないでください。(ちゃんとした実装はこちら)
さらなる課題と解決は次のステップへ。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vuexのモジュールを使用してストアを管理する

Vuex で状態管理を行っているアプリケーションでは、基本的にストアは1つだと思います。
アプリケーションが大きくなるにつれストアで管理する必要がある状態は多くなった場合、沢山のステートやミューテーションなどを一つのオブジェクトで管理する事になってしまうでしょう。
そういった事態を防ぐ為に、Vuex にはモジュールオプションが用意されています。
これに加えて、個人的にモジュールオプションを使用する事のメリットとして、管理するステートに紐づくオプションをまとめてグループ化できる事だと感じています。

私がVuexのモジュールオプションについて調べた時、分割したモジュールのステートへのアクセスや更新を行う方法に混乱して、なかなか理解が進みませんでした。
個人的な振り返りも含めて、簡単な「TODOアプリ」の作成を通して Vuex のモジュールの使用について記事にします。

Jul-01-2019 12-17-41.gif

この記事では、以下の環境で作業を進めていきます。

  • node -> 10.15.3
  • npm -> 6.4.1
  • @vue/cli -> 3.8.4

プロジェクトの作成

まずは、適当なディレクトリでプロジェクトを作成します。

vue create vue-todo-app

Vuexを使用する為、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.jssrc/store/modules/配下に作成します。

touch src/store/modules/tasks.js src/store/modules/persons.js

最後にsrc/main.jsでストアの読み込み先を変更します。

src/main.js
import 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.js
export 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.js
export default {
  state: {
    persons: [
      {id: 1, name: '一郎'},
      {id: 2, name: '次郎'}
    ]
  }
}

modulesのオブジェクトの中でインポートしたモジュールを読み込みます、

src/store/store.js
import 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
  }
})

スクリーンショット 2019-07-01 13.50.05.png

ストアに登録したモジュールのステートをコンポーネント側で読み込む

モジュールとして分割した場合でもステートを読み込む方法は変わりません。
コンポーネント側で読み込んだステートはモジュールの名前が付いたオブジェクトにラップされた状態となります。
これによってステートは、モジュール毎にスコープを分離する事ができます。

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>

これで、ストアにモジュールとして登録したステートを使用する事ができました。

スクリーンショット 2019-07-01 13.55.34.png

ステートの状態を更新する

チェックボックスの状態が変更されたタイミングでタスクのステートを変更できるようにします。
まずは、src/store/modules/tasks.jsにミューテーションとアクションを定義します。

src/store/modules/tasks.js
export 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>

Jul-01-2019 14-09-58.gif

モジュールとして登録されているミューテーション・アクション・ゲッターは、モジュールを使用しない場合と同じようにコンポーネント側での読み込み・使用が可能です。
しかし、モジュールを分けていても同じスコープ上に各ミューテーション・アクション・ゲッターが登録される為、モジュール間で使用している名前が競合する場合があります。

試しに tasks モジュールと persons モジュールに test というミューテーションを定義してみます。

src/store/modules/tasks.js
export 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.js
export 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>

Jul-01-2019 14-16-23.gif

モジュール間において同じ名前で登録されたミューテーションは、コミットで呼び出される時に一致する名前の処理がすべて実行されます。
(アクションも同様の挙動・ゲッターはエラーが発生します。)

名前空間の指定

モジュールとして分割するだけでは、ゲッター・ミューテーション・アクションも同一のスコープ上に登録されてしまいます。それらを、モジュール内に閉じ込める場合はnamespacedオプションにtrueを渡して名前空間を指定します。

src/store/modules/tasks.js
export 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.js
export 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.js
export 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>

まとめ

  • モジュールとして分割する事でコードの見通しがよくなる
  • モジュールとして分割する事でモジュール間のステートのスコープを分ける事ができる
  • モジュールとして分割 + 名前空間を指定する事でゲッター・ミューテーション・アクションもモジュール内に閉じ込める事ができる

名前空間の指定をしない場合は、コミットやディスパッチで指定した名前の処理がすべてのモジュールに対して実行される為、どのモジュールにどのような処理が定義されているか把握している必要がありそうです。しかし、チームで開発している場合や規模の大きなアプリケーションだと全て把握する事は難しいので、基本的にモジュールを分ける場合は名前空間を指定した方が良いかなと思いました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ESLintのバージョンあげたらhasOwnPropertyで怒られるようになった。

ESLintのバージョンを6にあげたらいろいろな箇所でエラーになるように...

object.hasOwnProperty(key)で怒られてました。

検索すると説明がありました。

ESLint v6.0.0 の変更点まとめ

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);
}

みたいな?(関数名が長いし、名前の英語はこれでいいのか??)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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を使う

  • 簡単
  • インターネットにつながってないと使えない
  1. 以下にアクセス
    https://code.jquery.com/

  2. jQuery Core 3.4.1のminifiedをクリック

    (どれでも良いですが、slim系のを選ぶと純粋なjQueryの機能に絞ったものになるので最後に紹介するjQueryUIなどが使えません)

  3. 出てきた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>

方法② ローカルにダウンロードして使う

  1. 以下にアクセス
    https://jquery.com/

  2. Download jQueryをクリック

  3. Download the compressed, production jQuery 3.4.1を右クリックし、名前を付けて保存する

  4. 適用したい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/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

# Node.jsとTypeScriptによる高速かつ軽量なWebシステム構築1 ~ 地獄の門前 ~

Node.jsとTypeScriptによる高速かつ軽量なWebシステム構築1 ~ 地獄の門前 ~

1.はじめに

 ツリー型情報掲載システム(Node.js+TypeScript版)
 ソースコード

 Node.jsとTypeScriptで上記のものを作ったので、このあたりの開発に関する話をしていきます。今回は導入部分です。

output_1.gif

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.次回の予定

 次回はバックエンドとフロントエンドで導入した開発手法の話になる予定output_1.gif

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptの"仕様にまだ導入されていない新機能"紹介

概要

ECMAScript の仕様は TC39 という組織によって決められているが、この記事ではまだ ECMAScript の仕様に導入されていないが TC39 が proposal (提案)として認めている新機能(ESNext とも呼ばれる)をいくつか紹介する。各見出しは該当の GitHub ページにリンクされている。

今すぐこれらの機能を使いたい場合は babel の plugin を使用するのがいいだろう。また、ブラウザの中でも特に Google Chrome は新機能の実装に積極的であり、仕様に導入される前の proposal を早めに Chrome に実装することがよくある。これについて最新情報を知りたい場合は @ChromiumDev を見ればよいだろう。

proposal ごとに stage (「提案されただけの段階」から「仕様に導入された段階」までのどれくらいの進捗度かを表す)が割り振られているが、今後変わっていくものであるためここでは書かない。stage ごとの一覧は以下のページで見られる。

Optional Chaining

ネストされたオブジェクトの深いプロパティに安全にアクセスしたいとき、一段階深く行くたびに undefinednull であるかどうかチェックする必要がある(なぜなら undefinednull のプロパティにアクセスしようとするとエラーが発生する)が、optional chaining ではその必要がなくなる。

Swift の同名の operator や C# の null-conditional operator に類似している。名前の由来はおそらくその Swift のものであり、Optional型の値からOptional型の値を得る operator であるため chain (連ねて使用すること)できるからだと思われる。

例えば o.a.b.c にアクセスしたいとき、以下のように、まず o が nullish (undefinednull)であるかチェックし、次に o.a が nullish であるかチェックし...と冗長なコードになってしまう。

Note: x == nullx が nullish のときのみ true になることに注意。

const o_a_b_c = (o == null || o.a == null || o.a.b == null) ? undefined : o.a.b.c

optional chaining により次のように簡潔に書ける。

const o_a_b_c = o?.a?.b?.c

key を expression で指定するプロパティアクセスや関数呼び出しもできる。

o?.[propName]  // o == null ? undefined : o[propName]
f?.(arg)       // f == null ? undefined : f(arg)

Nullish Coalescing

何らかの値が nullundefined である場合は代わりに何か自分の指定したデフォルト値を使用したいということがある。nullish coalescing はこれを簡潔に書くためのシンタックスである。nullish coalescing と呼ばれる理由は、左に nullish な値があると右の値と融合(coalescing)させられて結果一つの値となるからである。

Before

// o.a が null か undefined なら代わりに 1 を使用する
const o_a = o.a == null ? 1 : o.a

After

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.allPromise.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 に function f1 を適用し、その返り値に 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()  // 3

Private 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
  }
}

  1. data property は{ prop: 1 }のようにして作られる通常のプロパティであるのに対し、accessor property とは getter と setter によって定義されたプロパティである。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

はじめての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

設計

とにかく簡単にポップアップさせたかったので、コンテキストメニュー(右クリックで開くやつ)から動作させるようにします。こんな感じ。
無vvv題.png

超絶シンプルな機能なのでコードは短くできそうです。

コードを書く

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から書くのは初めてなのでどこかおかしいかもしれない。
が、まあ動くので大丈夫!(本当?)

おわりに

実際に使っているのですが、普通に便利で良いです。色々と作っていきたいですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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;
      <!-- URLpostcodeの値(入力値)を加える -->
      URL = URL+postcode;
      var request = new XMLHttpRequest();
      <!-- URLGETリクエストを投げる -->
      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でやってみる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【脱jQuery】AjaxをFetch APIで実現する

ナウいAjaxのやり方として、Fetch APIのテストソースを書きました。
呼び先のAPIは天気を取得するOpenWeatherMapを使用しています。

snippet.js
var 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);
    }
})();
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.proto
syntax = "proto3";

package helloworld;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply);
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

3. gRPCサービスの実装

nodeで作成

server.js
const 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.yaml
admin:
  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.Dockerfile
FROM envoyproxy/envoy:latest
COPY ./envoy.yaml /etc/envoy/envoy.yaml
CMD /usr/local/bin/envoy -c /etc/envoy/envoy.yaml

5. クライアントのコードを作成

5-1. client.js

client.js
const {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.jshelloworld_grpc_web_pb.jshelloworld.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ファイルを作成するためには、protocprotoc-gen-grpc-webが必要

6-1. protocをインストール

こちらを参考にインストールしました
http://google.github.io/proto-lens/installing-protoc.html

macで試したのでbrewでインストールしました。

bash
$ brew install protobuf
$ which protoc #インストールされたか確認
/usr/local/bin/protoc

6-2. protoc-gen-grpc-webをダウンロード

こちらのリンクからmac用のprotoc-gen-grpc-web-1.0.4-darwin-x86_64をダウンロードさせていただきました。

https://github.com/grpc/grpc-web/releases

Screen Shot 2019-06-30 at 16.png

ダウンロードしたら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

これでprotocprotoc-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.jshelloworld_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.js

7. jsファイルをwebpackでバンドルする

bash
yarn
yarn webpack client.js --mode development
#または、
npm i
npx webpack client.js --mode development

dist/main.jsが作られていればOK

8. サンプルを実行

ここまでで実行の準備ができましたので、実際に動かしてみます。

server.js実行

bash
node server.js #ポート9090でリッスンします

Envoyプロキシを実行

bash
docker 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

http://localhost:8081/

Screen Shot 2019-06-30 at 18.12.45.png

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

Screen Shot 2019-06-30 at 17.28.51.png

Hey! World0Hey! World4の部分で繰り返し処理のサンプル。
Got error, ...の部分でエラー処理のサンプルのようですので、こちも参考にすると良いと思いました。


gRPC-Webのchrome拡張も試してみた

こちらを試してみました

gRPC-Web Developer Tools - Chrome Web Store

githubはこちら

SafetyCulture/grpc-web-devtools

client.js
const {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で定義した内容に対して、値がわかりやすいように表示してくれました。

Screen Shot 2019-06-30 at 18.41.06.png


最後まで読んでいただいてありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む