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

javascript 3分カウントダウンタイマー

要件は
1.カウントは03:00からスタートする
2.スタートボタンを押すと1秒ずつカウントが進む
3.カウントが00:00になったら「Time UP!」と表示する
4.ストップボタンを押すとカウントが止まる
5.リセットボタンを押すとカウントが03:00に戻り、カウントが止まる

<!doctype html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <title>タイマー練習</title>
        <script>
            var to_timeup = 180;
            var max = 180;
            var intervalid;
            var start_flag = false;
            // var timer=document.getElementById("timer");

            function count_start(){
                console.log("count_start");
               if(start_flag===false){                          
                intervalid = setInterval(count_down,1000);  
                start_flag = true;
               }
            }

            function count_down(){
                console.log("count_down");
                   var timer = document.getElementById("timer");  
                if(to_timeup===0){
                    timer.innerHTML = 'Time up!'
                    timer.style.color="red";      
                    // clearInterval(intervalid);
                    // to_timeup=10;            
                    count_stop();
                }   else{
                    to_timeup--;
                    padding();
                }
            }

            function padding(){
                var timer=document.getElementById("timer");   
                var min = 0;
                var sec = 0;
                min = Math.floor(to_timeup/60);
                sec = (to_timeup%60);
                min = ("0"+min).slice(-2);
                sec = ("0"+sec).slice(-2);
                timer.innerHTML = min +":"+ sec;
            }

            function count_stop(){
                console.log(count_stop);
                clearInterval(intervalid);
                start_flag = false;
            }

            function count_reset(){
                console.log(count_reset);
                var timer = document.getElementById("timer");
                clearInterval(intervalid);
                start_flag = false;
                to_timeup = max; 
                padding();
                timer.style.color = "black";   
            }

            window.onload = function(){
                console.log("mumei");
                padding();
                var startbutton=document.getElementById("startbutton");
                startbutton.addEventListener("click",count_start,false);
                var stopbutton=document.getElementById("stopbutton");
                stopbutton.addEventListener("click",count_stop,false);
                var resetbutton=document.getElementById("resetbutton");
                resetbutton.addEventListener("click",count_reset,false);
            }           
        </script>
    </head>
    <body>
        <h1 id="timer"></h1>
        <button id="startbutton">start</button>
        <button id="stopbutton">stop</button>
        <button id="resetbutton">reset</button>
    </body>
</html>

最初に異なる変数に同じ値を入れるのはcount resetのところで180という数字を直接書くよりも、後でカウントが5分、10分に変更になったときに最初の変数の値を変更するだけで済ませるため。

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

【Rails】いいね機能を非同期実装

1. はじめに

以下のデモ動画の様に、ユーザーが投稿した内容に対して"いいね"が出来る機能を実装していきます。
Image from Gyazo

2. 前提条件

既にユーザー登録機能と投稿機能は実装されている前提で、そこに"いいね機能"を追加実装する。という流れで進めていきます。

下記の様なデータベース構造をイメージしてもらえたら分かりやすいと思います。
ER 図(Qiita).png

3. いいね機能の実装

■実装するまでの流れ

ざっくり説明すると、以下の流れで実装していきます。
・モデルの作成
   ↓
・ルーティングの追加
   ↓
・コントローラーの作成
   ↓
・ビューの作成

それでは、早速いってみましょー。

3-1. Likeモデルの作成

まずはLikeモデルを作成します。
ターミナルで以下のコマンドを実行してください。

ターミナル
$ rails g model Like

新しくマイグレーションファイルが作成されるので、以下の通りに編集してください。

db>migrate>xxxxxx_create_likes.rb
class CreateLikes < ActiveRecord::Migration[6.0]
  def change
    create_table :likes do |t|
      # ===追記部分===
      t.references :tweet, foreign_key: true, null: false
      t.references :user, foreign_key: true, null: false
      # ===追記部分===
      t.timestamps
    end
  end
end

上記の様にreferences型で保存すると、tweet_iduser_idを外部キーとして指定することが出来ます。
それではマイグレーションファイルを実行して、likesテーブルを作成しましょう。

ターミナル
$ rails db:migrate

上記のコマンドを実行した後、likesテーブルが作成されたかどうか確認して下さい。
無事作成されている事が確認出来たら、次はアソシエーションの設定です。

3-2. アソシエーションの設定

アソシエーションとは、2つのモデル同士の関連付けのことを指します。
UserモデルとLikeモデル、TweetモデルとLikeモデル、それぞれのアソシエーションを設定していきます。

UserモデルとLikeモデルのアソシエーションの設定

まずはUserモデルとLikeモデルのアソシエーションを設定していきます。

2つのモデルの関係性は以下の通りです。
・ ユーザーは複数のいいねが可能
・ いいねAをしたユーザーは1人しかいない

つまり、UserモデルとLikeモデルは「1対多」の関係になります。
それでは、実際にコードを書いていきましょう。

Userモデルに以下の通りコードを追記して下さい。

app>models>user.rb
class User < ApplicationRecord

  has_many :tweets, dependent: :destroy

  # この行を追加
  has_many :likes

end

has_many は、他のモデルとの間に「1対多」の関係があることを示します。

次はLikeモデルに以下の通りコードを追記して下さい。

app>models>like.rb
class Like < ApplicationRecord

  # この行を追加
  belongs_to :user

end

belongs_tohas_many の逆で、他のモデルとの間に「多対1」の関係があることを示しています。

これで、UserモデルとLikeモデルのアソシエーション設定が出来ました。

TweetモデルとLikeモデルのアソシエーションの設定

同じ要領でTweetモデルとLikeモデルのアソシエーションも設定していきます。

2つのモデルの関係性は以下の通りです。
・ 1つの投稿に対して、複数のいいねがつく
・ いいねAに紐づく投稿は1つしかない

つまり、TweetモデルとLikeモデルも「1対多」の関係になります。
それでは、実際にコードを書いていきましょう。

Tweetモデルに以下の通りコードを追記して下さい。

app>models>tweet.rb
class Tweet < ApplicationRecord
  belongs_to :user

  # この行を追加
  has_many :likes, dependent: :destroy

end

dependent: :destroy をつける事で、投稿が削除された時に、その投稿に紐づくいいねも削除されます。

次はLikeモデルです。

app>models>like.rb
class Like < ApplicationRecord

  belongs_to :user

  # この行を追加
  belongs_to :tweet

end

以上で、全てのモデルのアソシエーションの設定が完了しました。

3-3. バリデーションの設定

投稿Aに対して1人のユーザーがいいねを押せる回数は1回にしたいので、1回以上は押せない様にバリデーションを設定します。

Likeモデルに以下の通り追記して下さい。

app>models>like.rb
class Like < ApplicationRecord

  belongs_to :user
  belongs_to :tweet

  # この行を追加
  validates :user_id, uniqueness: { scope: :tweet_id }

end

上記の様に書く事で、user_idtweet_id が重複しない様にする事が出来ます。
以上で、バリデーションの設定は完了です。

3-4. ルーティングの追加

いよいよ本格的にいいね機能を実装していきます。

まずは、いいね機能で使うルーティングを追加しましょう。
以下の通りコードを追記して下さい。

config>routes.rb
Rails.application.routes.draw do
  devise_for :users,
    controllers: { registrations: 'registrations' }

  resources :tweets, only: [:index, :new, :create, :show, :destroy] do

    # この行を追加
    resources :likes, only: [:create, :destroy]

  end

end

いいね情報の保存と削除のルーティングを追加する必要があるので、likesコントローラーのcreate アクションとdestroy アクションを定義しています。

ルーティングをネストにする事で、いいねがどの投稿に紐づくかを明示できます。

コードを追加したらrails routes コマンドで、ルーティングの設定が問題ないか忘れずに確認しておきましょう。

3-5. likesコントローラーの作成

次にlikesコントローラーを作成していきます。
ターミナルで以下のコマンドを実行してください。

ターミナル
$ rails g controller likes

上記のコマンドを実行すると、likesコントローラーが作成できます。

それでは作成したlikesコントローラーにcreate アクションとdestroy アクションを作成していきます。
以下の通りコードを追記して下さい。

app>controllers>likes_controller.rb
class LikesController < ApplicationController

  # ===追記部分===
  def create
    @like = current_user.likes.build(like_params)
    @tweet = @like.tweet
    if @like.save
      respond_to :js
    end
  end

  def destroy
    @like = Like.find_by(id: params[:id])
    @tweet = @like.tweet
    if @like.destroy
      respond_to :js
    end
  end

  private
    def like_params
      params.permit(:tweet_id)
    end
  # ===追記部分===

end

privateメソッドやparamsは理解できているものとして、追加したコードについて簡単に説明していきます。

createアクション

まず@like には投稿に"いいね"をしたユーザーのuser_id と、"いいね"された投稿のtweet_id の情報が入っています。
このコードはbuildメソッドを使って、インスタンスを作成しています。

次に@tweet には@like に紐づく投稿の情報、つまり"いいね"された投稿の情報が入ります。
@tweet はどの投稿に"いいね"を押したのかを判断するために、ビューを作成するところで使います。

最後のif @like.save の部分は、"いいね"が押された時に返すレスポンスのフォーマットをrespond_to メソッドで切り替えています。
"いいね"が押されたらリアルタイムでビューを反映させるために、JS形式のフォーマットでレスポンスを返すようにしています。

destroyアクション

createアクションのところで説明した内容と重複している部分が多いので簡単に説明すると、受け取ったHTTPリクエストからid を判別し、@like に指定のレコードの情報を入れています。

こちらもリアルタイムでビューを反映させるために、JS形式のフォーマットでレスポンスを返すようにしています。

3-5. ビューの作成

いよいよビューの作成です。

まずは投稿一覧のビュー画面を編集していきましょう・・・と言いたいところですが、ビューで使うためのメソッドを先に定義しておきます。

Tweetモデルに以下の通りコードを追記して下さい。

app>models>tweet.rb
class Tweet < ApplicationRecord
  belongs_to :user
  has_many :likes, dependent: :destroy

  # 追加部分(liked_byメソッド)
  def liked_by(user)
    Like.find_by(user_id: user.id, tweet_id: id)
  end
  # 追加部分

end

上記で追加したliked_by メソッドは、user_idtweet_id が一致するlikeを探して、無ければnillを返します。

それでは、app/views/tweets/index.html.erb に以下のコードを追記して下さい。

app>views>tweets>index.html.erb
<% @tweets.each do |tweet| %>

  # いいねボタンを表示したい部分に追加
  <div class="content-like">
    <ul class="content-like__icons">
      <li id="<%= tweet.id.to_s %>">
        <% if tweet.liked_by(current_user).present? %>
          <%= link_to (tweet_like_path(tweet.id, tweet.liked_by(current_user)), method: :DELETE, remote: true, class: "liked") do %>
            <i class="far fa-thumbs-up"></i>
          <% end %>
        <% else %>
          <%= link_to (tweet_likes_path(tweet), method: :POST, remote: true, class: "like") do %>
            <i class="far fa-thumbs-up"></i>
          <% end %>
        <% end %>
      </li>
    </ul>
  </div>
  # 追加部分はここまで

<% end %>

liked_by に引数としてcurrent_userを渡すことで、現在ログインしているユーザーが投稿に"いいね"をしているかどうか判断しています。

これでユーザーが"いいね"をしていない時に"いいねボタン"をクリックすると、先ほど作成したcreate アクションを実行、ユーザーが"いいね"をしている時はdestroy アクションを実行と、条件分岐させる事ができました。

リンクが押された時に.js.erb ファイルを呼び出す必要があるので、link_toremote: true オプションを追加することを忘れないでください。

なお"いいねボタン"のアイコンについては、Font Awesome を利用しています。
導入方法については、以下のqiita記事などが参考になるかと思います。
rails font-awesome-sass導入方法

次は、createアクションが実行された時に出力するファイルを作成します。
app/views フォルダ直下にlikes フォルダを作成し、その中にcreate.js.erb ファイルを作成してください。

ファイルの作成ができたら、以下の通りコードを追記してください。

app>views>likes>create.js.erb
$('#<%= @tweet.id.to_s %>').
  html('<%= j render "tweets/liked", { tweet: @tweet } %>');

上記のコードで、createアクションが実行されたらtweets フォルダ内の_liked.html.erb ファイルを呼び出しています。

tweets フォルダの中に_liked.html.erb ファイルを作成し、以下のコードを追加してください。

app>views>tweets>_liked.html.erb
<%= link_to (tweet_like_path(tweet.id, tweet.liked_by(current_user)), method: :DELETE, remote: true, class: "liked") do %>
  <i class="far fa-thumbs-up"></i>
<% end %>

上記のコードで、"いいねボタン"を押したら"いいね"を取り消すHTMLを表示するようにしています。

同じ流れで、destroyアクションが実行された時に呼び出されるファイルも作っていきましょう。
app/views>likes フォルダの中にdestroy.js.erb ファイルを作成してください。

ファイルの作成ができたら、以下の通りコードを追記してください。

app>views>likes>destroy.js.erb
$('#<%= @tweet.id.to_s %>').
  html('<%= j render "tweets/like", { tweet: @tweet } %>');

tweets フォルダの中に_like.html.erb ファイルを作成し、以下のコードを追加してください。

app>views>tweets>_like.html.erb
<%= link_to (tweet_likes_path(tweet), method: :POST, remote: true, class: "like") do %>
  <i class="far fa-thumbs-up"></i>
<% end %>

以上で非同期でいいね機能を実装する事ができました。

あとは見た目ですが、クラス名を、いいねされている時はliked されていない時はlike としていますので、CSSで自分好みにカスタマイズしてみてください。

私の場合は以下の様にして、いいねがされた時はレッド、されていない時はグレーにアイコンの色を変えています。

app>assets>stylesheets>tweets>_tweet.css
.like {
  color: gray;
}

.liked {
  color: red;
}

4. さいごに

今回がはじめての投稿になりますが、記事を書くのって想像していたよりも大変ですね。
もし分かりにくい部分や、間違っている部分がある場合はご指摘いただけると嬉しいです。

最後まで読んでいただき、ありがとうございました☺️

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

【javascript】新しい配列メソッド

こちらの記事は以下の書籍を参考にアウトプットとして執筆しました。

入門JavaScriptプログラミング

Array.fromで配列を作成する

argumentsオブジェクトは配列ではない

arugumentsは配列ではなく、配列風のオブジェクトである。

配列風とは、配列のようにlengthプロパティを持っていて、展示アクセスができ、forループによる処理もできるオブジェクトのこと

以下のコードはargumentsが配列でないためにエラーになる

function avg(){
  const sum=arguments.reduce(function(a,b){
    return a+b;
  })
  return sum/arguments.length;
}

出典:入門JavaScriptプログラミング

reduce

構文
arr.reduce(callback( accumulator, currentValue[, index[, array]] )[, initialValue])
callback引数 説明
accumulator callback値を蓄積する
currentValue 現在処理されている配列の要素
index 現在処理中の配列要素のインデックス。initialValueが指定されている場合は0から。そうじゃないなら1から
array reduce()が呼び出された配列
引数 説明
initialValue callbackの最初の呼び出しの最初の引数として使用する値

出典:Array.prototype.reduce()

配列風を配列に変換する

Array.fromの目的は配列風を配列に変換することで、配列風とはlengthプロパティを持つオブジェクトのこと
0からlengthまで適切なインデックス位置におかれ、文字列ではlengthプロパティに加えて、各文字のインデックスを示す数値のプロパティがある。このため文字列でArray.fromを呼び出すと文字の配列が返される。
これを使ってargumentsも配列にする。

NodeListを配列にする

document.querySelectorAllなどのDOMノードリストを返すようなオブジェクトでも配列に変換できる。
というかlengthプロパティを持つオブジェクトであれば何でもうまくいく

参考

入門JavaScriptプログラミング

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

自分用メモ javascript 演算子

はじめに

基礎の基礎である演算子をここで定着させるためにアウトプットしておきます!!

javascript 演算子集

計算 演算子

記号 演算子
足し算
-- デクリメント(対象を一つ減らす)
++ インクリメント(対象を一つ増やす)
/ 割り算
% 余り
* 掛け算
- 引き算
- マイナス符号

論理演算子

記号 演算子
? 条件演算子
== 等しい
> 大きい
>= 以上
=== 等価演算子
!= 等しくない
< 小さい
<= 以下
&& AND
! NOT
!== 非等価演算子

覚えておかないといけない物!!

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

[react]nextjs関連のメモ

nextjs関連で学んだことをメモ

現在nextjsの勉強をしているので覚えておいた方が良さそうなところや、
仕組みが不明なとこなどをメモがわりに残しておく。

ページのルーティング

nextjsをなんとなく使っていたが、どういうルールでルーティングができてるのかわからなかった。
nextjsのディレクトリは

pages/
  - index.js
  - sample/
    - index.js
    - show.js

このような構成になっているが
pages/index.jsはlocalhost:3000/,
pages/sample/index.jsはlocalhost:3000/sample/,
pages/sample/show.jsはlocalhost:3000/sample/show/
という風にpagesディレクトリに存在するファイルからエクスポートされたコンポーネントがルーティングになるらしい。

sample/index.js
```
export default function Sample() {
return

sample


}

localhost:3000/sample/にアクセスすると確認できた。
```

プリレンダリングとは

あらかじめhtmlを生成しておき、後からjavascriptコードが読み込まれる。
(*詳しい方いたら補足して頂けると助かります。)

プリレンダリングの2つの形式

静的生成(Static Generation)

本番にビルドしたタイミングでhtmlを生成しておき、ユーザーのリクエストに応じて生成されたhtmlを再利用して表示する。

サーバサイドレンダリング(SSR)

ユーザーからのリクエスト毎にhtmlが生成される。

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

JavaScript / 直積を生成する

目的

  • 総当たりは直積 (デカルト積) で表現できるらしい
  • 入れ子の反復処理を無くす方法で検索していたら出てきた
  • 標準ライブラリに直積の生成は用意されていないので書いてみる

直積

これだと

[0, 1, 2]
[3, 4, 5]

こういうこと

[0, 3]
[0, 4]
[0, 5]
[1, 3]
[1, 4]
[1, 5]
[2, 3]
[2, 4]
[2, 5]

コード

制御文

for...of 文の例

const array_a = [0, 1, 2]
const array_b = [3, 4, 5]

for (const x of array_a) {
    for (const y of array_b) {
        console.log([x,y])
    }
}

関数化

高階関数だと短く書ける

const product = (...args) => args.reduce((acc, cur) => acc.flatMap(x => cur.map(y => x.concat([y]))), [[]])

使い方

const array_a = [0, 1, 2]
const array_b = [3, 4, 5]

for (const xy of product(array_a, array_b)) {
    console.log(xy)
}

何をしているのかは次のコードが分かりやすいかもしれない
高階関数は遅い、for 文は速いというイメージがあったので試しに書いたもの

const product = (...args) => {
    const args_size = args.length
    let   result    = [[]]

    for (let a = 0; a < args_size; a++) {
        const arg         = args[a]
        const arg_size    = arg.length
        const result_size = result.length
        const halfway     = []

        for (let b = 0; b < result_size; b++) {
            const x = result[b]

            for (let c = 0; c < arg_size; c++) {
                halfway.push(x.concat([arg[c]]))
            }
        }

        result = halfway
    }

    return result
}

最後に

入れ子の反復処理を無くす方法というよりは関数化して隠す方法ですね

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

【33大特典付】JavaScript関数ドリル一般公開しました!

lp-header.gif

概要

詳細は以下の記事で解説しております^^

https://bit.ly/3abWMmX

対象

  • JavaScript初学者
  • 関数の実装に苦手意識を持っている方
  • 関数の演習問題をたくさん解きたい方
  • プログラミング学習に挫折しそうな方
  • 効率の良い学習方法を知りたい方
  • Webエンジニアを目指すための学習ロードマップの作り方を知りたい方
  • フロントエンドエンジニアを目指す人向けの学習ロードマップを観たい方
  • 無料・有料のメンターを見つける方法を知りたい方
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初めてアドオン(WebExtensions)を作ってみたのでハマった点をまとめてみる

アドオン(WebExtensions)を初めて作ってみました。その過程で、いろいろハマったので、まとめたいと思います。

作ったもの

Qiine というアドオンを作りました。1 2 いまのところ、Firefox にのみ対応しています。

主な機能は以下のとおりです。

  • LGTM 表示

    • Qiita の記事を開きながらツールバーのアイコンをクリックすると、ポップアップが開きます
    • ポップアップでは、その記事の LGTM を最大 100 件まで表示します
    • 100 件以上表示したい場合や別タブで表示したい場合は、一番下の More をクリックしてください3
  • アクセストークン追加

    • Qiita で発行されたアクセストークンを設定画面に追加すると、API の利用制限を緩和することができます4

Qiineポップアップ.png
Qiineタブ.png
Qiineオプション.png

MDN

今回、MDN の ブラウザー拡張機能 にはかなり助けられました。

初めての拡張機能 はあまりにも簡単なので、ざっと眺める程度にして、 2つめの拡張機能 から作り始めるのが良いように感じました。

リファレンス の海にいきなり飛び込むのもアリだと思いますが、個人的には 逆引きリファレンス を先に見ておけば良かったと思うこともありました(後述)。

web-ext

web-ext は「WebExtensions のビルド・実行・テストを支援するコマンドラインツール」5 です。

2つめの拡張機能 では最後のほうに小さく書かれているだけだったり、入門記事が 英語 しかなかったりと敷居が高いですが、使ってみるとかなり便利だったので、オススメです。

インストール

web-ext は Node.js ベースなので、あらかじめ Node.js をインストールしておく必要があります。

Node.js をインストールしたら、以下のコマンドを実行して web-ext をインストールします。

npm install --global web-ext

実行

アドオンを開発しているディレクトリへ移動し、 web-ext run を実行します。

$ cd アドオンの開発ディレクトリ
$ web-ext run

web-ext run はディレクトリに格納されているアドオンを読み込んで、Firefox を起動します。 about:debugging を開くと、 一時的な拡張機能 としてアドオンが読み込まれていることを確認できます。

about:debugging.png

web-ext run は、デフォルトでは一時プロファイルを作って Firefox を起動しますが、既存のプロファイルを指定することもできます。例えば、 develop というプロファイルを使いたい場合は、以下のように指定します。

$ web-ext run --firefox-profile=develop

反映

web-ext を使わない場合、コードを変更するたびに、 about:debugging を開き、「再読み込み」ボタンを押す必要があります。実際に何回かやってみると分かると思いますが、コードを変更するたびにボタンを押すのは面倒ですし、押し忘れていたときのイライラ感は耐えがたいものです。

web-ext を使う場合、「再読み込み」ボタンを押す必要はありません。コードが変更されたことを web-ext run が検知して、自動的に再読み込みを行ってくれるからです。6

なお、 web-ext run を実行しているときに誤って「再読み込み」ボタンを押すと、同じアドオンが重複して読み込まれてしまうので注意が必要です。

コードチェック

web-ext lint を実行すると、コードをチェックしてくれます。例えば、サニタイズせずに innerHTML に代入しようとしている場合は、以下のような警告が表示されます。7

$ cd アドオンの開発ディレクトリ
$ web-ext lint
{
    "count": 1,
    "summary": {
        "errors": 0,
        "notices": 0,
        "warnings": 1
    },

    (中略)

    "errors": [],
    "notices": [],
    "warnings": [
        {
            "_type": "warning",
            "code": "UNSAFE_VAR_ASSIGNMENT",
            "message": "Unsafe assignment to innerHTML",
            "description": "Due to both security and performance concerns, this may not be set using dynamic values which have not been adequately sanitized. This can lead to security issues or fairly serious performance degradation.",
            "column": 9,
            "file": "src/controller/OptionsController.js",
            "line": 102
        }
    ]
}

ビルド

アドオンが完成して addons.mozilla.org へ登録する際は、zip ファイルで提出することになります。

web-ext build を使うと、 .git など通常パッケージ化する上で不要なファイルを無視して zip 化してくれます。

$ cd アドオンの開発ディレクトリ
$ web-ext build

開発ツール

アドオンといっても、WebExtensions API を除けば、普通の HTML / CSS / JavaScript です。通常の Web 開発と同様に、インスペクターやコンソール、デバッガーなどの 開発ツール を使うことができます。

アドオンで開発ツールを使うには、 about:debugging の「調査」ボタンを押します。

about:debugging_調査.png

ただし、ポップアップを表示する場合は、開発ツールの設定で「ポップアップを自動で隠さない」にチェックを入れておいたほうが良いでしょう( 参考 )。画面の他の場所をクリックしてもポップアップが開いたままになります(ポップアップを閉じたいときは Esc キーを押します)。

開発ツール_ポップアップを自動で隠さない.png

WebExtensions API

アクティブなタブの情報を取得するには

現在開いているタブの URL を取得する場合のように、アクティブなタブの情報を取得したいことがあると思います。そうした場合は tabs.query()MDN )を使います。

const tabs = await browser.tabs.query({active: true, currentWindow: true});
const url = tabs[0].url;

tabs.query() の戻り値は、 tabs.Tab 型( MDN )の配列です。 tabs.Taburl プロパティに URL が格納されています。

なお、メソッドをコールせずに tabs.Tab 型を直接参照することはできません。また、メソッド名が紛らわしいですが、 tabs.getCurrent()MDN )も使えません。バックグラウンドスクリプトやポップアップで使うと undefined が返ってくるからです。MDN にも以下のように注意書きがあります。

Note: This function is only useful in contexts where there is a browser tab, such as an options page.

If you call it from a background script or a popup, it will return undefined.

Tabs API については 逆引きリファレンス に情報がまとめられているので、リファレンスを見る前に、そちらに目を通したほうが良いでしょう。

ポップアップからタブへメッセージを送るには

もしかすると、ここが一番苦戦したところかもしれません。Qiine では、ポップアップを開いた際に Qiita API を使って、以下の情報を取得します。

ポップアップ最下部の More ボタンを押した場合、新しくタブを開きますが、そこにも同じ情報を表示します。すでに取得済みのデータをもう一度 API で取得するのは API を消費しすぎるので避けたいところです。

そこで、ポップアップからタブへデータを受け渡す方法を探しました。コンテンツスクリプトの解説に バックグラウンドスクリプトとの通信 が載っています。どうやらバックグラウンドスクリプトとコンテンツスクリプトのあいだでメッセージを送り合うことで、データを受け渡すことができるようです。

ポップアップはそのどちらでもありませんが、 tabs.sendMessage()MDN )の概要を見ると、ポップアップでも使えるように読める記載がありました。

Sends a single message from the extension's background scripts (or other privileged scripts, such as popup scripts or options page scripts) to any content scripts or extension pages/iframes that belong to the extension and are running in the specified tab.

まず、メッセージの送信元であるポップアップ側を作ります。 tabs.sendMessage() をコールして、指定したタブにメッセージを送信します。第1引数には送信先のタブ ID を、第2引数にはメッセージを指定します。メッセージには、JSON でシリアライズできるオブジェクトなら何でも指定できるようです。

await browser.tabs.sendMessage(tabId, {
    item: this._item,
    lgtms: this._lgtmList,
    lastPageNo: this._lastPageNo
});

次に、メッセージの送信先では runtime.onMessage イベント( MDN )を使って受信します。 addListener() の第1引数に、イベントが発生したときに実行される関数を指定します。送られてきたメッセージは、関数の第1引数(下記のコードでは request )に格納されています。

browser.runtime.onMessage.addListener((request) => {
    const controller = new MoreController();
    controller.init(request.item, request.lgtms, request.lastPageNo);
});

これでうまくいくと思ったのですが... コンソールに Could not establish connection. Receiving end does not exist. と表示されて、うまく動いてくれません。

検索すると、同じ事象に遭遇した方々を見つけました。

どうやら、メッセージ送信時に、送信先のタブの読み込みが完了していないことが原因のようです。タブの読み込み状況は tabs.Tab.status で確認でき、読み込み中の場合は loading を、読み込みが完了した場合は complete を返します。実際に試してみると、たしかに loading のままになっていました。

そこで、teratail の解決策を参考に tabs.onUpdatedMDN )を使うことにしました。これはタブが何らか更新されたときに発火するイベントです。その中には statusloading から complete へ更新された場合も含まれています。このイベントにリスナー関数を追加すれば、「送信先の読み込みが完了しているときにのみメッセージを送信する」こともできそうです。

最終的に書いたコードは以下のとおりです。 tabs.onUpdated にリスナー関数を登録し、送信先のタブの読み込みが完了したときにのみ sendMessage() をコールしています。

async displayMorePage() {
    // 別タブを作成する
    const tab = await browser.tabs.create({
        url: "/page/more/more.html"
    });

    // onUpdated で実行されるリスナー関数
    const listener = async (tabId, changeInfo, tab) => {
        // 読み込み中の場合は何もしない
        if (tab.status !== "complete") {
            return;
        }

        // メッセージを送信する
        await browser.tabs.sendMessage(tabId, {
            item: this._item,
            lgtms: this._lgtmList,
            lastPageNo: this._lastPageNo
        });
    };

    // onUpdated にリスナー関数を追加する
    browser.tabs.onUpdated.addListener(listener, {
        properties: ["status"],
        tabId: tab.id
    });
}

なお、 tabs.onUpdated.addListener() の第2引数は tabId (タブ ID)だけでも問題なさそうですが、念の為、 propertiesstatus を指定しています。こうすることで、 status (タブの読み込み状況)が更新されたときにのみ、リスナー関数が実行されるようにしています。

Qiita API

ここからは、Qiita API に特化した話題を書いていきます。

OAuth

Qiita API には、以下のような 利用制限 があります。

認証している状態ではユーザごとに1時間に1000回まで、認証していない状態ではIPアドレスごとに1時間に60回までリクエストを受け付けます。

Qiine では、Qiita API で一度に取得できる上限の100件単位で LGTM を取得していますが、それでもかなりの量の API を消費することが考えられ、認証認可はぜひ実装したいと思いました。8

もともと、Qiita API には OAuth を使った認可の仕組みが用意されています( API ドキュメント )。また、WebExtensions API でも identity API が用意されており、簡単に OAuth を利用することができます。

そこで、それらを組み合わせて、当初、以下のようなコードを書いていました。最終的には破棄しましたが、参考までに記載しておきます。

document.querySelector("#login").addEventListener("click", () => {
    // Qiita で発行された Client ID、Client Secret
    const clientId = "****************************************";
    const clientSecret = "******************";

    let authUrl = "https://qiita.com/api/v2/oauth/authorize";
    authUrl += "?client_id=" + clientId;
    authUrl += "&scope=read_qiita";

    browser.identity.launchWebAuthFlow({
        interactive: true,
        url: authUrl,
    }).then(authResult => {
        // リダイレクト先の URL から code を抜き出す
        const code = (new URL(authResult)).searchParams.get("code");

        const body = {
            "client_id": clientId,
            "client_secret": clientSecret,
            "code": code
        };

        // アクセストークンを取得する
        fetch("https://qiita.com/api/v2/access_tokens", {
            method: "POST",
            headers: {
                "Content-Type": "application/json"
            },
            body: JSON.stringify(body),
            mode: "cors"
        }).then(response => {
            if (!response.ok) {
                throw new Error("Fail to Authorize.");
            }

            return response.json();
        }).then(data => {
            const token = data.token;

            // ストレージへトークンを保存する
            browser.storage.local.set({
                token: token
            });
        });
    })
});

このコードを破棄した理由は「 client_secret を配布物に含めたり GitHub に載せるのは良くないのでは?」と思ったからです。配布物にOAuth2で利用するclient_secretを含むことのリスク なども見て、やっぱり違うよなと。

Qiita API が他のフローも用意してくれているのであれば、それを使ったのですが、OAuth を使ったフローは client_secret を使うフローしか無いようでした。

他にアクセストークンを発行する方法は、ユーザが自分で Qiita の管理画面から発行する方法しか無いようでした。

アクセストークンは、後述するOAuthを利用した認可フローか、ユーザの管理画面から発行できます。

そこで、ユーザが自分でアクセストークンを発行し、それをアドオンの設定画面で追加できるようにすることで、お茶を濁すことにしました。

user.organization

GET /api/v2/items/:item_id/likes で LGTM を取得したところ、 user.organization (ユーザの属する組織名)に 空文字null が混在していました。9

以下のように ドキュメント にも null が返ってくる可能性があることは示されているのですが、空文字しか想定していなかったので、危うくバグになるところでした。

organization
所属している組織
Example: "Increments Inc"
Type: null, string

感想

実際に作ってみると、アドオンも、中身は普通の HTML / CSS / JavaScript なんだなと思いました。正直、WebExtensions API で躓いたところよりも、CSS や JavaScript で躓いたところのほうが多かった気がします(あまりにも低レベル過ぎて、あえて記事には書きませんでしたが)。

火狐教徒なので Firefox を優先しましたが、いずれは Chrome にも対応したいですね。同じ WebExtensions API なので、そのまま動くのかと思いきや、どうやら Polyfill を使わないといけないらしく... リリースを優先して、そちらは一旦先送りにしました。10 何かありましたら、GitHub のほうへ Issue を立てていただければ幸いです。

転職活動とは全く関係なく11、純粋に個人的に欲しかったので作りはじめたのですが、addons.mozilla.org の審査もパスしたし、これをポートフォリオにしようかなと思いました。PHP の経験を PR しているのに、ポートフォリオが JavaScript ってどうなん?とは自分でも思いますが(苦笑)


  1. きっかけは、はてなブックマークの 公式アドオン を重宝していて、その Qiita 版のようなものがあったらいいのになーと思ったことです。半年ぐらい前からアイディアだけあったのですが、ある日曜日(8/2)の夜に、酒を飲みながら MDN を眺めていたら、意外と簡単そうで、これなら1週間ぐらいで作れそうな気がした。なので作ってみました。 

  2. 「Qiine」の語源は「Qiita」+「いいね」です。思いつきで名付けました。 

  3. Qiita API の ページネーション の仕様上、最大 10,000 件まで表示することができます。(1 ページ最大 100 件 × 最大 100 ページ = 10,000 件) 

  4. Qiita API の 利用制限 を参照。 

  5. GitHub の以下の文章の和訳。 "This is a command line tool to help build, run, and test WebExtensions." 

  6. create-react-app では、 npm start を実行しているあいだにコードを変更すると、自動で再コンパイルが行われますが、それに近いものを感じました。 

  7. デフォルトではテキスト形式で出力されますが、横に長くてコピペしにくかったので JSON 形式で出力しました。使用したコマンドは web-ext lint -o json --pretty です。 

  8. そういえば、Qiita API のドキュメントには、利用制限に到達した際に、どのくらいの時間が経過したら利用制限が解除されるかが明記されていないように見えます。 

  9. ちなみに、一度も組織名を登録したことがない私の user.organization は空文字でした。一度組織名を登録して削除した場合とかに null になるのでしょうか?(試していないですが...) 

  10. こだわりはじめると、芋づる式にこだわって永遠に完成しないという悪癖があるので... 「1週間ぐらいで」と期限を決めたのもそのためです。 

  11. 現在、無職3ヶ月目。そろそろどこかに転職しなければと思いつつ、なかなか良いところが見つからず。 

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

【Nuxt.js】Nuxt実践編:ユーザーアイコン表示の切り替えをしよう

? この記事はWP専用です
https://wp.me/pc9NHC-ye

前置き

今回は簡単な実践編です??
作るものはこちら!

スクリーンショット 2020-08-09 9.28.22.png

マイページのあるサイトで
⬆️ユーザーアイコンの初期状態と
⬇️画像を設定した時の
表示の切り替えをやってみましょう??

コンポーネント間のやりとりや
演算子の復習になるのでぜひチャレンジしてください??

今回のような簡単な実践編も
今後増やしていこうと思ってます?

Let's try!

ディレクトリ

file
components/
--| atom/
----| icons/
------| IconUser.vue
--| molecule/
----| items/
------| ItemUserIconName.vue

pages/
--| user.vue

アトミックデザインに基づき作成します?
atomはアイコン部分。
他のmoleculeにも使い回す前提です。
moleculeはアイコンとユーザーの名前を追加したものです。

アドミックデザイン

要素の大きさごとにファイルを分けます?‍♀️
コンポーネントがどこにあるのか
分かりやすくなります?
大きさの分類はここが参考になります!
Atomic Designとは

atom

構成

IconUser.vueを作成しましょう?‍♀️
初期アイコンと、
アイコンが設定された場合の
表示を切り替えます。
アイコン画像は親で設定します。

背景は縦横64px
アイコンは縦横24px

アイコンはiconmonstrのsvgを使用しています?
https://iconmonstr.com/user-5-svg/

それではやってみましょう!!!

ticktack…

まずは切り替えをするので
v-ifとv-elseが使えますね…
親で画像を設定するのでpropsをstringで渡します。
stringを真偽値にしてv-ifとv-elseにしたい…

それではコードを見てみましょう✨?

解説

? 続きはWPでご覧ください?
https://wp.me/pc9NHC-ye

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

スムーススクロール(ページ内移動)

//ページ内リンクのURLが "#"から始まる時(ex. "#service"や"#about")

script.js
  $('a[href^="#"]').click(function () {
    // スクロールの速度
    var speed = 400; // ミリ秒で記述
    var href = $(this).attr('href');
    var target = $(href === '#' || href === "" ? 'html' : href);
    var position = target.offset().top;
    $('body,html').animate({ scrollTop: position }, speed, 'swing');
    return false;
  });

//ページ内リンクのURLが "/#"から始まる時(ex. "/#service"や"/#about")

script.js
$(document).on('click', 'a[href*="#"]', function() {
  let time = 400;
  let target = $(this.hash);
  if (!target.length) return;
  let targetY = target.offset().top;
  $('html,body').animate({scrollTop: targetY}, time, 'swing');
  return false;
});
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React初心者のための Material-UI 【Dialogの使い方編】

React + Material-UI を勉強しています。今日は「ダイアログ」「モーダル」などと呼ばれる機能の使い方をまとめます。

React のチュートリアルは一通り終えていざ自分で作ろうとするとつまずくことがあるかと思います。なかでも Material-UI の公式ページは便利だけど理解し辛いなあと思うので「使い方」の点からまとめます。ちなみに Dialog ってのはこんな奴ですね。GIFはMaterial-UI のサンプルページです。

2020-08-09 12.25.21.mov.gif

大雑把なプロセスは

  • Dialog のデザインを決める
  • Dialog を含むコンポーネントを作る SampleDialog
  • SampleDialog を開くトリガーを作る
  • state のリフトアップ

となります。

ダイアログのデザインを決める

Dialog を含むコンポーネントのデザインを決めます。直接 <Dialog /> に props を渡せるなら一手間省けますが、今回はひとまわりラッピングされたコンポーネントを想定します。

コンポーネントを作る

ダイアログとトリガーのコンポーネントを作ります。ここでは仮に SampleDialog, SampleCard と名付けます。トリガーのコンポーネントは Card にするつもりなので SampleCard と名付けました。 SampleCard のなかの Button をクリックしてダイアログを開くようにしたいと思います。

図のような構造になります。

trigger-dialog-Page-4.png

SampleDialog を開くトリガーを作る

SampleDialog を開くトリガーを作ります。つまり、 SampleCard のなかに Button を配置します。

class SampleCard extends React.Component {
  render() {
    return (
      <Card>
        <CardContent>Lorem ipsum dolor sit amet</CardContent>
        <CardActions>
          <Button variant="contained" color="primary">
            Open Dialog!
          </Button>
        </CardActions>
      </Card>
    );
  }
}

state のリフトアップ

このセクションがいちばん大変ですね笑。

React では2つ以上のコンポーネントで状態のやりとりをするために共通の親コンポーネントを作り state を一元化するという手法を取ります。これを state のリフトアップと呼んでいます。今回の場合は SampleDialog と SampleCard の間で open という bool 値を共有する必要があります。それを仮に Parent と名付けます。 Parent の state に open を用意します。

trigger-dialog-Copy of Page-4.png

ダイアログを開く

  1. SampleCard 内部の Button がクリックされる
  2. Parent の open が true になる
  3. SampleDialog の props.open が true になりダイアログが開く

というステップがあります。 では、1 から 2 を繋ぐにはどうすればいいのでしょうか?子コンポーネントから親コンポーネントの state をいじるのはできません。そのため、親コンポーネントから渡す props に親コンポーネントの state を変更するメソッドを渡します。たとえば、handleOpen というメソッドを作ります。中身は this.setState({open: true}) です。つまり、 Parent の state を変更するメソッドです。そして、 constructor でメソッドを.bind(this)しておきます(ここは説明不十分だと思います。後日追記する予定)。

trigger-dialog-Copy of Copy of Page-4.png
そして、このメソッドを SampleCard に渡します。
trigger-dialog-Copy of Copy of Copy of Page-4-2.png
Card の props にonClick: handleOpen が入っていますね。これを Button への props に渡すことで 1 から 2 へのステップが実現します。
trigger-dialog-Copy of Page-8-3.png

2から3へのステップは簡単です。Parent から SampleDialog に渡された props.open を<Dialog> に渡します。これでダイアログを開けるようになりました。

ダイアログを閉じる

Dialog は Backdrop(ダイアログの背景、つまり本体より外側の部分)をクリックすると onClose がコールバックされます。onCloseで Parent の open を false にすればダイアログも閉じられます。「ダイアログを開く」でやったのと同じように

  • Parent で メソッド handleClose を定義する
  • Parent から SampleDialog へ props でonClose={handleClose}を渡す

というステップです。

わかりにくかった点

Material-UI のサンプルは Hooks を使ったコードです。それを知らずに読んでいると混乱してしまいました。「俺の知ってる React じゃないぞ???」となりました笑。

おわり

途中で力尽きたので、コードサンプルは載せていません。気が向けば書きます笑。

自分を含めた初心者のための備忘録として書いています。間違い、もっとよい方法などございましたらぜひ教えてください。

参考

https://material-ui.com/api/dialog/
Dialog の props を調べられます。いろいろカスタムできます。

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

一から作るモダンフロントエンド環境【Webpack & Babel編】

どうも、ディベロッパーの羽田です。
最近業務でモダンJSフレームワークやwebpackを使うことが多くなってきたので開発環境の構築手順をまとめてみます。

概要

ウェブ開発のデファクトスタンダードとなりつつあるWebpackとモダンJSフレームワークを使用したフロント開発環境構築の手順をまとめました。
自分用のメモも兼ねてるので雑な部分や端折ったりしてる部分があります。

対象者

  • ある程度JSの知識がある人
  • モダンJSフレームワークに興味がある人
  • Webpackを使用した開発環境構築に興味ある人

章節

  • 【基本構築編】
  • 【Webpack & Babel編】←イマココ
  • 【css & sass編】(執筆中)
  • 【React & React Router編】(執筆中)
  • 【Redux編】(執筆中)

Webpack周辺を構築

パッケージ名 説明
webpack 複数のファイルを1つにバンドルして出力してくれるツール。JSファイルを起点にローダーを読み込ませることであらゆるファイルをバンドルできる。
webpack-cli コマンドラインでwebpackを実行するために必要なパッケージ。
webpack-dev-server webpackを使用した開発環境向けのwebサーバー。様々なオプションを設定可能。
html-webpack-plugin webpackから生成したJavaScriptを埋め込んだHTMLテンプレートを生成できる。
html-loader webpackにHTMLを読み込む。

webpack周りの構築を行います。
以下のコマンドを実行してパッケージをダウンロードしてください。

$ npm i -D webpack webpack-cli webpack-dev-server html-webpack-plugin html-loader

次にwebpackの設定を行うためにwebpack.config.jsを作成します。

webpackにはプラグインやローダー(JSファイル以外のファイルをバンドルするための拡張プログラム)を追加することができますがその際の設定もここに記述できます。

webpack.config.js
const path = require('path');
//node.js上でファイルパスを操作できるモジュール

const HtmlWebpackPlugin = require('html-webpack-plugin');
//html-webpack-pluginモジュール

const htmlWebpackPlugin = new HtmlWebpackPlugin({
  template: "./src/index.html",
  filename: "./index.html"
});

module.exports = {

    mode: 'development',
    //ビルド時の出力ファイルを圧縮するかしないかの設定。今回はdevelopment。

    entry: './src/index.js',
    //ビルド時に起点となるJSファイルを指定。

    output: {
      filename: 'bundle.js',
      //ビルド時に出力されるファイル名を設定
      path: path.resolve(__dirname, 'dist')
      //出力先のパスを指定(ここで絶対パスを指定するためにpathモジュールを使用)
    },

    devServer: {
        port: 8000,
        //ローカルサーバーのポート番号を指定
        open: true
        //webpack-dev-server起動時に自動でブラウザを開く設定
    },

    plugins: [
      htmlWebpackPlugin
      //html-webpack-pluginをwebpackに追加
    ]
}

Babelの設定

Babelとは最新バージョンのECMAScript(Ecma Internationalによって策定されたJavaScriptの標準仕様)を古い型へとコンパイルするツールです。ブラウザによっては最新のECMAScriptをサポートしていない場合があるのでReactなどのモダンJSフレームワークを使用する場合は必須となります。

パッケージ名 説明
@babel/core Babel本体のモジュール
@babel/cli コマンドラインでBabelを実行するために必要なパッケージ。
@babel/preset-env 自動で実行環境に合わせたバージョンでコードをコンパイルする。
babel-loader webpackにBabelを読み込む。

導入するには以下のコマンドを実行

$ npm i -D @babel/core @babel/cli @babel/preset-env babel-loader

Babelの導入に合わせてwebpack.config.jsに設定を追記します。

webpack.config.js
const path = require('path');
//node.js上でファイルパスを操作できるモジュール

const HtmlWebpackPlugin = require('html-webpack-plugin');
//html-webpack-pluginモジュール

const htmlWebpackPlugin = new HtmlWebpackPlugin({
  template: "./src/index.html",
  filename: "./index.html"
});

module.exports = {

    mode: 'development',
    //ビルド時の出力ファイルを圧縮するかしないかの設定。今回はdevelopment。

    entry: './src/index.js',
    //ビルド時に起点となるJSファイルを指定。

    output: {
      filename: 'bundle.js',
      //ビルド時に出力されるファイル名を設定
      path: path.resolve(__dirname, 'dist')
      //出力先のパスを指定(ここで絶対パスを指定するためにpathモジュールを使用)
    },

    devServer: {
        port: 8000,
        //ローカルサーバーのポート番号を指定
        open: true
        //webpack-dev-server起動時に自動でブラウザを開く設定
    },

    //Babel追記箇所
    loader: {
      //ここに各種ローダーを追加する
      rules: [
        {
          test: /\.js$/,
          exclude: /node_modules/,
          //コンパイル対象からnode_modulesを除外
          loader: "babel-loader",
          //ローダーの指定
          query:{
            presets: ["@babel/preset-env"]
          }
      },
      ]
    },

    plugins: [
      htmlWebpackPlugin
      //html-webpack-pluginをwebpackに追加
    ]
}

ビルド用のファイルを作成

Webpackを実行してアプリケーションファイルを出力するためには元となるファイルが必要です。

myAppフォルダのディレクトリ下にsrcフォルダを作成、srcフォルダ内部にindex.htmlファイルとindex.jsファイルを作成して下記のコードをコピペしてください。

index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>myapp</title>
</head>
<body>
    <h1 id="text"></h1>
</body>
</html>
index.js
const text = document.getElementById('text');
text.innerHTML = "Hello Webpack!"

実際にwebpackを実行してみる

webpackを実行するためのスクリプトをpackage.jsonに追記します。
内容は前回の続きからです。

package.json
{
  "name": "myapp",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack",
    "start": "webpack-dev-server"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/cli": "^7.10.5",
    "@babel/core": "^7.11.1",
    "@babel/preset-env": "^7.11.0",
    "babel-loader": "^8.1.0",
    "html-loader": "^1.1.0",
    "html-webpack-plugin": "^4.3.0",
    "webpack": "^4.44.1",
    "webpack-cli": "^3.3.12",
    "webpack-dev-server": "^3.11.0"
  }
}

追記箇所はscriptsプロパティの値です。
これで以下コマンドが使えるようになりました。

$ npm run build

$ npm run start

npm run buildがwebpackを実行してアプリケーションをビルドするコマンド。

npm run startがwebpack-dev-serverを実行してローカルサーバー上でファイルを実行するコマンドになります。

実際にビルドしてみるとディレクトリ内にdistフォルダが生成され、その中にアプリケーションファイルがあるのが確認できるはずです。

確認のためwebpack-dev-serverを実行して確認してみましょう。
npm run startを実行してください。

ブラウザ上にHello Webpack!と表示されれば成功しています。

ここまで出来たら一旦webpackの基礎構築は完了です。

次はcssや画像周りを構築していきます。
続く...

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

『GAFAコーディング面接こんな感じでした』を読んで

GAFAコーディング面接こんな感じでした - yambe2002’s diary を読んで、課題のコードを書いてみる気に。

元記事だと「URL の履歴を保存するデータ構造」がひとつだけど、ふたつ (進む用 + 戻る用) に分割した方が良さそう。

あと、今更 React のチュートリアルを読んだので、練習を兼ねて。

ついでに Bootstrap 5 alpha もチラ見しよう。

Web ブラウザの履歴を管理するようなコード

比較用に 2 種類をつくる。

感想

Bootstrap 5 alpha

React

  • state と props の違い
    • state
      • コンポーネント内部で使うプライベート変数のようなオブジェクト
      • 変更は必ず setState() メソッドを使うこと
    • props
      • 別のコンポーネントから渡される、読み取り専用のオブジェクト
  • React と HTML における属性名の差異
    • class -> className
    • for -> htmlFor
    • readOnly -> readOnly など
  • jQuery との比較
    • JSX とコンポーネントにより、JS 上での CSS セレクターによる要素の指定がほぼ不要に。
    • sate オブジェクトで状態を保持して、それを更新するだけで自動的に DOM 側にも反映できるので簡潔に記述できる。
  • TODO
    • コンポーネントをもっと分割できるのか?
    • フォームの検証はどう書くのが良いのか?
    • 良いコードをもっと読まないと。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ReactのRedux内でaxiosを使用した通信をする

はじめに

前々回のReactを使用してWeb画面を作成するでは、reactの簡単なサンプルと共に説明をまとめました。
前回のReduxとReduxToolkitを使用してReact内でデータを管理するでは、reduxのの簡単なサンプルと共に説明をまとめました。
今回はaxiosを使用してREST APIから情報を取得して画面に表示させるサンプルと説明をまとめようと思います。

環境

  • node.js: v12.18.2
  • webpack: 4.44.1
  • React: 16.13.1
  • Redux: 7.2.1
  • axios: 0.19.2

環境作成

前回のReactを使用してWeb画面を作成する前回のReduxとReduxToolkitを使用してReact内でデータを管理するの続きとなります。
環境構築などはそちらを見てください。

axios

REST APIへ通信するためにaxiosというライブラリを使用します。

axiosのインストール

npm install axios

Redux Thunk

axiosは非同期で通信を実行します。しかし、Reduxは非同期の処理をするのは若干手間です。それらを楽にするためにRedux Thunkというミドルウェアを使用します。

Redux Thunkのインストール

npm install redux-thunk

Babel/polyfill

axiosを使用するにあたってpolyfillが必要になります。

Babel/polyfillのインストール

npm install @babel/polyfill --save-dev

Reactでaxiosを使用する

Reactのソースの作成

前回のReduxとReduxToolkitを使用してReact内でデータを管理するの続きなので、ソースも前回のに対して変更していく形にします。

sliceファイルにREST API呼び出しの処理を追加

REST APIの呼び出しと呼び出した後の処理をsliceファイルに追加する。
今回呼び出すREST APIはhttp://localhost:3000/mess_api、ボディは{["message":"testMessage"]}を想定して、このtestMessageをstateのmessに入れて、画面に表示させます。

messageSlice.js
import { createSlice } from '@reduxjs/toolkit';
import axios from 'axios';

export const messageSlice = createSlice({
  name: 'message',
  ~~~ 前回と同じ ~~~
  reducers: {
  ~~~ 前回と同じ ~~~
    // action内にREST APIから取ってきたデータが入っている
    getRequest: (state, action) => {
      // body内の最初のmessageキーの値をstateに入れる
      state.mess = action.payload[0].message;
    }
  },
});

// 今回追加したgetRequestもエクスポートする
export const { sayhello, sayAmount, getRequest} = messageSlice.actions;

// RESTAPIの発行とgetRequestの呼び出しをする
export const getRequestAsync = amount => async dispatch => {
  // ここでREST APIの呼び出しをする
  const response = await axios.get('http://localhost:3000/mess_api').then(response => {
    // 呼び出した結果のボディだけgetRequestに与える(与えるものを返れば上のaction引数に入る内容も変わる)
    dispatch(getRequest(response.data));
  });
};

export default messageSlice.reducer;

storeファイルにthunkを追加

store.js
import { configureStore } from '@reduxjs/toolkit';
import thunk from 'redux-thunk';
import messageReducer from './slice/messageSlice';

export default configureStore({
  reducer: {
    message: messageReducer,
  },
  middlewares: thunk
});

slice経由でstoreを使用する

コンポーネントでREST APIを発行する際には、sliceで作成したactionを呼び出してstoreを操作します。

App.js
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { sayhello, sayAmount, getRequestAsync } from '../store/slice/messageSlice';

export function Message() {
  const message = useSelector(state => state.message.mess);
  const dispatch = useDispatch();
  const [messsageAmount, setMesssageAmount] = useState('2');
  ~~~ 前回と同じ ~~~
        {/* sliceで作成したgetRequestAsyncアクションを使用する */}
        <button aria-label="rest get" onClick={() => dispatch(getRequestAsync())}>
          通信
        </button>
        {/* テキストボックスとボタンにインポートしたものを適用する */}
        <input aria-label="set amount" value={messsageAmount} onChange={e => setMesssageAmount(e.target.value)} />
        <button onClick={() => dispatch(sayAmount(messsageAmount))}>
          テキスト変更
        </button>
      </div>
    </div>
  );
}

Reactの実行

前回同様にReactを開発用のサーバで起動してブラウザからアクセスしてください。

"./node_modules/.bin/webpack-dev-server"

通信ボタンを押すとREST APIを発行して、そのボディをstoreに格納することでWeb画面上のテキストも変わります。

REST APIサーバを立てる

REST APIさえ発行できれば何を使用しても良いのですが、手ごろな環境がない場合に簡単なREST APIサーバを立てる方法もまとめます。
node.jsがある環境なのでJSON Serverというライブラリを使用してREST APIを立てます。

JSON Serverのインストール

npm install -g json-server

JSON用ファイルの作成

JSONのエンドポイントとボディを定義したファイルをJSON形式で作成します。
今回はReactからエンドポイント:mess_api、ボディのキー:messageから情報を取っているためそのように記載します。

db.json

{
  "mess_api": [
    { "id": 1, "message": "testMessage" }
  ]
}

JSON Serverの起動

先ほど作成したJSON用ファイルを指定してJSON Serverを起動します。

json-server --watch db.json

コンソールに起動した旨が表示されればREST APIサーバの完成です。

終わりに

ReactのWeb画面作成に関しては、ここで一旦終わりにしようかと思います。React Routerなど他のライブラリもReactにありますが、特に難しくなかったのでまとめなくて良いかなと思います。
REST APIに通信できるようになるとWeb画面で出来ることが増えるため、とても便利だと思います。しかし、Reactでのaxiosの使い方とReduxでのaxiosの使い方が結構違うので苦労しました。
次回はreact-testing-libraryの使い方でもまとめてみようと思います。

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

コールバック関数について

はじめに

コールバック関数について全く理解していなかったのでここで自分用にアウトプット、メモしておきたいと思います!!
何か至らない点などあれば教えていただけると幸いです!!

コールバック関数とは

別の関数に渡す関数のこと
関数内では実際に処理を行わず、関数を引数として引き渡しす関数のこと
簡単に言うとこんなところ!!

使い方

//コールバック関数を実行する関数
const parentsCallback = (callback) => {
  console.log('I call callback');
  callback();
}
//渡すコールバック関数
const callback = () => {
  console.log('This is my callback');
}
//コールバック関数を引数で渡して実行!!
parentsCallback(callback);

このように関数を引数として扱う!!

なぜ使うのか

1.非同期処理などのときに行って欲しい順番で処理を実行したいとき
2.関数実行後の処理を自由に決めるため(関数を渡すことでその後の処理)

コールバック関数を使うと非同期での処理を管理できるようになる!!

const showHoge = () => {
  var hoge = "";
  setTimeout(function(){
    hoge  = "hoge";
    console.log(hoge);
  });
}
showHoge();

これだと再代入した "hoge"は入ってこない!!なぜならsetTimeoutと言う関数が非同期処理により飛ばされてしまい、そのままconsole.log(hoge)に移ってしまうからである!!

そのためsetTimeoutと言う外部通信での非同期処理を制御するためコールバック関数を使う!!

const logOut = (param) => {
  console.log(param)
}

function WithCallback(e){ 
  var hoge;
  setTimeout(function(){
    hoge = 'hoge';
    callback(hoge); //logOutにhogeを渡している!!
  });
}

WithCallback(logOut);
//こうすることで再代入後の値がlogに入ってくる!!

console.log(withCallback

このように非同期などの処理の後に行って欲しい処理を行う場合、関数をそこで呼び出すのではなく、引数として渡して呼び出す!!

最後に

複雑なことはまだ理解できていないのでコールバック関数の簡単な説明としてここではアウトプットしています!!
簡単すぎた、またもっと理解を深めたい方はこちらからどうぞ!!

参考

JavaScript中級者への道【5. コールバック関数】
JavaScriptコールバックを整理してみた【再入門】

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

自分用メモ コールバック関数について

はじめに

コールバック関数について全く理解していなかったのでここで自分用にアウトプット、メモしておきたいと思います!!
何か至らない点などあれば教えていただけると幸いです!!

コールバック関数とは

別の関数に渡す関数のこと
関数内では実際に処理を行わず、関数を引数として引き渡しす関数のこと
簡単に言うとこんなところ!!

使い方

//コールバック関数を実行する関数
const parentsCallback = (callback) => {
  console.log('I call callback');
  callback();
}
//渡すコールバック関数
const callback = () => {
  console.log('This is my callback');
}
//コールバック関数を引数で渡して実行!!
parentsCallback(callback);

このように関数を引数として扱う!!

なぜ使うのか

1.非同期処理などのときに行って欲しい順番で処理を実行したいとき
2.関数実行後の処理を自由に決めるため(関数を渡すことでその後の処理)

コールバック関数を使うと非同期での処理を管理できるようになる!!

const showHoge = () => {
  var hoge = "";
  setTimeout(function(){
    hoge  = "hoge";
    console.log(hoge);
  });
}
showHoge();

これだと再代入した "hoge"は入ってこない!!なぜならsetTimeoutと言う関数が非同期処理により飛ばされてしまい、そのままconsole.log(hoge)に移ってしまうからである!!

そのためsetTimeoutと言う外部通信での非同期処理を制御するためコールバック関数を使う!!

const logOut = (param) => {
  console.log(param)
}

function WithCallback(e){ 
  var hoge;
  setTimeout(function(){
    hoge = 'hoge';
    callback(hoge); //logOutにhogeを渡している!!
  });
}

WithCallback(logOut);
//こうすることで再代入後の値がlogに入ってくる!!

console.log(withCallback

このように非同期などの処理の後に行って欲しい処理を行う場合、関数をそこで呼び出すのではなく、引数として渡して呼び出す!!

最後に

複雑なことはまだ理解できていないのでコールバック関数の簡単な説明としてここではアウトプットしています!!
簡単すぎた、またもっと理解を深めたい方はこちらからどうぞ!!

参考

JavaScript中級者への道【5. コールバック関数】
JavaScriptコールバックを整理してみた【再入門】

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

一から作るモダンフロントエンド環境【基本構築編】

どうも、ディベロッパーの羽田です。
最近業務でモダンJSフレームワークやwebpackを使うことが多くなってきたので開発環境の構築手順をまとめてみます。

概要

ウェブ開発のデファクトスタンダードとなりつつあるWebpackとモダンJSフレームワークを使用したフロント開発環境構築の手順をまとめました。
自分用のメモも兼ねてるので雑な部分や端折ったりしてる部分があります。

対象者

  • ある程度JSの知識がある人
  • モダンJSフレームワークに興味がある人
  • Webpackを使用した開発環境構築に興味ある人

章節

  • 【基本構築編】←イマココ
  • 【Webpack & Babel編】
  • 【css & 画像バンドル編】(執筆中)
  • 【React & React Router編】(執筆中)
  • 【Redux編】(執筆中)

前提

実際のウェブ開発を想定して構築します

事前に導入しておくもの

  • Node.js
  • npm

上記ソフトウェアを使用するので環境に無ければインストールしておきます

$ node -v
v12.16.1

$npm -v
v6.13.4

執筆時点のバージョン情報。

作業環境と下準備

ターミナル or コマンドラインを開き、任意のディレクトリ内で以下コマンドを実行。

mkdir myapp

cd myapp

myAppディレクトリ内で、下記コマンドを実行。

$ npm init -y

すると以下のjsonファイルが生成されます。

packege.json
{
  "name": "myapp",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

ここまでが下準備。
続きは以下リンクから。

一から作るモダンフロントエンド環境【Webpack & Babel編】

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

三項演算子の世界へようこそ

はじめに

三項演算子、使ってますか?
先日とうとう後輩の @miriwo 氏がめでたく三項演算子に目覚めたということで、これはめでたいということで筆を執りました。

PHP 「?:」 の正体を知ってすっきりした話

まあ、タイトルだけ見るとエルビス演算子に目覚めたのかなって気になってましたけど。
それはそれで大変便利なのです。

三項演算子とは

例を見てみましょう。

image.png
転載元: https://chiicomi.com/smp/r_news_detail?id=19e92c3c-0b86-11e6-8bd0-a0369f7b8e6e

こちらは我らがジェフユナイテッド市原千葉のマスコット、ジェフィとユニティです。
(千葉なのに)秋田犬の双子のわんこなんですが、キャラクターデザイン上の取り計らいで、大きさと背番号以外は区別がつかないようになっています。
ある程度のレベルのジェフサポになると 「キリッとしてるほうがジェフィ、やんちゃな方がユニティ」 とか 「耳がシャキッとしてるほうがジェフィ、丸っこい耳がユニティ」 とか言い出したりするんですが、他サポからは 「ジェフサポは(いろいろな意味で)あたまがおかしい」 と思われているので聞き入れてもらえません。

なので、犬の背番号が2だったらジェフィ、2じゃなかったユニティを name に代入するコードをJavaScriptで書いてみました。

const name = (dog.uniform_number === 2) ? 'Jefy' : 'Unity';

? の左に判定条件、その右に判定が true のときの値、 : を挟んでその右が false のときの値です。
これをたまにいる三項演算子否定派が書くとこんな感じになります。

const name = '';

if(dog.uniform_number === 2){
    name = 'Jefy';
} else {
    name = 'Unity';
}

長い……

要はこういうのをif文ではなく式で簡便に表現できるというのが三項演算子です。
実は本名は「条件演算子」といいます。

https://ja.wikipedia.org/wiki/%E6%9D%A1%E4%BB%B6%E6%BC%94%E7%AE%97%E5%AD%90

三項演算子は結構どの言語にも実装されています。

入れ子の三項演算子

あんまりやってはいけないのですが、三項演算子はネスト(入れ子)して書くことができます。

例を見てみましょう。

image.png

実はジェフのマスコットはジェフィとユニティのオス犬の双子だけではなく、どこからともなく現れた みなちゃん という白い雑種のメス犬もいます。写真の一番右の子ですね。
ちなみにジェフィとユニティは毎年シーズンシートを買って入っている一般サポーターらしいのですが、みなちゃんはれっきとしたジェフユナイテッド株式会社の社員です。

そんな設定の話は置いといて、みなちゃんの背番号はサポーターナンバーと呼ばれる12番です。つまりこんな感じになります。

const name = (dog.uniform_number === 2) ? 'Jefy' : (dog.uniform_number === 9) ? 'Unity' : 'Mina-chang';

どうですか? 見づらくてハゲそうですよね。
見やすくするテクニックとして、記号の前で改行するというのがあります。
外人が書くコードでよく見かけますよね。

const name = (dog.uniform_number === 2) 
    ? 'Jefy'
    : (dog.uniform_number === 9)
        ? 'Unity'
        : 'Mina-chang';

ただ、この入れ子の三項演算子、PHPだけなぜか動作が違います。あいつはなぜか 右から評価 するので、こんな感じに書かないと思ったとおりになりません。

$name = (dog.uniform_number === 2) 
    ? 'Jefy'
    : ( (dog.uniform_number === 9)
        ? 'Unity'
        : 'Mina-chang');

不毛感がすごい……

おわりに

三項演算子の特殊な書き方として、エルビス演算子とかnull合体演算子の話は次回書きます。
それではお元気で! Enjoy, Summer!!

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

(ジェフユナイテッドで覚える)三項演算子の世界へようこそ

はじめに

三項演算子、使ってますか?
先日とうとう後輩の @miriwo 氏がめでたく三項演算子に目覚めたということで、これはめでたいということで筆を執りました。

PHP 「?:」 の正体を知ってすっきりした話

まあ、タイトルだけ見るとエルビス演算子に目覚めたのかなって気になってましたけど。
それはそれで大変便利なのです。

三項演算子とは

例を見てみましょう。

image.png
転載元: https://chiicomi.com/smp/r_news_detail?id=19e92c3c-0b86-11e6-8bd0-a0369f7b8e6e

こちらは我らがジェフユナイテッド市原千葉のマスコット、ジェフィとユニティです。
(千葉なのに)秋田犬の双子のわんこなんですが、キャラクターデザイン上の取り計らいで、大きさと背番号以外は区別がつかないようになっています。
ある程度のレベルのジェフサポになると 「キリッとしてるほうがジェフィ、やんちゃな方がユニティ」 とか 「耳がシャキッとしてるほうがジェフィ、丸っこい耳がユニティ」 とか言い出したりするんですが、他サポからは 「ジェフサポは(いろいろな意味で)あたまがおかしい」 と思われているので聞き入れてもらえません。

なので、犬の背番号が2だったらジェフィ、2じゃなかったユニティを name に代入するコードをJavaScriptで書いてみました。

const name = (dog.uniform_number === 2) ? 'Jefy' : 'Unity';

? の左に判定条件、その右に判定が true のときの値、 : を挟んでその右が false のときの値です。
これをたまにいる三項演算子否定派が書くとこんな感じになります。

const name = '';

if(dog.uniform_number === 2){
    name = 'Jefy';
} else {
    name = 'Unity';
}

長い……

要はこういうのをif文ではなく式で簡便に表現できるというのが三項演算子です。
実は本名は「条件演算子」といいます。

https://ja.wikipedia.org/wiki/%E6%9D%A1%E4%BB%B6%E6%BC%94%E7%AE%97%E5%AD%90

三項演算子は結構どの言語にも実装されています。

入れ子の三項演算子

あんまりやってはいけないのですが、三項演算子はネスト(入れ子)して書くことができます。

例を見てみましょう。

image.png

実はジェフのマスコットはジェフィとユニティのオス犬の双子だけではなく、どこからともなく現れた みなちゃん という白い雑種のメス犬もいます。写真の一番右の子ですね。
ちなみにジェフィとユニティは毎年シーズンシートを買って入っている一般サポーターらしいのですが、みなちゃんはれっきとしたジェフユナイテッド株式会社の社員です。

そんな設定の話は置いといて、みなちゃんの背番号はサポーターナンバーと呼ばれる12番です。つまりこんな感じになります。

const name = (dog.uniform_number === 2) ? 'Jefy' : (dog.uniform_number === 9) ? 'Unity' : 'Mina-chang';

どうですか? 見づらくてハゲそうですよね。
見やすくするテクニックとして、記号の前で改行するというのがあります。
外人が書くコードでよく見かけますよね。

const name = (dog.uniform_number === 2) 
    ? 'Jefy'
    : (dog.uniform_number === 9)
        ? 'Unity'
        : 'Mina-chang';

ただ、この入れ子の三項演算子、PHPだけなぜか動作が違います。あいつはなぜか 右から評価 するので、こんな感じに書かないと思ったとおりになりません。

$name = (dog.uniform_number === 2) 
    ? 'Jefy'
    : ( (dog.uniform_number === 9)
        ? 'Unity'
        : 'Mina-chang');

不毛感がすごい……

おわりに

三項演算子の特殊な書き方として、エルビス演算子とかnull合体演算子の話は次回書きます。
それではお元気で! Enjoy, Summer!!

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

【Rails5】gem gonの使い方 ~RailsからJSに変数を渡す方法~

実装した機能

開発環境

ruby > 2.6.5
rails > 5.2.4.2
gon > 6.3.2

今回はシンプルに、RailsのControllerで定義した変数をJSに渡してhtmlに表示します。
Railsに標準搭載されているhoge.js.erb形式やcoffeescriptで書いたほうがスムーズだったりしますが、うまく表示できないときの回避策としても使えるGemかなと思ったのでご紹介します。

※注意
Gemの仕様上、htmlソース内に変数自体が表示されるので、セキュリティ的に隠す必要があるものには使わないほうが良いです。

導入方法

まずは、Gemfileに追加

Gemfile
gem 'gon'

そして、インストール

Terminal
$ bundle install

RailsのView内にコードを記述
今回は全ページで使う想定ですが、必要なviewに記述されていればOKです。
JSを読み込むのと同じく、読み込みのタイミングによって挙動が異なるので読み込み順には注意しましょう。

application.html.erb
・・・
<%= include_gon %>
<%= javascript_include_tag "application" %>
・・・
</body>

これで準備完了です。

変数を渡す

RailsのControllerでGon用の変数を定義します。
といっても、変数の頭にgon.をつけるだけです。

Users.erb
def show
  @user = User.find(1)
  gon.username = @user.name #これをJSに渡します
end

JSに変数を渡してhtmlに表示します。

index.html.erb
・・・
<p id="name"></p>
・・・
application.js
・・・
let name = gon.username
$('#name').html(name);
・・・

このように表示されればOKです。

index.html
・・・
<p id="name">こうへい</p>
・・・

html内の表示について

冒頭で「html内に変数が見えちゃう」という話をしましたが
実際にはこのように読み込まれています。
良くも悪くもわかりやすい構造になっていますね笑

index.html
・・・
  <script>
    //<![CDATA[
      window.gon={};gon.username="こうへい";
    //]]>
  </script>
</body>
・・・

その他機能

単純に変数を渡す以外にも

  • 配列、ハッシュを渡す
  • 変数をすべて読み込む

などの使い方もできるようです。
むしろ、Gemの役割としてはこっちのほうが大きいのかもしれませんね。
詳しくは公式ページをご確認ください。

Gon公式
https://github.com/gazay/gon

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

react-hook-formの基本的な使い方

Reactで入力フォームを開発する際に便利なライブラリであるreact-hook-formの基本的な使い方についてまとめておきます。

公式ドキュメントが丁寧に用意されていますので、網羅的に機能を知りたい方はそちらをご覧ください。

実装手順

基本

以下のようなフォームに値を入力させて、submitする想定で改修を加えていきます。

import React from 'react';

function App() {
    return (
        <form>
            <input name="title" />
            <textarea name="content" />
        </form>
    );
}

export default App;

ライブラリのインストール

ライブラリをインストールします。

npm install react-hook-form

or 

yarn add react-hook-form

refの登録

次にライブラリが各フィールドを参照できるよう、registerメソッドをそれぞれのrefに追加します。
TypeScriptの場合は予め、useFormに対して各フィールドを持った型を渡しておきましょう。

import React from 'react';
import { useForm } from 'react-hook-form';

type Post = {
    title: string;
    content: string;
};

function App() {
    const { register } = useForm<Post>();
    return (
        <form>
            <input
                name="title"
                ref={register({ required: true, maxLength: 30 })}
            />
            <textarea name="content" ref={register} />
        </form>
    );
}

export default App;

react-hook-formには基本的なバリデーションも用意されており、このregisterを追加する際に設定することが可能です。

利用できるバリデーションについては以下を参照ください。

https://react-hook-form.com/get-started/#Applyvalidation

バリデーションエラーについて

設定したバリデーションでエラーが発生した場合はreact-hook-formが用意しているerrorオブジェクトを利用してハンドリングすることができます。

import React from 'react';
import { useForm } from 'react-hook-form';

type Post = {
    title: string;
    content: string;
};

function App() {
    const { register, errors } = useForm<Post>();
    return (
        <form>
            <input
                name="title"
                ref={register({ required: true, maxLength: 30 })}
            />
            {errors.title && <span>タイトルは必須です。</span>}
            <textarea name="content" ref={register} />
        </form>
    );
}

export default App;

submit

そして、formタグに対して、コールバックを渡したhandleSubmitを設定します。

import React from 'react';
import { useForm } from 'react-hook-form';

type Post = {
    title: string;
    content: string;
};

function App() {
    const { register, handleSubmit, errors } = useForm<Post>();
    const onSubmit = (data: Post) => {
        console.log(data);
    };
    return (
        <form onSubmit={handleSubmit(onSubmit)}>
            <input
                name="title"
                ref={register({ required: true, maxLength: 30 })}
            />
            {errors.title && <span>タイトルは必須です。</span>}
            <textarea name="content" ref={register} />
        </form>
    );
}

export default App;

これでフォームが送信アクションが発生した際に、入力した値がonSubmitに渡されるようになります。

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

JavaScriptの制御構文(1)〜if命令とswitch命令〜

はじめに

こんにちは!
今日から時間をかけて、1度ある程度読んだJavaScriptのテキストの復習をしていこうと計画したので、ついでにこちらに投稿して備忘録として残してみようと思います!
まず、最初のテーマは、制御構文です!

制御構文

制御構文って色々ありますよね!

  • if命令

  • switch命令

  • while/do...while命令

  • for命令
    (for命令の中でも、配列なのかオブジェクトなのかによって使い分けもあります。)

この中で、今回は、if命令とswitch命令について投稿します。
この2つの共通点としては、処理を「分岐する」ことです!

if命令

処理を分岐するための命令には、if命令とswitch命令の2つがあります。

if (条件式) {
    trueの場合の処理
} else {
    falseの場合の処理
}
let x = 75;
if (x >= 60) {
  console.log('合格');
} else {
  console.log('不合格');
}

/* 変数xの値は、60以上であるため、条件式はtrueになるので、consoleには、
「合格」が表示される。*/

上記のような構文を用いて、if命令を実行し、処理の分岐を実現できます。
また、else以降のfalseの場合の処理を省略して、trueの場合のみ、処理を指定することも可能です。

また、trueかfalseの2つに処理を分岐させるのではなく、複数分岐させることもできます。

if (条件式1) {
    条件式1がtrueの場合の処理
} else if (条件式2) {
    条件式2がtrueの場合の処理
} else {
    全ての条件式がfalseの場合の処理
}
let x = 77;
if (x >= 90) {
  console.log('Aです。');
} else if (x >= 75) {
  console.log('Bです。'); 
} else if (x >= 60) {
  console.log('Cです。');
} else {
  console.log('不合格です。');
}

/* 変数xの値は、77であり、75以上、90未満である。したがって、consoleには、
「Bです。」が表示される。*/

else ifを必要な数だけ書き、分岐させます。
しかし、条件式を適切な順番で記述しないと、思わぬところで条件が一致し、処理が終了してしまうので、注意が必要です。

switch命令

if命令は、条件によっては、同じような形式で条件が並ぶので、冗長な(長ったらしい)記述になってしまいます。。

例)

if (rank === 'A') {
    console.log('Aランクです。');
} else if (rank === 'B') {
    console.log('Bランクです。');
} else if (rank === 'C') {
    console.log('Cランクです。');
} else {
    console.log('ランク外です。');
}

上記のような、コードが存在したとすします。条件式をそれぞれ見てみると、rank === ‘A’や、rank === ‘b’というように、「rank ===」という形式の条件が複数連なっています。
しかし、このような、長ったらしい、同じような条件の分岐命令も、switch命令を利用すれば、見やすく改善できます。

switch (式) {
    case 値1:
        式 = 値1の場合の処理
        break;
    case 値2:
        式 = 値2の場合の処理
        break;
    case 値3:
        式 = 値3の場合の処理
        break;
    default:
        式の値が全て一致しなかった場合の処理
        break;
}

このswitch命令には1つ注意点があり、それぞれのcaseの処理の最後には、break命令の記述を忘れないことです。switch命令は、条件式に合致するcaseに移動するだけで、そのcaseの処理を終えたとしても、switch命令自体が終了するわけではないため、そのまま、合致したcase以降のcaseの処理も次々と実行してしまうのです。そうならないために、breakを忘れないように気をつけましょう。

意図的にbreak命令を記述しない時はある。(フォールスルー)

let rank = 'B';

switch (rank) {
    case 'A':
    case 'B': 
    case 'C':
        console.log('合格');
        break;
    case 'D':
        console.log('不合格');
        break;
    default:
        console.log('ランク外');
        break;
}

/* 変数rankの値は、Bであるため、consoleには、
「合格」が表示される。*/

上記では、変数rankの値が、Aの場合、Bの場合、Cの場合では、全て同じ処理をしようとしています。この場合、case ‘A’とcase ‘B’、case ‘C’の間にbreak命令を挟んで、終わらせる必要はありません。
仮にbreak命令を記述してしまうと、そこでswitch命令自体が終了してしまうため、適切な処理が行われなくなってしまいます。(行うべき処理があるにも関わらず、case 'A'やcase 'B'の場合に、何も処理が行われない)
ただし、処理内容の詳細を記述している、case ‘C’やcase ‘D’、defaultの後には、break命令が必要になってきます。

以上!!今日のif命令・switch命令のまとめでした!
理解不足による不備等ございましたら、コメントをお願い致します。

参考文献

山田祥寛著『[改訂新版]JavaScript本格入門 ~モダンスタイルによる基礎から現場での応用まで』(2018)
※第1刷は、2016年。

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

JavaScriptで全角のひらがなカタカナ小文字を対応する大文字に変換する

例えば「ぁ」は「あ」、「っ」は「つ」、「ァ」なら「ア」みたいに、小文字を大文字に変換する。
Unicode上でぁはU+3041であはU+3042、他の文字も同様に小文字が来て、次に大文字が来るといったように並んでいるので、小文字のUnicode(16進数値)に1足した値が大文字になる。

unescape("%u" + (parseInt(escape("").substr(2,4), 16) + 1).toString(16))

無理やりやればこれでできたけど釈然としない。。。

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

jsでHTML要素を追加する方法はこれを見てくれ。

欲しいのはこれだろう??

jqueryは使わない。
insertAdjacentHTMLしか載せない。
けどそれでいいだろ?

<div id="BASE"><p>ぺぽー</p></div>

① ここには「beforebegin」

var BASE = document.getElementById('BASE');
BASE.insertAdjacentHTML('beforebegin','<p>①に入るよん</p>');

② ここには「afterbegin」

var BASE = document.getElementById('BASE');
BASE.insertAdjacentHTML('afterbegin','<p>②に入るよん</p>');

③ ここには「beforeend」

var BASE = document.getElementById('BASE');
BASE.insertAdjacentHTML('beforeend','<p>③に入るよん</p>');

④ ここには「afterend」

var BASE = document.getElementById('BASE');
BASE.insertAdjacentHTML('afterend','<p>④に入るよん</p>');

以上。

一瞬でわかるやつが欲しかった、、

「js 要素 追加」でググって3秒で答えが欲しかった。
ありがとう、たくさんの記事がヒットしてどれも丁寧に書かれてました。
が、丁寧すぎた!

内容量が多くて欲しい情報を探すのに時間がかかる。
実際、コーダーにはこれくらいで十分だ。

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

【一番速い】jsでHTML要素を追加する方法はこれを見てくれ。

欲しいのはこれだろう??

jqueryは使わない。
insertAdjacentHTMLしか載せない。
けどそれでいいだろ?

<div id="BASE"><p>ぺぽー</p></div>

① ここには「beforebegin」

var BASE = document.getElementById('BASE');
BASE.insertAdjacentHTML('beforebegin','<p>①に入るよん</p>');

② ここには「afterbegin」

var BASE = document.getElementById('BASE');
BASE.insertAdjacentHTML('afterbegin','<p>②に入るよん</p>');

③ ここには「beforeend」

var BASE = document.getElementById('BASE');
BASE.insertAdjacentHTML('beforeend','<p>③に入るよん</p>');

④ ここには「afterend」

var BASE = document.getElementById('BASE');
BASE.insertAdjacentHTML('afterend','<p>④に入るよん</p>');

以上。

一瞬でわかるやつが欲しかった、、

「js 要素 追加」でググって3秒で答えが欲しかった。
ありがとう、たくさんの記事がヒットしてどれも丁寧に書かれてました。
が、丁寧すぎた!

内容量が多くて欲しい情報を探すのに時間がかかる。
実際、コーダーにはこれくらいで十分だろう?

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

二度と忘れるな。swiperで見切れさせる方法はこれだって言ってんだろ!!

俺は、バカなのか...

swiperでのカルーセル案件、今までに何度もやってきただろ...
なんで見切れさせるのに毎回苦戦しているんだよ...!

二度とこいつに時間をかけなくてもいいように、ここに私の奮闘記を残します。

やりたいのはこういうカルーセル。
スクリーンショット 2020-08-09 22.51.25.png
端っこのコンテンツがちょっと見切れちゃってるようなのを実装したい時のお話です。

slidesPerViewは小数が使えるんだ!!

つまりこう。

var myswiper = new Swiper ('.swiper-container', {
  centeredSlides: true,
  slidesPerView: 2.2,
  spaceBetween: 16,
  pagination: {
    el: '.swiper-pagination',
    clickable: true,
  },
  navigation: {
    nextEl: '.swiper-button-next',
    prevEl: '.swiper-button-prev',
  },
});

この小数が使えるってことと、
「centeredSlides: true」で中央寄せにしてあげるってことがわかってれば一瞬。
なんてことはない。

なんだこれはぁぁっぁあ!

スクリーンショット 2020-08-09 23.03.00.png
中央寄せじゃなくなったとき(偶数個見せたいとき)。
これが微妙にめんどくさかった。

こうしなさい。

var myswiper = new Swiper ('.swiper-container', {
  slidesPerView: 2.2,
  spaceBetween: 16,
  pagination: {
    el: '.swiper-pagination',
    clickable: true,
  },
  navigation: {
    nextEl: '.swiper-button-next',
    prevEl: '.swiper-button-prev',
  },
});

js側ではやることはあまりない。
さっきのと比べると中央寄せの「centeredSlides: true」を消したくらい。

大事なのはcssで、「.swiper-container」に対してpadding-leftを当ててあげること。

.swiper-container {
  padding-left: 125px;
}

みたいに。
ちゃんとそれっぽい位置になるように微調整が必要だったり、
SPとかだとpx→vwにして、、とか必要になるけど、
やり方を検討するとこはもう終わってるから簡単だよね☆

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

swiperの雛形にはこれを使え!!

※公式のフォーマットとは異なります!
※使えなくなるオプションがあるかもしれません!

完全に個人用です。
よくあれこれどうやったっけと忘れて、その度時間けかちゃってるのでメモ残しておきます。

これを、使え!!

<div class="mycontainer">

  <div class="swiper-container">
    <div class="swiper-wrapper">
      <div class="item swiper-slide">1</div>
      <div class="item swiper-slide">2</div>
      <div class="item swiper-slide">3</div>
      <div class="item swiper-slide">4</div>
      <div class="item swiper-slide">5</div>
    </div>
  </div>

  <!-- ページネーション -->
  <div class="swiper-pagination"></div>

  <!-- ナビゲーションボタン -->
  <div class="swiper-button-prev"></div>
  <div class="swiper-button-next"></div>

</div>

公式と違うところは、
マルポチやボタンが「.swiper-container」の外に出てることです。

ここから下では、その理由とか経緯を説明していきます。

なんでそんなことを

swiperで次へ/戻るボタンや、マルポチを公式通りの方法でhtmlを書くと、

<div class="swiper-container">
  <div class="swiper-wrapper">
    <div class="item swiper-slide">1</div>
    <div class="item swiper-slide">2</div>
    <div class="item swiper-slide">3</div>
    <div class="item swiper-slide">4</div>
    <div class="item swiper-slide">5</div>
  </div>

  <!-- マルポチ -->
  <div class="swiper-pagination"></div>

  <!-- 次へ/戻るボタン -->
  <div class="swiper-button-prev"></div>
  <div class="swiper-button-next"></div>
</div>

ですね。

ただ、僕の扱う案件で多いのは、こんなデザイン。
スクリーンショット 2020-08-09 22.35.18.png

緑で囲った部分が赤枠のカルーセルエリアから飛び出していることがお分かりいただけるかと思います。
これ、公式のフォーマット通りの形で実現しようとするとかなりめんどくさい。

なぜか。

理由はswiperの一番外側のクラスにあたる「.swiper-container」に対して公式cssでoverflow: hiddenが当たってしまっているからです。
なので素直にフォーマットに従いつつcssでマルポチなんかのbottomを下げたとしてもこうなっちゃうわけです。
スクリーンショット 2020-08-09 22.16.09.png
いやん!

だから俺は外に出した

cssで公式のoverflow:hiddenを上書きして、、
とかもありかもしれませんが一番外に出しちゃうのが楽かなという結論に至りました。

つまりこうです。

<div class="mycontainer">

  <div class="swiper-container">
    <div class="swiper-wrapper">
      <div class="item swiper-slide">1</div>
      <div class="item swiper-slide">2</div>
      <div class="item swiper-slide">3</div>
      <div class="item swiper-slide">4</div>
      <div class="item swiper-slide">5</div>
    </div>
  </div>

  <!-- ページネーション -->
  <div class="swiper-pagination"></div>

  <!-- ナビゲーションボタン -->
  <div class="swiper-button-prev"></div>
  <div class="swiper-button-next"></div>

</div>

これで公式cssのoverflow:hiddenから解き放たれました。
代償として、マルポチのスタイルが死んでしまうようですが...
スクリーンショット 2020-08-09 22.26.27.png

ただ、案件でswiper公式のスタイルをそのまま使うことなんてないでしょう。
どうせ何かしらで上書きするのだからこんなの屁でもありません。

一応、それっぽくする修正css置いてはおきますが。

.swiper-pagination {
  left: 50%;
  bottom: -40px !important;
  transform: translateX(-50%);
}
.swiper-pagination-bullet {
  margin: 0 4px;
}

スクリーンショット 2020-08-09 22.33.11.png

完璧ですね☆

※使用したswiperのバージョンは5.4.5でした。

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

addClassで追加したクラスのstyleが反映されない件についての質問

jQueryのaddClass,removeClassを利用して、クリックしたらプロフィールが右からスライドしてくるアニメーションを作っています。
ハンバーガーメニューをクリックした際にtranslateX(100vw)が0%となってほしいのですが、デベロッパーツールで確認したところcssが上書きされていないようです。(クラスの付与は行われています)
!importantを使用しても同じ結果でした。

よろしくお願い致します。

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

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="stylesheet.css">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.min.css" integrity="sha256-UzFD2WYH2U1dQpKDjjZK72VtPeWP50NoJjd26rnAdUI=" crossorigin="anonymous" />
  <title></title>
</head>
<body>
  <script src="https://code.jquery.com/jquery-3.3.1.js"></script>
  <script>
    $(document).ready(function() {
      $('.menu').on('click', function() {
        if ($('.profile').hasClass('.is-active')) {
          $('.profile').removeClass('.is-active');
        } else {
          $('.profile').addClass('.is-active');
        }
      });
    });
  </script>
  <header>
    <div class="menu">
      <i class="fas fa-bars"></i>
    </div>
    <div class="profile">
      <p>texttexttexttext</p>
      <p>texttexttexttext</p>
      <div class="mail">
        <p><i class="far fa-envelope"></i></p>
      </div>
    </div>
  </header>
</body>
</html>
.menu {
  display: block;
  cursor: pointer;
}
/* アニメーション前の状態 */
.profile {
  transform: translateX(100vw);
  transition: all 0.3s linear;
  z-index: 2;
  top: 0;
  right: 0;
}
/* アニメーション後の状態 */
.profile.is-active {
  transform: translateX(0%);
}

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

Webの表示速度を意識する1(idle速度の改善)

今読んでいる本。

Webフロントエンド ハイパフォーマンス チューニング

気になったところをまとめていきたいと思い書きます。その1。

ーーーーーーーーーーーーーーーーーーー

いくつかのポートフォリオサイトを検証ツールでみてみたところ、あるサイトだけ数値上「idle」のところが非常に時間がかかっておりました。

idleとは
responseやanimation、Loadが終わった後に、ユーザーのアクションを待っている状態
のことらしい。

では、「ユーザーのリアクションを待つ」状態がなぜ特定のサイトだけ長くなるのか?というと

最近のブラウザのレンダリングエンジンは、jsの処理とユーザーからのアクションを同じメインスレッドで実行しているため、
webページが何も処理を行っていないように見えても、裏側でjsが実行されているとユーザーからの入力が遅延される。。。

とのこと。(前述の本より)

それを踏まえてサイトを見てみると、確かに、最初にJqueryのsettimeoutで「アニメーションでボワっ」と文字を出して、その後に操作を受け付けるようにしています。
このタイミングが少し長いのが原因かも、、?と思い半分にしてみたところやっぱりここが原因で、速度が2200ミリ秒弱短くなりました。

スクリーンショット 2020-08-11 2.38.08.png

スクリーンショット_2020-08-11_0_34_31_png-2.png

例えば逆に5倍くらいの長さにすると、もちろんidolの値はグッと伸びました。し、体感ももちろん長く感じました

ーーーーーーーーーーーーーーーーーーーー

じゃあもっと短くしてもいいんじゃない?

確かにそれも一つの選択肢ですが、
・サイト訪問時に間を持たせた動きをつけて目をひきたい
・そもそもどれくらいの長さが妥当なのかは主観

という考え方や、そもそもコンテンツの読み込みデータ量が多くて、
最初にアニメーションをわざと表示させてる間に読み込ませている、、、といったケースもあると思います。(ローディングアニメーションなど。読み込み中で何も表示されないより個人的には良いと思います。)

そんな感じで必ずしも一瞬で終わるアニメーションが良いとも限らないんじゃないかなと思ったりしています。

そして速度を遅いと感じるかどうかは主観であるところが大きいので、
感覚ではなく、数値で見ようというのが今回使った検証ツールのPerformanceでした。

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

$('input[value="aaa"]') は「"aaa"と入力されたinput要素」ではない

かなり驚いたので初めて記事を書きます。

だめな例

"aaa"と入力されたinput要素のvalue属性を表示するプログラムのつもり。(つまり"aaa"が返ってくるはず)

HTML
<input type="text" name="phrase">
JavaScript
const check = () =>{
   const target = $('input[value="aaa"]');
   console.log(target.val());
}

image.png
"aaa"と入力して、consoleでcheck();を呼び出すと当然"aaa"...ではなくundefined

原因

どうやらこちらによると、

If you are changing value dynamically it wouldn't get selected by attribute selector.

この属性セレクタはユーザーによって動的に変更された値に対しては使えないそうです。1

Attribute selector will not check the dom node's value property it only targets the element's attribute

この属性セレクタは、その要素の属性のみを対象とし、ノードのvalueプロパティはチェックされません。1

解決策

filterを使いましょう。2

HTML
<input type="text" name="phrase">
JavaScript
const check = () =>{
   const target = $('input').filter(function() {
       return this.value == "aaa"
   });
   console.log(target.val());
}

image.png
"aaa"と入力して、consoleでcheck();を呼び出すと..."aaa"!!

参考

javascript - Why Jquery selector by 'value' not work in case of dynamic change - Stack Overflow


  1. もちろん、元からinputvalue="aaa"と指定されているものをセレクタで見つける際には使えます。また、ブラウザのデバッグツールを使って後から属性を追加した場合も同様。ポイントは動的かどうかというより属性として与えられているかどうかみたいですね。 

  2. ここでアロー関数で書こうとするとコケます。詳しくは"js アロー関数 this"。  

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