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

オブジェクトの配列から、特定Keyのみの配列をつくる

JavaScript でオブジェクトの配列から、特定のKeyのValueで配列を作る

const a = [
 { a : 1, b : 4 },
 { a : 2, b : 5 },
 { a : 3, b : 6 }
];

const b = a.map((obj) => obj.a);

console.log(b); // [1,2,3]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsコンポーネント内でLaravelの .envを使用する方法

これまで

axiosインスタンスの作成(カスタム設定)をしたいときのbaseURLの記述部分で直接記述しても良いのか?という疑問に当たりました。

また、本番環境・開発環境などとこれから設定値も切り替えたいものでもありました。

let $axios = axios.create({
    baseURL: 'http://localhost:8000',
    timeout: 5000,
    headers: {
        'Content-Type': 'application/json'
    }
})

対処

process.env.[変数名]でアクセスできる.

Laravel環境変数

let $axios = axios.create({
    baseURL: process.env.MIX_BASE_URL,
    timeout: 5000,
    headers: {
        'Content-Type': 'application/json'
    }
})
env
MIX_BASE_URL=http://localhost:8000

所存

環境で変化するものはenvに入れてみようかなと思います(仮)

参考

https://medium.com/@patrickcurl/using-laravel-env-variables-inside-vue-js-components-29faa9a344c5

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

NuxtのasyncDataとdataの使い分け方

NuxtにはasyncDataがあります。Vueはdataのみが使えますが、NuxtはasyncDataとdataどちらも使えます。

では、asyncDataとdataはNuxtでどう使い分ければ良いのか?

これが疑問でしたので、asyncDataとdataの違いについて調査してみました。結論から言うと、私はasyncDataのみを使い、mountedでwindowやdocumentにアクセスする方針に決めました。

なおfetchはasyncDataと似た挙動をしますが、コンポーネントに値をセットすることはできません。fetchはVuexに値をセットする目的で使用されます。

以下は私の調査結果と、考察になります。間違いがあればご指摘をお願いいたします。

asyncDataとdataのに違いについて調査してみた

async/awaitについて調査

まずはなぜasyncDataが必要なのかを調べるために、dataをasync functionにして非同期処理を含めてみました。
すると、dataが実行されていることは確認できましたが、コンポーネントに値がセットされていませんでした。

dataはawaitなしで実行され、asyncDataはawaitつきで実行されているようです。
今回のケースでは、dataはPeomiseを返していたため、コンポーネントに値がセットされていなかったということです。

SSR時の実行順序について調査

サーバーでasyncDataの呼び出し→
サーバーでdataの呼び出し→
クライアントでdataの呼び出し

となりました。クライアントでasyncDataは実行されないので、windowやdocumentにアクセスできるのはdataだけとなります。(サーバーでアクセスしないように、process.clientがtrueかチェックする必要あり)
SSRする初回アクセス時に、windowやdocumentにアクセスできるのがdataの唯一のメリットといえそうです。

SPAページ遷移時の実行順序について

クライアントでasyncDataの呼び出し→
クライアントでdataの呼び出し

となりました。

それぞれがreturnしたオブジェクトの上書きについて

コンポーネントへ値をセットすることに関しては、asyncDataの値が優先されます。
ややこしいことにdataの方が後に実行されるのですが、同じプロパティについてはasyncDataの値が優先されます。

結論

asyncDataだけで良さそうです。繰り返しになりますが私はasyncDataのみを使い、mountedでwindowやdocumentにアクセスする方針に決めました。

好みの問題ですが、非同期処理のみをasyncDataに書いて、それ以外をdataにするセマンティックを重視した方針もあるかと思います。

asyncDataとdataうまく使い分けたところで、ページの表示が大幅に速くなることはなさそうです。

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

React hooksとclassコンポーネントにおけるsetStateの挙動の違いについて(動かせるコードあり)

概要

こちらは以下の記事に書いたhooksとclassコンポーネントにおけるsetStateの違いを別記事にしたものです.
https://qiita.com/yseki_/items/71511db1a60ab22ee663

忙しい人向けまとめ

hooksとクラス型コンポーネントのsetStateでは挙動が違う
hooksでは古いステートが引き継がれないため,更新の際は古いステートもセットする必要がある

それぞれのステート更新について

まずはhooksとclassのsetStateの違いを体験してみてください.
以下二つをhtmlファイルに貼り付け,お好きなブラウザで開いてみてください.
うまく動作しない場合,以下のリポジトリをcloneしてファイルをブラウザに貼り付けてください.
https://github.com/YutaSeki36/reactSetStateDiff

クラス型コンポーネント

classComponent.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>ClassComponent</title>
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script><!-- Don't use this in production: -->
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
</head>
<body>
<div id="root"></div><script type="text/babel">
class ClassComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
a: 0,
b: 0
};

this.handleChangeA = this.handleChangeA.bind(this);
}

handleChangeA(a) {
this.setState({ a: this.state.a + a });
}

render() {
console.log(`a: ${this.state.a}, b: ${this.state.b}`);
return (
<div>
<p>a: {this.state.a}</p>
<p>b: {this.state.b}</p>
​
<button onClick={() => this.handleChangeA(1)}>a+1</button>
</div>
);
}
}

ReactDOM.render(<ClassComponent />, document.getElementById("root"));
</script>
</body>
</html>

関数型コンポーネント

FunctionComponent.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>FuntionComponent</title>
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script><!-- Don't use this in production: -->
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
</head>
<body>
<div id="root"></div><script type="text/babel">
const FuncComponent = props => {
const [state, setState] = React.useState({ a: 0, b: 0 });

const handleChangeA = a => {
setState({ a: state.a + a });
};

console.log(`a: ${state.a}, b: ${state.b}`);

return (
<div>
<p>a: {state.a}</p>
<p>b: {state.b}</p>
​
<button onClick={() => handleChangeA(1)}>a+1</button>
</div>
);
};

ReactDOM.render(<FuncComponent />, document.getElementById("root"));
</script>
</body>
</html>

検証

それぞれ見た目は以下のようになるかと思います.

スクリーンショット 2020-01-20 20.25.43.png

まずはクラス型コンポーネントの a+1 を押してみましょう.
スクリーンショット 2020-01-20 20.27.44.png

このように,無事更新ができました.

次は関数型コンポーネントで動作確認を行なってみましょう.

スクリーンショット 2020-01-20 20.29.37.png

このように,hooksでステートの更新を行うと,bがundefinedになってしまいました.

解決策

公式チュートリアルに,以下のような記述があります.

しかしクラスでの this.setState とは異なり、state 変数の更新は、マージではなく必ず古い値を置換します。

とありますので,hooksによるステート更新の際は古いステートもセットする必要があります.

先ほどの関数型コンポーネントファイルのステート更新mesoddo部分を以下のように書き換えます.

funcCom.html
const handleChangeA = a => {
 // スプレッド構文を用いて、古いステートも記述する
          setState({ ...state, a: state.a + a });
};

これで無事にステート更新を行うことができました.
スクリーンショット 2020-01-20 20.38.32.png

結び

あまりReactに慣れていない状態でhooksに手を出してしまい,初歩的なところで躓いてしまいました.
しっかりと公式ドキュメントにも書いている内容だったため,今後新たな技術を学ぶ際は公式ドキュメントに目を通そうと思いました.

(誤字脱字,間違った内容などあればコメントください?‍♂️)

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

obnizのBLEでペリフェラルに接続する

BLEデバイスを見つけるだけでなく接続するというのをやってみます。
接続することで実際にデータを送ったり、データを受け取ることができるようになります。

見つけてから接続

接続するためにはまず、見つけなければなりません。
そして見つけたperipheralに対してconnect()を呼ぶことで接続できます。

接続とスキャンの同時実行はできないため、自動でスキャンは停止されます。
ただ、接続してしまったあとの両立はできますので再度スキャンの実行ができます。

それでは、同じくアプリで作ったBlankというBLEデバイスに対して接続してみます。

var obniz = new Obniz("OBNIZ_ID_HERE");
obniz.onconnect = async function () {
  await obniz.ble.initWait();
  var target = {
    localName: "Blank"
  };
  var peripheral = await obniz.ble.scan.startOneWait(target);
  if(peripheral) {
    console.log("found");
    await peripheral.connectWait();
    console.log("connected");
    obniz.ble.scan.start();
  }
}

callbackではこのように記載できます。

var obniz = new Obniz("OBNIZ_ID_HERE");
obniz.onconnect = async function () {
  await obniz.ble.initWait();
  var target = {
    localName: "Blank"
  };
  obniz.ble.scan.start(target);

  obniz.ble.scan.onfind = function(peripheral){
    console.log("found");
    obniz.ble.scan.end();

    peripheral.onconnect = function(){
      console.log("connected");
    }
    peripheral.ondisconnect = function(){
      console.log("closed");
    }
    peripheral.connect();
  };

  obniz.ble.scan.onfinish = function(peripheral){
   console.log("scan timeout!")
  };
}

接続できたでしょうか。
うまく行けばこのようなログが出ると思います。

lessons_obnizjs_ble_conn.png

接続するためにはscanを終了してから接続する必要があります。

awaitな書き方

スキャンと同じく、ペリフェラルとの接続もawaitを利用して書くことができます。

var obniz = new Obniz("OBNIZ_ID_HERE");
obniz.onconnect = async function () {
  await obniz.ble.initWait();
  var target = {
    localName: "Blank"
  };
  var peripheral = await obniz.ble.scan.startOneWait(target);
  if(peripheral) {
    console.log("found");
    var connected = await peripheral.connectWait();
    if(connected){
      console.log("connected");
    }else{
      console.log("failed");
    }
  }
}

エラーハンドリング

今は接続しているだけですが、通信を始めるとエラーが起こることがあります。
途中で相手がいなくなったとか、できないことを要求したとか。

ペリフェラルとのあいだで起きたエラーはこのように検出できます。

var obniz = new Obniz("OBNIZ_ID_HERE");
obniz.onconnect = async function () {
  await obniz.ble.initWait();
  var target = {
    localName: "Blank"
  };
  obniz.ble.scan.start(target);

  obniz.ble.scan.onfind = function(peripheral){
    console.log("found");
    obniz.ble.scan.end();

    peripheral.onerror = function(err){
      console.log("error : " + err.message);
    }

    peripheral.onconnect = function(){
      console.log("connected");
    }
    peripheral.ondisconnect = function(){
      console.log("closed");
    }
    peripheral.connect();
  };

  obniz.ble.scan.onfinish = function(peripheral){
   console.log("scan timeout!")
  };
}

エラーには何に対してやった何のエラーなのかが入っています。
詳しくはBLE Central Docをご覧ください。

切断

接続できたペリフェラルと切断するには

peripheral.disconnectWait();

を利用します。
繋がったらすぐに切るというプログラムにする場合はこのようになります。

var obniz = new Obniz("OBNIZ_ID_HERE");
obniz.onconnect = async function () {
  await obniz.ble.initWait();
  var target = {
    localName: "Blank"
  };
  obniz.ble.scan.start(target);

  obniz.ble.scan.onfind = function(peripheral){
    console.log("found");
    obniz.ble.scan.end();

    peripheral.onconnect = async function(){
      console.log("connected");
      peripheral.disconnect();
    }
    peripheral.ondisconnect = function(){
      console.log("closed");
    }
    peripheral.connect();
  };

  obniz.ble.scan.onfinish = function(peripheral){
   console.log("scan timeout!")
  };
}

これを実行するとこのようになります。

lessons_obnizjs_ble_disconnect.png

または、同期的な切断は以下のようなやり方です。

var obniz = new Obniz("OBNIZ_ID_HERE");
obniz.onconnect = async function () {
  await obniz.ble.initWait();
  var target = {
    localName: "Blank"
  };
  var peripheral = await obniz.ble.scan.startOneWait(target);
  if(peripheral) {
    console.log("found");
    await peripheral.connectWait();
    console.log("connected");
    await peripheral.disconnectWait();
    console.log("disconnected");
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

javascript デジタルなカウントダウンタイマー

デジタルチックな良い感じのタイマーを実装したので速攻で共有します。
成果物はこんな感じです。
スクリーンショット 2020-01-17 20.47.30.png
コードはこんな感じ。
なお、スタイリングには Bootstrap4&CSS を使用しています。
bootstrap4 download

index.html
 /* 画面の右下に配置 */
<div class="" style="position:fixed;bottom:-30px;right: -100px;z-index:100;width: 17em">
        <main>
            <!-- 時計 -->
            <div class="clock text-center">
                <!-- 時間 -->
                <div class="numbers">
                    <p class="hours mb-0"></p>
                    <p class="placeholder mb-0">88</p>
                </div>

                <div class="colon">
                    <p class="mb-0">:</p>
                </div>

                <!-- 分 -->
                <div class="numbers">
                    <p class="minutes mb-0"></p>
                    <p class="placeholder mb-0">88</p>
                </div>

                <div class="colon">
                    <p class="mb-0">:</p>
                </div>

                <!-- 秒 -->
                <div class="numbers">
                    <p class="seconds mb-0"></p>
                    <p class="placeholder mb-0">88</p>
                </div>
            </div>
        <!-- タイマーを止めるボタン(機能は実装しません) -->
            <div class="text-center">
                <button type="button" class="btn btn-light font-weight-bold my-0 px-5" >タイマーを止める</button>
            </div>
        </main>
    </div>

お次にcss

style.css
* {
    margin: 0;
    padding: 0;
}
main {
    color: #ffffff;
    background-color: #000000;
    font-family: 'digital-7', sans-serif;
}
main {
    transform: translate(-50%,-50%);
    padding: 15px;
    border: solid 5px #ffffff;
}

div.days {
    margin: 0 auto;
    color: #131212;
}

div.days .day {
    display: inline-block;
}

div.days .day p {
    font-size: 12px;
    font-weight: bold;
    font-family: sans-serif;
    text-transform: uppercase;
}

div.clock div{
    display: inline-block;
    position: relative;
}

div.clock div p{
    font-size: 30px;
    position: relative;
    z-index: 100;
}

div.clock .placeholder {
    color: #131212;
    position: absolute;
    top: 0;
    z-index: 50;
}

.light-on {
    color: #ffffff;
}

からのjavascript

index.js
// 9:00:00 -> 09:00:00 って一桁の数値の場合に0を付与してあげる関数
function addZero (val){
    return (val <= 9) ? ("0" + val) : val;
}

const second = 1000,
      minute = second * 60,
      hour = minute * 60,
      day = hour * 24;

let countDown = new Date('Nov 10, 2020 00:00:00').getTime(),
    hoge = setInterval(function() {

      let now = new Date().getTime(),
          distance = countDown - now;

        document.getElementsByClassName('hours')[0].innerText = addZero(Math.floor((distance % (day)) / (hour))),
       document.getElementsByClassName('minutes')[0].innerText = addZero(Math.floor((distance % (hour)) / (minute))),
       document.getElementsByClassName('seconds')[0].innerText = addZero(Math.floor((distance % (minute)) / (second)));

      // 時間が0以下になった時の処理
      if (distance < 0) {
        clearInterval(hoge);
      }

    }, second)

デジタルチックなフォント表現をするために Digital 7フォントを使用しています。
Digital 7フォントから任意のファイルをダウンロードしてhtmlファイル内で読み込んであげてください。

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

javascript 別窓を開く

一応メモとして


window.open(リンク先URL ,ウィンドウ名 , オプション)

width ウィンドウの幅 数値

height ウィンドウの高さ 数値

left ウィンドウの左の位置 数値

top ウィンドウの上の位置 数値

menubar メニューバーの有無 0 あるいは 1

toolbar ツールバーの有無 0 あるいは 1

status ステータスバーの有無 0 あるいは 1

scrollbars スクロールバーの有無 0 あるいは 1

resizable サイズ変更の可否 0 あるいは 1


別ウィンドウを開く際にPOSTでパラメータを送る

window.open("" ,"pdf_win");

window.openで空のページを開いてからそこに対してSUBMITを行います。

document.frmCart.action='cart_pdf.php';

document.frmCart.method = 'POST';

document.frmCart.target = 'pdf_win';

document.frmCart.submit();

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

Reactでの関数のバインド 4つの方法のメモ

はじめに

Reactでの関数のくっ付け方の方法のメモ
Bind(バインド)ってやつです

目次

  1. renderの中でくっ付けてしまう
  2. renderの中でアロー関数を使う
  3. コンストラクタの中でくっ付ける
  4. クラスの属性としてアロー関数を使う
  5. まとめ

1. renderの中でくっ付けてしまう

onClick={this.clickHandler.bind(this)で、そのまま行けるんですね。
ただし、これだと複雑になってくると辛そう。。。
Reactを業務で触ってないので、想像でしか無いですが。。

import React, { Component } from 'react'

export class EventBind extends Component {
    constructor(props) {
        super(props)

        this.state = {
            message: 'こんにちは'
        }
    }
    clickHandler() {
        this.setState({
            message: 'さようなら'
        })
    }
    render() {
        return (
            <div>
                <button onClick={this.clickHandler.bind(this)}>挨拶</button>
                <p>{this.state.message}</p>
            </div>
        )
    }
}
export default EventBind

2. renderの中でアロー関数を使う

アロー関数を使用すると、こんな感じ。
onClick={() => this.clickHandler()}

import React, { Component } from 'react'

export class EventBind extends Component {
    constructor(props) {
        super(props)

        this.state = {
            message: 'こんにちは'
        }
    }
    clickHandler() {
        this.setState({
            message: 'さようなら'
        })
    }
    render() {
        return (
            <div>
                <button onClick={() => this.clickHandler()}>挨拶</button>
                <p>{this.state.message}</p>
            </div>
        )
    }
}
export default EventBind

3. コンストラクタの中でくっ付ける

これは、公式のドキュメントに載ってるものですね。

import React, { Component } from 'react'

export class EventBind extends Component {
    constructor(props) {
        super(props)

        this.state = {
            message: 'こんにちは'
        }
        this.clickHandler = this.clickHandler.bind(this)
    }
    clickHandler() {
        this.setState({
            message: 'さようなら'
        })
    }
    render() {
        return (
            <div>
                <button onClick={this.clickHandler}>挨拶</button>
                <p>{this.state.message}</p>
            </div>
        )
    }
}
export default EventBind

4. クラスの属性としてアロー関数を使う

アロー関数とクラスの属性を組み合わせると、こんな風にも書けるんですね。

import React, { Component } from 'react'

export class EventBind extends Component {
    constructor(props) {
        super(props)

        this.state = {
            message: 'こんにちは'
        }
    }
    clickHandler = () =>{
        this.setState({
            message: 'さようなら'
        })
    }
    render() {
        return (
            <div>
                <button onClick={this.clickHandler}>挨拶</button>
                <p>{this.state.message}</p>
            </div>
        )
    }
}
export default EventBind

5. まとめ

色々な書き方がありますが、公式のが一番無難かな??
どこで付けてるのか、すぐに分かるのは大切。
最後に、まとめたのを置いとおきます。

See the Pen React Function Bind by oq-Yuki-po (@oq-yuki-po) on CodePen.

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

JavaScriptの差分ライブラリを使ってERBに入れてみた

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>vue_pager</title>
  <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/diff2html/2.3.3/diff2html.min.css">
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jsdiff/3.4.0/diff.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.js"></script>
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/diff2html/2.3.3/diff2html.min.js"></script>
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/diff2html/2.3.3/diff2html-ui.min.js"></script>
</head>
<body>
<div id="user" userJson="<%= @user.to_json %>"></div>
<div id="app">
</div>
</body>

<script>
    document.addEventListener('DOMContentLoaded', function() {
        var diff = document.getElementById('user');
        var diffJson = JSON.parse(diff.getAttribute('userJson'));
        const unifiedDiff = JsDiff.createPatch("user", diffJson[0], diffJson[1], "変更前", "変更後");
        const diff2htmlUi = new Diff2HtmlUI({diff: unifiedDiff});
        diff2htmlUi.draw('#app', {inputFormat: 'json', showFiles: true, matching: 'lines'});
    });
</script>
</html>

HTMLでdiffを表示させてみる
デモ

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

備忘録 JavaScriptの差分ライブラリを使ってERBに入れてみただけ

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>vue_pager</title>
  <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/diff2html/2.3.3/diff2html.min.css">
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jsdiff/3.4.0/diff.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.js"></script>
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/diff2html/2.3.3/diff2html.min.js"></script>
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/diff2html/2.3.3/diff2html-ui.min.js"></script>
</head>
<body>
<div id="user" userJson="<%= @user.to_json %>"></div>
<div id="app">
</div>
</body>

<script>
    document.addEventListener('DOMContentLoaded', function() {
        var diff = document.getElementById('user');
        var diffJson = JSON.parse(diff.getAttribute('userJson'));
        const unifiedDiff = JsDiff.createPatch("user", diffJson[0], diffJson[1], "変更前", "変更後");
        const diff2htmlUi = new Diff2HtmlUI({diff: unifiedDiff});
        diff2htmlUi.draw('#app', {inputFormat: 'json', showFiles: true, matching: 'lines'});
    });
</script>
</html>

HTMLでdiffを表示させてみる
デモ

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

WEBエンジニアへの転職を目指す外資系新卒が、8週間の学習過程を晒してみる

何をやっているのか

  • 2019年3月に東京の大学(ド文系)を卒業し、続く4月に新卒として米系のITサービスプロバイダに就職
  • 3ヶ月のC言語研修を経て、IBMのメインフレームやオフィスコンピュータといった汎用システムの運用・保守を行うプロジェクトに配属

なぜ転職するのか

  1. 就活時、IT業界を目指しており、テクノロジー、ビジネスサイド両方に興味があった
  2. 技術のわからないPMになりたくなかったので、最初はITエンジニアとして開発の仕事を行い、徐々にマネジメントのキャリアを歩める会社を探していた
  3. 日本のSIerのほとんどが最初から上流工程でほとんどプログラミングをできない、もしくは下流工程でずっとコーダーという、極端なパターンが多いことを就活の業界研究の過程で学んでいった
  4. 最初に開発、徐々にマネジメントというキャリアを歩める会社に絞って就活を行い、理想的な会社に就職できた(と思っていた)
  5. 入社後、3ヶ月の研修が終わると、汎用システムの運用保守を行うプロジェクトに配属される
  6. 仕事内容はマニュアル通りにコマンドを実行し、システムに問題がないか確認したり、定期監視のレポートをチェックして、問題があれば該当部門に連絡するという技術的なスキルが何も身につかないものだった
  7. 自分のキャリアが腐っていく音が聞こえたので、2019年11月から転職を決意して独学でWEB界隈の勉強を開始する

なぜこの記事を書くのか

  • 自分が勉強を始めたとき、どういったロードマップを設定して進めていけばいいのかわからなかったので、同じような境遇の人の参考程度になればと思った
  • 勉強してきたことを整理して、今後の学習方針の見直し、参考にするため
  • 識者の方々から学習方針に関するアドバイスが得られればいいなあという希望的観測

どう勉強してきたか

早速本題。

基本的な自分のスタンスとして、まずは未経験の門戸が広いフロントエンドエンジニアとしての就職を目指している。実際のところはフロントもバックエンドも興味がありやりたいことが多すぎるので、とにかくまずは転職を成功させてスキルを伸ばしていった上で自分の進む分野を決めたいと思っている。

また、スクールに通うつもりはなくこれまでもすべて独学で勉強を進めてきた。スクールに行かないのはお金の理由もあるけれど、どこも胡散臭い印象が拭えないというのが一つと、大学受験のときから一人で黙々と勉強を進めるのが得意だし好きだったというのが主な理由。

勉強開始して1週間ほど経ったころからTwitterを始め、勉強した日は日報を残すようにしているので、それをベースにしながらどんなことをやってきたのか思い出しながら下記に1週間単位でまとめていく。

ちなみに現時点(2020年1月20日)で学習開始から59日目、学習総時間は272時間となっている。

1週目

HTML・CSS

HTML5+CSS3 手を動かしてマスターする WEBデザイン/プログラミング動画講座
最初に手を付けたのがUdemyの上記講座。10時間程度の講座で、1.5倍速で再生して進めていった。このコースでは、タイトルにもあるように動画に沿って実際に手を動かしながら進めたので、実質15時間ぐらいかかったと思う。
これでHTML・CSSの基礎の基礎は理解できたと思ったのでJavaScriptに移った。

JavaScript

The Complete JavaScript Course 2020: Build Real Projects!
Udemy for Businnessという法人向けのサービスを会社が契約していることを知り、それを利用して見始めたのが上記のコース。会社のアカウントでは英語のコースしか利用できないものの、全て無料で登録、視聴できるのがかなり嬉しかった。

このコースではJavaScriptの基本から始まり、検索結果を元にAPIを叩くようなある程度本格的なWEBアプリの開発まで体験できる。JavaScriptの基本パートではES5をまず教わり、その後ES5との違いを比較しながらES6をわかりやすく説明してくれる。

このコースのいいところは以下の点。

  1. Q&A機能が開放されていて、複数のスタッフによるサポート体制が整っている
  2. 講義→実践という流れで、学んだことを実際に使う機会が豊富に用意されているのでしっかり理解できる
  3. 本当に知識がゼロでも、Whyがわかる構成になっている

特に3は自分の中で大きくて、本質的な理解ができないと気がすまない自分にはありがたかった。先生のJonasはこのコース以外にも、HTML・CSS基礎、CSS上級コースなどをやっていて、自分はこの人がすごく気に入ったので後にこの2つも受講した。

これが初めての英語のコースだったが、英語の講座の充実度とコンテンツの質の高さに驚いた。英語のリスニングに問題がない人は絶対に英語の講座がオススメ。JonasのHTML・CSS講座を後に受けることになるが、最初に見たUdemyの日本語講座に比べて量も質も圧倒的に勝っていて、もう日本語の講座は受けなくていいやとなった。有料だし。

2週目

LP模写

動画を見続けることに疲れたのと、1週目にやったHTML・CSSの復習がしたくなったので、「LP模写 初心者 オススメ」とかで適当に検索して出てきた以下のサイトを模写することにした。
flower | かわいいが届く、ずっと無料のお花便

目的はHTML・CSSの基本的な書き方やよく使うプロパティを覚えることだったので、完璧に模写するつもりはなかった。Google Developer Toolもガンガン使ってずるしまくったのに結局完成させるのに20時間ぐらいかかった。でも1週目の講座の学びも思い出せたのでやってよかったと思う。アウトプット本当に大切。

3週目

CSS上級

Advanced CSS and Sass: Flexbox, Grid, Animations and More!
上記のコースを開始する。JavaScriptの講座でお世話になったJonas先生のCSS上級講座。CSSの仕組みを始めとして、複雑なアニメーション、NPM、Sass、BEM設計、Flexbox、CSS Gridなどなど、幅広いながらも実際の業務では必須とも言える(実務未経験のため予想w)内容を網羅してくれている。Jonas先生の良さは言うまでもなく、実際の演習で作るWebサイトがとてもおしゃれで作っていて楽しかった。

28時間のボリュームのため骨が折れるかもしれないが、つまみ食いしていく進め方でも良いと思う。自分は1.5倍速で進めたものの、さすがのボリュームに途中で疲れたので一旦視聴をやめて、いままで習ったことの総復習としてToDoアプリを作り始めた。その作成過程でやっぱりFlexboxあったほうが便利だろうなあと思い、Jonasのコースに戻ってそのパートだけ視聴したりした。実際CSS Gridのパートは見ないまま終わっている。

必要になったら学習するスタイルのメリットは3つほどあると思っていて、

  1. 学んだことをすぐにアウトプットする場があるので「わかる」→「できる」の転換が早い
  2. なぜその技術が重要なのか、必要なのか理解しやすい
  3. 「時間をかけて学んだのに、その知識・技術は結局使うことがなかった」という事態を避けられる

これに関してはどの3つもすごく大きなメリットだと思っている。3に関しては、とにかく一刻も早く今の会社を抜け出したい自分にとって、時間効率という観点からも非常に重要な要素だ。

一方で、網羅的な知識を身に着けることの重要さも感じているので、そのあたりは転職活動を終えて時間に余裕ができたら進めていきたいと思っている。

ともかく、フロントエンドの基礎を学びたいに人にはJonas先生おすすめです。セールのときにでもぜひ買ってみてください。

4週目

Git

Git Complete: The definitive, step-by-step guide to Git
次に模写をするときにセーブ機能が欲しくなったので、Gitの勉強を始めようと受講したのが上記のコース。ただ、これは途中で視聴をやめてしまった。というのも、Git自体は複雑なものではないので、動画で学習するより公式のドキュメントなどの文章形式のほうが時間効率的にもよろしいと思ったから。

実際、Gitに関する資料は充実していて、
- サルでもわかるGit入門
- gittutorial Documentation

このへんを読んで、あとはわからないことを都度調べていくぐらいでいいのかなと思った。

LP模写

JonasのCSS上級コースで作成したWebサイトを、Google Dev Toolを最低限使用して自分でゼロから作り直す作業を始めた。

目的としては、

  1. CSS上級の内容を復習したかった
  2. Gitを使ったバージョン管理を試してみたかった

という2つだったので、こちらも詰まったらすぐDev Toolでズルしつつサクサクと進めていった。

ToDoアプリ作り

先に少し話した、ToDoアプリの作成を開始した。 この目的は3つで、

  1. CSS上級コースの視聴(インプット)に疲れて、逃げ場としてアウトプットしたくなった
  2. JavaScriptの内容を忘れている気がしたので復習したかった
  3. そろそろ一つぐらいまともな成果物を作りたかった

このToDoアプリ作りは割と正解だったと思っている。必要なことを必要なタイミングで学ぶことの有用さや、Gitの便利さ、HTML・CSS・JavaScriptの理解の深化といったことはもちろん、ゼロから自分の手を動かしてモノを一つ作ったという達成感と自信を得られたのは良かった。その後の学習の大きなモチベーションにもなったと思う。

挙動はめちゃくちゃおかしいけど作って満足して放置しているw
Trellon_Progress-compressor.gif

5週目

Chingu事前課題作成

Chinguというサービスを発見する。これは、世界中の人とリモートでチームを組んで、チームで設定した成果物を一緒に進めていくことができるサービスだ。本開発への参加の前に事前課題の提出が必要なため、こちらに取り組んでいた。事前課題をもとに、運営が適切なレベルのチームに振り分けてくれるらしい。

自分は一番下のコースであるHTML・CSS・JavaScriptのチーム開発をしたかったので、それに対応する事前課題を選択してLP模写を行った。LP自体は完成したものの、結局Chinguの本開発には参加しなかった。というのも、本開発に参加するとその作業に時間をかなりとられることになり、他の作業に時間を割けなくなりそうだと途中で思ってしまったから(登録する前に気付けw)。これに割かなければいけない時間に対して、オンラインでチーム開発をしたという経験は、果たして転職活動においてどれほど有利になるだろうかと考えた結果、そこまで優先順位は高くないと考えた。

この事前課題に使ってしまった時間は今思うともったいなかったなと。Chinguというサービスを見つけて「このサービスおもしれー!」みたいな勢いで始めてしまったので、何事も作業に取り掛かる前に時間対効果を熟慮することが大切だと感じた。当たり前の話だけど、時間がないなら作業の優先順位を厳格につけなければならない…。

React

次にJSフレームワークを学習しようと思い調べてみると、Angular, React, Vueの3つが現在の主流であることを知った。悩みに悩んだ結果、大学時代の友人がReactでサービスをなにか作ろうと思っているという話を聞いて、自分も勉強ついでに手伝えればと思いReactを選択した。結局その話はなくなったので意味はなかったのだけどw

使用した教材はこれ。
React - The Complete Guide (incl Hooks, React Router, Redux)

先生のMaxがこれまたいい。表情が豊か、ジェスチャーも豊か。テンションがポジティブで聞いているこちらも上向きな気持ちで勉強を進められる。動画の総時間は驚異の45.5時間。かなりのボリュームなので全て一度でマスターするのは不可能。自分は例にも漏れず必要になったタイミングで必要なパートを視聴するスタイルで進めた。

自分の場合、ReactとRailsで転職活動に使うポートフォリオを作るつもりだったので、まずはReactの基礎は知らないとアプリを作り始められないのでReduxの前のパートまでは一気に進めていった。

6週目

神経衰弱ゲーム作成

引き続きMaxの動画を見てReactの学習を進める。結局Reduxのパートまで動画を見進めて、アウトプットがしたくなったので神経衰弱ゲームを作成することにした。が、これは途中でやめることになる。

このころ、2020年の4月中に転職活動を終えるため、いつまでに何を終わらせればいいのか逆算をしていた。その結果、このままのペースではスケジュールに的に間に合わないことがわかってしまったのだ。1月中にポートフォリオのフロント部分(View)完成とRails基礎、2月にはRails TutorialとAWS、Dockerの勉強、3月にはポートフォリオのバック部分(Model, Controller)完成、4月に転職活動という流れだ。

そのため、神経衰弱ゲーム作成は早々に切り上げて、Reactのインプットと並行して転職活動で使うポートフォリオ作りに入ることにした。

7週目

ポートフォリオ作成

どうせ作るなら自分が使いたいと思えるものを作りたかったので何を作るか丸1日考えた。自分は小さい頃からゲームが大好きだったので、ゲームをテーマとして、ゲームプレイの感想を記録できるサービスを作ることにした。ただ、長々とした感想を書かなければいけないとなると大仰だし億劫になってあまり使わなそうだったので記録できる項目を絞ることにし、

  1. 星5点満点での評価
  2. 良い点を3行で書く
  3. 悪い点を3行で書く

という3つに限定して、ユーザー(自分)が使用するハードルを下げてみた。

アイデアが固まったらプロトタイプ作成もしてみたかったので、Justinmindというプロトタイプ作成アプリ(無料)で画面のレイアウトをざっくりと作ってみたりした。

Main.png
Your Game Review.png

こういう作業を年末年始にしていた。年明けからは本格的な実装に入ってみたものの、やっぱりReactの書き方がわからんすぎてMaxの講座で作ったサービスのコードを真似しながら少しずつ進めていった、というか現在進行系で進めている。

8週目 〜 現在

Ruby + Rails

ポートフォリオ作成は変わらず進めていたが、並行してバックエンドの技術として使うRailsの勉強を始めた。最初はUdemyの動画で学習を進めようと思っていたが、一番レビューが多い講座の内容が冗長な気がしたのでやめた。

結局Progateに登録して、基礎を固めることにした。Progateは評判の通り本当に素晴らしくて、楽しくサクサクと進めることができた。最初見ていたUdemyの講座内容に比べると、内容が端折られている気はしたが、プログラミング学習のきっかけを提供するというProgateの理念を考えると仕方がないと思った。網羅的な学習はポートフォリオ完成後に余裕ができてから行うつもり。

今はRubyとRailsのコースが全て終わったところで、次はRails Tutorialに着手する予定。もし難しすぎたらUdemyの動画を挟んでからリベンジしようと思っている。

Jest + Enzyme

ポートフォリオの成果物だけでなく、設計やテストなどの思想・周辺技術をどれほど考慮できているかということが重要という情報をツイッターで見かけた。なるほど確かにテストは実際の業務で必ず必要になってくるなと思い、ポートフォリオ作りの際にもテストを行っていこうと考えた。現在はUdemyで2019 Update! React Testing with Jest and Enzymeというコースを受講している。

そのため、一旦ポートフォリオ作成の作業は止めている。このコースでテストが何たるかというのを理解したら、ポートフォリオ作成を再開して、TDDを試してみようと思っている。

さいごに

今後の目標

1月の目標は、ポートフォリオのフロント側を完成させること、ProgateのRuby + Railsコースを終わらせることだったので後者は達成している。正直あと10日程度で前者を達成する自信はないが、引き続きストイックに作業を進めていってできるだけ2月に作業がずれ込まないようにしたい。

2月はRails Tutorialの終了とAWS、DockerのUdemy講座各1つずつ見る。

あと全体目標として、Qiita投稿を月に4回を設定してあるので今月はあと2記事書かなければwアウトプットが何かしら自分の糧になると信じて頑張っていこう。

問い合わせ

この記事に関してでもそれ以外のことでも、もし何かお聞きしたいことなどありましたらTwitter(@Ryo_Code)でもこちらでもお気軽にコメントください。自分の分かる範囲でお答えさせていただきます。

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

最近名前をよく見かけるsvelte/sapperを試してみた ~その2 構文編~

はじめに

この記事は前回投稿した記事
最近名前をよく見かけるsvelte/sapperを試してみた ~その1 導入編~
の続きの記事になります。

今回は、 svelte の構文について、メインに取り上げていきたいと思います。

参考リンク

svelteのサンプル集
svelteドキュメント

前提

今回は前回作成したプロジェクトの、
sapper_sample/src/routes/index.svelte
(笑顔のおじさんが映っているページ)のファイルを修正していきつつ、構文の確認を行いたいと思います。
現状は下記のようになっています。
(style部分は除く)

index.svelte
<head>
    <title>Sapper project template</title>
</head>

<h1>Great success!</h1>

<figure>
    <img alt='Borat' src='great-success.png'>
    <figcaption>HIGH FIVE!</figcaption>
</figure>

<p><strong>Try editing this file (src/routes/index.svelte) to test live reloading.</strong></p>

それでは始めていきます!
(長くなるので追加部分のみ記載しています。ご了承ください。)

構文

if/else

index.svelte
<script>
    let state = { switch: false };

    function toggle() {
        state.switch = !state.switch;
    }
</script>

{#if state.switch}
    <button on:click={toggle}>
        スイッチON
    </button>
    <div>スイッチONだよ!</div>
{:else}
    <button on:click={toggle}>
        スイッチOFF
    </button>
    <div>スイッチOFFだよ!</div>
{/if}

上記を実行すると、下記のようにクリックでスイッチのON/OFFが切り替わるようなボタンを設置することができます。

each

index.svelte
<script>
    let members = [
        { name: 'たろー', gender: '男性' },
        { name: 'じろー', gender: '男性' },
        { name: 'はなこ', gender: '女性' },
        { name: 'さぶろー', gender: '男性' }
    ];
</script>

<ul>
    {#each members as { name, gender }, i}
        <li>
            {i + 1}: {name} {gender}
        </li>
    {/each}
</ul>

上記を実行すると、定義したリストに応じた情報(ここの例ではメンバー)が表示されます。簡単ですね。

await

index.svelte
<script>
    let promise = getName();

    async function getName() {
                // 遅延させる
        await new Promise(r => setTimeout(r, 5000));
        return "JIRO"
    }
</script>

{#await promise}
    <p style="font-size:20px;color:#f00;">...waiting</p>
{:then member}
    <p style="font-size:20px;">{member}</p>
{/await}

上記を実行すると、async関数が値(ここではJIRO)を返すまで、 ...waiting の文字が表示されます。

リアクティブ

index.svelte
<script>
    let count = 0;
    function handleClick() {
        count += 1;
    }
</script>

<figure on:click={handleClick}>
    <img alt='Borat' src='great-success.png'>
    <figcaption>HIGH FIVE!</figcaption>
</figure>

<div style="text-align: center;">
    <div style="font-size:20px;">
        クリック回数:{count}
    </div>
</div>

上記を実行すると、 figure 要素(おじさん写真)をクリックするたびに、数字がインクリメントされます。
(下記画像ではおじさんを連打しています。)

ちなみに下記のようにすると、クリックした回数×2,3が画面に表示されるようになります。

index.svelte
<script>
    let count = 0;
    $: doubled = count * 2;
    $: tripled = count * 3;

    function handleClick() {
        count += 1;
    }
</script>

<figure on:click={handleClick}>
    <img alt='Borat' src='great-success.png'>
    <figcaption>HIGH FIVE!</figcaption>
</figure>

<div style="text-align: center;">
    <div style="font-size:20px;">
        クリック回数:{count}
    </div>
    <div style="font-size:20px;">
        クリック回数×2:{doubled}
    </div>
    <div style="font-size:20px;">
        クリック回数×3:{tripled}
    </div>
</div>

$:の意味は?

上記の例で $: が出てきましたが、
$: ラベルが付与された式や文は、変数の更新のたびに再計算されるようになります。
この時、 let 等は不要になります。
代入やバインドと関係のない変数に対して、再計算を行いたい場合に用います。

データバインディング

textbox

index.svelte
<script>
    let input_text = '';
</script>

<div style="text-align:center; font-size:20px;">
    <input bind:value={input_text} placeholder="入力してください。">
    <p>{input_text }!</p>
</div>

上記を実行すると、 テキストボックスに入力した文字が、その下に即時反映されます。

checkbox

index.svelte
<script>
    let check_status = false;
</script>

<div style="text-align: center;font-size:20px;">
    <label>
        <input type=checkbox bind:checked={check_status}>
        ここをチェック!
    </label>

    {#if check_status}
        <p>チェックしてくれてありがとう( ⁎ᵕᴗᵕ⁎ )</p>
    {:else}
        <p>チェックしてください(´・ω・`)</p>
    {/if}
</div>

上記を実行すると、 チェック可否によって文字が変更されます。

selectbox

index.svelte
<script>
    let questions = [
        { id: 1, text: `このおじさんの名前は?` },
        { id: 2, text: `このおじさんの年齢は?` },
    ];

    let selected;

    let answer = '';
    let message = ``;

    function handleSubmit() {
        message = `質問:「${selected.text}」 答え:「${answer}」`
    }
</script>

<form on:submit|preventDefault={handleSubmit}>
    <div>
        <div style="text-align: center">
            <select bind:value={selected} on:change="{() => answer = ''}"  style="font-size:20px;">
                {#each questions as question}
                    <option value={question}>
                        {question.text}
                    </option>
                {/each}
            </select>
        </div>
        <div style="text-align: center">
            <input bind:value={answer}  style="font-size:20px;">
            <button disabled={!answer} type=submit  style="font-size:20px;">
                Submit
            </button>

            <div style="font-size: 24px;color: #f00;">{message}</div>
        </div>
    </div>
</form>

上記を実行すると、 セレクトボックスの内容、テキストボックスの内容を表示します。

トランジション

index.svelte
<script>
    import { fade } from 'svelte/transition';
    let visible = true;
</script>

<div style="text-align: center;margin-bottom:40px; font-size:20px;">
<label>
    <input type="checkbox" bind:checked={visible}>
    表示する
</label>
</div>

{#if visible}
<figure transition:fade>
    <img alt='Borat' src='great-success.png'>
    <figcaption>HIGH FIVE!</figcaption>
</figure>
{/if}

上記を実行すると、 チェック可否によっておじさんがフェードインされます。
(GIFアニメがちょっと早かったです・・・。)

CSSと組み合わせて、下記のようなものも作れます。

index.svelte
<script>
    import { fade } from 'svelte/transition';
    import { elasticOut } from 'svelte/easing';

    let visible = true;

    function spin(node, { duration }) {
        return {
            duration,
            css: t => {
                const eased = elasticOut(t);

                return `
                    transform: scale(${eased}) rotate(${eased * 1080}deg);
                    color: hsl(
                        ${~~(t * 360)},
                        ${Math.min(100, 1000 - 1000 * t)}%,
                        ${Math.min(50, 500 - 500 * t)}%
                    );`
            }
        };
    }
</script>

<label>
    <input type="checkbox" bind:checked={visible}>
    実行
</label>

{#if visible}
    <div class="centered" in:spin="{{duration: 8000}}" out:fade>
        <span>GREAT SUCCESS!</span>
    </div>
{/if}

まとめ

今回はsvelteの基本的な構文について触れました。だいぶ直感的に描けますので、わかりやすいです。
まだまだ紹介しきれていないこと、色々なことができますので、ご興味のある方は
svelteのサンプル集svelteドキュメントを参考に試していただければと思います!

次回は、何か一つ簡単なアプリケーションを作成していこうかと考えています。
読んで頂きありがとうございました!

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

【Sentry】Vue.js まとめ

【Sentry】Vue.js まとめ

1. Setup

entry.js
Sentry.init({
  dsn: process.env.SENTRY_DSN,
  integrations: [new Integrations.Vue({ Vue, attachProps: true })]
})

2. Set user info

auth.js
const user = data.user
Sentry.configureScope(scope => {
  scope.setUser({
    id: user.staff_id,
    username: user.account,
    email: user.email_account
  })
})

3. Catch api error

action.js
try {
  dispatch('get api')
} catch (error) {
  Sentry.captureException(error)
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Cypress で form 送信後の画面を待ち受けたい

既存の WP 製システムにリグレッションテストを作っていく際に必要になったので調べました。

// form を submit で送信
cy.get('#my_submit_button').submit()

// `location.pathname` に
// 指定の文字列 (/path/to/new/page) が現れる事をテストする (ここでは最大10秒待つ指定)
cy.location('pathname', {timeout: 10000})
    .should('include', '/path/to/new/page')

// 遷移後の画面のテスト
cy.click('#new_page_button')

参考

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

【初心者】JavaScriptで「おみくじゲーム」を作ってみた

最近、JavaScriptを勉強しています。
練習として、簡単すぎる「おみくじゲーム」を作ってみました。
ちなみに、10分くらいで完成しました。笑

HTMLを記述する

index.html
<input type="button" value="今日のあなたの運勢は?" id="btn">
<p id="fortune"></p>

<script src="main.js"></script>

一番上にボタンを配置して、
その下に運勢を表示するための要素を記述しています。
JavaScriptを読み込むことも忘れずに。

JavaScriptを記述する

main.js
const btn = document.getElementById('btn');

btn.addEventListener('click', () => {
  const fortunes = ['大吉', '', '中吉', '小吉', '末吉', '', '大凶'];
  const randomNumber = Math.floor(Math.random() * fortunes.length);
  fortune.textContent = fortunes[randomNumber] + '';
});

クリックされるボタンを定数 btn に代入します。
idbtn の要素を getElementById で取ってきています。

そのボタンがクリックされたときのイベントを、その下に記述しています。
運勢のうちのいずれかを、運勢を表示する要素の文字列として格納しています。

7つの運勢を配列として、定数 fortunes に格納し、
いずれかを取ってくるようにしています。

運勢について

運勢の良さは、fortunes に記述してある順番の通りみたいです。
大吉の次が吉なのは知りませんでした。
僕はてっきり、小吉、吉、末吉の順番かと思ってました。(世界で一番どうでもいい余談)

addEventListener について

addEventListener(種類, 関数, false) という形で使います。
種類とは、イベントの種類のことで、clickload などがあります。
アロー関数の形であれば、先述したようにコードを書くことができます。

Math.random() について

Math.random() は、0以上1未満 の数を返します。
今回の場合、fortunes.length7 であるため、
Math.random() * fortunes.length は、0以上7未満 の数を返します。

Math.floor について

Math.random だと小数も返すことになるので、都合が悪いです。
できれば整数が返ってきてほしいところです。
そこで Math.floor を使うことで、小数点を切り捨ててくれます。

そのため、Math.floor(Math.random * fortunes.length)
0以上7未満の整数 すなわち 0から6のいずれかの整数 を返してくれます。

おわりに

すごく簡単に「おみくじゲーム」を作ることができました。
JavaScriptの一番最初の練習としては良かったです。
これはあまりにも簡単だったので、他にもミニゲームを作ってみたいと思います。

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

RxJS: map, concatMap, mergeMap, switchMap, exhaustMap の動作を理解する

サンプルコード書いたら大体理解できた。

利用シーンのイメージ:

  • map: 値の変換操作
  • concatMap: 同期処理など、キューイングして直列実行したい処理。
  • mergeMap: TODOのDone操作など、並列実行したい処理。
  • switchMap: リアルタイム検索のキーボード入力から検索する処理など、後続の処理のみ優先させたい場合。
  • exhaustMap: フォームのサブミットなど、多重実行されたくない処理。
import { of } from 'rxjs';
import {
  concatMap,
  exhaustMap,
  map,
  mergeMap,
  switchMap,
} from 'rxjs/operators';

const sleep = (ms: number) => {
  return new Promise(resolve => {
    console.log('sleep: start');
    setTimeout(() => {
      console.log('sleep: end');
      resolve();
    }, ms);
  });
};

const array = [1, 2, 3];

/**
 * map
 * * Promiseは展開されない
 * * 配列操作と同じ
 */
const mapExample = () => {
  of(...array)
    .pipe(
      map(i => {
        return sleep(1000).then(() => i * 2);
      }),
    )
    .subscribe(i => console.log(i));
};

// 出力:
// Promise { <pending> }
// Promise { <pending> }
// Promise { <pending> }

/**
 * concatMap
 * * Promiseが展開される
 * * 連続で値が流れてきた場合は直列で実行される。
 */
const concatMapExample = () => {
  of(...array)
    .pipe(
      concatMap(i => {
        return sleep(1000).then(() => i * 2);
      }),
    )
    .subscribe(i => console.log(i));
};

// 出力:
// sleep: start
// sleep: end
// 2
// sleep: start
// sleep: end
// 4
// sleep: start
// sleep: end
// 6

/**
 * mergeMap
 * * Promiseが展開される
 * * 連続で値が流れてきた場合は並列で実行される。
 */
const mergeMapExample = () => {
  of(...array)
    .pipe(
      mergeMap(i => {
        return sleep(1000).then(() => i * 2);
      }),
    )
    .subscribe(i => console.log(i));
};

// 出力:
// sleep: start
// sleep: start
// sleep: start
// sleep: end
// 2
// sleep: end
// sleep: end
// 4
// 6

/**
 * switchMap
 * * Promiseが展開される
 * * 連続で値が流れてきた場合は並列で実行され、ストリームには最後の結果しか流れない。
 */
const switchMapExample = () => {
  of(...array)
    .pipe(
      switchMap(i => {
        return sleep(1000).then(() => i * 2);
      }),
    )
    .subscribe(i => console.log(i));
};

// 出力:
// sleep: start
// sleep: start
// sleep: start
// sleep: end
// sleep: end
// sleep: end
// 6

/**
 * exhaustMap
 * * Promiseが展開される
 * * 連続で値が流れてきた場合は最初のPromiseのみ実行され、実行中は後続の値を切り捨てる。
 */
const exhaustMapExample = () => {
  of(...array)
    .pipe(
      exhaustMap(i => {
        return sleep(1000).then(() => i * 2);
      }),
    )
    .subscribe(i => console.log(i));
};

// 出力:
// sleep: start
// sleep: end
// 2

(async () => {
  // 動作確認はコメントを切り替える
  mapExample();
  // switchMapExample();
  // mergeMapExample();
  // concatMapExample();
  // exhaustMapExample();
})();

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

padStart で日付・時刻を二桁表示する書き方

Date オブジェクトで月日、時間や分を取得することは多いですね。
例えば を取得したいとき、 14:05 などの場合 getMinutes() を使いますが、結果は 5 となってしまいます。
こと時刻の分の表示に関しては、二桁じゃないと時刻っぽくないですね。

二桁記述にする際によくある実装方法と、別の実装方法と合わせて紹介します。

よくある記述

const date = date = new Date('2020-01-01 14:05:30')
// Wed Jan 01 2020 14:05:30 GMT+0900 (日本標準時)

('0' + date.getMinutes()).slice(-2)
// "05"

これで実装は問題ないんですが、 slice でやってるあたりが「上手い !!」と思いつつもあまり美しいコードではないなと常々思っていました。

それを解消できたコードが以下です。とはいえまだ課題はあるかなと思ってます。

padStart を使った記述

const date = date = new Date('2020-01-01 14:05:30')
// Wed Jan 01 2020 14:05:30 GMT+0900 (日本標準時)

date.getMinutes().toString().padStart(2, '0')
// "05"

String.prototype.padStart() は、文字列を任意の長さに変えるのに使えるメソッドなのでこちらを使ってみました。

padStart() メソッドは、結果の文字列が指定した長さになるように、現在の文字列を他の文字列で (必要に応じて繰り返して) 延長します。延長は、現在の文字列の先頭から適用されます。

残っている課題としては getMinites の結果が Number で返ってくるため、 toString() で文字列化してあげないといけないことですかね。それでもだいぶモヤモヤは抜けました。

ただし IE は非対応

なのでご注意を。

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

ReduxにおけるImmutableの概念についてまとめてみた

はじめに

ReactやReduxを触っていると、多くの人は、Immutable、mutableというワードを耳にするのではないでしょうか。もちろんこれらは他の言語やフレームワークを使用するにあたっても重要とは思いますが、React、Reduxで開発するにあたってはとりわけ重要な概念です。実際、Reduxのstyleガイドで挙げられているReduxによるコーディングの原則の一番初めにデカデカと書かれているのが、mutableにデータを変更してはならないという指摘です。それくらい重要な概念であるImmutableですが、ちょっと理解が難しく、また理解しなくても一通り開発が進めらてしまうため(もちろんそのうち壁にぶち当たりますが)、言われてもあまりピンとこないのではないでしょうか?(筆者自身もそうでした)。この記事では、その概念について、利点や、実際のコーディング例も交えながらわかりやすく解説していきたいと思います。これを最後まで読めば、あなたもImmutableについての理解が深まると思います。

Immutableコーディングの利点

Immutableなコーディングには複数のメリットがあります。
1. シンプルで可読性の高いコーディングが可能になる
2. デバックしやすい
3. React等のDOM動作のフレームワークが正確に動作する

なぜReduxにおいてImmutableなコーディングが必要なのか

  • Redux、React-Reduxはともにshallow equality checkingを採用しています。ReduxのcombineReducersでは、そのcombineReducersが呼び出したreducerの変化をshallow checkingしている。React-Reduxのconnectメソッドでは、RootStateの変化をshallow checkingして、コンポーネントが再renderされるかを判別しています。
  • Immutableなコーディングでは、より安全にデータを扱うことができるようになります。
  • 副作用のない関数によってreducerが定義されることにより、デバックの際、別個のstate間を正しく追跡することが可能になります。

そもそもshallow equality checkingとは?deep equality checkingとの違い

shallow equality checking => 2つの異なる変数が同じ参照元(データの保存先のメモリの番地)かどうかをみているのみです。そのため、実際のデータの中身のみが変化しても、そのデータの参照元が同じである場合、変更が検知されません。
deep equality checking => 2つの異なる変数の実際のデータの値が同一のものであるかどうかをみています。

Reduxでは、そのパフォーマンスの向上の理由上からshallow equality checkingが採用されています。

具体例1

Reduxのstateを変更する際。
悪いコード例)

const exampleReducer = reducerWithInitailState(initialState)
    .case(actions.exampleAction, (state, payload) => (
        state.push(payload)
    ))

良いコード例)

const exampleReducer = reducerWithInitailState(initialState)
    .case(actions.exampleAction, (state, payload) => (
        state.concat(payload)
    ))

上記の2つの例の違いは、元のstate(ここでは配列を想定)にどのように引数を追加しているかという点です。1つ目の例で使われているpushメソッドは、配列に対して要素を追加する際、元の配列を直接書き換えて、変更します。そのため、その配列の参照元(データの保存先のメモリの番地)が変わることはありません。
一方で、2つ目の例にあるconcatメソッドは、元の配列に影響を与えず、結合された新たなオブジェクトを生み出します。そのため、元の配列を複製した上で新たな配列(結合された新しい配列)を生成するため、元のstateの配列と新しい配列ではその参照元が異なります。

具体例2

悪いコード例)

const initialstate = {
    name: "Rui",
    age: 22,
    address: "多摩"
}
const exampleReducer = reducerWithInitialState(initialState)
    .case(action.exampleAction, (state, payload) => (
      state.address = "六本木"
    ))

良いコード例)

const initialstate = {
    name: "Rui",
    age: 22,
    address: "多摩"
}
const exampleReducer = reducerWithInitialState(initialState)
    .case(action.exampleAction, (state, payload) => (
       ...state,
       address: "六本木"
    ))

悪いコード例では、stateのaddressを直接書き換えていますが、良いコード例では、stateを一度複製し、複製したstateに対して変更を加えているため変更前のstateが書き換わっていません。

おわりに

ReduxおよびReact-Reduxにおいては、shallow equality checkingが採用されているためstate等のデータを直接書き換えるようなImmutableでないコーディングを行ってしまうと、その変更が検知されず、stateの変更があったにも関わらず、componentが正しく再renderされないなどの事態が生じる恐れがあります。また、はじめの方でも書いたように、Immutableなコーディングはデバックの際にもたいへん有効ですし、コード全体の可読性も上がるため、チーム開発の際にもとてもメリットがあります。気にかけていないと、ついうっかりmutableなコーディングをしてしまいがちなので、ぜひ注意してくださいね! Reduxの公式FAQ(下にもリンクを貼っておきます)はかなり詳細にこの概念について書かれていて、とても勉強ななるので、ぜひ一読してみてください。

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

Excelマクロから脱却しよう。HTMLとJavascriptでエクセルマクロで実現していた処理を実現する方法(JavaScriptで行追加、JavaScriptでCSVファイル読み込み、JavaScriptでCSVファイル出力)

背景

業務でよくエクセルマクロを作成したりメンテナンスしたりしますが、どうしても記述方法などが受け入れらませ。
ブラウザで出来ればとずっと思っていたので、開発することにしました。
有識者も多いHTMLとJavaScriptを使用してエクセルマクロから脱却します。

概要

  • JavaScriptでHTMLのタグを追加して行を追加することが出来ます。
    (列追加は対応していない為、HTMLを修正する必要があります)
  • JavaScriptでinputタグに入力された値達をカンマ区切り(CSVファイル)にしてダウンロード出来ます。
  • JavaScriptでファイルの内容をinputタグ内に追加することが出来ます。

上記のことが出来れば、エクセルを採用しなくてもよくなるのではないかと思っています。
なお、HTMLファイルをビジネス部門に渡して利用してもらう想定の為、JavaScriptを外部ファイルにしていません。

作成画面

業務で使うものになるので、見た目(CSS)は考慮しておりません。
必要であれば適宜追加するつもりです。
image.png

GitHub

https://github.com/KOJI-YAMAMOTO-GitHub/html-like-excel/tree/master
(2時間ほどで作成した為、無駄なロジックや不適切な変数名になっています。以降のプログラム変更は、GitHubにて行うつもりです。)

GitHub Pages

https://koji-yamamoto-github.github.io/html-like-excel/

プログラム

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <style type="text/css">
    </style>
    <script type="text/javascript">

        //行数保持変数
        var lineNumber = 0;

        /**
        *ファイルダウンロードメソッド
        */
        function onClickDownload() {

            //ファイルに出力する値を保持する変数
            var result = "";

            //ファイル名
            const filename = document.getElementById('downloadFileNmae').value;
            //ファイル名に値がない場合
            if (filename === "") {
                document.getElementById("errormessage").textContent = "ファイル名が未入力です。";
                return;
            }

            //行数分ループ
            for (var i = 0; i < lineNumber; i++) {

                //取得するタグのid名生成
                var idName = "record" + (i + 1);

                //該当のidが存在しなかった場合
                if (!document.getElementById(idName)) {
                    console.log("id = " + idName + " が存在しませんでした。処理をが終了します。");
                    break;
                }

                console.log((i + 1) + "行目の処理を開始しました。");

                //行内の値を保持する変数
                var valueList = [];

                //エラーメッセージを保持する変数
                var errormessage = "";

                //項目から値を取得する(項目数を増やした場合は、このループ数もそれに併せて増やす必要有)
                for (var j = 0; j < 5; j++) {
                    //取得するタグのclass名生成
                    var className = "text" + (j + 1);
                    //取得した値を保持する変数
                    var value = "";

                    //該当のclass名が存在した場合
                    if (document.getElementById(idName).getElementsByClassName(className)[0]) {
                        //class名に紐づく値を取得
                        value = document.getElementById(idName).getElementsByClassName(className)[0].value;
                        //配列に取得した値を格納
                        valueList.push(value);
                    }

                    //空が設定されていた場合
                    if (!value) {
                        errormessage = "値が設定されていない項目があります。";
                        document.getElementById("errormessage").innerHTML = "値が設定されていない項目があります。";
                        break;
                    }
                    console.log(idName + "." + className + " = " + value);
                }

                console.log(valueList);

                //取得した値をカンマ区切りで文字列結合(項目数を増やした場合は、これもそれに併せて増やす必要有)
                var valuesString =
                    valueList[0] + "," +
                    valueList[1] + "," +
                    valueList[2] + "," +
                    valueList[3] + "," +
                    valueList[4] +
                    "\r\n";

                console.log(valuesString);

                //ファイルに出力する為の値に設定
                result = result + valuesString;
            }
            console.log(result);

            //エラーメッセージが存在しない場合
            if (errormessage === "") {
                // ファイル出力
                outputFile(result, filename);
            } else {
                //エラーメッセージを出力
                document.getElementById("errormessage").innerHTML = errormessage;
            }
        }

        /**
        * ファイル出力処理
        * @param {string} value ファイルに出力する値
        * @param {string} filename ファイル名
        */
        function outputFile(value, filename) {

            var blob = new Blob([value], { "type": "text/plain" });

            // Internet exproerの場合
            if (window.navigator.msSaveBlob) {
                window.navigator.msSaveBlob(blob, filename);
            } else {
                const a = document.createElement('a');
                a.href = URL.createObjectURL(blob);
                a.download = filename;
                a.style.display = 'none';
                document.body.appendChild(a);
                a.click();
                document.body.removeChild(a);
            }
        }

        /**
        *行追加メソッド
        */
        function addRecord() {
            //行ナンバーをカウントアップ
            lineNumber++;

            //追加する箇所タグを取得
            var baseLineElement = document.getElementById('baseLine');

            //追加するタグ
            var addTag = "<tr id=record" +
                lineNumber +
                "><td><input class=text1 type=text name=name size=15 /></td>" +
                "<td><input class=text2 type=text name=name size=15 value=text2 /></td>" +
                "<td><input class=text3 type=text name=name size=15 /></td>" +
                "<td>" +
                "<select name=text4 size=1 class=text4>" +
                "<option value=TRUE selected>TRUE</option>" +
                "<option value=FALSE>FALSE</option>" +
                "</select>" +
                "</td>" +
                "<td><input class=text5 type=text name=name size=15 /></td>" +
                "</tr>";

            //タグを追加
            baseLineElement.insertAdjacentHTML('beforebegin', addTag);
        }

        /**
        *ファイル読み込みメソッド
        */
        function inputFile() {
            //ファイル読み込み
            var uploadFile = document.getElementById("inputFile");

            console.log(uploadFile);

            //読み込んだファイルの内容の出力先
            var outFrame = document.getElementById('errormessage');

            //ファイル内に1行以上文字列がある場合
            if (1 <= uploadFile.files.length) {
                //ファイル読み込みインスタンス作成
                var fileReader = new FileReader();

                //ファイルの読み込み成功時
                fileReader.onload = function (e) {
                    //ファイルの内容取得
                    var fileValues = fileReader.result;

                    console.log(fileValues);

                    //ファイルを1行ずつに分割
                    var splitValues = fileValues.split("\r\n");

                    //ファイルの行数分だけ画面の行を追加
                    for (var i = 0; i < splitValues.length; i++) {
                        //行追加メソッド呼出し
                        addRecord();
                    }

                    //ファイルの行数分だけループ
                    for (var j = 0; j < splitValues.length; j++) {

                        //1レコードをカンマ区切りに分割
                        var splitValues2 = splitValues[j].split(",");

                        console.log(splitValues2);

                        //カラム数分ループ
                        for (var k = 0; k < splitValues2.length; k++) {

                            var idName = "record" + (j + 1);
                            var className = "text" + (k + 1);
                            //追加したレコードに値を設定
                            document.getElementById(idName).getElementsByClassName(className)[0].value = splitValues2[k];
                        }
                    }
                }

                //ファイルの読み込み開始
                fileReader.readAsText(uploadFile.files[0], "shift-jis");

            }
        }
    </script>
    <title>Javascriptで行を追加してファイルをダウンロードする</title>
</head>

<body>
    <h1>Javascriptで行を追加してファイルをダウンロードする</h1>
    <h2>そろそろエクセルマクロから脱却したかったので、htmlとJavascriptでエクセルlikeなモノを作成してみました。</h2>
    <table>
        <tr>
            <td>ダウンロードファイル名</td>
        </tr>
        <td><input id="downloadFileNmae" type="text" name="downloadFileNmae" size="30" value="sample.txt" /></td>
        <tr>
            <td><input type="button" value="download" onclick="onClickDownload()" /></td>
        </tr>
        <tr>
            <td><input type="button" value="行追加" onclick="addRecord()" /></td>
        </tr>
        <tr>
            <td><input id="inputFile" type="file" /></td>
        </tr>
        <tr>
            <td><input type="button" value="ファイル読み込み" onclick="inputFile()" /></td>
        </tr>
    </table>
    </br>
    <table>
        <tr>
            <td>title1</td>
            <td>title2</td>
            <td>title3</td>
            <td>title4</td>
            <td>title5</td>
        </tr>
        <tr id="baseLine"></tr>
    </table>
    <div id="errormessage"></div>
</body>

</html>

プログラムの解説

未稿

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

Excelマクロから脱却しよう。HTMLとJavascriptでエクセルマクロで実現していた処理を実現する方法(JavaScriptで行追加、JavaScriptでファイル読み込み、JavaScriptでファイル出力)

背景

業務でよくエクセルマクロを作成したりメンテナンスしたりしますが、どうしても記述方法などが受け入れらませ。
ブラウザで出来ればとずっと思っていたので、開発することにしました。
有識者も多いHTMLとJavaScriptを使用してエクセルマクロから脱却します。

概要

JavaScriptでHTMLのタグを追加して行を追加することが出来ます。
(列追加は対応していない為、HTMLを修正する必要があります)
JavaScriptでinputタグに入力された値達をカンマ区切り(CSVファイル)にしてダウンロード出来ます。
JavaScriptでファイルの内容をinputタグ内に追加することが出来ます。

上記のことが出来れば、エクセルを採用しなくてもよくなるのではないかと思っています。

作成画面

業務で使うものになるので、見た目(CSS)は考慮しておりません。
必要であれば適宜追加するつもりです。
image.png

GitHub

https://github.com/KOJI-YAMAMOTO-GitHub/html-like-excel/tree/master

GitHub Pages

https://koji-yamamoto-github.github.io/html-like-excel/

プログラム

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <style type="text/css">
    </style>
    <script type="text/javascript">

        //行数保持変数
        var lineNumber = 0;

        /**
        *ファイルダウンロードメソッド
        */
        function onClickDownload() {

            //ファイルに出力する値を保持する変数
            var result = "";

            //ファイル名
            const filename = document.getElementById('downloadFileNmae').value;
            //ファイル名に値がない場合
            if (filename === "") {
                document.getElementById("errormessage").textContent = "ファイル名が未入力です。";
                return;
            }

            //行数分ループ
            for (var i = 0; i < lineNumber; i++) {

                //取得するタグのid名生成
                var idName = "record" + (i + 1);

                //該当のidが存在しなかった場合
                if (!document.getElementById(idName)) {
                    console.log("id = " + idName + " が存在しませんでした。処理をが終了します。");
                    break;
                }

                console.log((i + 1) + "行目の処理を開始しました。");

                //行内の値を保持する変数
                var valueList = [];

                //エラーメッセージを保持する変数
                var errormessage = "";

                //項目から値を取得する(項目数を増やした場合は、このループ数もそれに併せて増やす必要有)
                for (var j = 0; j < 5; j++) {
                    //取得するタグのclass名生成
                    var className = "text" + (j + 1);
                    //取得した値を保持する変数
                    var value = "";

                    //該当のclass名が存在した場合
                    if (document.getElementById(idName).getElementsByClassName(className)[0]) {
                        //class名に紐づく値を取得
                        value = document.getElementById(idName).getElementsByClassName(className)[0].value;
                        //配列に取得した値を格納
                        valueList.push(value);
                    }

                    //空が設定されていた場合
                    if (!value) {
                        errormessage = "値が設定されていない項目があります。";
                        document.getElementById("errormessage").innerHTML = "値が設定されていない項目があります。";
                        break;
                    }
                    console.log(idName + "." + className + " = " + value);
                }

                console.log(valueList);

                //取得した値をカンマ区切りで文字列結合(項目数を増やした場合は、これもそれに併せて増やす必要有)
                var valuesString =
                    valueList[0] + "," +
                    valueList[1] + "," +
                    valueList[2] + "," +
                    valueList[3] + "," +
                    valueList[4] +
                    "\r\n";

                console.log(valuesString);

                //ファイルに出力する為の値に設定
                result = result + valuesString;
            }
            console.log(result);

            //エラーメッセージが存在しない場合
            if (errormessage === "") {
                // ファイル出力
                outputFile(result, filename);
            } else {
                //エラーメッセージを出力
                document.getElementById("errormessage").innerHTML = errormessage;
            }
        }

        /**
        * ファイル出力処理
        * @param {string} value ファイルに出力する値
        * @param {string} filename ファイル名
        */
        function outputFile(value, filename) {

            var blob = new Blob([value], { "type": "text/plain" });

            // Internet exproerの場合
            if (window.navigator.msSaveBlob) {
                window.navigator.msSaveBlob(blob, filename);
            } else {
                const a = document.createElement('a');
                a.href = URL.createObjectURL(blob);
                a.download = filename;
                a.style.display = 'none';
                document.body.appendChild(a);
                a.click();
                document.body.removeChild(a);
            }
        }

        /**
        *行追加メソッド
        */
        function addRecord() {
            //行ナンバーをカウントアップ
            lineNumber++;

            //追加する箇所タグを取得
            var baseLineElement = document.getElementById('baseLine');

            //追加するタグ
            var addTag = "<tr id=record" +
                lineNumber +
                "><td><input class=text1 type=text name=name size=15 /></td>" +
                "<td><input class=text2 type=text name=name size=15 value=text2 /></td>" +
                "<td><input class=text3 type=text name=name size=15 /></td>" +
                "<td>" +
                "<select name=text4 size=1 class=text4>" +
                "<option value=TRUE selected>TRUE</option>" +
                "<option value=FALSE>FALSE</option>" +
                "</select>" +
                "</td>" +
                "<td><input class=text5 type=text name=name size=15 /></td>" +
                "</tr>";

            //タグを追加
            baseLineElement.insertAdjacentHTML('beforebegin', addTag);
        }

        /**
        *ファイル読み込みメソッド
        */
        function inputFile() {
            //ファイル読み込み
            var uploadFile = document.getElementById("inputFile");

            console.log(uploadFile);

            //読み込んだファイルの内容の出力先
            var outFrame = document.getElementById('errormessage');

            //ファイル内に1行以上文字列がある場合
            if (1 <= uploadFile.files.length) {
                //ファイル読み込みインスタンス作成
                var fileReader = new FileReader();

                //ファイルの読み込み成功時
                fileReader.onload = function (e) {
                    //ファイルの内容取得
                    var fileValues = fileReader.result;

                    console.log(fileValues);

                    //ファイルを1行ずつに分割
                    var splitValues = fileValues.split("\r\n");

                    //ファイルの行数分だけ画面の行を追加
                    for (var i = 0; i < splitValues.length; i++) {
                        //行追加メソッド呼出し
                        addRecord();
                    }

                    //ファイルの行数分だけループ
                    for (var j = 0; j < splitValues.length; j++) {

                        //1レコードをカンマ区切りに分割
                        var splitValues2 = splitValues[j].split(",");

                        console.log(splitValues2);

                        //カラム数分ループ
                        for (var k = 0; k < splitValues2.length; k++) {

                            var idName = "record" + (j + 1);
                            var className = "text" + (k + 1);
                            //追加したレコードに値を設定
                            document.getElementById(idName).getElementsByClassName(className)[0].value = splitValues2[k];
                        }
                    }
                }

                //ファイルの読み込み開始
                fileReader.readAsText(uploadFile.files[0], "shift-jis");

            }
        }
    </script>
    <title>Javascriptで行を追加してファイルをダウンロードする</title>
</head>

<body>
    <h1>Javascriptで行を追加してファイルをダウンロードする</h1>
    <h2>そろそろエクセルマクロから脱却したかったので、htmlとJavascriptでエクセルlikeなモノを作成してみました。</h2>
    <table>
        <tr>
            <td>ダウンロードファイル名</td>
        </tr>
        <td><input id="downloadFileNmae" type="text" name="downloadFileNmae" size="30" value="sample.txt" /></td>
        <tr>
            <td><input type="button" value="download" onclick="onClickDownload()" /></td>
        </tr>
        <tr>
            <td><input type="button" value="行追加" onclick="addRecord()" /></td>
        </tr>
        <tr>
            <td><input id="inputFile" type="file" /></td>
        </tr>
        <tr>
            <td><input type="button" value="ファイル読み込み" onclick="inputFile()" /></td>
        </tr>
    </table>
    </br>
    <table>
        <tr>
            <td>title1</td>
            <td>title2</td>
            <td>title3</td>
            <td>title4</td>
            <td>title5</td>
        </tr>
        <tr id="baseLine"></tr>
    </table>
    <div id="errormessage"></div>
</body>

</html>

プログラムの解説

未稿

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

Understand Promise in 5 minutes

Synchrounous and asynchrounous requests

A process need to wait for another process got finished and then does something else in synchrounous model; in contract, asynchrounous one does not. ( It will describe with sync and async below.)

image.png

image source

Concept of async programming

Sample for async functions
function first(){
  // Simulate a code delay
  setTimeout( function(){
    console.log(1);
  }, 500 );
}
function second(){
  console.log(2);
}
first();
second();
// 2
// 1

Non-blocking environment

  • how to handle the result of each async processes below??
  • how does our system know the async process is done?? Async requests(1).png

To handle async processes

what is ES6, ES7, ECMAScript2015...

Method 1: Callback function // ES4 standard
function first(subject, callback) {
  console.log(subject + " 1");
  callback();
}
function second(){
  console.log('Call 2');
}
first('Hello', second);
// Hello 1
// Call 2

what is Callback hell

Real example 1: XMLHttpRequest ES5 standard
const oReq = new XMLHttpRequest();

// Handle async responses
oReq.addEventListener("load", function () {
  console.log('show: '+this.responseText);
});
oReq.upload.addEventListener("progress", function(){
  console.log('updateProgress');
});

// Async request
oReq.open("GET", "http://www.example.org/example.txt");
oReq.send();
Method 2: Promise ES6 standard

An object to wrap and unwrap objects and functions asynchronously

// Wrap async function using Promise
function timeoutPromise(interval) {
  return new Promise((resolve, reject) => {
    setTimeout(function(){
      console.log("processing");
      resolve('processed');
    }, interval);
  });
};

// Use then(), catch() to unwrap Promise when calling
timeoutPromise(1000).then(function(result) {
  console.log(result); // processing, processed
  console.log('done'); // done
});

// Unwrap Promise sequentially
Promise.all([timeoutPromise(1000), timeoutPromise(1000)]).then(function(values) {
  console.log(values);
});
// processing, processing, ['processed','processed']

Real example 2a: jQuery
// Async request
$.get('http://www.example.org/example.json', function(response){
  console.log(JSON.stringify(response));
});
Real example 2b: Fetch HTML5 standard
// Async request
fetch('http://www.example.org/example.json')
  .then((response) => {
    return response.json();
  })
  .then((jsonContent) => { // extract json
    console.log(jsonContent);
  });
Method 3: async/await ES7 candidate

For unwrap Promise object easily

function timeoutPromise(interval) {
  return new Promise((resolve, reject) => {
    setTimeout(function(){
      console.log("processing");
      resolve();
    }, interval);
  });
};
// Unwrap Promise sequentially
async function timeTest() {
  await timeoutPromise(3000);
  await timeoutPromise(3000);
  console.log('done');
}

timeTest(); // processing, processing, done 

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

【Rails×Ajax】いいね機能の実装で上手く出来ないあなたへの2つの注意喚起 #学習者向け

目的

はじめまして。
今回、Railsでいいね機能をQiita等の記事通りに行っても上手く行かない!という方へ向けた、ちょっとした実装の際のチェック項目を列挙させていただきます。

前提条件

対象となる読者

  • 「いいね機能」の実装において、railsの同期処理では問題なく処理されるが、Ajax通信が上手く行かない

開発環境

  • ruby 2.5.1
  • Rails 5.2.4.1
  • mysql Ver 14.14

実装済み機能

  • 同期処理で「いいね機能」が正しく処理されていること

筆者が参考にした記事

Ajax処理のいいね機能の実装方法のチェック項目

ずばり、先に結論をここで提示させていただきます

1. 部分テンプレートの呼び出しが正しく相対パスで指定されているか

2. インスタンス変数の指定がfavorites_controller.rbで指定されているか

の2点です。では、詳しく見ていきましょう。

1. 部分テンプレートの呼び出しが正しく相対パスで指定されているか

よく陥りがちなミスの一つですね。実際にどのように間違えて実装しどの様なエラー文が出たのでしょうか?

view/items/show.html.haml
.btn-bar
  .btn-box
    = render partial: "favorite_ajax", locals: { item: @item }
-# view/items/_favorite_ajax.html.hamlでいいねボタンを部分テンプレートを作成した

view/items/_favorite_ajax.html.haml
- if user_signed_in? -# ユーザーがログインしているか判断
  - if item.favorited_by?(current_user) -# ログイン中のユーザーがいいねしているかしていないかを判断
    = link_to item_favorites_path(item.id), method: :delete, class: "favorite red", remote: true do -# リクエストをjs形式で送信
      = icon('fas', 'heart')
      いいね!
      = item.favorites.count
  - else
    = link_to item_favorites_path(item.id), method: :post, class: "favorite", remote: true do -# リクエストをjs形式で送信
      = icon('far', 'heart')
      いいね!
      = item.favorites.count
- else
  = link_to new_user_session_path, class: "favorite", remote: false do -# リクエストをjs形式で送信
    = icon('far', 'heart')
    いいね!
    = item.favorites.count
view/favorites/destroy.js.haml(失敗例)
$('.btn-box').html("#{escape_javascript(render partial: "favorite_ajax", locals: { item: @item })}");
-# この記述ではview/favorites/_favorite_ajax.html.hamlを呼び出していることとなる。従って、対応するファイルが無いことからTemplate::Error(Missing partial)が発生
view/favorites/destroy.js.haml(失敗例)
$('.btn-box').html("#{escape_javascript(render partial: "favorite_ajax", locals: { item: @item })}");
-# この記述ではview/favorites/_favorite_ajax.html.hamlを呼び出していることとなる。従って、対応するファイルが無いことからTemplate::Error(Missing partial)が発生

エラー文
スクリーンショット 2020-01-20 10.40.39.png

items_controller.rbのshowアクションのビューでいいね機能の実装をしています。また、いいね機能のDBへの保存・削除はfavorites_controller.rbのcreateアクション・destroyアクションで実装をしています。

今回、いいねボタンを押した際にビューが切り替わる部分をview/items/_favorite_ajax.html.hamlで切り出し部分テンプレートを作成しました。ajaxではview/favorites/destroy.js.haml view/favorites/destroy.js.hamlをそれぞれ用意し、view/items/_favorite_ajax.html.hamlを呼び出したかったのですが、相対パスの指定が誤っていました。以下のように修正するとTemplate::Error(Missing partial)は解消されます。

view/favorites/destroy.js.haml
$('.btn-box').html("#{escape_javascript(render partial: "items/favorite_ajax", locals: { item: @item })}");
-# partial: にitems/ を追加
view/favorites/destroy.js.haml
$('.btn-box').html("#{escape_javascript(render partial: "items/favorite_ajax", locals: { item: @item })}");
-# partial: にitems/ を追加

2. インスタンス変数の指定がfavorites_controller.rbで指定されているか

こちらはまず、どんなエラー文が出たか確認して見ましょう
スクリーンショット 2020-01-20 11.21.25.png
renderの中身のitem.favorited_by?に対して

undefined method `favorited_by?' for nil:NilClass

とエラーが出ています。ここでいうitemとはitems_controller.rbのshowアクションで定義されているインスタンス変数@itemをrenderの中身ではitemとして記述している、という意味です。favorited_by?については、item.rbで事前に定義した「ログイン中のユーザーがいいねしているかしていないかを判断」するメソッドです。

models/item.rb
class Item < ApplicationRecord
# (中略)
  def favorited_by?(user)
    favorites.where(user_id: user.id).exists?
  end
end

このことから、
 render内ではitem.favorited_by?が定義されていない
→ render内ではitemそのものが定義されていない
view/favorites/destroy.js.hamlでは、@itemが定義されていない
favorites_controller.rbでは、@itemが定義されていない!!

ということが判明しました。確認してみると確かにfavorites_controller.rbでは、@itemが定義されていなかったので、以下のように記述を加えたところ、正しくAjax処理が実行されました。
(items_controller.rbでも同様のset_itemメソッドを定義済みです)

favorites_controller.rb
class FavoritesController < ApplicationController
  before_action :authenticate_user!
# 追記==========================================================================
  before_action :set_item 
# ==============================================================================
  def create
    favorite = current_user.favorites.build(item_id: params[:item_id])
    if favorite.save
    else
      flash.now[:alert] = favorite.errors.full_messages
    end
  end

  def destroy
    favorite = Favorite.find_by(item_id: params[:item_id], user_id: current_user.id)
    if favorite.destroy
    else
      flash.now[:alert] = '削除できませんでした。'
    end
  end

  private
# 追記==========================================================================
  def set_item
   @item = Item.find(params[:item_id])
 end
# ==============================================================================
end

まとめ

いかがだったでしょうか。
いいね機能のAjaxは、実装の手順そのものはすごくシンプルです。しかし、いいね機能専用のビューを用意していなかったり、部分テンプレートの保存場所の違いによって記述内容が異なるケースがあります。当たり前のことではあるのですが、記事通り実装してみて上手く出来なかった時、解決の一助となれば幸いです。

※私自身初めてのQiitaの投稿です!
ご指摘等ございましたらコメントにてお待ちしております。

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

Auth0で簡単にユーザー認証を実装(花粉カレンダー作成②)

概要

耳鼻咽喉科の開業医をしています。
花粉症の患者さんに使ってもらえるような花粉飛散情報が分かるカレンダーアプリを作りたいと思っています。
前回カレンダーを表示して予定を入れるところまで挑戦してみました。
Vue.js×FullCallendarでカレンダー作成(花粉カレンダー作成①)

今回はユーザー個別のカレンダーを表示するために、ユーザー認証機能の実装に挑戦しました。

完成動画

https://youtu.be/koMpYAiKE2k

作成

こちらの記事を参考にさせていただきました。ありがとうございました。
Auth0 + GitHub Pagesでミニマムなログインサンプルを作る
ファイル一つコピペで試すAuth0+Vue.jsのログインサンプル

1. Auth0のアカウント作成・サインイン
こちらから行ってください。
Auth0ホームページ

2.アプリケーションの作成
[Applications]>[Settings]のタブに移動し、
Allowed Callback URLs、Allowed Logout URLsを以下に設定します。
http://localhost:8080/#/calendar
Allowed Web Origins、Allowed Origins (CORS)を以下に設定します。
http://localhost:8080
DomainとClient IDは後で使うのでひかえておきます。

2020-01-18_10h10_54.png

3.ユーザー認証の実装
以前作成したCalendar.vueに書き込んでいきます。
Vue.js×FullCallendarでWEBカレンダー作成(花粉カレンダー作成①)

templateタグの中に以下を追加していきます。

<button id="btn-login" v-on:click="auth0login()">Log in</button>
        <button id="btn-logout" v-on:click="auth0logout()">Log out</button>

        <div v-if="login" id="gated-content">
          <p>
            You're seeing this content because you're currently
          </p>
          <label>
              User profile Image:
              <img :src="userData.picture" id="ipt-user-profile-image" width="200px">
              <br />
          </label>
          <label>
            User profile:
            <pre id="ipt-user-profile">{{userData}}</pre>
          </label>

          <label>
            Access token: 
            <pre id="ipt-access-token">{{userData.token}}</pre> 
          </label>      
        </div>

ログイン後にカレンダーが表示されるようにします。

 <div v-if="login">
        <FullCalendar 
    default-view="dayGridMonth"
    :locale="locale"
    :header="calendarHeader"
    :weekends="calendarWeekends"
    :plugins="calendarPlugins"
    :events="calendarEvents"
    @dateClick="handleDateClick"
  />
  </div>

scriptタグの内に以下を書き込んでいきます。

import createAuth0Client from '@auth0/auth0-spa-js';

data(){ }の中に以下を追記します。

data() {
    return {
      auth0:{},
      APP_PATH:'/#/calendar',
      userData:{},
      login:false,
    }
}

methods: { }の中に以下を追記します。

async updateUI(){
        const isAuthenticated = await this.auth0.isAuthenticated();
        if (isAuthenticated) {
            this.login = true; //ログイン後の情報が表示
            this.userData = await this.auth0.getUser();
            this.userData.token = await this.auth0.getTokenSilently();
        }
    },
    async auth0login(){
        await this.auth0.loginWithRedirect({
            redirect_uri: window.location.origin + this.APP_PATH
        });
    },
    auth0logout(){
        this.auth0.logout({
            returnTo: window.location.origin + this.APP_PATH
        });
    },

最後にこちらを追記して完了です。

async created() {
    this.auth0 = await createAuth0Client({
      domain: 'domain名を記載',
      client_id: 'client_idを記載',
      // redirect_uri: '<MY_CALLBACK_URL>'
    });
    this.updateUI();
    const isAuthenticated = await this.auth0.isAuthenticated();    
    if (isAuthenticated) {
        return;
    }
    const query = window.location.search;
    if (query.includes("code=") && query.includes("state=")) {

        // Process the login state
        await this.auth0.handleRedirectCallback();

        this.updateUI();

        // Use replaceState to redirect the user away and remove the querystring parameters
        window.history.replaceState({}, document.title, this.APP_PATH);
    }
}

Auth0でユーザー認証を入れたいところをONにします。

image.png

考察
最初は参考記事のコピペで出来るかな?と軽く考えていました。しかし、vue.jsを全く勉強していなかったのですこし時間がかかってしまいました。追記する場所が分かればこれだけで面倒なユーザー認証が実装できるので便利ですね。

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

【Nuxt】ページとコンポーネントの関係と管理について

nuxtを書いていって、ある程度まとまったのでメモとして。

結論

  • ページ → コンポーネント間のデータの受け渡しはvuexのstateを用いる。
  • propsでも渡せるがコンポーネント内でコンポーネントを更に読み込むケースもあるため、バケツリレーになってしまう。
  • コンポーネントはvuexのstateを参照する。コンポーネント内でデータをfetchしない。
  • 読み込み順の制御はページで行う。

基本

ページ

  • /page配下
  • ページ全体の表示に必要なHTML・CSS・JSが入っている
  • コンポーネントを読み込むこともできる

コンポーネント

  • /components配下
  • ページ上で使うパーツファイル
  • そのコンポーネントを表示するために必要なHTML、CSS、JSのみが格納されている
  • コンポーネント内で更にコンポーネントを読み込むことも可能

ページからコンポーネントへのデータの受け渡し

2パターンある。

  1. vuexのstateを使う
  2. propsを使う

単純なのは2だが、propsの定義が必要・バケツリレーが発生するため基本的に1の形式に統一したい。
(のだが適切に使い分けるべき。後述)

コンポーネント内でAPIコールをしない理由

  • コンポーネント間の読み込み順の制御ができない
  • 認証を通った上で読み込みが必要な場合にその順番の制御ができない

なので、コンポーネントはあくまでも渡されたデータ、stateで管理されたデータの表示のみを行うべき。
APIコールの処理はページで行い、順番を適切に制御する。

破綻するケース

  • 全部storeに寄せすぎるとstoreの肥大化を招いて破綻する。
    • storeは結局グローバル変数と変わりない。
  • 保持する必要の無いデータはstoreを使わずpropsで適切に使ったほうが良い
    • パフォーマンス的にもデータ管理的にも。

制約がない故に

その気になれば、コンポーネント側でデータを取り寄せ、ページ側ではstateを使う、といったスタイルに統一することも出来るし、
コンポーネント側でデータをロードすることもあれば、ページ側で行ったり、バラバラにすることもできる。
いくらでもできる。

なので、基本を決めておいて、「例外的にここではこうしている」といったコメントを残した上でコードを書いていくと、
メンテナンスの際にコードが追いやすり、変更もしやすくなると思った。

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

ソート機能を実装した際に感じたfilterメソッドの魅力

はじめに

先日、開発中のサービスにおいてリアルタイム検索とソート機能を実装した際にJavaScriptのFilterメソットに大いに助けて頂きました。
備忘録を兼ねて機能や使い方を書き残していきたいと思います。

Filterメソットとは?

filter() メソッドは、引数として与えられたテスト関数を各配列要素に対して実行し、それに合格したすべての配列要素からなる新しい配列を生成します。
MDNより

つまりは関数内で既存の配列から必要箇所を抜き出して、戻り値を用いて新たに配列を作ることも出来ますし、引数を指定してフロント側から欲しい値を持ってくることも出来ます。

How To Use

  const items = [
        { name: "itemA", price: 1500, madeIn: "Japan" },
        { name: "itemB", price: 700, madeIn: "Japan" },
        { name: "itemC", price: 2000, madeIn: "USA" },
        { name: "itemD", price: 300, madeIn: "USA" },
        { name: "itemE", price: 5000, madeIn: "China" }
      ];

      //アロー関数で書くとすっきりして見える
      const filter = arr => {
        return arr.filter(el => el.madeIn == "USA");
      };
      console.log(filter(items)); //madeINがUSAの要素だけが配列として返ってくる

      const filter2 = arr => {
        return arr.filter(el => el.madeIn == "USA" && el.price <= 1000);
        //このようにANDやORで更なる絞り込みも可能です。
      };
      console.log(filter2(items)); //条件に合致するitems[3]だけが返ってくる。

呼び出しの際に、引数の部分に定義ずみの配列を指定して、filter内で受け取り、今回の例では「el」の部分で各要素にアクセスしています。
私は最近Vueを使うことが多く、filterメソットを使って新しく生成した配列をv-forでぶん回すだけでソート機能や検索機能を実装することが出来、大変重宝しました。

以上になります。
それでは、また!

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

Svelte+Sapperで電影予告集会を作りながら利点と欠点を考える

The State of JavaScript 2019を見ていて気になったSvelteを触ってみる延長でサイトを作りました。SvelteとSapperを使いたくなったときの参考にしてもらえたら。


電影予告集会

要点のまとめ

  概要 仲間
Svelte 最小限のコードでUIを作れる React/Vue
Sapper SSRから静的サイトまで Next.js/Nuxt.js

利点

  • バンドルサイズが小さい
  • 書くコード量も少ない
  • ビルドが速い(体感)

欠点

  • TypeScriptに公式には対応してない
  • 開発環境の快適さはReactやVueに劣る
  • シェアが少ないので採用するリスクが大きい

ワンポイントリリーフとして

まだ快適に開発できるとは言いにくい状態なので試行錯誤が必要ですが、短いコードでシンプルに書ける気持ちよさと、バンドルサイズを抑えられる利点は魅力的です。特に、ファイルサイズに制約がある環境では有力な選択肢になると思います。

あとは、SPAじゃないWebページの一部にコンポーネントを埋め込みたい時などは、大きなランタイムが不要なSvelteの強みを活かせそう。ReactやVueの置き換えというよりも、ポイントで使うほうが現実味がある。

仕事で使う勇気はまだないので、しばらくは趣味で触っときます。

Svelte 3.17.1
Sapper 0.27.9

※2020年1月20日現在のバージョン

Svelteについて

UIコンポーネントを最小限のJSに変換してくれるコンパイラ。
rollupで有名なRich Harrisさんがメインで開発しています。

ReactとVueを足してKonMariしたらSvelteになる。
と考えていただけたらイメージしやすいと思います。

$: svelte = konmari(react + vue)

Write less code

ReactやVueはコンポーネントを各ランタイム上で動くようにコンパイルしますが、Svelteは最小限のヘルパーを含むシンプルなJavaScriptにコンパイルするため、大きなランタイムを読み込む必要がありません。

リソースが限られたブラウザ上での最適化に注力するのではなく、コンパイル時にやれる事はやってしまえという発想の転換で、バンドルサイズを削減すると同時にブラウザへ与える負荷の軽減を実現しています。

設計の哲学的なところは、Blogにまとまってました。

小さくて速い、は本当か

RealWorld のベンチマークがあったので、そのデータから。このプロジェクトは、TODOよりも現実的なサンプルアプリが欲しいよね、みたいなモチベーションで作られたらしいです。Mediumを模したconduitというサイトが集まっています。

ベンチマーク対象のフレームワークは18個。

項目 スコア 順位
パフォーマンス 91 12位
ファイルサイズ 9.7KB 1位
コードの行数 1116行 3位

※ パフォーマンスはLighthouse AuditのMobile/Simulated Fast 3G
A RealWorld Comparison of Front-End Frameworks with Benchmarks (2019 update)

パフォーマンスの項目は、Fast 3G91なのでかなり良い数値だと思いますが、それでも18個中の12位。他のフレームワークもだいぶ速い。ファイルサイズはしっかり1位で良かった。

結果からわかるとおり、ファイルサイズが小さいからSvelteを選ぶ、というのはあると思います。

どんなコードが出力されるか

<script>
  let count = 0
</script>
<button on:click={() => count++}>count : {count}</button>

↑のコードが↓になります。
コンポーネントと最小限のヘルパーが一緒に出力されてますね。これで300行ぐらいです。

See the Pen minimal svelte output by nishinoshake (@nishinoshake) on CodePen.

Does this JavaScript spark joy?

konmari
[YouTube] Rich Harris - Rethinking reactivity

書いていて気分が上がったところをいくつか。
見た感じは、ほとんどVueのシングルファイルコンポーネントと同じです。

コードが短い

<script>let count = 0</script>
<button on:click={() => count++}>count : {count}</button>

ボタンのクリック&カウントを、ぐっと縮めたらこうなります。

状態管理がシンプル

stores.js
import { writable, derived } from 'svelte/store';

export const count = writable(0);
export const doubled = derived(count, $count => $count * 2);
export const increment = () => count.update(n => n + 1)
App.svelte
<script>
  import { count, doubled } from './stores.js';
  import Button from './Button.svelte';
</script>

<p>count : {$count}</p>
<p>doubled : {$doubled}</p>
<Button />
Button.svelte
<script>
  import { increment } from './stores.js';
</script>

<button on:click={increment}>+</button>

コンポーネントと切り離して状態を管理する仕組みが備わっています。VuexのGettersにあたる、derivedという関数も用意されているので便利。

トップレベルにHTMLタグを置き放題

<p>A</p>
<p>B</p>
<p>C</p>

Reactでいうところの<React.Fragment>が不要。
これは楽な反面、秩序がなくなると、あちこちで祭りが開催されそう。

HTMLのテンプレートでawait

<script>
  const sleep = () =>
    new Promise(resolve => setTimeout(() => resolve("Loaded!"), 3000))
</script>

{#await sleep()}
  <p>Loading...</p>
{:then message}
  <p>{message}</p>
{:catch error}
  <p>{error.message}</p>
{/await}

すごい。ただの変態。
テンプレート側でPromiseを受け取れるのは面白いけど、 <script> 側で await して状態を変更する方が、コードとしては見やすいと思う。

要素のサイズやスクロール量をバインドできる

<script>
  let w
  let h
  let y
</script>

<svelte:window bind:scrollY={y} />
<div bind:clientWidth={w} bind:clientHeight={h}></div>
<p>w: {w}px h: {h}px y: {y}</p>

要素のサイズ取得は、リサイズ時の再取得とかまで含めると結構面倒なので、これは嬉しい。チュートリアルにも書いてありますが、DOMへのアクセスを伴う重い処理なので控えめに。

さらに取扱注意ですが、 windowbind:scrollY とかもできます。

CSSはデフォルトでスコープあり

scoped-css.png

scopedがデフォルトで、globalがオプション。

クラスの付け外しが楽

<script>
  let isActive = false;
</script>

<style>
  .isActive {
    color: red;
  }
</style>

<p class:isActive>Hello!</p>
<button on:click={() => isActive = !isActive}>toggle</button>

変数名をそのままクラス名として付け外せるショートハンドがある。悩ましいのが、JSの変数名なので、ショートハンドを使うと is-active みたいなケバブな命名ができない。ここはこだわらずにJSの変数名に合わせました。スコープがある前提であれば、そこら辺は割り切りやすい。

アニメーションが豊富

TweenSpringTransitionなどが用意されています。Springまで入っているのはすごい。Vueにもあるけど、flipのアニメーションは感動的。

Sapperとは

ここからは、Svelteの開発環境構築からSSRや静的化まで、必要なことをまとめてやってくれるSapperの話。SvelteのためのNext.js的なやつです。

こちらもSvelteと同じくRich Harrisさんがメインで開発しています。

rollup or webpack

# for Rollup
npx degit "sveltejs/sapper-template#rollup" my-app

# for webpack
npx degit "sveltejs/sapper-template#webpack" my-app

テンプレートの作成にはrollupとwebpackを選べますが、全体的にrollup推しな雰囲気を感じたので郷に従うことに。degitとは?と思って調べたら、これもRich Harrisさんが作ってた。なんとなく分かると思いますが、sveltejs/sapper-templateがリポジトリ名で#rollupがブランチ名です。

Next.js → Sapperがスムーズ

Next.js is close to this ideal. If you haven't encountered it yet, I strongly recommend going through the tutorials at learnnextjs.com.

Sapper: Towards the ideal web app framework

Next.jsを使ったことがなかったら、とりあえずチュートリアルをやってみて!とブログにも書いてありました。実際、Next.jsかNuxt.jsを触ってからSapperを使ったほうが、すんなり馴染むと思います。

逆に言えば、これらのフレームワークと大きく違うところがないので、ここに書くことがない・・・

SSRして使う場合、ミドルウェアやページ遷移まわりが気になると思うんですが、今回は必要なかったので触ってないです。。

デプロイ

あとは、Sapperの上にSvelteのコンポーネントをゴリゴリ追加して完成です。静的ファイルを生成してS3にデプロイしました。

# 静的ファイルの生成
npm run export

https://github.com/nishinoshake/yokoku/blob/master/.github/workflows/deploy.yml

些細なことだけど、まだ .svelte の拡張子に慣れない。

気になったこと

サイトを作るうえで気になったことをまとめました。だいたいWikiのFAQに答えが書いてます。

https://github.com/sveltejs/svelte/wiki/FAQ

TypeScriptのサポート

今は未対応だけど対応の予定はある。進捗はこちらのissueから。

svelte-preprocess<script lang="typescript"> にしたらある程度は行けるっぽかったですが、公式でサポートされる前にハマりたくなかったので試してないです。

テストはどうする

How do I do testing Svelte apps?
We don't have a good answer to this yet, but it is a priority.

https://github.com/sveltejs/svelte/wiki/FAQ#how-do-i-do-testing-svelte-apps

まだ、これって方法は無さそう。

Storeなどのロジックのテストをメインでやって、大事なところはE2Eでもカバーしつつ、気が向いたらコンポーネントもテストする、ぐらいが今はちょうどいいかも。Sapperのテンプレートには、デフォルトでcypressが入ってました。

今回のサイトは、そんなに書くテストも無かったので、下のサンプルの分しか書いてないです。

Store

Storeはシンプルなオブジェクトなので、テストしやすいと思います。これはJestで書いてます。

src/states/player.js
import { writable } from 'svelte/store'

export const isPlayerOpen = writable(false)
export const openPlayer = () => isPlayerOpen.set(true)

test/unit/store/player.test.js
import { get } from 'svelte/store'
import { isPlayerOpen, openPlayer } from '../../../src/store/player'

describe('store/player', () => {
  describe('openPlayer', () => {
    test('最初は非表示の状態から', () => {
      expect(get(isPlayerOpen)).toBe(false)
    })
    test('表示できることを確認', () => {
      openPlayer()
      expect(get(isPlayerOpen)).toBe(true)
    })
  })
})

https://github.com/nishinoshake/yokoku/blob/master/test/unit/store/player.test.js

コンポーネント

コンポーネントのテストツールもありました。無意味なテストですが参考までに貼っておきます。環境の構築については、dev.toにあった記事がわかりやすかったです。

import { render } from '@testing-library/svelte'
import About from '../../../../src/components/index/About.svelte'

describe('About', () => {
  test('テキストある', () => {
    const { container } = render(About)

    expect(container).toHaveTextContent('映画館の近くに住みたい')
  })
})

わかりやすかった記事
Testing Svelte components with Jest
ツール
testing-library/svelte-testing-library
コード
https://github.com/nishinoshake/yokoku/blob/master/test/unit/components/index/About.test.js

スタイルガイドが欲しい

Vueのような公式のスタイルガイドや、ベストプラクティス的なものは見当たらなかったので、sveltejs/sapper-templatesveltejs/realworldのコードを参考にしました。

ディレクトリ構成は、なんとなくNuxt風に。

Sassで書きたい

svelte-preprocessのパッケージを追加して設定を変更するとSassが使えるようになります。下記のブログを参考にしました。
Svelte / Sapper with Sass!

変数やmixinを共通で @import したかったんですが、issueを参考にrollupの設定をいじったら行けました。

.browserslistrc
> 0.5% in JP, not dead, not IE 11
rollup.config.js
// src/styles/variables.scss
// src/styles/mixins.scss
// にファイルを置いておく。

// ※ 必要なところのみ抜粋

import sveltePreprocess from 'svelte-preprocess';

const preprocess = sveltePreprocess({
  scss: {
    includePaths: ['src'],
    data: `
      @import 'styles/variables.scss';
      @import 'styles/mixins.scss';
    `
  },
  postcss: {
    plugins: [
      require('autoprefixer')
    ]
  }
})

export default {
  client: {
    plugins: [
      svelte({
        preprocess
      })
    ]
  },
  server: {
    plugins: [
      svelte({
        preprocess
      })
    ]
  }
}

Svelteはスケールするか

<よく聞かれる質問>
大きなランタイムが不要なのはわかったけど、コンポーネントごとにプレーンなJSを生成してたら、コンポーネントを増やすコストが大きそうだから、そのうち従来のフレームワークよりもバンドルサイズが大きくならない?

<回答>
確かにそうなることはあるけど、コストがそこまで大きい訳ではないし、初期ロードのバンドルサイズを抑える方が重要。あとから読み込まれるバンドルが大きくても、Service Workerやpreloadでどうにかなるし。

===== ↑ざっくり訳↑ =====

バンドルサイズや実行時の性能もそうですが、知見が少ない分、スケールするアプリケーションの設計が難しいのがネックになりそう(特に状態管理)。

おわり

できたもの
https://yokoku.cc

GitHub
https://github.com/nishinoshake/yokoku

KONMARI 〜人生がときめく片付けの魔法〜
https://www.netflix.com/title/80209379

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

Svelteの特徴を整理していくとKonMariさんに行き着く

The State of JavaScript 2019を見ていて気になったSvelteを触ってみる延長でサイトを作りました。作るついでに、現実的な使い道と気になったことをまとめたので、Svelteを使いたくなったときの参考にしてもらえたら。Sapperというフレームワークも使ってみたので、軽めに触れてます。

作ったサイト → 電影予告集会

要点のまとめ

? 概要 仲間
Svelte 最小限のコードでUIを作れる React/Vue
Sapper 環境構築・SSR・静的化 Next.js/Nuxt.js

利点

  • バンドルサイズが小さい
  • 書くコード量も少ない
  • ビルドが速い(体感)

欠点

  • TypeScriptに公式には対応してない
  • 開発環境の快適さはReactやVueに劣る
  • シェアが少ないので採用するリスクが大きい

ワンポイントリリーフとしての使い道

まだ快適に開発できるとは言いにくい状態なので試行錯誤が必要ですが、短いコードでシンプルに書ける気持ちよさと、バンドルサイズを抑えられる利点は魅力的です。特に、ファイルサイズに制約がある環境では有力な選択肢になると思います。

あとは、SPAじゃないWebページの一部にコンポーネントを埋め込みたい時などは、大きなランタイムが不要なSvelteの強みを活かせそう。ReactやVueの置き換えというよりも、ポイントで使うほうが現実味がある。

仕事で使う勇気はまだないので、しばらくは趣味で触っときます。

Svelte 3.17.1
Sapper 0.27.9

※2020年1月20日現在のバージョン

Svelteについて

UIコンポーネントを最小限のJSに変換してくれるコンパイラ。
rollupで有名なRich Harrisさんがメインで開発しています。

ReactとVueを足してKonMariしたらSvelteになる。
と考えていただけたらイメージしやすいと思います。

$: svelte = konmari(react + vue)

Write less code

ReactやVueはコンポーネントを各ランタイム上で動くようにコンパイルしますが、Svelteは最小限のヘルパーを含むシンプルなJavaScriptにコンパイルするため、大きなランタイムを読み込む必要がありません。

リソースが限られたブラウザ上での最適化に注力するのではなく、コンパイル時にやれる事はやってしまえという発想の転換で、バンドルサイズを削減すると同時にブラウザへ与える負荷の軽減を実現しています。

設計の哲学的なところは、Blogにまとまってました。

小さくて速い、は本当か

RealWorld のベンチマークがあったので、そのデータから。このプロジェクトは、TODOよりも現実的なサンプルアプリが欲しいよね、みたいなモチベーションで作られたらしいです。Mediumを模したconduitというサイトが集まっています。

ベンチマーク対象のフレームワークは18個。

項目 スコア 順位
パフォーマンス 91 12位
ファイルサイズ 9.7KB 1位
コードの行数 1116行 3位

※ パフォーマンスはLighthouse AuditのMobile/Simulated Fast 3G
A RealWorld Comparison of Front-End Frameworks with Benchmarks (2019 update)

パフォーマンスの項目は、Fast 3G91なのでかなり良い数値だと思いますが、それでも18個中の12位。他のフレームワークもだいぶ速い。ファイルサイズはしっかり1位で良かった。

結果からわかるとおり、ファイルサイズが小さいからSvelteを選ぶ、というのはあると思います。

どんなコードが出力されるか

<script>
  let count = 0
</script>
<button on:click={() => count++}>count : {count}</button>

↑のコードが↓になります。
コンポーネントと最小限のヘルパーが一緒に出力されてますね。これで300行ぐらいです。

See the Pen minimal svelte output by nishinoshake (@nishinoshake) on CodePen.

Does this JavaScript spark joy?

konmari
[YouTube] Rich Harris - Rethinking reactivity

書いていて気分が上がったところをいくつか。
見た感じは、ほとんどVueのシングルファイルコンポーネントと同じです。

コードが短い

<script>let count = 0</script>
<button on:click={() => count++}>count : {count}</button>

ボタンのクリック&カウントを、ぐっと縮めたらこうなります。

状態管理がシンプル

stores.js
import { writable, derived } from 'svelte/store';

export const count = writable(0);
export const doubled = derived(count, $count => $count * 2);
export const increment = () => count.update(n => n + 1)
App.svelte
<script>
  import { count, doubled } from './stores.js';
  import Button from './Button.svelte';
</script>

<p>count : {$count}</p>
<p>doubled : {$doubled}</p>
<Button />
Button.svelte
<script>
  import { increment } from './stores.js';
</script>

<button on:click={increment}>+</button>

コンポーネントと切り離して状態を管理する仕組みが備わっています。VuexのGettersにあたる、derivedという関数も用意されているので便利。

トップレベルにHTMLタグを置き放題

<p>A</p>
<p>B</p>
<p>C</p>

Reactでいうところの<React.Fragment>が不要。
これは楽な反面、秩序がなくなると、あちこちで祭りが開催されそう。

HTMLのテンプレートでawait

<script>
  const sleep = () =>
    new Promise(resolve => setTimeout(() => resolve("Loaded!"), 3000))
</script>

{#await sleep()}
  <p>Loading...</p>
{:then message}
  <p>{message}</p>
{:catch error}
  <p>{error.message}</p>
{/await}

すごい。ただの変態。
テンプレート側でPromiseを受け取れるのは面白いけど、 <script> 側で await して状態を変更する方が、コードとしては見やすいと思う。

要素のサイズやスクロール量をバインドできる

<script>
  let w
  let h
  let y
</script>

<svelte:window bind:scrollY={y} />
<div bind:clientWidth={w} bind:clientHeight={h}></div>
<p>w: {w}px h: {h}px y: {y}</p>

要素のサイズ取得は、リサイズ時の再取得とかまで含めると結構面倒なので、これは嬉しい。チュートリアルにも書いてありますが、DOMへのアクセスを伴う重い処理なので控えめに。

さらに取扱注意ですが、 windowbind:scrollY とかもできます。

CSSはデフォルトでスコープあり

scoped-css.png

scopedがデフォルトで、globalがオプション。

クラスの付け外しが楽

<script>
  let isActive = false;
</script>

<style>
  .isActive {
    color: red;
  }
</style>

<p class:isActive>Hello!</p>
<button on:click={() => isActive = !isActive}>toggle</button>

変数名をそのままクラス名として付け外せるショートハンドがある。悩ましいのが、JSの変数名なので、ショートハンドを使うと is-active みたいなケバブな命名ができない。ここはこだわらずにJSの変数名に合わせました。スコープがある前提であれば、そこら辺は割り切りやすい。

アニメーションが豊富

TweenSpringTransitionなどが用意されています。Springまで入っているのはすごい。Vueにもあるけど、flipのアニメーションは感動的。

Sapperとは

ここからは、Svelteの開発環境構築からSSRや静的化まで、必要なことをまとめてやってくれるSapperの話。SvelteのためのNext.js的なやつです。

こちらもSvelteと同じくRich Harrisさんがメインで開発しています。

rollup or webpack

# for Rollup
npx degit "sveltejs/sapper-template#rollup" my-app

# for webpack
npx degit "sveltejs/sapper-template#webpack" my-app

テンプレートの作成にはrollupとwebpackを選べますが、全体的にrollup推しな雰囲気を感じたので郷に従うことに。degitとは?と思って調べたら、これもRich Harrisさんが作ってた。なんとなく分かると思いますが、sveltejs/sapper-templateがリポジトリ名で#rollupがブランチ名です。

Next.js → Sapperがスムーズ

Next.js is close to this ideal. If you haven't encountered it yet, I strongly recommend going through the tutorials at learnnextjs.com.

Sapper: Towards the ideal web app framework

Next.jsを使ったことがなかったら、とりあえずチュートリアルをやってみて!とブログにも書いてありました。実際、Next.jsかNuxt.jsを触ってからSapperを使ったほうが、すんなり馴染むと思います。

逆に言えば、これらのフレームワークと大きく違うところがないので、ここに書くことがない・・・

SSRして使う場合、ミドルウェアやページ遷移まわりが気になると思うんですが、今回は必要なかったので触ってないです。。

デプロイ

あとは、Sapperの上にSvelteのコンポーネントをゴリゴリ追加して完成です。静的ファイルを生成してS3にデプロイしました。

# 静的ファイルの生成
npm run export

https://github.com/nishinoshake/yokoku/blob/master/.github/workflows/deploy.yml

些細なことだけど、まだ .svelte の拡張子に慣れない。


電影予告集会

↑ 作ったサイト

気になったこと

サイトを作るうえで気になったことをまとめました。だいたいWikiのFAQに答えが書いてます。

https://github.com/sveltejs/svelte/wiki/FAQ

TypeScriptのサポート

今は未対応だけど対応の予定はある。進捗はこちらのissueから。

svelte-preprocess<script lang="typescript"> にしたらある程度は行けるっぽかったですが、公式でサポートされる前にハマりたくなかったので試してないです。

テストはどうする

How do I do testing Svelte apps?
We don't have a good answer to this yet, but it is a priority.

https://github.com/sveltejs/svelte/wiki/FAQ#how-do-i-do-testing-svelte-apps

まだ、これって方法は無さそう。

Storeなどのロジックのテストをメインでやって、大事なところはE2Eでもカバーしつつ、気が向いたらコンポーネントもテストする、ぐらいが今はちょうどいいかも。Sapperのテンプレートには、デフォルトでcypressが入ってました。

今回のサイトは、そんなに書くテストも無かったので、下のサンプルの分しか書いてないです。

Store

Storeはシンプルなオブジェクトなので、テストしやすいと思います。これはJestで書いてます。

src/states/player.js
import { writable } from 'svelte/store'

export const isPlayerOpen = writable(false)
export const openPlayer = () => isPlayerOpen.set(true)

test/unit/store/player.test.js
import { get } from 'svelte/store'
import { isPlayerOpen, openPlayer } from '../../../src/store/player'

describe('store/player', () => {
  describe('openPlayer', () => {
    test('最初は非表示の状態から', () => {
      expect(get(isPlayerOpen)).toBe(false)
    })
    test('表示できることを確認', () => {
      openPlayer()
      expect(get(isPlayerOpen)).toBe(true)
    })
  })
})

https://github.com/nishinoshake/yokoku/blob/master/test/unit/store/player.test.js

コンポーネント

コンポーネントのテストツールもありました。無意味なテストですが参考までに貼っておきます。環境の構築については、dev.toにあった記事がわかりやすかったです。

import { render } from '@testing-library/svelte'
import About from '../../../../src/components/index/About.svelte'

describe('About', () => {
  test('テキストある', () => {
    const { container } = render(About)

    expect(container).toHaveTextContent('映画館の近くに住みたい')
  })
})

わかりやすかった記事
Testing Svelte components with Jest
ツール
testing-library/svelte-testing-library
コード
https://github.com/nishinoshake/yokoku/blob/master/test/unit/components/index/About.test.js

スタイルガイドが欲しい

Vueのような公式のスタイルガイドや、ベストプラクティス的なものは見当たらなかったので、sveltejs/sapper-templatesveltejs/realworldのコードを参考にしました。

ディレクトリ構成は、なんとなくNuxt風に。

Sassで書きたい

svelte-preprocessのパッケージを追加して設定を変更するとSassが使えるようになります。下記のブログを参考にしました。
Svelte / Sapper with Sass!

変数やmixinを共通で @import したかったんですが、issueを参考にrollupの設定をいじったら行けました。

.browserslistrc
> 0.5% in JP, not dead, not IE 11
rollup.config.js
// src/styles/variables.scss
// src/styles/mixins.scss
// にファイルを置いておく。

// ※ 必要なところのみ抜粋

import sveltePreprocess from 'svelte-preprocess';

const preprocess = sveltePreprocess({
  scss: {
    includePaths: ['src'],
    data: `
      @import 'styles/variables.scss';
      @import 'styles/mixins.scss';
    `
  },
  postcss: {
    plugins: [
      require('autoprefixer')
    ]
  }
})

export default {
  client: {
    plugins: [
      svelte({
        preprocess
      })
    ]
  },
  server: {
    plugins: [
      svelte({
        preprocess
      })
    ]
  }
}

Svelteはスケールするか

<よく聞かれる質問>
大きなランタイムが不要なのはわかったけど、コンポーネントごとにプレーンなJSを生成してたら、コンポーネントを増やすコストが大きそうだから、そのうち従来のフレームワークよりもバンドルサイズが大きくならない?

<回答>
確かにそうなることはあるけど、コストがそこまで大きい訳ではないし、初期ロードのバンドルサイズを抑える方が重要。あとから読み込まれるバンドルが大きくても、Service Workerやpreloadでどうにかなるし。

===== ↑ざっくり訳↑ =====

バンドルサイズや実行時の性能もそうですが、知見が少ない分、スケールするアプリケーションの設計が難しいのがネックになりそう(特に状態管理)。

おわり

できたもの
https://yokoku.cc

GitHub
https://github.com/nishinoshake/yokoku

KONMARI 〜人生がときめく片付けの魔法〜
https://www.netflix.com/title/80209379

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

Svelte+Sapperで作る電影予告集会

The State of JavaScript 2019を見ていて気になったSvelteを触ってみる延長でサイトを作りました。SvelteとSapperを使いたくなったときの参考にしてもらえたら。


電影予告集会

要点のまとめ

  概要 仲間
Svelte 最小限のコードでUIを作れる React/Vue
Sapper SSRから静的サイトまで Next.js/Nuxt.js

利点

  • バンドルサイズが小さい
  • 書くコード量も少ない
  • ビルドが速い(体感)

欠点

  • TypeScriptに公式には対応してない
  • 開発環境の快適さはReactやVueに劣る
  • シェアが少ないので採用するリスクが大きい

ワンポイントリリーフとして

まだ快適に開発できるとは言いにくい状態なので試行錯誤が必要ですが、短いコードでシンプルに書ける気持ちよさと、バンドルサイズを抑えられる利点は魅力的です。特に、ファイルサイズに制約がある環境では有力な選択肢になると思います。

あとは、SPAじゃないWebページの一部にコンポーネントを埋め込みたい時などは、大きなランタイムが不要なSvelteの強みを活かせそう。ReactやVueの置き換えというよりも、ポイントで使うほうが現実味がある。

仕事で使う勇気はまだないので、しばらくは趣味で触っときます。

Svelte 3.17.1
Sapper 0.27.9

※2020年1月20日現在のバージョン

Svelteについて

UIコンポーネントを最小限のJSに変換してくれるコンパイラ。
rollupで有名なRich Harrisさんがメインで開発しています。

ReactとVueを足してKonMariしたらSvelteになる。
と考えていただけたらイメージしやすいと思います。

$: svelte = konmari(react + vue)

Write less code

ReactやVueはコンポーネントを各ランタイム上で動くようにコンパイルしますが、Svelteは最小限のヘルパーを含むシンプルなJavaScriptにコンパイルするため、大きなランタイムを読み込む必要がありません。

リソースが限られたブラウザ上での最適化に注力するのではなく、コンパイル時にやれる事はやってしまえという発想の転換で、バンドルサイズを削減すると同時にブラウザへ与える負荷の軽減を実現しています。

設計の哲学的なところは、Blogにまとまってました。

小さくて速い、は本当か

RealWorld のベンチマークがあったので、そのデータから。このプロジェクトは、TODOよりも現実的なサンプルアプリが欲しいよね、みたいなモチベーションで作られたらしいです。Mediumを模したconduitというサイトが集まっています。

ベンチマーク対象のフレームワークは18個。

項目 スコア 順位
パフォーマンス 91 12位
ファイルサイズ 9.7KB 1位
コードの行数 1116行 3位

※ パフォーマンスはLighthouse AuditのMobile/Simulated Fast 3G
A RealWorld Comparison of Front-End Frameworks with Benchmarks (2019 update)

パフォーマンスの項目は、Fast 3G91なのでかなり良い数値だと思いますが、それでも18個中の12位。他のフレームワークもだいぶ速い。ファイルサイズはしっかり1位で良かった。

結果からわかるとおり、ファイルサイズが小さいからSvelteを選ぶ、というのはあると思います。

どんなコードが出力されるか

<script>
  let count = 0
</script>
<button on:click={() => count++}>count : {count}</button>

↑のコードが↓になります。
コンポーネントと最小限のヘルパーが一緒に出力されてますね。これで300行ぐらいです。

See the Pen minimal svelte output by nishinoshake (@nishinoshake) on CodePen.

Does this JavaScript spark joy?

konmari
[YouTube] Rich Harris - Rethinking reactivity

書いていて気分が上がったところをいくつか。
見た感じは、ほとんどVueのシングルファイルコンポーネントと同じです。

コードが短い

<script>let count = 0</script>
<button on:click={() => count++}>count : {count}</button>

ボタンのクリック&カウントを、ぐっと縮めたらこうなります。

状態管理がシンプル

stores.js
import { writable, derived } from 'svelte/store';

export const count = writable(0);
export const doubled = derived(count, $count => $count * 2);
export const increment = () => count.update(n => n + 1)
App.svelte
<script>
  import { count, doubled } from './stores.js';
  import Button from './Button.svelte';
</script>

<p>count : {$count}</p>
<p>doubled : {$doubled}</p>
<Button />
Button.svelte
<script>
  import { increment } from './stores.js';
</script>

<button on:click={increment}>+</button>

コンポーネントと切り離して状態を管理する仕組みが備わっています。VuexのGettersにあたる、derivedという関数も用意されているので便利。

トップレベルにHTMLタグを置き放題

<p>A</p>
<p>B</p>
<p>C</p>

Reactでいうところの<React.Fragment>が不要。
これは楽な反面、秩序がなくなると、あちこちで祭りが開催されそう。

HTMLのテンプレートでawait

<script>
  const sleep = () =>
    new Promise(resolve => setTimeout(() => resolve("Loaded!"), 3000))
</script>

{#await sleep()}
  <p>Loading...</p>
{:then message}
  <p>{message}</p>
{:catch error}
  <p>{error.message}</p>
{/await}

すごい。ただの変態。
テンプレート側でPromiseを受け取れるのは面白いけど、 <script> 側で await して状態を変更する方が、コードとしては見やすいと思う。

要素のサイズやスクロール量をバインドできる

<script>
  let w
  let h
  let y
</script>

<svelte:window bind:scrollY={y} />
<div bind:clientWidth={w} bind:clientHeight={h}></div>
<p>w: {w}px h: {h}px y: {y}</p>

要素のサイズ取得は、リサイズ時の再取得とかまで含めると結構面倒なので、これは嬉しい。チュートリアルにも書いてありますが、DOMへのアクセスを伴う重い処理なので控えめに。

さらに取扱注意ですが、 windowbind:scrollY とかもできます。

CSSはデフォルトでスコープあり

scoped-css.png

scopedがデフォルトで、globalがオプション。

クラスの付け外しが楽

<script>
  let isActive = false;
</script>

<style>
  .isActive {
    color: red;
  }
</style>

<p class:isActive>Hello!</p>
<button on:click={() => isActive = !isActive}>toggle</button>

変数名をそのままクラス名として付け外せるショートハンドがある。悩ましいのが、JSの変数名なので、ショートハンドを使うと is-active みたいなケバブな命名ができない。ここはこだわらずにJSの変数名に合わせました。スコープがある前提であれば、そこら辺は割り切りやすい。

アニメーションが豊富

TweenSpringTransitionなどが用意されています。Springまで入っているのはすごい。Vueにもあるけど、flipのアニメーションは感動的。

Sapperとは

ここからは、Svelteの開発環境構築からSSRや静的化まで、必要なことをまとめてやってくれるSapperの話。SvelteのためのNext.js的なやつです。

こちらもSvelteと同じくRich Harrisさんがメインで開発しています。

rollup or webpack

# for Rollup
npx degit "sveltejs/sapper-template#rollup" my-app

# for webpack
npx degit "sveltejs/sapper-template#webpack" my-app

テンプレートの作成にはrollupとwebpackを選べますが、全体的にrollup推しな雰囲気を感じたので郷に従うことに。degitとは?と思って調べたら、これもRich Harrisさんが作ってた。なんとなく分かると思いますが、sveltejs/sapper-templateがリポジトリ名で#rollupがブランチ名です。

Next.js → Sapperがスムーズ

Next.js is close to this ideal. If you haven't encountered it yet, I strongly recommend going through the tutorials at learnnextjs.com.

Sapper: Towards the ideal web app framework

Next.jsを使ったことがなかったら、とりあえずチュートリアルをやってみて!とブログにも書いてありました。実際、Next.jsかNuxt.jsを触ってからSapperを使ったほうが、すんなり馴染むと思います。

逆に言えば、これらのフレームワークと大きく違うところがないので、ここに書くことがない・・・

SSRして使う場合、ミドルウェアやページ遷移まわりが気になると思うんですが、今回は必要なかったので触ってないです。。

デプロイ

あとは、Sapperの上にSvelteのコンポーネントをゴリゴリ追加して完成です。静的ファイルを生成してS3にデプロイしました。

# 静的ファイルの生成
npm run export

https://github.com/nishinoshake/yokoku/blob/master/.github/workflows/deploy.yml

些細なことだけど、まだ .svelte の拡張子に慣れない。

気になったこと

サイトを作るうえで気になったことをまとめました。だいたいWikiのFAQに答えが書いてます。

https://github.com/sveltejs/svelte/wiki/FAQ

TypeScriptのサポート

今は未対応だけど対応の予定はある。進捗はこちらのissueから。

svelte-preprocess<script lang="typescript"> にしたらある程度は行けるっぽかったですが、公式でサポートされる前にハマりたくなかったので試してないです。

テストはどうする

How do I do testing Svelte apps?
We don't have a good answer to this yet, but it is a priority.

https://github.com/sveltejs/svelte/wiki/FAQ#how-do-i-do-testing-svelte-apps

まだ、これって方法は無さそう。

Storeなどのロジックのテストをメインでやって、大事なところはE2Eでもカバーしつつ、気が向いたらコンポーネントもテストする、ぐらいが今はちょうどいいかも。Sapperのテンプレートには、デフォルトでcypressが入ってました。

今回のサイトは、そんなに書くテストも無かったので、下のサンプルの分しか書いてないです。

Store

Storeはシンプルなオブジェクトなので、テストしやすいと思います。これはJestで書いてます。

src/states/player.js
import { writable } from 'svelte/store'

export const isPlayerOpen = writable(false)
export const openPlayer = () => isPlayerOpen.set(true)

test/unit/store/player.test.js
import { get } from 'svelte/store'
import { isPlayerOpen, openPlayer } from '../../../src/store/player'

describe('store/player', () => {
  describe('openPlayer', () => {
    test('最初は非表示の状態から', () => {
      expect(get(isPlayerOpen)).toBe(false)
    })
    test('表示できることを確認', () => {
      openPlayer()
      expect(get(isPlayerOpen)).toBe(true)
    })
  })
})

https://github.com/nishinoshake/yokoku/blob/master/test/unit/store/player.test.js

コンポーネント

コンポーネントのテストツールもありました。無意味なテストですが参考までに貼っておきます。環境の構築については、dev.toにあった記事がわかりやすかったです。

import { render } from '@testing-library/svelte'
import About from '../../../../src/components/index/About.svelte'

describe('About', () => {
  test('テキストある', () => {
    const { container } = render(About)

    expect(container).toHaveTextContent('映画館の近くに住みたい')
  })
})

わかりやすかった記事
Testing Svelte components with Jest
ツール
testing-library/svelte-testing-library
コード
https://github.com/nishinoshake/yokoku/blob/master/test/unit/components/index/About.test.js

スタイルガイドが欲しい

Vueのような公式のスタイルガイドや、ベストプラクティス的なものは見当たらなかったので、sveltejs/sapper-templatesveltejs/realworldのコードを参考にしました。

ディレクトリ構成は、なんとなくNuxt風に。

Sassで書きたい

svelte-preprocessのパッケージを追加して設定を変更するとSassが使えるようになります。下記のブログを参考にしました。
Svelte / Sapper with Sass!

変数やmixinを共通で @import したかったんですが、issueを参考にrollupの設定をいじったら行けました。

.browserslistrc
> 0.5% in JP, not dead, not IE 11
rollup.config.js
// src/styles/variables.scss
// src/styles/mixins.scss
// にファイルを置いておく。

// ※ 必要なところのみ抜粋

import sveltePreprocess from 'svelte-preprocess';

const preprocess = sveltePreprocess({
  scss: {
    includePaths: ['src'],
    data: `
      @import 'styles/variables.scss';
      @import 'styles/mixins.scss';
    `
  },
  postcss: {
    plugins: [
      require('autoprefixer')
    ]
  }
})

export default {
  client: {
    plugins: [
      svelte({
        preprocess
      })
    ]
  },
  server: {
    plugins: [
      svelte({
        preprocess
      })
    ]
  }
}

Svelteはスケールするか

<よく聞かれる質問>
大きなランタイムが不要なのはわかったけど、コンポーネントごとにプレーンなJSを生成してたら、コンポーネントを増やすコストが大きそうだから、そのうち従来のフレームワークよりもバンドルサイズが大きくならない?

<回答>
確かにそうなることはあるけど、コストがそこまで大きい訳ではないし、初期ロードのバンドルサイズを抑える方が重要。あとから読み込まれるバンドルが大きくても、Service Workerやpreloadでどうにかなるし。

===== ↑ざっくり訳↑ =====

バンドルサイズや実行時の性能もそうですが、知見が少ない分、スケールするアプリケーションの設計が難しいのがネックになりそう(特に状態管理)。

おわり

できたもの
https://yokoku.cc

GitHub
https://github.com/nishinoshake/yokoku

KONMARI 〜人生がときめく片付けの魔法〜
https://www.netflix.com/title/80209379

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

JavaScriptのプロジェクトをTypeScriptに移植する手順メモ

自分用メモです。

必要パッケージインストール

  • yarn add typescript @types/node // TypeScript導入
  • yarn add -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin prettier eslint-config-prettier eslint-plugin-prettier // ESLint + Prettier導入
  • yarn add json-loader // JSON読むやつ

設定ファイル生成・編集

package.jsonのscriptsに以下を追加

"scripts": {
  "tsc-init": "tsc --init",
  "watch": "tsc --watch",
  "build": "tsc"
}

tsconfig.jsonの生成

  • yarn tsc-init
  • tsconfig.json内の以下を変更
    • targetをes2015
    • outDirを./.dist/
    • rootDirを./src/
    • types(配列)にnodeを追加

ESLint + Prettierの設定

  • eslint --init
    • TypeScriptを使用しないを選択
  • .prettierrcに"singleQuote": trueを追加
  • .eslintrc.jsonに以下を追加
{
  "extends": [
      "eslint:recommended",
      "plugin:@typescript-eslint/recommended",
      "plugin:@typescript-eslint/eslint-recommended",

      "plugin:prettier/recommended",
      "prettier/@typescript-eslint"
  ],
  "plugins": ["@typescript-eslint"],
  "parser": "@typescript-eslint/parser"
}

あとは全コードをTypeScript化しておわり!

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

2020年流行するであろうwebデザインのトレンド

ここ最近はざっくり言ってしまえばフラットデザイン2.0、マテリアルデザインが主流になっていると思います。
視認性もよく操作もしやすいのが好まれる傾向になています。
そんな中でこれからあるいは、今も流行っているものもありますが2020年で流行するwebデザインを紹介したいと思います。
なるべくサイトも載せていこうと思いますが、ページが更新されて違うデザインになっていたり、サイトがなくなってしまっていたらごめんなさい。

・ダークモードに対応したデザイン

ios13からiphoneでもダークモードが搭載されました。
すでに対応しているサイトもありますがまだまだダークモードに対応していないサイトが多く見れれます。
目に優しいから、かっこいいからなどの理由で利用している人は多いと思いますのでこれからもっと増えてくるでしょう。
ただカラーが変わるのでブランドイメージとかの兼ね合いもあるのでなかなか難しい部分ではありますが…
スクリーンショット 2020-01-20 7.15.19.png
https://www.webcreatorbox.com/tech/dark-mode

・余白スペースを多く利用したデザイン

最近のwebサイトはフルスクリーンの画像を背景に設定しているページが多く存在しますが、
あえて画像をフルスクリーンサイズにせず、空いた余白をうまく利用することによってその画像や文字を引き立たせ、
伝えたいコンテンツをうまく伝えることができる作り方になっています。

スクリーンショット 2020-01-20 7.00.09.png
https://www.crazy.co.jp/

スクリーンショット 2020-01-20 6.58.33.png
https://www.rainbow.co.jp/

・3Dアニメーションを利用したデザイン

アニメーションを利用したサイトが増えてきましたが、
さらにこれからは3Dイラストをアニメーションさせる時代になってくるのではないかと思います。
PCのスペックも昔と比べて格段に上がりwebサイトでも表現できるようになってきたと思います。
もちろんそれでもパフォーマンスの向上も必ず必要にはなりますが…

スクリーンショット 2020-01-20 6.48.31.png
https://w-wired.com/ww/pc/

・イラストを多く使ったデザイン

イラストを使うことで硬いイメージの題材でも親しみのあるものにすることができるのと、とてもわかりやすく物事を伝えることができるのでデザインになります。

スクリーンショット 2020-01-20 6.50.32.png

https://payme.tokyo/

・スクロールに合わせてアニメーションが展開されるデザイン

google pixel4 や macbook pro のページが主なところかと思います。
次はどんなアニメーションが起こるんだろうとスクロールしたくなる感情を揺さぶられます。

スクリーンショット 2020-01-20 6.41.48.png
https://www.apple.com/jp/macbook-pro-16/

スクリーンショット 2020-01-20 6.42.19.png
https://store.google.com/jp/product/pixel_4

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