20200926のJavaScriptに関する記事は29件です。

Socket.IO を触ってみた

Socket.IO とは

https://socket.io/

WebSocket 通信を実装するための JavaScript ライブラリです
WebSocket とはなんぞや、という記事は こちら

公式のチュートリアル に沿って「チャットアプリ」を作っていきます
細かいことはチュートリアルを見ればわかるので、ポイントだけ

コネクションの確立

サーバ側

index.js
var app = require('express')();
var http = require('http').createServer(app);
// httpサーバーオブジェクトを渡して Socket.IO のインスタンスを初期化する
var io = require('socket.io')(http); 

// 中略

// コネクションが確立すると呼ばれる
io.on('connection', (socket) => {
  console.log('a user connected');
});

クライアント側

jQuery はサンプルコードで使用するので読み込んでます

index.html
<!-- いろいろ省略 -->
<html>
  <body>
    <!-- クライアント用の js を読み込む(これはサーバからGETしてるだけ) -->
    <script src="/socket.io/socket.io.js"></script>
    <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
      // インスタンスを初期化、これで接続される
      var socket = io();
    </script>
  </body>
</html>

クライアント => サーバ

クライアント側

emit('イベント名', {データ}) でサーバにイベントを通知します

index.html
<html>
  <body>
    <script src="/socket.io/socket.io.js"></script>
    <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
    <script>
      var socket = io();

      // チャットメッセージの送信
      $('form').submit(function(e) {
          e.preventDefault();
          var msg = $('#m').val()
          socket.emit('chat message', msg); // サーバに伝達
          $('#m').val('');
          return false;
      });
    </script>
  </body>
</html>

サーバ側

socket.on('イベント名', {コールバック関数}) でイベントを受け取ります

index.js
// 中略

io.on('connection', (socket) => {
  console.log('a user connected');

  socket.on('chat message', (msg) => {
    // なんらかの処理
  });
});

サーバ => クライアント

サーバ側

さきほどのクライアント側と同じく emit('イベント名', {データ}) で送ります

index.js
// 中略

io.on('connection', (socket) => {
  console.log('a user connected');

  socket.on('chat message', (msg) => {
    // io.emit => 送信者含む全員に送る場合
    io.emit('chat message', msg);

    // socket.broadcast.emit => 送信者を除く全員に送る場合
    socket.broadcast.emit('chat message', msg);
  });
});

クライアント側

さきほどサーバ側と同じく socket.on('イベント名', {コールバック関数}) でイベントを受け取ります

index.html
<html>
  <body>
    <script src="/socket.io/socket.io.js"></script>
    <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
    <script>
      var socket = io();
      // 中略
      socket.on('chat message', function(msg){
        // なんらかの処理
      });
    </script>
  </body>
</html>

手っ取り早く動きを確認してみたい場合は、チャットアプリのサンプルが公式で提供されているので clone してすぐに動かすことができます
https://github.com/socketio/chat-example

Homework

チュートリアルで Homework なるより学習を深めるためのお題が公開されているのでやってみました
コードは こちら に置いてます

意訳も入っていますが、課題はこの6つです

  • ユーザーが接続/切断した際にメッセージを送信する
  • ニックネーム機能を追加する
  • 送信者自身にはサーバを介してメッセージを送信せず、直接メッセージを追加する
  • タイピング中の人を表示する
  • オンラインの人を表示する
  • プライベートメッセージ機能を追加する

個人的にポイントに感じたことだけ書きます(笑)

オンラインの人を表示する

オンラインのユーザを管理する配列を用意して、入退室時に配列に格納/削除する、というところまでは普通です
socket.id で接続中のユーザのIDが取得できるのでこれを利用します

index.js
// 中略

var onlineUsers = []

io.on('connection', (socket) => {

  // 退室
  socket.on('disconnect', () => {
    var index = onlineUsers.findIndex(item => item.id === socket.id);
    onlineUsers.splice(index, 1);
    io.emit('user update', onlineUsers);
  });

  // 入室
  socket.on('enter room', (nickname) => {
    onlineUsers.push({ id: socket.id, nickname: nickname });
    io.emit('user update', onlineUsers);
  });

詰まったところ

確認するときに、ブラウザのタブを複数開いて「リロード」していたのですが、なにやらうまくいかないぞ...
結論としては、リロードすると一度 disconnect イベントが発生してしまうので、それが原因のようでした
確認の際は、面倒ですが一度タブを閉じ、再度ページにアクセスするとよいと思います

プライベートメッセージ機能を追加する

クライアント側の実装などが面倒そうだったのでちゃんと作ってはないです(笑)

io.to({ソケットID}).emit('イベント名', {データ}) で特定の人にだけ送れます

最後に

チュートリアルを通して Socket.IO の基本的な使い方を勉強してみました
room 機能などもあるみたいなのでまたいつか使ってみたいです

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

機械学習メモ - ブラウザ上でMNISTデータファイルをDrag&Dropで受け取って手書き数字をとりあえず表示する

やったこと

とりあえず、ニューラルネットワークで手書き数字認識を一からやってみようと思い、MNISTのデータの読み込みまでをやってみた。

できたもの

See the Pen MNIST handwritten digits viewer by kob58im (@kob58im) on CodePen.

■使いかた
事前にMNISTのサイトからgzファイルをダウンロードして解凍した結果の4ファイルをローカルに保存してください。
これらのファイルをDrag&Dropすると、ブラウザ上でデータを読み込んで表示します。(ファイル名のチェックのみ実施していますが、ファイルサイズやデータ内容の整合性チェックはしていません。)

参考サイト

MNISTのデータ

File の Drag & Drop 読み込み

canvas画像のPixel単位操作

ニューラルネットワーク

すごい良さげな解説動画をみつけたので、これ見ながらニューラルネットワーク学習の実装までやってみたい。

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

typescript 型について

typescriptの型まとめ

// primitive
const apple: number = 5
const speed: string = 'fast'
const hasName: boolean = true

const nothingMuch: null = null
const nothing: undefined = undefined
const now: Date = new Date()

// array
const colors: string[] = ['red', 'green', 'blue']
const myNumbers: number[] = [1, 2, 3]
const truths: boolean[] = [true, false, true]

// class
class Car {}

const car: Car = new Car()

// object literal
const point: { x: number, y: number } = {
  x: 10,
  y: 20
}

const destinations = {
  Japan: {
    longitude: 153,
    latitude: 24
  },
  America: {
    longitude: 38,
    latitude: 97
  }
}
const { Japan }: { Japan: { longitude: number; latitude: number } } = destinations


//any
const every: any[] = [10, 'Jon', true, null, undefined]

// tuple
type Drink = [string, boolean, number]

const pepsi: Drink = ['brown', true, 40]
const sprite: Drink = ['clear', true, 40]
const tea: Drink = ['brown', false, 0]

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

ReactとPython flaskを使ってWebアプリを作りたい

はじめに

仕事でReactによるフロント開発に携わり始めたので、アウトプットの練習も兼ねて簡易webアプリを作成しました。

どんなアプリにするかアイデアはまったく思い浮かばなかったので、手元にあったmecabを使った分かち書きスクリプトを使って、フロントで受け取った入力テキストをサーバー側で分かち書きをし、その結果をフロントで表示するという非常にシンプルなアプリです。
(主目的はreactとflaskをつなぐ部分を勉強することだったため、アプリの見た目や機能は全然作り込んでいませんのであしからず。)

表題の通り、フロント側はReact、サーバー側はpython flaskで実装しています。

今回実装したスクリプトはこちらで公開しています。

完成品

画面
app_top.png

分かち書きしてみる
app_res.png

実装環境

OS: Ubuntu 18.04.2 LTS
Python: 3.6
flask==1.0.2

npm: 6.14.7

reactの環境構築については今回触れませんが、公式チュートリアルが日本語でも充実していて非常に参考になりました。
- https://ja.reactjs.org/

こちらもすごくおすすめです。
- https://mae.chab.in/archives/2529

実装する

構成図

今回実装したアプリの構成は以下のようになっています(主要部分のみ)。

app_architect.png

サーバー側

サーバー側は以下のような構成になっています。

backend/
   ├─ requirements.txt
   ├─ server.py
   └─ utils.py

server.pyはflaskサーバーを立ち上げるコードです。

アドレスやポートは一番下、app.run(host='127.0.0.1', port=5000)で指定します。

server.py
from flask import Flask
from flask import request, make_response, jsonify
from flask_cors import CORS
from utils import wakati

app = Flask(__name__, static_folder="./build/static", template_folder="./build")
CORS(app) #Cross Origin Resource Sharing

@app.route("/", methods=['GET'])
def index():
    return "text parser:)"

@app.route("/wakati", methods=['GET','POST'])
def parse():
    #print(request.get_json()) # -> {'post_text': 'テストテストテスト'}
    data = request.get_json()
    text = data['post_text']

    res = wakati(text)
    response = {'result': res}
    #print(response)
    return make_response(jsonify(response))

if __name__ == "__main__":
    app.debug = True
    app.run(host='127.0.0.1', port=5000)

@app.route("/wakati", methods=['GET','POST')部分でフロントからテキストを受け取り、分かち書き処理した後、フロントへ返す処理をしています。
data = request.get_json()によってフロントからポストされてきた内容をjson形式で取得します。
ここから必要なデータを取り出して、何らかの処理(関数にかけたり、DBに入れたりし)をし、response = {'result': res}のようにjson形式にしてフロントに返します。

(補足:CORSとは)
別リソースへアクセス(=クロスサイトHTTPリクエスト)できるようにするために必要なルールです。これがないとフロント側から立ち上げたflaskサーバへアクセスできません。
- 参考:https://aloerina01.github.io/blog/2016-10-13-1

フロント側

今回はcreate-react-appの雛形を用いました。
(create-react-appの設定および使い方はこちらが非常にわかりやすいです!)

フロント側は以下のような構成になっています(主要ファイルのみ掲載)。

frontend/app/
   ├─ node_modules/
   ├─ public/
   ├─ src/
   |   ├─ App.css
   |   ├─ App.js
   |   ├─ index.js
   |   └─ ...
   └─ ...

自動生成された雛形の中のApp.jsを以下のように書き換えました。

App.js
import React from 'react';
import './App.css';
import Axios from 'axios';

//function App() {
export class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  render() {
    return (
      <div className="App">
        <header className="App-header">
          <h1>text parser</h1>
          <form onSubmit={this.handleSubmit}>
            <label>
              <textarea name="text" cols="80" rows="4" value={this.state.value} onChange={this.handleChange} />
            </label>
            <br/>
            <input type="submit" value="Parse" />
          </form>
        </header>
      </div>
    );
  }


  wakati = text => {
    //console.log("input text >>"+text)
    Axios.post('http://127.0.0.1:5000/wakati', {
      post_text: text
    }).then(function(res) {
      alert(res.data.result);
    })
  };

  handleSubmit = event => {
    this.wakati(this.state.value)
    event.preventDefault();
  };

  handleChange = event => {
    this.setState({ value: event.target.value });
  };
}

export default App;

この中の以下の部分でサーバー側とのやり取りを行なっています。

  wakati = text => {
    //console.log("input text >>"+text)
    Axios.post('http://127.0.0.1:5000/wakati', {
      post_text: text
    }).then(function(res) {
      alert(res.data.result);
    })
  };

server.pyで立てたhttp://127.0.0.1:5000/wakatithis.state.valueの値をポストします。
サーバー側で処理された後、返ってきたresultの値がalert(res.data.result);によってブラウザに表示されます。

動かす

フロントエンド/バックエンド用にそれぞれターミナルを立ち上げて以下のコマンドを実行します。

サーバー側

$ cd backend
$ python server.py

フロント側

$ cd frontend/app
$ yarn start

ブラウザからlocalhost:3000にアクセスすることでアプリを利用できます(yarn startで自動で立ち上がります)。

おわりに

今回はReactとPython flaskを用いて簡易的なWebアプリを実装しました。
簡易的とはいえ、短時間で楽にWebアプリを実装できるので素晴らしいですね。

フロント修行中の身なので、見た目や機能についてはまだまだなのでご意見、アドバイス等いただければ幸いです。
最後まで読んでいただきありがとうございました!

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

いきなりWebアプリケーションエンジニアになった未経験者に最低限知っておいてほしいこと

あらすじ

仕事でSpring Boot2を使ったWebアプリケーション開発をすることになりました。
自分が参画した段階で、SpringはおろかWebアプリケーションの仕組み自体を理解しているメンバーはゼロでした。
自分もSpringはおろかJava自体6年ぶりくらいだったのですが、Webの仕組みを知っているのが自分しかいなかったので教えるしかなかったのが、今回執筆しようと思ったきっかけです。

Webアプリケーションとは

EdgeとかGoogle ChromeとかFirefoxとかSafariとかのウェブブラウザでアクセスしてログインしてポチポチして登録とか更新とか閲覧とか削除とかするソフトウェアです。
ヤフーにしろグーグルにしろアマゾンにしろ楽天市場にしろ、ウェブブラウザを使って利用するソフトウェアはWebアプリケーションです。
特徴としては、クライアント/サーバーシステム(いわゆるクラサバ)と違い、クライアント端末(パソコン)に特定のアプリケーションをインストールする必要がありません。

Webアプリケーション概略図

めっちゃ適当に書くとこんな感じ。

image.png

Webアプリケーションを構成する技術には様々なものがあります。
難しい話は抜きにして、最低限理解しておきたいところを説明します。

クライアントサイドとサーバサイド

Webアプリケーション開発でしっかりと意識しておきたいこととして、クライアントサイドとサーバサイドの技術の違いです。
まずはサーバサイドから。

サーバサイド

JavaやC#やVB.NETやPHPやRubyやPythonやPerlやGoなどのプログラミング言語を使った開発は、サーバサイドになります。
データベースからデータを取り出したり保存したりする処理はこちらで行います。
サーバーは、クライアントからのリクエストがあった時に処理を実行してクライアントに結果を返します。
クライアントからのリクエストがなければ何もしません。

クライアントサイド

ウェブブラウザでポチポチしてる時に見えているものがクライアントサイドになります。
HTML、CSS、JavaScriptを使います。
JavaScriptは基本的にクライアントサイドで動作します。
つまり、ユーザーのPCスペックの影響を受けます。
何かしらの操作によって発生したイベントによってプログラムが動作します。
これを、イベントドリブンといいます。

HTTP通信

クライアント側からサーバー側にリクエストを送る方式がHTTP通信になります。
送る方法は、HTMLで記述されたsubmitもしくはJavaScriptで行います。

同期通信と非同期通信

クライアントとサーバー間のHTTP通信において、同期通信と非同期通信があります。
同期通信では、クライアントからサーバーへリクエストを送信した際、サーバーからのレスポンスがあるまで待機します。
ウェブブラウザのタブがくるくる回っていたら同期通信です。
また、同期通信では画面がリフレッシュされます。
HTMLでいうsubmitは同期通信です。

非同期通信では、クライアントからサーバーへリクエストを送ってもクライアント側は別の処理を実行できます。
レスポンスがあったらそれに応じて処理を続行します。
ウェブブラウザ上で動的にデータの取得や更新を行う際に非同期通信を利用します。
非同期通信では画面の一部のみを差し替えたりできます。
JavaScriptで実装します。

メソッド

HTTP通信の際、メソッドというものがあります。
HTMLでは、formタグのmethod属性で指定します。

GET

GETメソッドでは、URLの後ろにクエリストリング(クエリパラメータ)というものが付きます。
URLの後ろに?マークを付け、その後ろにキーと値の組み合わせで記述されます。
クエリストリングには長さ制限があったりするので大量のデータをGETメソッドで送ることはありません。
また、パラメータがURLに乗るので秘匿すべき情報はGETメソッドでは送らないのが一般的です。

POST

POSTメソッドではHTTPリクエストのボディにデータが乗ります。
ウェブブラウザに表示すべきHTMLもレスポンスのHTTPボディに入っています。

ヘッダーとボディ

話が前後しますが、HTTPにはヘッダーとボディがあります。
URLはヘッダーにあります。
POSTした際のデータはボディにあります。
データの形式がなんであるかは、ヘッダーで指定します。
リクエスト時もそうですが、レスポンス時もヘッダーで指定されています。

終わり

端折りすぎたかもしれない。
ここから先は、より具体的なお話です。


Appendix

JavaScriptでの非同期通信とサーバサイドの処理

JavaScriptでform要素内のデータをsubmitすることもできますが、昨今は非同期通信を当たり前のように利用します。
Twitterをウェブブラウザで開いている際に下にスクロールするとその都度データを取得して表示していますよね。あれです。
JavaScriptでの非同期通信のことをAjaxと呼びます。
昔はXML形式でデータのやり取りをしていましたが、今はJSON形式でデータのやり取りをするのが一般的です。

同期通信と非同期通信で、サーバサイドの実装も変わります。
同期通信ではウェブブラウザ上に描画するためのHTMLをレスポンスとして返す必要がありますが、非同期通信では必要なデータのみを返します。
そのため、JSON形式のデータを作成してレスポンスとして返すことになります。
サーバサイドで利用するプログラミング言語が何であっても、その言語のフレームワークを使用して開発するでしょうからそのフレームワークにJSON形式でレスポンスを返す機能が大抵あります。

JavaScriptでの非同期通信における注意

JavaScriptで非同期通信を行う際、非同期処理のため、実行結果を受け取る前に処理が進んでいきます。
そのため、実行結果を受け取ってから処理する必要があるものについてはコールバック関数というもので処理したりします。
jQueryで説明すると、以下のような感じです。

$.get('/foo/bar', function(data) {
    // /foo/bar というURLに対してリクエストを送った際に、レスポンスが data に入っている
    console.log(data);
});
// こっちの処理は上のconsole.logより先に実行される
console.log('hello, world!');

上記の例で、function(data)の関数定義は無名関数というものです。
この無名関数内の処理が、非同期通信の結果を受け取った後に処理されます。
JavaScriptの関数は第一級関数というもので、関数自体を変数に代入したりすることが出来ます。
つまり、$.getは以下のような実装イメージだと思うと理解できます。

function get(arg1, arg2) {
    // 以下はただのイメージです
    var response = http.get(arg1);
    arg2(response);
}

第二引数で受け取った変数を関数実行し、その前に受け取っているレスポンスを引数としてセットしています。
無名関数として定義しているfunction(data)dataはいわゆる仮引数なので、getメソッド内で引数として渡しているresponsedataとして無名関数内で利用される、というイメージです。
このイメージが理解できていないと、非同期通信後に行う処理を先に実施してしまったりといった不具合を引き起こしてしまいます。


ひとまずこのあたりで。
後日追記するかもしれません。

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

GASからTwilioを動かしたい、条件分岐させたい

前提

  • Twilioで電話番号取得済み

概要 & コード

  • 電話の受け付け
    参考文献をご覧になってください。とてもわかりやすい記事でした。
    (ここで作成したGASアプリケーションを、以下「app1」と表記)

  • 条件分岐
    GASアプリケーションをもう1つ用意してください。(以下「app2」と表記)
    <Gather>を以下のように少し工夫します。actionで、「app2」の「web app URL」を記載します。
    そして、「app1」からのPOSTを「app2」で受け取ります。

//「app1」の記述(一部)

<Gather action='https://script.google.com/macros/s/{GASID}/exec' method='post' numDigits='1'>
  <Say>
    "ボタンを押してください。"
  </Say>
</Gather>
//「app2」の記述

function doPost(e) {
  var digits = e["parameters"]["parameter"]["Digits"];
  //ボタン「1」を押していた場合
  if(digits = 1){
    var response_str = "<Response>\n <Say voice='woman'  language='ja-jp'> 1が押されました。</Say>\n</Response>";
    var out = ContentService.createTextOutput(response_str);
    out.setMimeType(ContentService.MimeType.XML);
    return out;
  }else{
    var response_str = "<Response>\n <Say voice='woman'  language='ja-jp'> 1以外のボタンが押されました。</Say>\n</Response>";
    var out = ContentService.createTextOutput(response_str);
    out.setMimeType(ContentService.MimeType.XML);
    return out;
  }
}

参考文献

Google Apps Script と Twilio で自作留守電サービスを構築してみた話

twilioからGoogle Apps Scriptを経由して別サービスにつなぐ

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

GASからTwilioを動かしたい、条件分岐させたい、着信番号を取得したい

前提

  • Twilioで電話番号取得済み

概要 & コード

電話の受け付け
参考文献をご覧になってください。とてもわかりやすい記事でした。
(ここで作成したGASアプリケーションを、以下「app1」と表記)

条件分岐
GASアプリケーションをもう1つ用意してください。(以下「app2」と表記)
<Gather>を以下のように少し工夫します。actionで、「app2」の「web app URL」を記載します。これによって、「app1」から「app2」にパラメーターを渡しますことができます!

//「app1」の記述(一部)

<Gather action='https://script.google.com/macros/s/{GASID}/exec' method='post' numDigits='1'>
  <Say>
    "ボタンを押してください。"
  </Say>
</Gather>

あとは、「app2」側でパラメーターから値を取得すればいいだけ!

//「app2」の記述

function doPost(e) {
  //押されたボタンを取得
  var digits = e["parameter"]["Digits"];
  //ボタン「1」を押していた場合
  if(digits = 1){
    var response_str = "<Response>\n <Say voice='woman'  language='ja-jp'> 1が押されました。</Say>\n</Response>";
    var out = ContentService.createTextOutput(response_str);
    out.setMimeType(ContentService.MimeType.XML);
    return out;
  }else{
    var response_str = "<Response>\n <Say voice='woman'  language='ja-jp'> 1以外のボタンが押されました。</Say>\n</Response>";
    var out = ContentService.createTextOutput(response_str);
    out.setMimeType(ContentService.MimeType.XML);
    return out;
  }
}

着信番号などその他
着信番号はe["parameter"]["From"]で取得できます。その他いろいろe["parameter"]に入ってます。

参考文献

Google Apps Script と Twilio で自作留守電サービスを構築してみた話

twilioからGoogle Apps Scriptを経由して別サービスにつなぐ

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

with IE(JavaScriptのIE11対応)

はじめに

政府や行政サイトの「IE縛り」に代表されるように、IE汚染国である日本で仕事としてJavaScriptを書く場合、IEと共存するしなければならない場合が多いです。
with コロナならぬwith IEです。

IE生活様式

  • Transpile
  • Bundle
  • Polyfill

TranspileとBundle は、ES5の書き方で1つのファイルに書いていれば不要です。
エンジニアが不便を我慢すれば必要ない工程ではありますが、そんな我慢はしたくありません。
コロナで言えばマスク、消毒、手洗い、うがい、密を避ける、換気のような個人でできる対策に当たると思います。

Polyfillは、もともとIE11が対応していない機能に対応させるもので、コロナでいえば治療薬ともいえるものだと思いますが、薬なのでリスクもあります。

コロナと違って、みんなでIEやめる合意が取れればIE対応せずに済む世界になるんですが。
河野大臣、ハンコ、書面、FAXに続いて、IE対応不要もお願いします。

JavaScriptをIE11対応する

webpackとBabelのインストール

terminal
npm install -D webpack webpack-cli babel-loader @babel/core @babel/preset-env 
パッケージ 内容
webpack webpack本体
webpack-cli webpackのコマンドラインツール
babel-loader webpackでBabelを使えるようにする
@babel/core Babel本体
@babel/preset-env 指定したブラウザ環境で動作するように変換するプラグイン

設定ファイルの準備

package.jsonの編集

package.jsonにビルドコマンドを追加します。

package.json
{
 "scripts": {
   "dev": "webpack --mode development",
   "build": "webpack --mode production"
  }
}

webpack.config.jsの作成

プロジェクトルートにwebpackの設定ファイルのwebpack.config.jsを作成します。
babelでtargetsを指定しないとbabel/preset-envがbrowserslistとの連携を行なってくれるので指定しません。

webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [
          {
            loader: "babel-loader"
          }
        ]
      }
    ]
  }
};

.babelrc

プロジェクトルートにBabelの設定ファイルの.babelrcファイルを作成します。
※webpack.config.js内に記述することもできます。

.babelrc
{
  "presets": ["@babel/preset-env"]
}

.browserslistrc

プロジェクトルートに対象ブラウザを指定する.browserslistrcファイルを作成します。

.browserslistrc
ie 11

.browserslistrcで対象ブラウザを指定する

Transpile

TranspileするJavaScriptの準備

src/index.js
window.addEventListener("load", () => {
  alert("InternetExplorer");
});

Transpile後のJavaScriptを読み込むHTMLの準備

dist/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="main.js"></script>
</body>
</html>

ビルド

terminal
npm run build

動作確認

IE11でdist/index.htmlを開いて、JavaScriptが実行できているか確認します。

polyfillの追加

APIがサポートされていないなど、構文の変換だけでは対応できない機能への対応を行うpolyfillを追加できるようにします。

core-jsのインストール

terminal
npm install -S core-js

.babelrcの変更

.babelrc
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "usage",
        "corejs": {
          "version": 3,
          "proposals": false  
        }
      }
    ]
  ]
}

Polyfillを追加するJavaScriptの準備

src/index.js
alert(Array.from("InternetExplorer"));

ビルド

terminal
npm run build

動作確認

IE11でdist/index.htmlを開いて、JavaScriptが実行できているか確認します。

regenerator-runtime

必要に応じてregenerator-runtimeを使用します。
(ビルド時にCan't resolve 'regenerator-runtime/runtime'が表示されたとき)

regenerator-runtimeのインストール
terminal
npm i -S regenerator
regenerator-runtimeのインポート
JavaScript
import  regeneratorRuntime  from  "regenerator-runtime" ;

//もしくは
//import  "regenerator-runtime / runtime.js" ;

TypeSctiptをIE11対応する

webpackとTypeScriptのインストール

terminal
npm install -D webpack webpack-cli typescript ts-loader 

table:パッケージ

パッケージ 内容
webpack webpack本体
webpack-cli webpackのコマンドラインツール
typescript TypeScript本体
ts-loader webpackでTypeScriptを読み込む

設定ファイルの準備

package.jsonの編集

package.jsonにビルドコマンドを追加します。

package.json
{
  "scripts": {
    "dev": "webpack --mode development",
    "build": "webpack --mode production"
},

tsconfig.jsonの作成

tsconfigでは.browserslistrcが使えないので、targetでJavaScriptのバージョンを指定します。

tsconfig.json
{
  "compilerOptions": {
    "target": "ES5",
    "lib": ["dom", "ES5","ScriptHost"]
  }
}
target lib省略時に暗黙的に指定されているもの
ES5 DOM,ES5,ScriptHost
ES6 lib: DOM,ES6,DOM.Iterable,ScriptHost

※この時点ではlibの記述は不要ですが、後でlibを追加するので先にデフォルトのlibを記述しています。

webpack.config.jsの作成

webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.ts$/,
        loader: 'ts-loader'
      }
    ]
  },
  resolve: {
    extensions: ['.ts', '.js']
  }
}

Transpile

TranspileするJavaScriptの準備

アロー関数のコードをIEで実行できるように変換

src/index.ts
window.addEventListener("load", () => {
  alert("InternetExplorer");
});

Transpile後のJavaScriptを読み込むHTMLの準備

dist/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="main.js"></script>
</body>
</html>

ビルド

terminal
npm run build

IE11での動作確認

IE11でdist/index.htmlを開いて、JavaScriptが実行できているか確認します。

polyfillの追加

APIがサポートされていないなど、構文の変換だけでは対応できない機能への対応を行うpolyfillを追加できるようにします。

core-jsのインストール

terminal
npm install -S core-js

動作確認用のコードの準備

src/index.js
import "core-js";

alert(Array.from("InternetExplorer"));

tsconfig.jsonの変更

libに追加します。

tsconfig.json
{
  "compilerOptions": {
    "target": "ES5",
      "lib": ["dom", "ES5","ScriptHost","ES2015.Core"]
  }
}

今回はlibに"ES2015.Core"を追加しましたが、コードによって追加する内容は変わります。

libの値
  • es5
  • es6
  • es2015
  • es7
  • es2016
  • es2017
  • es2018
  • es2019
  • es2020
  • esnext
  • dom
  • dom.iterable
  • webworker
  • webworker.importscripts
  • scripthost
  • es2015.core
  • es2015.collection
  • es2015.generator
  • es2015.iterable
  • es2015.promise
  • es2015.proxy
  • es2015.reflect
  • es2015.symbol
  • es2015.symbol.wellknown
  • es2016.array.includees2017.object
  • es2017.sharedmemory
  • es2017.string
  • es2017.intl
  • es2017.typedarrays
  • es2018.asyncgenerator
  • es2018.asynciterable
  • es2018.intl
  • es2018.promise
  • es2018.regexp
  • es2019.array
  • es2019.object
  • es2019.string
  • es2019.symbol
  • es2020.bigint
  • es2020.promise
  • es2020.string
  • es2020.symbol.wellknown
  • es2020.intl
  • esnext.array
  • esnext.symbol
  • esnext.asynciterable
  • esnext.intl
  • esnext.bigint
  • esnext.string
  • esnext.promise

https://github.com/microsoft/TypeScript/tree/master/lib

ビルド

terminal
npm run build

動作確認

IE11でdist/index.htmlを開いて、JavaScriptが実行できているか確認します。

regenerator-runtime

必要に応じてregenerator-runtimeを使用します。
(ビルド時にCan't resolve 'regenerator-runtime/runtime'が表示されたとき)

regenerator-runtimeのインストール
terminal
npm i -S regenerator
regenerator-runtimeのインポート
JavaScript
import  regeneratorRuntime  from  "regenerator-runtime" ;

//もしくは
//import  "regenerator-runtime / runtime.js" ;

環境を作らずにIE11対応する

あまり手をかけずにIE11対応をします。

Babel

https://babeljs.io/
https://babeljs.io/repl

アクセスしたブラウザ用にTranspileできるようですが、肝心のIE11が非対応のようです・・・・。

Polyfill

TranspileもBundleも不要で、Polyfillだけ使いたい場合はダウンロードしてきてHTMLから読み込めば使えるようになります。

例:intersectionObserver(https://github.com/w3c/IntersectionObserver/tree/master/polyfill)

index.html
<script src="intersection-observer.js"></script>

polyfill.io

https://polyfill.io/v3/
JavaScriptのPolyfillを配信しているサービスです。
※当然ですが、外部から読み込むので、polyfill.ioで障害が発生してしまったらPolyfillは無効になります。

下記URLで必要な機能を検索して、そのPolyfillを使えるURLを取得できます。
https://polyfill.io/v3/url-builder/

index.html
//ES2019の全polyfill取得
<script src="https://polyfill.io/v3/polyfill.min.js?features=es2019"></script>

//fetch機能だけのpolyfillを取得
<script src="https://polyfill.io/v3/polyfill.min.js?features=fetch"></script>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

簡単電子レシート receiptline で Web フォントを使ってみた

前回は、変換ライブラリに手を加えて、紙レシートの行間隔を調整しました。
今回は、紙ではなく電子レシートのフォントを変更してみようと思います。

標準のフォント

ReceiptLineをCLIでSVG変換のコマンドラインツールを使って SVG に変換します。

$ ./rltosvg 32 cp932 receipt.txt receipt.svg

入力データ

receipt.txt
{image:iVBORw0KGgoAAAANSUhEUgAAAIAAAAAwAQMAAADjOuD9AAAABlBMVEUAAAD///+l2Z/dAAAAZklEQVQoz2P4jwYYRrrABwYGOwYG5gMMDBUMDPxAgQcMDDJAgQYGhgJcAv//yMj//9/8//+HerAZRAsAzUASAJoGMhRF4AC6ANCIAhQz8AkAXQoUOIDidBQBkG8hAj8gAqPJAa8AAGjulhOsX97yAAAAAElFTkSuQmCC}
            市ヶ谷駅前店
    東京都千代田区九段1-Y-X

2019年 2月19日(火) 19:00
{border:line}
^領 収 証
{border:space}
{width:*,2,10}
ビール                 | 2|    ¥1,300
千鳥コース             | 2|   ¥17,280
-------------------------------------
{width:*,20}
^合計             |          ^¥18,580
現  金          |           ¥20,000
お 釣 り          |            ¥1,420

出力データ

01.png

出力データのフォント指定部分です。

receipt.svg
<g font-family="'MS Gothic', 'San Francisco', 'Osaka-Mono', 'Courier New', 'Courier', monospace" fill="#000" font-size="24" dominant-baseline="text-after-edge">

Windows では「MS ゴシック」、Mac では「San Francisco」、他の環境ではデフォルトの等幅フォントで表示されると思います。

異なる環境で表示を合わせるには、ビットマップ画像に変換するか、フォントを手作業でインストールしなければなりません。

Web フォントに変更

フォントのインストール作業を回避する方法があります。
そう、Web フォントです。ここでは Google Fonts を使います。
https://fonts.google.com/

変換ライブラリ lib/receiptline.js を変更します。

  • <style> を追加
  • font-family を変更
  • font-size を調整

以下は変更部分の抜粋です。フォントは「Kosugi Maru」を指定しています。

lib/receiptline.js
//
// SVG
//
const _svg = {
    ...
    // start printing:
    open: function (printer) {
        ...
        this.fontFamily = "'Kosugi Maru', monospace";
        return '';
    },
    // finish printing:
    close: function () {
        const style = '<style>@import url("https://fonts.googleapis.com/css2?family=Kosugi+Maru&amp;display=swap");</style>';
        const fontSize = 24;
        return `<svg width="${this.svgWidth}px" height="${this.svgHeight}px" viewBox="0 0 ${this.svgWidth} ${this.svgHeight}" preserveAspectRatio="xMinYMin meet" ` +
            `xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">` +
            `<defs>${style}<filter id="receiptlineinvert" x="0" y="0" width="100%" height="100%"><feFlood flood-color="#000"/><feComposite in="SourceGraphic" operator="xor"/></filter></defs>` +
            `<g font-family="${this.fontFamily}" fill="#000" font-size="${fontSize}" dominant-baseline="text-after-edge">${this.svgContent}</g></svg>\n`;
    },
    ...
};

フォントをいろいろ変えて、コマンドラインツールで変換してみます。

Kosugi Maru

02.png

  • style
    • <style>@import url("https://fonts.googleapis.com/css2?family=Kosugi+Maru&amp;display=swap");</style>
  • font-family
    • 'Kosugi Maru', monospace
  • font-size
    • 24

Kosugi

03.png

  • style
    • <style>@import url("https://fonts.googleapis.com/css2?family=Kosugi&amp;display=swap");</style>
  • font-family
    • 'Kosugi', monospace
  • font-size
    • 24

Sawarabi Mincho

04.png

  • style
    • <style>@import url("https://fonts.googleapis.com/css2?family=Sawarabi+Mincho&amp;display=swap");</style>
  • font-family
    • 'Sawarabi Mincho', monospace
  • font-size
    • 20

Ubuntu Mono & Kosugi Maru

05.png

  • style
    • <style>@import url("https://fonts.googleapis.com/css2?family=Ubuntu+Mono&amp;family=Kosugi+Maru&amp;display=swap");</style>
  • font-family
    • 'Ubuntu Mono', 'Kosugi Maru', monospace
  • font-size
    • 24

Cutive Mono & Noto Serif JP (Extra-light 200)

06.png

  • style
    • <style>@import url("https://fonts.googleapis.com/css2?family=Cutive+Mono&amp;family=Noto+Serif+JP:wght@200&amp;display=swap");</style>
  • font-family
    • 'Cutive Mono', 'Noto Serif JP', monospace
  • font-size
    • 22

Amatic SC (Bold 700) & Noto Sans JP (Thin 100)

07.png

  • style
    • <style>@import url("https://fonts.googleapis.com/css2?family=Amatic+SC:wght@700&amp;family=Noto+Sans+JP:wght@100&amp;display=swap");</style>
  • font-family
    • 'Amatic SC', 'Noto Sans JP', monospace
  • font-size
    • 22

まとめ

最適なフォントは、日本語は「Kosugi Maru」「kosugi」、英語は「Ubuntu Mono」でした。
monospace であっても縦横比 2 : 1 のフォントは少ないです。

等幅フォント・全角・半角・倍角は、今や絶滅危惧種。
レシートプリンターは、初期のワープロの生きた化石です。

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

大嫌いだったJavaScriptがプログラミングの楽しさを教えてくれた

この記事に技術的な話はありません。
ただ「プログラミングって楽しいなあ」と実感させてくれたのが、一番嫌いだったJavaScriptだったという話です。

もし、自分にはプログラミングの才能がないと思っている人がいたら、「それでもプログラミングと一緒に人生を歩けるかもですよ?」という同じ初心者からの感想をここに残しておきたいと思います。つまり、爆速で○○ができなかった人間でも「プログラミングを楽しむことは平等に可能」なのかもと。
ただちょっと、時間と参考書の縁が必要なだけで。

はじめに

手短に自己紹介させていただきますが、いま僕はSE系の会社でDjango/Angularメインでシステムとインターフェース関係をやってます。

プログラミングをはじめて2年強のアラフォーで、いまの会社は知人のツテで入りました。その頃は「HTMLとCSSをかろうじて触れます」レベルのひどいものでした。
いまもひどい部類なのによく会社も我慢してくれているもんだ。

学習の歩みと反省

プログラミングの基礎部分を作るときこそ、学習の手順および、書籍・本・動画そしてメンターや講師といった情報源の巡り合わせが本当に重要だなと思います。

僕は当時、Sのつくスクールで始めましたが講師との相性がおそらく相当に悪く、「これはお作法です」を鵜呑みできない自分の性分もあって、結局何も成果物がないまま半年が終わりました。それでもHTMLとCSSは多少触れるようになったので、友人知人の簡単なwebページを作ったりして小銭稼ぎをしていました。

その際に、JavaScriptはよくわかんないけどコピペでちょっと入れる的な使い方をしてました。ただJavaScriptは構文エラーがあってもページ自体は表示して、でもクリックイベントは反映しない、という無機質な感じにイライラして、深く学ぶ気持ちは起きませんでした。避けられる限り避けてやると決めたくらいに嫌いでした。

おいおい地獄か? AngularとTypeScript

そんな状態でなぜか入れていただけた会社は初日に、
「じゃあこれ引き継いで」
と業務管理アプリの開発を丸投げされます。担当は私一人。

Angularも知らなければもちろんTypeScriptなんぞ触ったことすらありません。
型定義もわからず、何に値を格納しているんだこれは・・・と混乱し、フレームワークのメリットもわからず、前任者もサーバーサイドはともかく、「Angularはこのひと月しか触ったことないから」と質問すらできない始末。なので一人、触ってはエラー。学んでは忘却。ひと月ふた月と過ぎていきます。

途中、
「DjangoでAPI作ってJSONを非同期通信して、うんぬんかんぬんでよろしく」
と、JavaScriptでなにか作っていたらどうってことのないワードも、このときは日本語なのに意味わからん状態。帰ってから手当たり次第本を読むも、目は書いてある文字を滑り続けるし、写経しても翌日には忘れることの繰り返し。「自分、本当にプログラミングの才能ないんだな」と自己評価は地中深くに潜っていきます。

とくにAngularは他のフレームワークに比べて利用者が少なく、そもそもの情報量が少ない&バージョンアップが早いので書籍は無いはAngularJSの情報も混ざるわで、本当に地獄の日々でした。

コード百遍、自ずから意通ず

読む。わからない。読む。わからない。
こんな繰り返しのある日、急に自分の中に仮説が立つようになりました。

(……あれ? あのメソッドってこういう時に使うんじゃね?)
(あれをこうすれば意図通りのものが作れるんじゃね?)

コードの基礎的な読み方や前任者の意図を読む精度は依然低いままでも、仮説を立てられるようになったところから、こう、主体的にコードを書けるようになった感覚が芽生えて、グッと面白さが湧いてきたのを覚えてます。

そのきっかけがJavaScriptだった

まあ正しくは触ってたTypeScriptなのですが、基礎的な部分の意でJavaScriptです。
構文のルール、()の意味、{}の役割などなどようやく線で繋がって、プロトタイプのメソッドとfunctuinで作るメソッド、Asyncの意味や注意点、デバッグの有効性、ようやく「あ、いまの自分なら教われば覚えられそう」という予感がしました。

そこからは買ってホコリをかぶっていたJavaScript コードレシピ集や、JavaScriptリファレンス 第6版を片手に、UdemyのThe Complete JavaScript Course 2020を、へー! すげー! と進めてたりして、いまでもUdemyさんのコンテンツにはめちゃくちゃお世話になってます。

もしかしたらどこかで「ある日突然、コードが読める」という言葉を聞いたことあるかもしれませんが、人のターニングポイントはそれぞれで、僕の場合は「ある日突然、コードの仮説が立つようになる」がターニングポイントと思っています。

きっとこうすればこう動く。とか、これはバックエンド側の方で処理する方がスマート。と、パズルゲーム感覚のプログラミングは、ヘマもしますが本当に楽しいです!

そして、触れていない言語は山ほどありますしこの認識が正しいことかわかりませんが、本当に納期優先な最悪ケースでならJavaScriptでゴリゴリすればいいのかも、というJavaScriptの全能感はいまの僕に安心感を与えてくれています。

つまりJavaScript楽しい

現在だって僕はまともにプログラミングできてるなんて毛ほども思ってませんが、ようやく、使えるもの・世の中の役に立つものを作る人側のスタートラインに立とうとしているのかなー、なんていう成長感が嬉しくて仕方ありません。

いまではWebpack使いこなせたらいいなあとか、Reactスキル欲しいとか、その次はTypeScript押さえてやろう、みたいにJavaScriptを軸にもっと知らないことを知っていきたい、もっと使えるようになりたいと思うくらいです。あれだけ苦しめられたAngularもいまは嫌いじゃないですし、(というか一番触ってる)ゆくゆくはもっとサーバーサイド側の開発で挑戦したいとも思ってます。

長々と書いてしまいましたが、JavaScriptで動くものが作れるようになって、はじめてプログラミングの楽しさを知りました。(プログラミングの良さと楽しさは別物という意味で言っています)

もし人と比べて凹んでたり、挫折したりしてても、諦めずに(でもちゃんと手と頭を動かす時間を使って)いれば、プログラミングはある時一気に楽しくなると思います。

人生のお供にプログラミング、ってのもいいんじゃないかなあと。
仕事でも携わりながらも、僕は今それくらいの視点でいます。
せっかく触り始めたのだから、自分の中で良さがわかるところまで行けたら幸せだと思うんですよね、と素人が言ってみたり。

とまあ、技術のギの字もない記事でしたが、ここまで読んでくださりありがとうございました。次回からは未熟なりに技術系記事を書いていこうと思います。
今後とも、どうぞよろしくお願いいたします。

あの大嫌いだったJavaScriptに感謝と、これからもよろしくという挨拶を込めて。
console.log("Hello world.")

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

react (face api) 簡単なアプリケーション

はじめに

今回はazureのface apiを叩いて、結果をviewに表示するという簡単なプログラムをつくったのでここでアウトプットしておきたいと思います。

完成形

スクリーンショット 2020-09-22 21.08.06.png
ご覧の通り、viewに関してはまだまだ改善点があり、私自身まだまだcssの勉強不足ですので暖かくみてください!!笑笑

使ったパッケージ

主に使ったものは、

recharts
axios
@material-ui/coreとicons

になります。環境構築はもちろんcreate-react-appです。

api通信処理

blobへの変換

export const inputImage = (e, setFace, setIsFetched, setImage ) => {
    e.preventDefault();
    const file = e.target.files[0]
    const reader = new FileReader()
    reader.onload = () => {
        fetchFace(reader.result)
    }
    reader.readAsDataURL(file)
}

FileReaderでは、Fileオブジェクトのファイルを実際に読み込みます。プレビューとして表示するというような動作はFileReaderを利用して行います

FileReaderの読み込みメソッドであるreadAsDataURL()を使いfileをDataURLとして読み込みます。
そしてその結果であるreader.resultを実際にapiを叩くfetchFaceに渡します。

    const bin = atob(image.split(',')[1]);
    const buffer = new Uint8Array(bin.length)
    for(let i = 0; i < bin.length; i++){
        buffer[i] = bin.charCodeAt(i);
    }
    const blob = new Blob([buffer.buffer], {type: 'application/octet-stream'});

ここでは先ほどのreader.resultをimageという引数として使っています。
このコードはblobの変換方法としてネットからそのまま引っ張ってきたコードになりますため、詳しい説明はできません。すいません。ちゃんと勉強します!!

今回face apiはurlのapplication/json以外のリクエストだとこのapplication/octet-streamですかリクエストできないようなのでこちらのtypeで行いました。。

axios api

変換したblobを使い実際にapiを叩く処理はこちらになります。

import axios from 'axios'

const apiKey = "apiKey"
const url = "https://yuuki.cognitiveservices.azure.com/face/v1.0/detect";
var params = {
    "returnFaceId": "true",
    "returnFaceLandmarks": "false",
    "returnFaceAttributes":
        "age,gender,headPose,smile,facialHair,glasses,emotion," +
        "hair,makeup,occlusion,accessories,blur,exposure,noise"
};
export const fetchFace = (blob) => {
    return axios({
        method: 'POST',
        url: `${url}?`,
        params: params,
        headers:{
            'Content-Type': 'application/octet-stream',
            'Ocp-Apim-Subscription-Key': apiKey,
        },
        data: blob
    })
    .then(response => response.data})
    .catch(error => console.log(error.response))
}

今回はjavascirptライブラリであるaxiosを使いました。
通常通りmethod,url,paramsを設定して、apiを叩きました。
このresponseとして返ってくるのが、

スクリーンショット 2020-09-26 18.32.05.png

こちらになります。

recharts pieグラフ

emotionは割合で返ってきていたので、rechartsをつかってグラフにしてみました。
スクリーンショット 2020-09-22 22.05.29.png

こちらがグラフになります。
まずこの中にセットするため受け取ったdataを配列にして管理します

const data = [
        { emotion: '怒り', value: emotion.anger * 100, color: "#996633"},
        { emotion: '混乱', value: emotion.contempt * 100, color: "#9933FF"},
        { emotion: '嫌悪', value: emotion.disgust * 100, color: "#9900FF"},
        { emotion: '無感情', value: emotion.neutral * 100, color: "#99FFFF"},
        { emotion: '恐怖', value: emotion.fear * 100, color: "#999966"},
        { emotion: '幸福', value: emotion.happiness * 100, color: "#99FF00"},
        { emotion: '悲しみ', value: emotion.sadness * 100, color: "#99CCFF" },
        { emotion: '驚き', value: emotion.surprise * 100, color: "#990033"},
    ];

    const newData = data.filter(item => item.value !== 0)

dataのvalueが受け取った数値を100倍してパーセント表示できるようにしています。
そしてnewDataでは、0%の値を表示することがないようにfilterにかけて新しく配列作っています。
このnewDataをPieに渡して表示してもらいます。

            <ResponsiveContainer width="99%" height={370}>
                <PieChart textAncor="center" className="pie__chart">
                    <Pie
                        data={newData}
                        cy={180}
                        label={renderCustomizedLabel}
                        outerRadius={180}
                        fillOpacity={0.7}
                        dataKey="value"
                        paddingAngle={1}
                    >
                        { data.map((data, index) => 
                            <Cell key={index} fill={data.color}/>
                        )}
                    </Pie>
                </PieChart>
            </ResponsiveContainer>

ここで実際にrechartsを使っています。
renderCustomizedLabelは、

    const RADIAN = Math.PI / 180;
    const renderCustomizedLabel = ({ cx, cy, midAngle, innerRadius, outerRadius, emotion, value}) => {
         const radius = innerRadius + (outerRadius - innerRadius) * 0.5;
         const sin = Math.sin(-RADIAN * midAngle);
         const cos = Math.cos(-RADIAN * midAngle)
         const mx = cx + (outerRadius + 30) * cos;
         const ex = mx + (cos >= 0 ? 1 : -1) * 22;
         const my = cy + (outerRadius + 90) * sin;
         const ey = my;
         const val = value.toFixed(1)
        return (
            <text textAnchor="middle" fill="black" x={ex + (cos >= 0 ? 1 : -1) * 12} y={ey} fontSize="12px" fontWeight={400}>{emotion}:{val}%</text>
        )
    };

ここでは円グラフの外のそれぞれの項目パーセンテージの設定を行っています。(幸福:81.4%)ここに関しても詳しくは理解できておりません。
ほとんどrechartsのドキュメント通りに書いたので深くは理解できていませんが簡単に実装できたので便利だなと感じました。

まとめ

ローカルファイル(写真)をblob(バイナリーデータ)に変換することに時間がかかりました。。
私自身まだまだプログミングを学習し初めて日が浅いので、blobとは、何かから調べることになりました。

今回はapiを叩いて取得したものただ羅列しただけのアプリケーションになるため、もっと何かのサービスに当てはめられるようにしたいと思っています。例えばazure face apiのfind similerでは顔の一致度などのapiもあるため、認証にも使えそうです。いずれは比較的大きめなサービスのごく一部としてもapiを利用できるようになりたいと感じています。

一応git hubにも公開しておりますので気になる方見ていただけると幸いです
こちらになります

azureのface apiはこちらです。

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

Node.jsでExcel⇒JSON、JSON⇒Excel変換するサンプル

やること

1.ExcelファイルをJSONで取得。
2.JSONの編集後、新しいExcelファイルに出力する。

環境

Node.js

・インストール
 https://nodejs.org/ja/
 推奨版(執筆時点:12.18.4)

・package.jsonの作成
 作業用ディレクトリで以下を実行する。
 node init -y(-y:全てYesでデフォルト設定)

xlsx(npm package)

・インストール
 npm install xlsx

・公式
 https://www.npmjs.com/package/xlsx

実装

前提

・本の貸出履歴から、各ユーザの未返却履歴のみを抽出する状況を想定。
・履歴データはユーザIDと書籍コードで管理されているため、抽出データは氏名と書籍名を追加する。
・データは以下とする。(BookLendingHistory.xlsx)

<貸出履歴>

<ユーザマスタ>

<書籍マスタ>

Excelファイルの読込

index.js
let XLSX = require('xlsx')
let workbook = XLSX.readFile('BookLendingHistory.xlsx', {cellDates:true})
// cellDates(日付セルの保持形式を指定)
// false:数値(シリアル値)[default]
// true :日付フォーマット

データ取得(JSON)

シート毎にJSONで取得します。

index.js
let history, users, books
workbook.SheetNames.forEach(sheet =&gt; {
    if("history" == sheet) history = XLSX.utils.sheet_to_json(workbook.Sheets[sheet])
    if("users" == sheet) users = XLSX.utils.sheet_to_json(workbook.Sheets[sheet])
    if("books" == sheet) books = XLSX.utils.sheet_to_json(workbook.Sheets[sheet])
})

history(貸出履歴)
※データが無いセルのJSONプロパティは取得されません。(返却日時)

users(ユーザマスタ)

books(書籍マスタ)

抽出

返却日時が無い履歴を抽出します。

index.js
let notReturned = history.filter(function(item) {
    return !("返却日時" in item) 
})

抽出結果

加工

ユーザ名と書籍名を加えたJSONを作成します。

index.js
let notReturnedReport = []
notReturned.forEach(item =&gt; {
    item.ユーザ名 = getUserName(item)
    item.書籍名 = getBookName(item)
    notReturnedReport.push(item)
})

/**
 * JSON内のユーザIDに一致するユーザ名を返す
 * @param {*} item 
 */
function getUserName(item){
    let userName = ""
    users.some(function(user) {
        if(user.ユーザID == item.ユーザID) userName = user.ユーザ名
    })
    return userName
}

/**
 * JSON内の貸出書籍コードに一致する書籍名を返す
 * @param {*} item 
 */
function getBookName(item){
    let bookName = ""
    books.some(function(book) {
        if(book.書籍コード == item.貸出書籍コード) bookName = book.書籍名
    })
    return bookName
}

加工結果

出力(Excel)

新しいExcelファイルに書き出します。

index.js
let exportBook = XLSX.utils.book_new()
let sexportSheet = XLSX.utils.json_to_sheet(notReturnedReport)
XLSX.utils.book_append_sheet(exportBook, sexportSheet, "sheetName")
XLSX.writeFile(exportBook, "NotReturnedReport.xlsx")

出力されたExcelファイル

Github

Sample of Excel to JSON or JSON to Excel

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

Seleniumの操作を音声付きで動画に記録する方法

目的

Seleniumの操作を動画で記録したい。
その際にブラウザの音声も記録したい。

方法

以下のelgalu/seleniumのコンテナを使用すると簡単にできる。
https://github.com/elgalu/docker-selenium

以下のサンプルはSeleniumでYoutubeの動画を30秒間再生して、その様子を動画に保存するサンプルである。

環境
macos Catalina 10.15.6
v14.10.1

事前準備

npm init -y
npm install --save selenium-webdriver
mkdir videos

テストコード

const {Builder, By, Key, until} = require('selenium-webdriver');
(async function example() {
  let driver = await new Builder()
    .forBrowser('chrome')
    .usingServer('http://localhost:4444/wd/hub')
    .build();
  try {
    await driver.get('https://www.youtube.com/watch?v=WX6_koWVVpY');
    // 再生をする
    await driver.findElement(By.className('ytp-play-button')).click();
    const elTimeCurrent = await driver.findElement(By.className('ytp-time-current'));
    let bFlg = true;
    const timeId = setInterval(async ()=>{
      // 一秒毎にマウスを動かして再生時間が隠れないようにする
      const rc = await elTimeCurrent.getRect();
      let x = Number(rc.x);
      if(bFlg) x += 1;
      driver.actions().move({x: x, y:rc.y}).perform();
      bFlg = !bFlg;
    }, 1000);
    // 30秒再生するまで待つ
    await driver.wait(until.elementTextContains(elTimeCurrent, '0:30'), 40*1000);
    clearInterval(timeId);
  } finally {
    await driver.quit();
  }
})();

dockerコンテナの起動〜テストコードの起動するシェルスクリプト

sample.sh
docker run -d --rm --name=grid -p 4444:24444 -p 5920:25900 \
  --shm-size=2g -e VNC_PASSWORD=hola \
  -e VIDEO=true \
  -e AUDIO=true elgalu/selenium

sleep 5
docker exec grid wait_all_done 30s

node test.js

docker cp grid:/videos/. videos
docker exec grid stop-video
docker stop grid

このスクリプトを実行するとvideosフォルダにvid_chrome_25550_firefox_25551.mkvが作成される。
このファイルはブラウザにドラッグ&ドロップすると再生して確認可能。

参考

https://www.selenium.dev/selenium/docs/api/javascript/index.html
https://github.com/elgalu/docker-selenium/blob/master/docs/videos.md

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

【JS学習その⑧】コンストラクター関数 ~prototype・new演算子・instanceof~

JS学習シリーズの目的

このシリーズは、私ジャックが学んだJavaScriptのメカニズムについてアウトプットも兼ねて、
皆さんと知識や理解を共有するためのものです。
(理解に間違いがあればご指摘いただけると幸いです)

コンストラクター関数とは

新しくオブジェクトを作成するための雛形となる関数

コンストラクター関数は、一般的な関数とは異なり、専用のnew演算子を使ってオブジェクトを生成します。

main.js
function A() {
    this.prop = 0;
}

const obj = new A();

上記のコードのように、
new演算子で作成したオブジェクトをインスタンスと言い、new演算子でインスタンスを作成することをインスタンス化と言います。
※コンストラクター関数では、慣例として関数名の先頭を大文字で書きます。

prototypeとは

オブジェクトに存在する特別なプロパティー
コンストラクター関数と合わせて使用

main.js
function Person(name, age) {
    this.name = name;
    this.age = age;
}

Person.prototype.hello = function() {
    console.log('hello ' + this.name);
}

const bob = new Person('Bob', 18);
const tom = new Person('Tom', 33);
const sun = new Person('Sun', 20);

bob.hello(); /*hello Bob*/
tom.hello(); /*hello Tom*/

console.log(bob);

上記のコードでは、
prototypeプロパティにhelloメソッドを追加して、それをnew演算子でインスタンス化したオブジェクトから呼び出しています。
よって、実行結果はそれぞれコメントした内容となります。

ここで、console.log(bob)の結果を見てみると、
image.png
上記の画像のように、インスタンスの中に存在する__proto__の中に、先ほどprototypeに追加した
helloメソッドが格納されています。

このことから、インスタンス化した際にはprototypeの参照が__proto__にコピーされるということが分かります。

↑は、非常に重要なので理解しておきましょう!

なぜprototypeを使うのか

前述したように、コンストラクター関数でメソッドを定義する際は、prototypeを使って定義します。
ただ、prototypeを使わなくても直接コンストラクター関数に定義することはできます。
では、なぜprototypeを使って定義するのでしょうか?
それは、「メモリの効率化」のためです。
下記のコードを見てみましょう。

main.js
function Person1(name, age) {
    this.name = name;
    this.age = age;
    this.hello = function() {
        console.log('hello ' + this.name);
    }
}

const bob = new Person1('Bob', 18);
bob.hello(); /*hello Bob*/

function Person2(name, age) {
    this.name = name;
    this.age = age;
}

Person2.prototype.hello = function() {
    console.log('hello ' + this.name);
}

const tom = new Person2('Tom', 33);
tom.hello(); /*hello Tom*/

上記のコードでは、
Person1ではコンストラクター関数に直接メソッドを定義して、Person2ではprototypeにメソッドを定義しています。
どちらも、インスタンス化してメソッドを呼び出した際の実行結果は同じです。
しかし、この2つの違いは、

  • Person1のインスタンスでは、メソッドそのものをメモリ空間にコピーして作成している
  • Person2のインスタンスでは、prototypeに定義したメソッドの参照を__proto__コピーしている

つまり、Person1の書き方だと、インスタンスを生成する度にhelloメソッドを追加する必要があるので、余分なメモリを消費することになります。
一方、'Person2'の書き方だと、インスタンスを生成した際にprototypeの参照をコピーするため、オブジェクトに格納されているメソッドの参照先は全て一致するので、余分なメモリを消費せずに効率的にプログラムを動かすことができます。

そのため、このprototypeというオブジェクトは、JavaScriptの仕組みを支える重要な技術となります。

new演算子

まず、new演算子とは
コンストラクター関数からインスタンスを作成するために使用する演算子

これまでもnew演算子は使ってきましたが、new演算子でコンストラクター関数からインスタンスを作成する際、
コンストラクター関数の戻り値によって動きが変わってきます。

  • コンストラクター関数の戻り値がオブジェクトの場合は、コンストラクターが返すreturnのオブジェクトを新しいインスタンスオブジェクトとして呼び出し元に返す
  • コンストラクター関数の戻り値がオブジェクト以外、もしくは戻り値のreturnが定義されていない場合は、コンストラクターのprototypeの参照をインスタンスの__proto__にコピーして、コンストラクターで使用している'this'を呼び出し元に返却する(※インスタンスを'this'の参照先としてコンストラクター関数を実行)
main.js
function F1(a, b) {
    this.a = a;
    this.b = b;
    return {};
}

const instance1 = new F1(1, 2);
console.log(instance1); /*{}*/

function F2(a, b) {
    this.a = a;
    this.b = b;
    return 1;
}

const instance2 = new F2(1, 2);
console.log(instance2); /*{a: 1, b: 2}*/

上記のコードでは、
F1では、戻り値がオブジェクトなので、new演算子でインスタンス化した時、returnのオブジェクトをインスタンスオブジェクトとして返すので、結果は(今回の場合){}(空のオブジェクト)となります。

F2では、戻り値がオブジェクト以外のプリミティブ値なので、'new'演算子でインスタンス化した時、(今回はprototypeでメソッドを定義していませんが)'prototype'の参照を__proto__にコピーして、'this'の参照するオブジェクトはインスタンス(今回の場合はinstance2)になります。

instanceofとは

どのコンストラクターから生成されたオブジェクトかを確認する

main.js
function F(a, b) {
    this.a = a;
    this.b = b;
}

F.prototype.c = function() {}

const instance = new F(1,2);

console.log(instance instanceof F); /*true*/
console.log(instance.__proto__ === F.prototype) /*true*/

上記のコードでは、
console.log(instance instanceof Object)で、instanceFのインスタンスなので、trueが返ってきます。
ちなみに、console.log(instance.__proto__ === F.prototype)で、instance.__proto__F.prototypeが同じ参照先のオブジェクトなのでこれもtrueが返ってきます。

instanceofの使用例

instanceofプロトタイプチェーンをさかのぼって検証を行うので、例えば次のように関数に渡す引数が配列オブジェクトかで条件分岐して関数を実行することもできます
(※プロトタイプチェーン・・・プロトタイプの多重形成プロトタイプチェーンと言う)

main.js
function fn(arg) {
    if(arg instanceof Array) {
        arg.push('value');
    } else if (arg instanceof Object) {
        arg['key'] = 'value';
    }
    console.log(arg)
}

fn([]) /*["value"]*/

まとめ

いかがでしたでしょうか。
コンストラクター関数は、ES6から導入されたクラス構文の基礎となる部分なので、しっかり理解しておきましょう!

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

[Node.js]Express Validatorが使えない??

こちらの書籍を使ってNode.jsの学習をしてました。(ちなみに今回私が使用しているのは初版です)

Node.js超入門 第3版

Express Validatorが使えない??

そこで学習を進めていると、以下のエラーが吐かれました。

TypeError: validator is not a function
    at Object.<anonymous> (作業フォルダ/app.js:38:9)

validatorが関数ではありませんと言われてしまいました。

そこで該当するソースコードをチェックしてみます。

app.js
var validator = require('express-validator');

(省略)

app.use(validator()); //エラーの該当部分

validatorを利用する前には、requireでモジュールをロードして、変数validatorで定義しています。
スペルミスもないです。

なぜエラーが???

とそこで、この書籍はメルカリで購入した初版だったこともあり、誤字や古い情報がちょこちょこあるのを思い出しました。
それで以下から
正誤表
を確認すると

やっぱりあった!!

本書で使用している Express Validator は、現在 ver.6 となり、仕様が変更されているため、本書の記述の通りでは正常
に動作しなくなっています。
Express Validator をインストールする際、以下のようにして ver. 5 をインストールしてご利用下さい。
・既に最新版をインストールしている場合、アンインストール
npm uninstall express-validator
・ver.5 をインストール
npm install --save express-validator@5.3.1
引用:Node.js 超入門[第 2 版] 【正誤表】 - 秀和システム

そもそもアプリにインストールしていた、Express Validatorに原因がありました。

正誤表の通りに再度、Express Validatorをインストールして無事エラーは解決しました。

なるべく新しい書籍を選ぶようにしたいけど、新品は高くて...(もっと給料上げて~)

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

プロパティアクセスで無限にアクセスしてみる

前置き

注意:この記事はとりあえず書いてみたかったので書いただけで、特に深い意味もなければ学ぶ事も特にないと思います。それでも良い方はどうぞ

コードを書いてみる

a.js
const a = {a:1,b:1};
a.a = a;
console.log(a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.b);

(このconsole.log内を書きたかったがために記事を書いてる)

コードの説明

これだけだとtwitterにでも投稿してろ、となるのでコードの説明。

一行目

a,bのキーを持つオブジェクトaを用意する。
この時、オブジェクトのaと、キーのaは全く別物なので注意

二行目

オブジェクトaのキーaに、オブジェクトaを代入する。
なお、a.aのようにドットでアクセスするのをプロパティアクセサーといい、オブジェクトのキーにアクセスする方法の一つ1。javascriptでこの記法を避けて通るのは不可能2なので記述方法自体は誰でも知っているはず。
元々int型の1が入っていたのにオブジェクト型のaを代入できるのは、javascriptに型の指定が無いから。
この代入により、オブジェクトaのキーaには、元々のオブジェクトa自体が入っている。

ちなみに、javascriptは参照渡しのような動作3を行うため、a===a.aはtrueになる。
なので、a===a.a.aもtrueになり、無限に辿ることが出来るようになる。

三行目

無限にアクセスできるaを辿り、bを出力する。
もちろん最後のa.bは、単純にa.bを記述したのと同じなので、定義したb=1が出力される。
ただ、実際に無限に書くのは難しいものもあるので、ここでは20個で止めている。

終わりに

実際にこのような使い方をすることはまず無いとは思いますが、参照渡しの言語だとこういう事もできるよというお話でした。


  1. もう一つはa["a"]という方法。キーを文字列 で指定する必要がある。 

  2. document.getElementByIdという表記で既に使っているし。 

  3. 曖昧表現の理由:JavaScriptに参照渡し/値渡しなど存在しない 

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

jQueryでセレクトボックスの選択要素を移動する

二つセレクトボックス(Selectタグ)があるサイトで、片方のセレクトボックスの選択要素を別のセレクトボックスに移動させるメモ

移動元の指定をoption:selectedにするのがミソ
$('#移動元SelectタグのID option:selected').appendTo($('#移動先SelectタグのID'));

移動ではなくコピーしたいときはclone()を挟むだけ
$('#コピー元SelectタグのID option:selected').clone().appendTo($('#コピー先SelectタグのID'));

Sample

html部分
<!-- 移動元 Selct Tag-->
<select id="selLeft" size="10">
    <option value="1">りんご</option>
    <option value="2">みかん</option>
    <option value="3">なし</option>
    <option value="4">いちご</option>
</select>

<!-- 移動先 Selct Tag-->
<select id="selRight" size="10">
    <option value="5">ぶどう</option>
</select>

<!-- 実行ボタン-->
<input type="button" id="toRight" value="->" />
javascript部分
<script type="text/javascript">
    $(function () {
        $('#toRight').on('click', function () {
            // 移動元のSelectから移動先のSelectに移動
            $('#selLeft option:selected').appendTo($('#selRight'));

            // 移動ではなくコピーしたいときはclone()を挟むだけ
            // $('#selLeft option:selected').clone().appendTo($('#selRight'));
        });
    });
</script>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

歌詞サイトから手軽にクリップボードにコピーするためのブックマークレット

歌詞サイトから歌詞をゲットしたい

転載対応なのか歌詞サイトの多くはコピペ禁止というか、古のテクニックで右クリック禁止をしている場合が多い。
ソースを開発者ツールでみると、要素自体は取りやすい模様。
ファイルとして保存してもいいが、とりあえずコピペできれば十分だとおもったので、ブックマークレットにすることにした。

今回は検索でよくヒットするJ-Lyric.netをターゲットにする。

ブックマークレットの作成

ブラウザのブックマーク編集で、以下のjavascriptをURLとして入力する。

javascript
javascript:function getLyric(){var a=document.createElement("textarea");var t = document.getElementById("Lyric").innerHTML.replace(/<br>/g, "\n").replace(/&amp;/g, "&");a.textContent=t;var d=document.getElementsByTagName("body")[0];d.appendChild(a);a.select();var b=document.execCommand("copy");d.removeChild(a);return b};getLyric();

image.png

使い方

  1. ブラウザのブックマーク編集で、ブックマークレットを作成。
  2. J-Lyric.netでなんらかの曲を表示。
  3. ブックマークレットを選択(クリップボードにコピーされる)
  4. メモ帳などにペースト

概説

元のソースは、こちらを参考にした。
単純にgetElementByID("Lyric")した内容をそのままクリップボードにもっていけなかったので、内部的にテキストエリアをつくってコピーさせているのがミソ。

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

FrontEndのいくつ技術の用途の取り纏め

FrontEndではいろいろな技術が創造されています。その中に、幅広く使ったものはHTML、JavaScript、CSS、DynamicHTML、DOM、AJAXです。
以下、用途により、説明します。

HTMLの用途はなんですか

今はWEB開発が流行しています。WEB開発以前はデスクトップアプリケーションが流行でした。WEB開発に変わってから、何の技術でブラウザにコンポーネントを表示しますか、画面でどのような効果を表示しますか。
この場合、HTMLが登場しました。
HTMLでは、テキストコンポーネント、ボタンコンポーネントが定義されています。文字色、背景色が指定できます。script、cssも組み込めます。
つまり、以下の二点の用途があります。
・画面でデータの輸出と輸入及びユーザ体験のために効果の表示
・script、cssなどほかの技術のインターフェースを持つ

JavaScriptの用途はなんですか

HTMLで生成されたページが固定です。変えたい場合、JavaScriptを使ったほうがいいです。例えば、検索した結果、一般的に、テーブル見たいなフォーマットで結果を表示します。JavaScriptを使って、表示する結果が
変わられます、しかも、表示するタイトルも変わられます。
用途にて、以下があります。
・ブラウザ自体の状況をコントロール
・入力したコンポーネントの値をチェック
・変わる結果を表示
下の二点に対してはサーバ側でも実現できるんですが、JavaScriptのほうでサーバ側へ通信の回数が少なくなって、通信のデータ量が少なくなりました。

CSSの用途はなんですか

画面の色、フォーマットなどstyleを設計する

DynamicHTMLの用途はなんですか

DynamicHTMLとは、DOM(Document Object Model)をサポートするHTMLです。ブラウザに動的な効果が要る場合使います。
ブラウザにDOMサポートしたら、DynamicHTMLのページを作れることは言えます。

DOMの用途はなんですか

ブラウザに動的な効果が要る場合使います。

AJAXの用途はなんですか

画面全体の刷新ではなく、画面内の一部を刷新したい場合、使います。
Googleの検索機能、検索ボックスに文字を入力中につづりの候補が絞り
込まれ、検索ヒット数と共に表示される

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

Nuxt.jsで認証認可入り掲示板APIのフロントエンド部分を構築する #3 個別記事ページの作成

Nuxt.jsで認証認可入り掲示板APIのフロントエンド部分を構築する #2 NuxtとRailsとの疎通テスト

個別記事ページの作成

$ mkdir pages/posts
$ touch pages/posts/_id.vue
pages/posts/_id.vue
<template>
  <v-layout
    column
    justify-center
    align-center
  >
    <v-flex
      xs12
      sm8
      md6
    >
      <v-card>
        <v-card-title class="headline">
          {{ post.subject }} - {{ post.user.name }}
        </v-card-title>
        <v-card-text>
          {{ post.body }}
        </v-card-text>
      </v-card>
    </v-flex>
  </v-layout>
</template>

<script>
export default {
  validate ({ params }) {
    return /^\d+$/.test(params.id)
  },
  async asyncData ({ $axios, params }) {
    const response = await $axios.$get(`/v1/posts/${params.id}`)
    return {
      post: response.post
    }
  }
}
</script>

Nuxtでは上記のようにpages/hoge/_id.vueとすることで、hoge/{id}のような動的ページが作られます。

バリデートは数値型のみ許容するように設定しています。以下、公式ドキュメント参照。
ルーティング - NuxtJS

そしてasyncDataというSSR用のメソッドでv1/posts/${params.id}とすることで、URLのid部分の数値から固定のpostを取得しているわけです。

なお、今回はpostの値をVuexのstoreに保存していません。
storeはなんでもかんでも保存を保存しておく場所ではなく、他のmoduleやサイト全体で使う必要があるか考え、そうでなければ不用意に突っ込まないほうがいいです。

もしブラウザで以下の通り表示されたら、とりあえずOKです。

show.png

indexから個別ページにリンクを設置

indexから個別ページにリンクを貼ります。

pages/index.vue
       <v-card v-for="post in posts" :key="post.id">
         <v-card-title class="headline">
-          {{ post.subject }} - {{ post.user.name }}
+          <n-link :to="`/posts/${post.id}`">
+            {{ post.subject }} - {{ post.user.name }}
+          </n-link>
         </v-card-title>
         <v-card-text>

list.png

リンクになっていますね。

CORS対策

ではindexから個別ページに飛んでみましょう。

error.png

エラーになりましたか?
実はこれがエラーメッセージ読んでも原因が特定しづらく結構厄介なものです。
以前この問題の対応策を書き残していますので、詳細を知りたい方は以下の記事を。
Nuxt.jsでURL直叩きの時はページが表示されるのに、nuxt-linkやブラウザバックでAn error occurredになる

さて上記記事にも書きましたが、原因はページ遷移した場合はSSR(サーバサイドレンダリング)ではなくCSR(クライアントサイドレンダリング)になるため、CORS(オリジン間リソース共有)の対策が必要です。

nuxt.config.js
   ** Nuxt.js modules
   */
   modules: [
-    '@nuxtjs/axios'
+    '@nuxtjs/axios',
+    '@nuxtjs/proxy'
   ],
-  axios: {},
+  axios: {
+    proxy: true
+  },
+  proxy: {
+    '/v1/': {
+      target: 'https:/xxxxxxxxxxx.ap-northeast-1.amazonaws.com:8080/'
+    }
+  },

targetはご自身のRails APIエンドポイントに置き換えてください。

この設定が済めば、indexも個別ページも、ブラウザバックやn-link(nuxt-link)による遷移時も正常に表示されるはずです。

続き


連載目次へ

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

Nuxt.jsで認証認可入り掲示板APIのフロントエンド部分を構築する #2 NuxtとRailsとの疎通テスト

Nuxt.jsで認証認可入り掲示板APIのフロントエンド部分を構築する #1 環境構築

axiosの導入

URLのリクエストを送る際はaxiosを使います。
create_nuxt_appの時にaxiosを入れているはずですが、もし入れるのをミスっていた場合は以下の手順で追加します。

$ yarn add @nuxtjs/axios
nuxt.config.js
export default {
   ** Nuxt.js modules
   */
   modules: [
+    '@nuxtjs/axios'
   ],

APIとの疎通テスト

pages/index.vue
           Welcome to the Vuetify + Nuxt.js template
         </v-card-title>
         <v-card-text>
+        <ul>
+          <li v-for="post in posts" :key="post.id">
+            {{ post.subject }}
+            {{ post.body }}
+          </li>
+        </ul>
...
   components: {
     Logo,
     VuetifyLogo
+  },
+  async asyncData({ $axios }) {
+    const response = await $axios.$get('/v1/posts');
+    return {
+      posts: response.posts
+    };
   }
 }
 </script>

雑ではありますが、Railsのseedで作ったデータが無事に取得できましたね。

sample.png

とりあえずこれでフロントエンドとバックエンドの連携確認は取れました。

Vuexで書き換える

特にルール無く記述していくことも可能なのですが、Vuexを利用して書いていきます。

Vuex は Vue.js アプリケーションのための 状態管理パターン + ライブラリです。 これは予測可能な方法によってのみ状態の変異を行うというルールを保証し、アプリケーション内の全てのコンポーネントのための集中型のストアとして機能します。 また Vue 公式の開発ツール拡張と連携し、設定なしでタイムトラベルデバッグやステートのスナップショットのエクスポートやインポートのような高度な機能を提供します。

vuex.png

文章・画像引用:Vuex とは何か? | Vuex

文章を読んだり画像をパッと見ただけではなかなか理解しづらいかもしれません。
actionsやらmutationsやらdispatch, commit…なにこれ?と思うかもしれませんが、

  • storeにstatemutationsactionsの3つ(+gettersの4つ)を持ってそれぞれを呼び出すというパターン
  • stateは値を持つだけ
  • mutationsはstateの値をセットするだけ
  • actionsで処理して、stateを変更する時は直接stateを呼ばずmutationsを呼ぶ
  • gettersはstateの値を取得するだけ
  • mutationsを呼ぶことをcommitと言う
  • actionsを呼ぶことをdispatchと言う

となります。

とりあえず以下のようにファイルを編集します。

store/posts.js
export const state = () => ({
  posts: []
})

export const getters = {
  posts (state) {
    return state.posts
  }
}

export const mutations = {
  setPosts (state, data) {
    state.posts = data
  }
}

export const actions = {
  async fetchPosts () {
    return await this.$axios.$get('/v1/posts')
  }
}

pages/index.vue
<template>
  <v-layout
    column
    justify-center
    align-center
  >
    <v-flex
      xs12
      sm8
      md6
    >
      <v-card v-for="post in posts" :key="post.id">
        <v-card-title class="headline">
          {{ post.subject }} - {{ post.user.name }}
        </v-card-title>
        <v-card-text>
          {{ post.body }}
        </v-card-text>
      </v-card>
    </v-flex>
  </v-layout>
</template>

<script>
export default {
  async fetch ({ store }) {
    const posts = await store.dispatch('posts/fetchPosts')
    store.commit('posts/setPosts', posts.posts)
  },
  computed: {
    posts () {
      return this.$store.getters['posts/posts']
    }
  }
}
</script>

storeディレクトリ以下に置いたファイルは、{ファイル名}/{関数名}で名前空間が切られます。
どのように処理されているかの流れは、上記2ファイルに1〜8でコメント記載しています。

まずはconst posts = await store.dispatch('posts/fetchList') // 1によってposts/fetchListdispatch
前述の通りdispatchはactionsを呼び出すので、posts.jsactionsにあるfetchListが呼ばれるわけですね。

一度見方が分かれば、そこまで読み解くのは難しくないはずです。

Vuexはルールを破って書くことができてその場合エラーを投げたりしないのですが、結局そうなるとコードの保守性は下がります。ですので上記ルールに従って、storeの直接呼び出しや書き換えをせずに、actionsやmutationsで制御していきましょう。

続き

Nuxt.jsで認証認可入り掲示板APIのフロントエンド部分を構築する #3 個別記事ページの作成
連載目次へ

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

【JavaScript/初学者向け】打倒!ズンドコキヨシ(後半戦)

こんにちは!どいこです。

ズンドコキヨシ問題について、前回の記事で
「「ズン」「ドコ」のいずれかをランダムで出力」する所までできました。

前回:【JavaScript/初学者向け】打倒!ズンドコキヨシ(前半戦)

しかし、まだキヨシは倒れていない・・・!

あきらめたらそこで試合終了なので、続きをやります。

ズンドコキヨシとは(再掲)

「ズン」「ドコ」のいずれかをランダムで出力し続けて
「ズン」「ズン」「ズン」「ズン」「ドコ」の配列が出たら
「キ・ヨ・シ!」って出力した後終了する

手順その②

「ズン」「ズン」「ズン」「ズン」「ドコ」の配列が出たら
「キ・ヨ・シ!」って出力した後終了する

・ まずは「この並びで出たら反応するよ~」の「この並び」を判定するための配列(targetList)と
 「反応」の内容(answer)を定義しておきます。

const targetList = ['ズン', 'ズン', 'ズン', 'ズン', 'ドコ']
const answer = 'キ・ヨ・シ!'


・ 次は「並びがtargetListと同じか」を判定する段階ですね。
  勇者どいこはココでつまづきました。

つまづいた所(キヨシが出てこない)

if (randomOutput === targetList) {
   console.log(answer)
}

要は「ランダムで作った配列の内容がtargetListと同じ=
['ズン', 'ズン', 'ズン', 'ズン', 'ドコ']だったら
'キ・ヨ・シ!'って表示させようぜ!」
って意味なんですが

「'ズン', 'ズン', 'ズン', 'ズン', 'ドコ'」って出力されてる時もキヨシが出てこない。

♪せつなさよりも遠くへ~:joy:

なぜ?
その理由とは・・・

変数の中に直接配列が入っているわけではありません。
変数の中には、「参照」と呼ばれる「配列の場所」が記録されていて、
配列の実体は別の場所(メモリ上のどこか)にあります。

JavaScriptの変数と配列と参照について


この事から、上のif文での比較においても
配列の中身の値を見比べているのではなく、参照先(保管場所)を見比べている事になります。

randomOutputtargetListは、そもそも保管場所は異なりますよね。
(それぞれ別々に作られたものなので)

中身の値ではなく「お互いの保管場所を比較している」ため
結果はelseとなり、我々は「キ・ヨ・シ!」と叫ぶ事もままならないわけです。

解決策

JSON.stringify()メソッドを使う事にしました。

if (JSON.stringify(randomOutput) === JSON.stringify(targetList)) {
   console.log(answer)
}

JSON.stringify() メソッドは、ある JavaScript のオブジェクトや値を JSON 文字列に変換します。

JSON.stringify() - MDN web docs
JavaScriptで配列やオブジェクトを比較するときはJSONに変換


JSON.stringify()効果で、むきだしの値同士を突き合わせる事ができ
randomOutputの出力が「'ズン', 'ズン', 'ズン', 'ズン', 'ドコ'」の時に
無事「'キ・ヨ・シ!'」と表示されるようになりました。

やったね:relaxed:


ちなみに「'キ・ヨ・シ!'」と叫んだら、ズンドコタイムは終了するのがお約束です。
forとは違って回数の決まっていないループ処理なので、while文を使います。
(条件式に当てはまる間だけ処理を繰り返し、当てはまらなくなったら終了させる)

while (JSON.stringify(randomOutput) !== JSON.stringify(targetList)) {
    // 配列randomOutputを生成して表示させる処理
}
// トリガー配列が現れたらループから抜けてanswerを表示させる
console.log(answer)


回答まとめ(コード全体)

function kiyoshi() {

  const wordList = ['ズン','ドコ'] // ランダムで表示するワード候補2種
  const elementsLength = 5
  const randomOutput = [...Array(elementsLength)] // 未定義の要素 × 5個の配列
  const targetList = ['ズン', 'ズン', 'ズン', 'ズン', 'ドコ'] // トリガーとなる配列
  const answer = 'キ・ヨ・シ!' //トリガーとなる配列が現れたら表示させるワード

  // トリガーとなる配列が現れるまで処理をループ
  while (JSON.stringify(randomOutput) !== JSON.stringify(targetList)) {
    // wordListのインデックス[0] or [1]をランダムで生成し、結果をrandomOutputの要素へ代入する処理を
    // 要素数(5個)分ループさせる
    for (let i = 0; i < randomOutput.length; i++) {
      randomOutput[i] = wordList[Math.floor(Math.random() * wordList.length)]
    }
    console.log(randomOutput) // ランダムに生成された配列を表示
  }
  // トリガー配列が現れたらループから抜けてanswerを表示させる
  console.log(answer)

  }

もっと良い書き方も色々ありそうですが、
ひとまず自分で導きだした一つの解として置いておきます。

俺たちの戦いはこれからだ!:innocent:


ー最後までお読み頂き、とてもうれしく思います。
 ありがとうございました。

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

【JavaScript/初学者向け】打倒!ズンドコキヨシ(前半戦)

こんにちは!どいこです。

初Qiita投稿です。

現在JavaScript絶賛勉強中のため
「fizzBuzz問題」をクリア後、「ズンドコキヨシ問題」にも手を出してみました。

(参考)フィズバズ問題 (FizzBuzz問題)とは

まさに今、キヨシと戦いながら書いています。

ズンドコキヨシとは

「ズン」「ドコ」のいずれかをランダムで出力し続けて
「ズン」「ズン」「ズン」「ズン」「ドコ」の配列が出たら
「キ・ヨ・シ!」って出力した後終了する

手順その①

「ズン」「ドコ」のいずれかをランダムで出力し続ける

・ 「ズン」と「ドコ」を配列(wordList)にする

const wordList = ['ズン','ドコ']


・ 配列(wordList)のインデックス([0] or [1])をランダムで出現させる
  ランダムな値はMath.floor()Math.random()を使って作る

wordList[Math.floor(Math.random() * wordList.length)]
//wordList[0]('ズン') またはwordList[1]('ドコ')


・ 出力する値は「ズン」「ズン」「ズン」「ズン」「ドコ」なので
  要素数が5個の配列を用意

const elementsLength = 5
const randomOutput = [...Array(elementsLength)]
// [undefined, undefined, undefined, undefined, undefined] (未定義の要素×5の配列ができる)



・ この配列に先ほどのランダムな値(wordList[0] or [1]
  =すなわち「ズン」or「ドコ」を代入する

randomOutput[0] = wordList[Math.floor(Math.random() * wordList.length)]


・ 要素数5個の配列にしたいので、for文の繰り返しで入れていく

for (let i = 0; i < randomOutput.length; i++) {
  randomOutput[i] = wordList[Math.floor(Math.random() * wordList.length)]
}
// randomOutputのインデックス0~4それぞれについて
// ランダムな値(「ズン」or「ドコ」)を代入


・ ここでいったん出力

console.log(randomOutput)
// [ 'ズン', 'ドコ', 'ドコ', 'ズン', 'ズン' ]

問題なくランダムな値が出力されるようになりました。

よし。あとはこのランダムな配列をforループで出力して、
「ズン」「ズン」「ズン」「ズン」「ドコ」って出た時に
「キ・ヨ・シ!」と叫んであげたらええんや!:blush:

と、この時の私は簡単に考えていた・・・。

次回、「城之内死す」 次回予告

目の前に立ちはだかるキヨシの灰色の牙城!!

ここはなんとかして「キ・ヨ・シ!」と叫ぶ機会を得たい!

さあ、スタンドのパワーを全開だッ!!:revolving_hearts:

次回・後半戦はこちらから

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

Safari で disable_with オプションを付けても変わらない場合

Railsでフォームを作る際、送信ボタンに disable_with オプションを付けることで、二重送信を防ぐことができる。

= f.button '送信する', data: { disable_with: '送信中…' }

しかし、Safariに限って、なぜかボタンの文言が変化しなかった。GitHub上でも、多々報告されている。
disable_with doesn't work with link in Safari #306

解決策

結論から書くと、JSで送信処理をあえて遅らせることで、とりいそぎは回避できた。
Issue内ではバニラJSで書かれた例が出てくるので、jQueryを使っていなけば、そちらを参考にした方が早いかもしれない。

= f.button '送信する', data: { disable_with: '送信中…' }, class: 'disable_with_safari'
$('.disable-with-safari').click(function (event) {
  if ($(this).data('disableWith')) {
    $(this).prop('disabled', true);
    $(this).text($(this).data('disableWith'));
    var form = $(this).closest('form');
    if (form.length) {
      event.preventDefault();
      setTimeout(() => form.submit(), 300);
    }
  }
});

備考

冒頭のIssueでも議論されていたが、Safariの独特な仕様で、一度submitが走った後はDOMの更新が行われなくなるようだ。(あくまでもIssue上でのコメントなので、正確な仕様は一次情報を参照してください)
そのため、disable_withをつけても、ボタンの文言は変わらなかった。

ちなみに、今回の問題について検索すると以下の解決策が出てきていたが、わたしの場合は効果がなかった。

  • cursor: pointer; をつける
  • 空のclickイベントを設定する
  • 空のtouchstartイベントを設定する
  • バインドを$(document)に変更する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

プロパティによってオブジェクトをグループ化したい

プロパティによってオブジェクトをグループ化したいです

オブジェクトの配列を、プロパティによってグループ化したいです。
想定するデータは以下のような感じです。

const data = [
  { type: '野菜', color: '', name: 'トマト' },
  { type: '野菜', color: '', name: 'パプリカ' },
  { type: '野菜', color: '', name: 'ブロッコリー' },
  { type: '果物', color: '', name: 'ぶどう' },
];

最終的に望む形式は以下のような感じです。

const groupingData = {
  '野菜': {
    '': ['トマト', 'パプリカ'],
    '': ['ブロッコリー'],
  },
  '果物': {
    '': ['ぶどう'],
  },
};

MDNにやりたいことが載ってた

まさにずばりやりたいことがそのまま載ってました。
Array.prototype.reduce()を使います。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce#Grouping_objects_by_a_property

// MDNに載っていたコードそのまま抜粋
return objectArray.reduce(function (acc, obj) {
  let key = obj[property]
  if (!acc[key]) {
    acc[key] = []
  }
  acc[key].push(obj)
  return acc
}, {});

今回の形式バージョンを考えました

// MDNに載っていたコードを少し改造
const groupBy = (objectArray, property, optionProperty = '') => {
  return objectArray.reduce((acc, obj) => {
    const key = obj[property];
    if (!acc[key]) {
      acc[key] = [];
    }
    if (optionProperty && obj.hasOwnProperty(optionProperty)) {
        acc[key].push(obj[optionProperty]);
    } else {
        acc[key].push(obj);
    }
    return acc;
  }, {});
};

// 階層構造にする
const createGroupingData = (objectArray, property1, property2, property3) => {
    const groupingData = groupBy(objectArray, property1);
    Object.keys(groupingData).forEach((key) => {
        groupingData[key] = groupBy(groupingData[key], property2, property3);
    });
    return groupingData;
};

// 使い方
const groupingData = createGroupingData(data, 'type', 'color', 'name');

もっと良い方法を教えて下さい。
再帰的な感じにしたいです。

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

styled-componentsでレスポンシブを楽に書く

はじめに

今回はstyled-componentsを使ってレスポンシブデザインをできるだけ楽にする方法を紹介します。

他のstyled-componentsの記事はこちら
* styled-componentsを使ってみる
* styled-componentsでJavascriptの値を使う
* styled-componentsのThemeを使ってみる

実際に描いてみる

今回のは一回書いてしまえばつかいまわせるので簡単に書きます。

src直下にmedia.tsを作成し、以下のコードを書きます。Javascriptで書く場合はtypeの部分は全部消します。

src/media.ts
import {
    css,
    CSSObject,
    FlattenSimpleInterpolation,
    SimpleInterpolation,
} from 'styled-components';

export const sp = (
    first: CSSObject | TemplateStringsArray,
    ...interpolations: SimpleInterpolation[]
): FlattenSimpleInterpolation => css`
    @media (max-width: 560px) {
        ${css(first, ...interpolations)}
    }
`;

export const tab = (
    first: CSSObject | TemplateStringsArray,
    ...interpolations: SimpleInterpolation[]
): FlattenSimpleInterpolation => css`
    @media (min-width: 561px) and (max-width: 1024px) {
        ${css(first, ...interpolations)}
    }
`;
export const pc = (
    first: CSSObject | TemplateStringsArray,
    ...interpolations: SimpleInterpolation[]
): FlattenSimpleInterpolation => css`
    @media (min-width: 1025px) {
        ${css(first, ...interpolations)}
    }
`;

styled-componentsからインポートしたcssは、関数みたいにも使えます。sp(スマートフォン)、tab(タブレット)、pc(パソコン)という関数を作って、それぞれのサイズでスタイルを当てたい時に呼び出して使います。

引数のタイプはVSCodeの型推論と同じになるようにしただけです。

次にApp.tsxで以下のコードを書いて、使ってみましょう。

src/App.tsx
import React from 'react';
import styled from 'styled-components';
import { pc, sp, tab } from './media';

export const App = () => <Box>レスポンシブ</Box>;

const Box = styled.div`
    background-color: red;
    ${sp`
        width: 20px;
        height: 20px;
    `}
    ${tab`
        width: 50px;
        height: 50px;
    `}
    ${pc`
        width: 100px;
        height: 100px;
    `}
`;

ブラウザで確認するとそれぞれのサイズでちゃんと赤い正方形のサイズが変わると思います。
ブレイクポイントなど自由に変えて使ってみてください。

終わりに

ここまで読んで頂きありがとうございます!現在、PHP(Laravel)を中心に勉強しているのでそちらの方の記事を多く投稿していくと思います。感想やリクエストなどどんどん送ってくれると嬉しいです!

参考記事

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

styled-componentsのThemeを使ってみる

はじめに

今回はstyled-componentsのThemeに焦点を当てていきたいと思います。
themeに関しては実務で使った事がなく、あまり理解できていないところもあるので、詳しい方コメントで教えていただけると幸いです^_^

他のstyled-componentsの記事はこちら

themeとは?

ReduxみたいにProviderで囲ったコンポーネント内のどこからでもアクセスできる値みたいなイメージです。

styled-componentsドキュメント引用↓

// Define our button, but with the use of props.theme this time
const Button = styled.button`
 color: ${props => props.theme.fg};
 border: 2px solid ${props => props.theme.fg};
 background: ${props => props.theme.bg};
 font-size: 1em;
 margin: 1em;
 padding: 0.25em 1em;
 border-radius: 3px;
`;
// Define our `fg` and `bg` on the theme
const theme = {
 fg: "palevioletred",
 bg: "white"
};
// This theme swaps `fg` and `bg`
const invertTheme = ({ fg, bg }) => ({
 fg: bg,
 bg: fg
});
render(
 <ThemeProvider theme={theme}>
   <div>
     <Button>Default Theme</Button>
     <ThemeProvider theme={invertTheme}>
       <Button>Inverted Theme</Button>
     </ThemeProvider>
   </div>
 </ThemeProvider>
);

themeのメリット

themeというぐらいなので、色を全体的に変えたりなどスタイルの雰囲気をガラッと変えたりするのに使いやすそうです。

実際に使ってみる

ドキュメントの例でも十分ですが、せっかくなので使ってみましょう!
index.tsxにAppProviderコンポーネントを作って、ボタンを押したらthemeが切り替わる感じにします。

AppコンポーネントはThemeProviderで挟んで、themeを渡します。これでAppコンポーネント内ではt hemeが使えます。

src/index.tsx
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
import { ThemeProvider } from 'styled-components';
import { App } from './App';

const AppProvider = () => {
    const [is_theme_dark, set_is_theme_dark] = useState(false);

    const default_theme = {
        text_color: 'black',
        background_color: 'white',
    }

    const dark_theme = {
        text_color: 'white',
        background_color: 'black'
    }

    const toggle_theme = () => {
        set_is_theme_dark(!is_theme_dark);
    };

    return (
        <>
            <button onClick={toggle_theme}>Change!</button>
            <ThemeProvider theme={is_theme_dark ? dark_theme : default_theme}>
                <App />
            </ThemeProvider>
        </>
    );
};

ReactDOM.render(<AppProvider />, document.getElementById('app'));

次にAppコンポーネントことApp.tsxに以下のコードを書きます。

src/App.tsx
import React, { useContext } from 'react';
import styled, { ThemeContext } from 'styled-components';

export const App = () => {
    const theme = useContext(ThemeContext);

    return (
        <TitleWrapper>
            <h1>Hello World!</h1>
            <Button>No Event</Button>
        </TitleWrapper>
    );
};

const TitleWrapper = styled.div`
    width: 100vw;
    height: 100vh;
    background-color: ${(props) => props.theme.background_color};
    text-align: center;
    h1 {
        color: ${(props) => props.theme.text_color};
    }
`;

const Button = styled.button`
    color: ${(props) => props.theme.text_color};
    background-color: ${(props) => props.theme.background_color};
`;

themeはhooksのuseContextにstyled-componentsからインポートしたThemeContextを渡してあげるとindex.tsxで定義したthemeの値が入ります。そしてテーマの値をスタイルに使っています。

ブラウザを見てみると下の画像のようにdefault_themeが表示されると思います。

左上のボタンを押すとdark_themeに変わります。

使ってみた感想

今回は簡単な物を作ったのであまり恩賜を受けられませんでしたが、グローバルステートと組み合わせて、サイト全体のテーマを変えたりするのに使えそうな感じがしました。

ここまで読んでいただきありがとうございます!今後もいろいろな記事を書いていきたいと思っているので感想や要望などいただけたら、モチベーションにもつながります。

参考記事

styled-componentsドキュメント

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

styled-componentsでJavascriptの値を使う

はじめに

一応、下の記事の続きとして書いているのでインストールなどはお手数ですが下の記事をご覧ください。

他のstyled-componentsの記事
* styled-componentsのThemeを使ってみる
* styled-componentsでレスポンシブを楽に書く

今回はstyled-componentsでJavascriptの値を使う方法を試していきます。

実際にやってみる

連想配列で定義した値を使う

まず、srcディレクトリ直下にstyle.tsファイルを作り、以下のコードを書きます。

src/style.ts
export const COLOR = {
    RED: '#FF0000',
    ORANGE: '#FFA500',
    YELLOW: '#FFFF00',
    GREENYELLOW: '#ADFF2F',
    GREEN: '#008000',
    BLUE: '#0000FF',
    SKYBLUE: '#87CEEB',
    PURPLE: '#800080',
    PINK: '#FFC0CB',
    BROWN: '#A52A2A',
    WHITE: '#FFFFFF',
    GRAY: '#808080',
    BLACK: '#000000',
};

上のコードを見ると、色の名前とカラーコードの連想配列になっています。

App.tsxに以下のようなコードを書いてみましょう。App.tsxはsrc直下にあり、index.tsxにインポートする感じで書いています。

src/App.tsx
import React from 'react';
import styled from 'styled-components';
import { COLOR } from './style';

export const App = () => {
    return (
        <TitleWrapper>
            <h1>Hello World!</h1>
            <Button>Click</Button>
        </TitleWrapper>
    );
};

const TitleWrapper = styled.div`
    text-align: center;
    h1 {
        color: ${COLOR.RED};
    }
`;

const Button = styled.button`
    color: ${COLOR.WHITE};
    background-color: ${COLOR.BLUE};
    &:hover {
        background-color: ${COLOR.SKYBLUE};
    }
`;

先ほど作ったstyle.tsをインポートしてスタイルに使っています。このように、色や大きさの値をどこかのファイルにまとめて、インポートして使う感じにすると共同開発でもわかりやすくなります。

propsを渡して、その値を使う(例1)

App.tsxを以下のように書き換えてみましょう。Typescriptを使わない場合は、interfaceやの部分は消しましょう。

src/App.tsx
import React, { useState } from 'react';
import styled from 'styled-components';

export const App = () => {
    const [is_red, set_is_red] = useState(true);

    const handleClick = () => {
        set_is_red(!is_red);
    };

    return (
        <TitleWrapper is_red={is_red}>
            <h1>Hello World!</h1>
            <Button onClick={handleClick}>Click</Button>
        </TitleWrapper>
    );
};

interface ITitleWrapper {
    is_red: boolean;
}

const TitleWrapper = styled.div<ITitleWrapper>`
    text-align: center;
    h1 {
        color: ${({ is_red }) => is_red ? 'red' : 'blue'};
    }
`;

const Button = styled.button`
    color: white;
    background-color: blue;
    &:hover {
        background-color: skyblue;
    }
`;


※実際はクリックすると赤と青で切り替わります

上から見ていきましょう。まず、is_redという初期値がtrueのstateを作り、is_redはButtonをクリックするたびにtrue<->falseで切り替わる事がわかります。また、TitleWrapperにはis_redを渡している事がわかります。

そして、下の方のTitleWrapperを見てみるとis_redの値が使われています。ここでは三項演算子を使って、h1のテキストの色を変えています。

is_redの状態
true      赤     
false

propsを渡して、その値を使う(例2)

また、下のようにスタイルの連想配列をそのまま渡す方法もあります。

src/App.tsx
import React, { useState } from 'react';
import styled from 'styled-components';

export const App = () => {
    const [text_color, set_text_color] = useState({ color: 'blue' });

    const handleClick = () => {
        set_text_color({ color: 'red' });
    };

    return (
        <TitleWrapper text_color={text_color}>
            <h1>Hello World!</h1>
            <Button onClick={handleClick}>Click</Button>
        </TitleWrapper>
    );
};

interface ITitleWrapper {
    text_color: {
        color: string;
    };
}

const TitleWrapper = styled.div<ITitleWrapper>`
    text-align: center;
    h1 {
        ${({ text_color }) => text_color};
    }
`;

const Button = styled.button`
    color: white;
    background-color: blue;
    &:hover {
        background-color: skyblue;
    }
`;

※この場合はクリックしたら青から赤になり、その後にボタンを押しても色は変わりません

渡された連想配列がそのままスタイルとして適用されています。

うまく説明できたかわかりませんが。styled-componentsでは今回説明したJavascriptの値を使えるという部分が個人的にかなりいいなと思っています。次はthemeについて書きたいと思います。

ここまで読んでいただきありがとうございます!少しでもお役に立てれば幸いです!

参考記事

styled-componentsドキュメント

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

styled-componentsを使ってみる

はじめに

今回はstyled-componentsの簡単な使い方をやります。

他のstyled-componentsの記事
* styled-componentsでJavascriptの値を使う
* styled-componentsのThemeを使ってみる
* styled-componentsでレスポンシブを楽に書く

styled-componentsとは?

styled-componentsはReactにおけるCSSの当て方の一つで、Reactのコンポーネントのようにjsの値を渡したりでき、コンポーネントのようにスコープが作られるため、使いやすいです。

インストール

Reactの環境ができている方は下のコマンドはスルーしてください。
できていない方は以下のコマンドを打つか、記事を見ながら作ってみてください。
typescriptを使わない場合は下のコマンドの--typescriptは必要ありません。

ターミナル
npx create-react-app --typescript [アプリ名]

webpackでReact+Typescriptの環境構築をする

VSCodeで開き、ターミナルで以下のコマンドを打ちます。Typescriptを使わない場合は@types/styled-componentsは必要ありません。

VSCodeのターミナル
//npm
npm install --save styled-components
npm install --save-dev @types/styled-components

//yarn
yarn add styled-components
yarn add -D @types/styled-components

準備完了!

実際に使ってみる

早速使ってみましょう!

普通にスタイルを当てる

まずはApp.tsxに以下のコードを書いてブラウザで見てみましょう。

src/App.tsx
import React from 'react';

export const App = () => {
    return <h1>Hello World!</h1>;
};

ちなみにindex.tsxは以下のようにしています。

src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import { App } from './App';

ReactDOM.render(<App/>, document.getElementById('app'));

ただの黒いHello World!が表示されるはずです。

次にstyled-componentsを使って、スタイルを当ててみましょう。
以下のような感じで使います。

VSCodeの拡張機能のvscode-styled-componentsを入れるとシンタックスハイライトが効いてみやすくなります。

const [コンポーネントとして使う名前] = styled.[タグ名]`
    //style
`;

App.tsxにstyled-componentsをインポートして、h1にスタイルを当てています。

src/App.tsx
import React from 'react';
import styled from 'styled-components'

export const App = () => {
    return <Title>Hello World!</Title>;
};

const Title = styled.h1`
    color: red;
`;

今度は赤く表示されるはずです。

補足:下のコードように連想配列で書く書き方もあるみたいです。詳しくはドキュメント

const Box = styled.div({
  background: 'palevioletred',
  height: '50px',
  width: '50px'
});

コンポーネント内の要素にスタイルを当てる

App.tsxを以下のように書き換えてみましょう。

src/App.tsx
import React from 'react';
import styled from 'styled-components';

export const App = () => {
    return (
        <TitleWrapper>
            <h1>Hello World!</h1>
        </TitleWrapper>
    );
};

const TitleWrapper = styled.div`
    text-align: center;
    h1 {
        color: red;
    }
`;

中心に赤くHello World!が表示されるはずです。
上のようにある要素の中の要素にスタイルを当てるといった使い方もできます。

擬似要素を使う

App.tsxを以下のように書き換えてみましょう。

src/App.tsx
import React from 'react';
import styled from 'styled-components';

export const App = () => {
    return (
        <TitleWrapper>
            <h1>Hello World!</h1>
            <Button>Hover</Button>
        </TitleWrapper>
    );
};

const TitleWrapper = styled.div`
    text-align: center;
    h1 {
        color: red;
    }
`;

const Button = styled.button`
    color: white;
    background-color: blue;
    &:hover {
        background-color: skyblue;
    }
`;

先ほどに加えて、hoverで水色になるボタンが表示されます(hover前は青)。

&の後に擬似要素を書くことで使えます。

コンポーネントにスタイルを当てる

App.tsxを以下のように書き換えてみましょう。

src/App.tsx
import React from 'react';
import styled from 'styled-components';

export const App = () => {
    return (
        <TitleWrapper>
            <h1>Hello World!</h1>
            <Button>Hover</Button>
            <StyledButton>Hover</StyledButton>
        </TitleWrapper>
    );
};

const TitleWrapper = styled.div`
    text-align: center;
    h1 {
        color: red;
    }
`;

const Button = styled.button`
    color: white;
    background-color: blue;
    &:hover {
        background-color: skyblue;
    }
`;

const StyledButton = styled(Button)`
    color: black;
    background-color: white;
`;

上では色だけを変えたボタンを新しく作っています。このように、作ったコンポーネントに上書きする形でスタイルを当てる事ができます。
MaterialUIなどにも使う事ができて便利です。

ここまで読んでいただきありがとうございます!少しでもお役に立てれば幸いです!

参考記事

styled-componentsドキュメント

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