【Sass】@extendと@mixinの使い分け

定義したスタイルを複数のクラスで使用できるSassの@extendと@mixinの違いが分からなかったので、両者を比較して使い分けるべきケースをまとめる。

@extendの特徴

定義したスタイルを他のクラスに継承して使用できる

/* Sass */
.btn-base{
  border:1px solid;
  border-radius: 0.5rem;
  padding: 0.5rem;
}
.btn-success{
  @extend .btn-base;
  background-color: green;
}
.btn-error{
  @extend .btn-base;
  background-color: red;
}
/* CSS */
.btn-base, .btn-error, .btn-success {
  border: 1px solid;
  border-radius: 0.5rem;
  padding: 0.5rem;
}

.btn-success {
  background-color: green;
}

.btn-error {
  background-color: red;
}

プレースホルダーを使用する場合

継承元になったクラス(.btn-base)を利用しない場合は、プレースホルダー(%)を使用することでCSSコンパイルされない

/* Sass */
%btn-base{
  border:1px solid;
  border-radius: 0.5rem;
  padding: 0.5rem;
}
.btn-success{
  @extend %btn-base;
  background-color: green;
}
.btn-error{
  @extend %btn-base;
  background-color: red;
}
/* CSS */
.btn-error, .btn-success {
  border: 1px solid;
  border-radius: 0.5rem;
  padding: 0.5rem;
}

.btn-success {
  background-color: green;
}

.btn-error {
  background-color: red;
}

@mixinの特徴

定義したスタイルを他のクラスで呼び出して使用できる

/* Sass */
@mixin btn-base{
  border:1px solid;
  border-radius: 0.5rem;
  padding: 0.5rem;
}
.btn-success{
  @include btn-base;
  background-color: green;
}
.btn-error{
  @include btn-base;
  background-color: red;
}
/* CSS */
.btn-success {
  border: 1px solid;
  border-radius: 0.5rem;
  padding: 0.5rem;
  background-color: green;
}

.btn-error {
  border: 1px solid;
  border-radius: 0.5rem;
  padding: 0.5rem;
  background-color: red;
}

引数を使用する場合

@includeに引数を渡すことで、呼び出し元のクラスごとにスタイルを調整することが出来る

/* Sass */
@mixin btn-base($bgc:transparent){
  border:1px solid;
  border-radius: 0.5rem;
  padding: 0.5rem;
  background-color: $bgc;
}
.btn-success{
  @include btn-base(green);
}
.btn-error{
  @include btn-base(red);
}
/* CSS */
.btn-success {
  border: 1px solid;
  border-radius: 0.5rem;
  padding: 0.5rem;
  background-color: green;
}

.btn-error {
  border: 1px solid;
  border-radius: 0.5rem;
  padding: 0.5rem;
  background-color: red;
}

比較

@extend

  • メリット

    • コード量が少ない
      継承元と継承先のクラスが1セレクタにグルーピングされるため、共通スタイルのコードの重複を防止できる
  • デメリット

    • 引数が使用できない
      @mixinと違い引数が使えない分、継承されるスタイルは固定になる

    • 可読性が下がる
      継承元と継承先のコードが離れるため、コードの可読性が落ちる。
      そのため、グルーピングできない関連性がないクラスへの@extendは控える。(関連性があるコードは距離が近く、関連性がないコードは距離が遠いため)

@mixin

  • メリット

    • 引数が使える
      @includeしたクラスごとにスタイルの調整が出来る

    • 可読性が高い
      コードが@includeしたクラスに全て記載されるため、可読性が高い

  • デメリット

    • コード量が増える
      @includeしたクラスに同じスタイルが記載されるため、コードが重複する。

まとめ

グルーピング出来るような関係性のあるスタイルは@extendを使用する
関係性がない、または引数を利用したいなら@mixinを使用する

まとめのまとめ

継承関係があるなら@extend、それ以外は@mixin

参考

【JavaScript】async/await

async/awaitとは

Promiseより快適に非同期処理を利用することが出来る構文。

async

  • 非同期関数を定義する
    関数名の前にasyncを書くと、その関数はPromiseオブジェクトを返す。
async function test() {
    return 'test';
}

test().then(value => {
    console.log(value); // => test
});

await

  • JavaScriptを待機させる
    async関数の中に記載すると、awaitを指定した関数のPromiseオブジェクトが結果を返すまでJavaScriptを待機させる。
function test(value) {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(value * 2);
        }, 1000);
    })
}

async function sample() {
    const result = await test(5);
    return result + 5;
}

sample().then(result => {
    console.log(result); // => 15
});

注意
async関数でない場合にawaitは使えないため、必ずセットで利用する

Promiseとの比較

先ほどのawaitの例で書いた処理をPromiseで書いた場合、async/awaitの方がより簡潔に非同期処理を書くことが出来る。

function test(value) {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(value * 2);
        }, 1000);
    })
}

function sample() {
    return test(5).then(result => {
        return result + 5;
    });
}

sample().then(result => {
    console.log(result); // => 15
});

参考

【JavaScript】Promiseオブジェクト

Promiseとは

JavaScriptの非同期処理のネストを深くせずに実装することができる仕組み。

背景

  • コールバック地獄
    JavaScriptの非同期処理はコールバック関数を利用して実装するが、処理が連続するとネストが深くなり、コードが肥大化してしまう問題があった。
// 引数を倍にする関数
function async(data, callback) {
    setTimeout(function() {
        callback(data * 2);
    }, 1000);
}

function callback_hell() {
    async(100, function(data) {
        console.log(data);// => 200
        async(data, function(data) {
            console.log(data);// => 400
            async(data, function(data) {
                console.log(data);// => 800
            });
        });
    });
}

そのため、連続する非同期処理をネストを深くせず同期処理のように記載するためにPromiseが利用される。

実装例

//Promiseオブジェクト
const async = value => {
    return new Promise((resolve,reject) => {
        setTimeout(()=>{
            if(value) {
                console.log(value)
                resolve('success')
            }else{
                console.log('値なし')
                reject('failure')
            }
        },1000)
    })
}
//thenメソッド
async('test1').then(
    response => {
        // => test1
        return async('test2')
        // => test2
    }
  ).then(
    response => { 
    },
    error => {
      console.log('エラー')
    }
)
  • Promiseオブジェクト
    非同期処理の最終的な完了もしくは失敗を表すオブジェクト
    Promiseオブジェクトのコンストラクターには3つの引数が必要。

    function(resolutionFunc, rejectionFunc){
    // typically, some asynchronous operation.
    }

    • resolutionFunc
      処理の成功を通知するための関数
    • rejectionFunc
      処理の失敗を通知するための関数
    • asynchronous operation
      処理本体

      上記コードでは処理としてseTimeOutを実行し、引数があればresolve,なければrejectを呼び出している。

  • thenメソッド
    Promiseが成功した場合と失敗した場合のコールバック関数を受け取るメソッド。

    p.then(onFulfilled[, onRejected]);
    p.then(value => {
    // fulfillment
    }, reason => {
    // rejection
    });

    • onFulfilled
      Promiseが成功した時に呼び出される関数(resolveによって呼び出し)
    • onRejected
      Promiseが失敗した時に呼び出される関数(rejectによって呼び出し)

メリット

  • 連続する非同期処理をネストせずに書ける
    Promiseを利用することで連続する非同期処理をthenメソッドでつなぐことができるため、コールバック地獄を回避でき、メソッドが肥大化することを防止できる。

  • エラー処理を必要な箇所にまとめることができる
    Promiseを利用することで、どの処理でエラーになってもthenメソッドで用意した失敗コールバック関数(reject)が呼び出されるため、メソッドごとにエラー処理を記載する必要がない。

参考

【Rails,Vue.js】JestでCSRFトークンを設定したaxiosを含むコンポーネントをテストするとき

事象

JestでCSRFトークンを設定したaxiosインスタンスを含むコンポーネントをマウントする際にTypeError: Cannot read property 'getAttribute' of nullになる

$ npm test
 ● Test suite failed to run

    TypeError: Cannot read property 'getAttribute' of null

       6 |         common: {
       7 |             'X-Requested-With': 'XMLHttpRequest',
    >  8 |             'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
         |                             ^
       9 |         }
      10 |     },
      11 | })

原因

マウントしたコンポーネントCSRFトークンが設定されているmetaタグが含まれていないため

Jestでマウントしたコンポーネントの中身をみると

wrapper = shallowMount(Message)
console.log(wrapper.html())
<div class="container">
    <div class="msg standard my-3"><span>メッセージ</span></div>
</div>

shallowMountしたコンポートにはmetaタグが含まれていない。
そのため、axiosが取得しようとしているmetaタグがないため、エラーになる。

対応

rails-ujsが発行したトークンを設定する

今回の原因がmetaタグからCSRFトークンを設定しているため、metaタグではなくCSRFトークンを発行してくれるモジュールであるrails-ujsを使う。

手順

インストール

npm install @rails/ujs

axiosインスタンスを生成しているファイルを編集する

import axios from 'axios'
import { csrfToken } from '@rails/ujs'; //追加

const http = axios.create({
    headers: {
        common: {
            'X-Requested-With': 'XMLHttpRequest',
            'X-CSRF-TOKEN': csrfToken() //追加
        }
    },
})


export default http

参考

【JavaScript】コールバック関数

コールバック関数

引数として渡される関数。
JavaScriptの関数はデータ型の1種のため、数値型や文字列型と同様に関数の引数・戻り値として扱うことができる。
「関数を引数、戻り値として扱う関数」を関数を高級関数と呼ぶ。

function execCallBack(callback){
    console.log('execCallBack')
    callback()
}

var callBack = function(){
    console.log('callBack')
}

execCallBack(callBack)

// 出力結果
// callBack

なぜコールバック関数が必要か

  • 非同期処理の順番を制御するため
    JavaScriptは前の処理結果を待たずに次の処理を実行する、非同期処理が可能。
    (例) サーバーからデータを取得している間にフロント側のhtmlを更新する
    ただし、前の処理結果を元にして次の処理に移りたい場合、コールバック関数によって処理の実行順序を制御することができる。
// 表示する値をセットする前に次の処理が実行されてしまう

function setMessage() {
  var msg;
  setTimeout(function() {
    msg = 'Hello World';
  })
  console.log(msg);
}

setMessage();  

// 出力結果
// undifined
// 表示する値がセットされた後に次の処理を実行する

function setMessage(callback) {
  var msg;
  setTimeout(function() {
    msg = 'Hello World';
    callback(msg)
  });
}

var showMessage = function (msg){
    console.log(msg)
}

setMessage(showMessage);  

// 出力結果
// Hello World

参考

【JavaScript】Ajax

Ajaxとは

Asynchronous JavaScriptXMLの略称。 ※Asynchronous=非同期
JavaScriptがサーバーと非同期で通信し、取得したデータを使用してHTMLを更新する技術。
代表的なのがGoogleマップ。表示位置を変えた際に、移動した分だけの地図情報を取得して更新される。

メリット

  • データ量を抑えられる
    必要なデータだけをサーバーから取得するため、HTMLそのものをやりとりするよりデータ量が抑えられる。

  • レスポンスを待つ間も作業できる
    サーバーからデータを取得する間に、ブラウザ側でHTMLを更新したり、ユーザーの入力を受け付けることができる。

背景

元々はブラウザとサーバーが交互に処理を行う同期通信を行なっていた。
同期通信だとサーバーからのレスポンスが返ってくるまで、ブラウザは何も処理ができない。
またHTMLをやりとするため、サーバーとの通信量が多くなりがちだった。

Ajaxで使用されている技術

  • XMLHttpRequest
    JavaScriptでHTTPリクエストを行うためのAPI
    JavaScriptがサーバーとやりとりするための技術で、ページ全体を更新することなく、データを受け取れる。

  • XML
    Extensible Markup Language。文書やデータの構造を記述するマークアップ言語。
    データをやりとするときの形式、近年はもっと読みやすく軽量なJSON形式でやりとりすることが多い。

  • DOM
    DocumentObjectModel。HTMLなどの文書の構造をプログラムで制御するためのAPI
    サーバーから受け取ったデータをDOMを利用して、変更・更新する。

実際の流れ

  1. ブラウザからデータ取得のためのイベントが発生
  2. JavaScript(XMLHttpRequest)によりサーバーへリクエス
  3. サーバーがリクエストに応じてデータをXML,JSON形式でレスポンスを返す
  4. データを受け取ったらJavaScrptがDOMを利用してページを更新

参考

【Rails】rails newした時にGem::GemNotFoundExceptionが発生する

事象

railsのプロジェクトを作成する時にGem::GemNotFoundExceptionが発生する

$ rails _5.2.2_ new rails_vue
Traceback (most recent call last):
        2: from /Users/(username)/.rbenv/versions/2.5.0/bin/rails:23:in `<main>'
        1: from /Users/(username)/.rbenv/versions/2.5.0/lib/ruby/2.5.0/rubygems.rb:308:in `activate_bin_path'
/Users/(username)/.rbenv/versions/2.5.0/lib/ruby/2.5.0/rubygems.rb:289:in `find_spec_for_exe': can't find gem railties (= 5.2.2) with executable rails (Gem::GemNotFoundException)

原因

指定したバーションのrailsがインストールされていなかったため

$ gem list rails
rails (6.0.1, 6.0.0, 5.2.4.1, 5.2.1)

# 指定した5.2.2がない

対応

該当のバージョンをインストールする

$ gem i -v 5.2.2 rails

# インストール後にインストールされているか確認

$ gem list rails
rails (6.0.1, 6.0.0, 5.2.4.1, 5.2.2, 5.2.1)

# 5.2.2が追加されている

$ rails _5.2.2_ new new_app

# 指定したバージョンでプロジェクトの作成が出来る

参考