20210117のvue.jsに関する記事は6件です。

【備忘録】Vue:methodsとcomputedの違い

Vueの動作オプションであるmethodsとcomputedの違いに関して

new Vue({
  computed: {
      ...
  },
  methods: {
      ...
  }
})

参考

これからはじめるVue.js実践入門

内容

下記のようなプログラムで違いを説明。
【プログラム内容】
- computedで乱数を表示
- methodsで乱数を表示
- クリックで現在日時を表示

sample.html
 ...
    <body>
        <div id="app">
            <form>
                <input type="button" value="click" v-on:click="onclick" />
            </form>
            <div>computed: {{randomc}}</div>
            <div>methods: {{randomm()}}</div>
            <div>daytime: {{current}}</div>
        </div>
     ...
    </body>
sample.s
new Vue({
    el: '#app',
    data: {
        current: new Date().toLocaleString()
    },
    computed: {
        randomc: function() {
            return Math.random();
        }
    },
    methods: {
        onclick: function(){
            this.current = new Date().toLocaleString();
        },
        randomm: function() {
            return Math.random();
        }
    },
});

↓ ブラウザで表示した実際の画
image.png

共通点

ページ遷移した時はcomputedとmethodsのどちらも実行される。

異なる点

1) methodsはhtml上で使うときに引数を持てる。computedは引数を持てない。

<div>methods: {{randomm(ここに引数を入れられる)}}</div>

2) methodsはページが再描画されるたびに実行される。一方で、computedはページに遷移した初回のみ呼び出したときと、依存したプロパティが更新されたときのみ実行される。
つまり、今回の例だと「クリック」ボタンをクリックしたときに、現在時間が再描画されるので、その際にmethodsの乱数生成は実行されて、computedの乱数生成は実行されない。
ただし、

    computed: {
        randomc: function() {
            console.log(this.current);
            return Math.random();
        }
    },

のように現在時刻を参照すると、computedも実行される。

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

Vue.jsの基本的な使い方 備忘録

Vue.jsを使うには

HTMLファイルでVue.jsをCDNを用いて直接読み込む場合、ソースコードは上から下に読み込まれて行くのでbodyタグの一番最後の記述する。

HTML
<body>
  <div id="app"> ⇦ divタグにid=appと指定することでappの部分は任意の文字でOKです。
   <!-- 今回の場合このdivタグ内がVue.jsが影響を及ぼす範囲です。-->
  </div>

  <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
  <script src="js/main.js"></script>
</body>

jsファイルで「 var app = new Vue 」と変数化することでコンソールからアクセスすることができる。

js
var app = new Vue({
  el: '#app',
})

v-if

真偽値を用いて要素を表示・非表示の切り替えを行う。要素自体を生成/破棄している

HTML
<div id="app">
  <p v-if="toggle"> ⇦ ここでtogglefalsev-ifが受け取り要素を非表示にしています
    Hello
  </p>
</div>
js
var app = new Vue({
  el: '#app',
  data: {
    toggle: false ⇦ この場合pタグの要素が非表示になる。trueにすると再表示されます。
  }
})

v-show

CSSのdisplayをnoneにすることで表示している要素を消しているためデベロッパーツールで該当の箇所を箇所を確認すると要素は残っている。

HTML
<div id="app">
  <p v-show="toggle"> ⇦ ここでtogglefalsev-ifが受け取り要素を非表示にしている。
    Hello
  </p>
</div>
js
var app = new Vue({
  el: '#app',
  data: {
    toggle: false ⇦ この場合pタグの要素(Hello)が非表示になる。trueにすると再表示されます。
  }
})

v-on

DOM要素にイベントリスナーを登録する

HTML
<div id="app">
  <button v-on:click="onClick"> ⇦ ここでv-on:clickにイベントハンドラのメソッド名を入れて呼ぶ
    クリック
  </button>
  <p>
    {{ now }} ⇦ 現在日時がnowに代入されて表示される
  </p>
</div>
js
var app = new Vue({
  el: '#app',
  data: {
    now: ''
  },
  methods: {
    onClick: function() {
      this.now = new Date().toLocaleString(); ⇦ nowに現在時刻を代入するメソッド
    }
  }
})

v-for

"color in colors"とすることで、colorsの配列に入っている値を繰り返し処理でcolorに代入している。その後liタグ内でcolorを呼び出すことで
1. Red
2. Green
3. Blue
と表示される。

HTML
<div id="app">
  <ol>
    <li v-for="color in colors">{{ color }}</li>
  </ol>
</div>
js
var app = new Vue({
  el: '#app',
  data: {
    colors: ['Red', 'Green', 'Blue']
  }
})

v-model

双方向データバインディングが行えるディレクテブ。
入力フォームに値を入れた場合、リアルタイムでプロパティを変更することができる。

HTML
<div id="app">
  <p>
    <input type="text" v-model="message"> ⇦ 双方向データバインディングがしたいプロパティをv-modelに入れる
  </p>
 <!-- 下記のようにすることでmessageオブジェクトの値が変わって行くことを確認できる -->
  <pre>{{ message }}</pre> ⇦ 入力フォームで変更を加えた場合messageの値も同じように変更される
</div>
js
var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue.js!'
  }
})

v-bind

属性へのデータバインディングを使用する場合は、マスタッシュ構文ではなくv-bindを使用する。
v-bindを下記のようにインプットタグで使う場合、属性の値にはプロパティ名を使用する。
下記のように記述することで入力欄に「Hello Vue.js」と表示される。

HTML
<div id="app">
  <input type="text" v-bind:value="message"/>
</div>
js
var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue.js'
  }
}) 
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TDD 入門 ~E2E テストを添えて~

あらすじ

TDD に入門してみました。
リグレッション対策となる E2E テストも書きました。

※ 注意 ※
僕自身は TDD 初心者なので正しくない解釈が含まれている可能性があります!
暖かい目で見ていただけると嬉しいです。

インデックス

TDD とは

TDD = テスト駆動開発 については、下記の本がお勧めです。

TDD は、

まず、失敗するテストを書く(レッド)
→ とりあえずテストを通すコードを書く(グリーン)
→ 意味のあるコードにする(リファクタリング) 
→ 失敗するテストを書く(レッド)
→ ...

というプロセスを繰り返す開発手法です。

個人的な解釈ですが、TDD のポイントは以下の 2 点です。

  1. 失敗するテストを書き、動くコードを書くプロセスを繰り返すこと
  2. テストが将来のメンバーのためのロゼッタストーンになること

テストは「何をしたいか」を表すメッセージになります。
テストコードを読むことで、そのコードが何をしたいコードなのか推測できます。
将来、案件を引き継ぐ仲間のことを思ってテストを書くと良いのではないでしょうか。

TDD やってみる

Vuetify + TypeScript のプロジェクトの 1 機能を TDD でやってみることで理解を深めたいと思います。
ついでに、 E2E テストも書いてデグレーション対策もします。

作るもの

FizzBuzz っぽい Web 画面を作ります。

動作
①. 数字を入力してボタンを押します。
②. 3 の倍数であれば Fizz を、5 の倍数であれば Buzz を、3 と 5 の倍数であれば FizzBuzz を画面上に出力します。

完成イメージ
image.png

下準備

Jest + Cypress の環境を作成する

以下の記事を参考に、 Vuetify + TypeScript + Jest + Cypress の環境を整えます。

設計する

先ほどのイメージをどうやって実現するか、何となく考えます。

  • 画面は Vue で作る
  • FizzBuzz の判定を行うクラスを作る
  • ボタンを押すと、FizzBuzz を判定するメソッドが呼ばれる

画面を作る

FizzBuzz クラスをどう作るかを想像しつつ、書きます。

FizzBuzzComponent.vue
<template>
  <div class="fizzbuzzcomponent">
    <v-card width="400" class="mx-auto mt-5">
      <v-card-text>
        <v-form ref="form">
          <v-text-field
            v-model.number="inputNumber"
            type="number"
            label="Number"
            data-cy="inputNumber"
            :rules="inputRules"
          />
        </v-form>
      </v-card-text>

      <v-card-actions class="pb-5 px-5">
        <v-row>
          <v-col cols="12">
            <v-btn
              block
              depressed
              color="success"
              data-cy="checkButton"
              @click="clickFizzBuzzButton(inputNumber)"
            >
              FizzBuzz チェック
            </v-btn>
          </v-col>
        </v-row>
      </v-card-actions>
    </v-card>
    <h1 v-if="showText.showNumber" data-cy="Number">{{ inputNumber }}</h1>
    <h1 v-if="showText.showFizz" class="Fizz" data-cy="Fizz">Fizz</h1>
    <h1 v-if="showText.showBuzz" class="Buzz" data-cy="Buzz">Buzz</h1>
    <h1 v-if="showText.showFizzBuzz" class="FizzBuzz" data-cy="FizzBuzz">
      FizzBuzz
    </h1>
  </div>
</template>

<script lang="ts">
/*eslint @typescript-eslint/no-explicit-any: "off"*/
import { Component, Emit, Vue } from 'vue-property-decorator';
import { TCFizzBuzz } from '@/class/TCFizzBuzz';

@Component
export default class FizzBuzzComponent extends Vue {
  showText: object = {
    showNumber: false,
    showFizz: false,
    showBuzz: false,
    showFizzBuzz: false,
  };

  name = 'FizzBuzzComponent';
  inputNumber = '';
  inputFizzBuzz: TCFizzBuzz = new TCFizzBuzz(0);

  @Emit('click-fizzbuzz-button')
  clickFizzBuzzButton(n: number): void {
    this.showText = this.inputFizzBuzz.checkFizzBuzz(n);
  }

  private inputRules: any = [
    (value: number) => !!value || '1~100 までの数字を入力してください。',
    (value: number) => value >= 1 || '1 より大きい数字を入力してください。',
    (value: number) => value <= 100 || '100 より小さい数字を入力してください。',
  ];
}
</script>

<style>
.Fizz {
  color: red;
}
.Buzz {
  color: blue;
}
.FizzBuzz {
  color: green;
}
</style>

Unit テストを書く

早速、FizzBuzz の判定を行うクラスを作ります。
(ぶっちゃけ関数だけ実装すれば良いのですが、練習がてらクラスでかっちり作ります)

TDD 実践していきます。
まずは FizzBuzz クラスにして欲しいことを洗い出します。

  • 入力が 3 の倍数の時、Fizz を返す
  • 入力が 5 の倍数の時、Buzz を返す
  • 入力が 15 の倍数の時、FizzBuzz を返す
  • 入力が 3, 5, 15 の倍数ではない時、その数値を返す
  • 入力が 1~100 ではない場合、エラーを返す
  • 入力が数字ではない場合、エラーを返す

これを元にテスト書いていきます。
例として、「入力が 3 の倍数の時、Fizz を返す」のテストを書きます。

実現したいことをテストに書きましょう(後で見る人がわかりやすいテストにしましょう!)。
「入力が 3 の時、TCFizzBuzz クラスの checkFizzBuzz は showFizz = true(画面上に Fizz を出力する)を返す。」というテストを書きました。

TCFizzBuzz.test.ts
import { TCFizzBuzz } from '@/class/TCFizzBuzz';

describe('Check the input.', () => {
  let fb: TCFizzBuzz;
  beforeAll(() => {
    fb = new TCFizzBuzz(0);
  });

  it('When the input is 3, checkFizzBuzz() returns {showNumber: false, showFizz: true, showBuzz: false, showFizzBuzz: false}.', () => {
    expect(fb.checkFizzBuzz(3)).toEqual({
      showNumber: false,
      showFizz: true,
      showBuzz: false,
      showFizzBuzz: false,
    });
  });
});

テストを動かします。当然失敗します。
image.png

レッドでは気持ちが落ち着かないので、早くグリーンにしましょう。
テストに合わせて TCFizzBuzz クラスを作ります。

TCFizzBuzz.ts
export class TCFizzBuzz {
  private n: number;

  /**
   * コンストラクター
   * @param n 入力された数字
   */
  constructor(n: number) {
    this.n = n;
  }

  /**
   * 入力された数字が 3 または 5 または 15 の倍数か確認する。
   * 3 の倍数の場合 Fizz を、
   * 5 の倍数の場合 Buzz を、
   * 15 の倍数の場合 FizzBuzz を、
   * それ以外の場合、入力された数字を返却する。
   * @param input 入力
   */
  public checkFizzBuzz(input: number): any {

    if (input == 3) {
      console.log('Fizz');
      return {
        showNumber: false,
        showFizz: true,
        showBuzz: false,
        showFizzBuzz: false,
      };
    }
  }
}

※ 一部抜粋。

またテストを動かします。
image.png
やった!⛳️になりました。

本当に 3 の倍数の時 Fizz となっているか心配になったので、テストを追加します。

TCFizzBuzz.test.ts
import { TCFizzBuzz } from '@/class/TCFizzBuzz';

describe('Check the input.', () => {
  let fb: TCFizzBuzz;
  beforeAll(() => {
    fb = new TCFizzBuzz(0);
  });

  it('When the input is 3, checkFizzBuzz() returns {showNumber: false, showFizz: true, showBuzz: false, showFizzBuzz: false}.', () => {
    expect(fb.checkFizzBuzz(3)).toEqual({
      showNumber: false,
      showFizz: true,
      showBuzz: false,
      showFizzBuzz: false,
    });
  });

  it('When the input is multiples of 3, checkFizzBuzz() returns {showNumber: false, showFizz: true, showBuzz: false, showFizzBuzz: false}.', () => {
    expect(fb.checkFizzBuzz(99)).toEqual({
      showNumber: false,
      showFizz: true,
      showBuzz: false,
      showFizzBuzz: false,
    });
  });
});

あれ、失敗してしまいました。
テストをパスするコードに書き直します。

TCFizzBuzz.ts
export class TCFizzBuzz {

  /**
   * 入力された数字が 3 または 5 または 15 の倍数か確認する。
   * 3 の倍数の場合 Fizz を、
   * 5 の倍数の場合 Buzz を、
   * 15 の倍数の場合 FizzBuzz を、
   * それ以外の場合、入力された数字を返却する。
   * @param input 入力
   */
  public checkFizzBuzz(input: number): any {
    if (input % 3 == 0) {
      return {
        showNumber: false,
        showFizz: true,
        showBuzz: false,
        showFizzBuzz: false,
      };
    }
  }
}

※ 一部抜粋。

⛳️になりました。
と、TDD はこんな感じで「とりあえずテストをパスするコードを書く」という考え方で進みます。
この例は簡単な例なので、こんなに丁寧にやる必要はないとは思いますが、難しいロジックを実装する場合は大事な考え方だと思います。

最終的に、Unit テストはこんな感じになりました。

TCFizzBuzz.test.ts
import { TCFizzBuzz } from '@/class/TCFizzBuzz';

describe('Validate the input', () => {
  let fb: TCFizzBuzz;
  beforeAll(() => {
    fb = new TCFizzBuzz(0);
  });
  it('When the input is less than 1, checkFizzBuzz() returns false.', () => {
    expect(fb.validateInput(0)).toBe(false);
  });
  it('When the input is more than 100, checkFizzBuzz() returns false.', () => {
    expect(fb.validateInput(101)).toBe(false);
  });
  it('When the input is not number, checkFizzBuzz() returns Error.', () => {
    expect(() => fb.validateInput('test')).toThrow();
  });
});

describe('Check the input.', () => {
  let fb: TCFizzBuzz;
  beforeAll(() => {
    fb = new TCFizzBuzz(0);
  });
  it('When the input is not multiples of 3 or 5 or 15, checkFizzBuzz() returns {showNumber: true, showFizz: false, showBuzz: false, showFizzBuzz: false}.', () => {
    expect(fb.checkFizzBuzz(1)).toEqual({
      showNumber: true,
      showFizz: false,
      showBuzz: false,
      showFizzBuzz: false,
    });
  });

  it('When the input is 3, checkFizzBuzz() returns {showNumber: false, showFizz: true, showBuzz: false, showFizzBuzz: false}.', () => {
    expect(fb.checkFizzBuzz(3)).toEqual({
      showNumber: false,
      showFizz: true,
      showBuzz: false,
      showFizzBuzz: false,
    });
  });

  it('When the input is multiples of 3, checkFizzBuzz() returns {showNumber: false, showFizz: true, showBuzz: false, showFizzBuzz: false}.', () => {
    expect(fb.checkFizzBuzz(99)).toEqual({
      showNumber: false,
      showFizz: true,
      showBuzz: false,
      showFizzBuzz: false,
    });
  });

  it('When the input is multiples of 5, checkFizzBuzz() returns {showNumber: false, showFizz: false, showBuzz: true, showFizzBuzz: false}.', () => {
    expect(fb.checkFizzBuzz(5)).toEqual({
      showNumber: false,
      showFizz: false,
      showBuzz: true,
      showFizzBuzz: false,
    });
  });

  it('When the input is multiples of 15, checkFizzBuzz() returns {showNumber: false, showFizz: false, showBuzz: false, showFizzBuzz: true}.', () => {
    expect(fb.checkFizzBuzz(15)).toEqual({
      showNumber: false,
      showFizz: false,
      showBuzz: false,
      showFizzBuzz: true,
    });
  });
});

TCFizzBuzz クラスはこんな感じ。

TCFizzBuzz.ts
export class TCFizzBuzz {
  private n: number;

  /**
   * コンストラクター
   * @param n 入力された数字
   */
  constructor(n: number) {
    this.n = n;
  }

  /**
   * 入力が数字かどうか判定する。
   * 数字であれば true を、それ以外であれば false を返す。
   * @param input 入力
   */
  public checkType(input: any): boolean {
    if (typeof input === 'number') {
      return true;
    }
    return false;
  }

  /**
   * 入力された数字の validation を行う。
   * @param input 入力
   */
  public validateInput(input: any): boolean {
    if (!this.checkType(input)) throw new Error('Not type of number.');

    if (input < 1) {
      console.log('Less than 1');
      return false;
    } else if (input > 100) {
      console.log('More than 100');
      return false;
    }
    console.log('Passed validations');
    return true;
  }

  /**
   * 入力された数字が 3 または 5 または 15 の倍数か確認する。
   * 3 の倍数の場合 Fizz を、
   * 5 の倍数の場合 Buzz を、
   * 15 の倍数の場合 FizzBuzz を、
   * それ以外の場合、入力された数字を返却する。
   * @param input 入力
   */
  public checkFizzBuzz(input: number): any {
    if (!this.validateInput(input)) {
      return {
        showNumber: false,
        showFizz: false,
        showBuzz: false,
        showFizzBuzz: false,
      };
    }

    if (input % 15 == 0) {
      console.log('FizzBuzz');
      return {
        showNumber: false,
        showFizz: false,
        showBuzz: false,
        showFizzBuzz: true,
      };
    } else if (input % 3 == 0) {
      console.log('Fizz');
      return {
        showNumber: false,
        showFizz: true,
        showBuzz: false,
        showFizzBuzz: false,
      };
    } else if (input % 5 == 0) {
      console.log('Buzz');
      return {
        showNumber: false,
        showFizz: false,
        showBuzz: true,
        showFizzBuzz: false,
      };
    }

    return {
      showNumber: true,
      showFizz: false,
      showBuzz: false,
      showFizzBuzz: false,
    };
  }
}

Jest は Coverage を出力してくれるので便利です。
(「Coverage 100[%] だから問題なし!」ってわけではないので、あくまで参考程度ですね。)
image.png

E2E テストを書く

リグレッション対策として E2E テストも書きます。
inputRules のテストも行ってます。

FizzBuzz.spec.ts
describe('FizzBuzz Test', () => {
  beforeEach(() => {
    cy.visit('http://localhost:8080/FizzBuzz');
    cy.url().should('include', '/FizzBuzz');
  });

  it('When the input is multiples of 3, show Fizz.', () => {
    cy.get('[data-cy=inputNumber]').type('99').should('have.value', '99');
    cy.get('[data-cy=checkButton]').click();
    cy.get('[data-cy=Fizz]').contains('Fizz');
  });

  it('When the input is multiples of 5, show Buzz.', () => {
    cy.get('[data-cy=inputNumber]').type('100').should('have.value', '100');
    cy.get('[data-cy=checkButton]').click();
    cy.get('[data-cy=Buzz]').contains('Buzz');
  });

  it('When the input is multiples of 15, show FizzBuzz.', () => {
    cy.get('[data-cy=inputNumber]').type('90').should('have.value', '90');
    cy.get('[data-cy=checkButton]').click();
    cy.get('[data-cy=FizzBuzz]').contains('FizzBuzz');
  });

  it('When the input is not multiples of 3 or 5 or 15, show Fizz.', () => {
    cy.get('[data-cy=inputNumber]').type('98').should('have.value', '98');
    cy.get('[data-cy=checkButton]').click();
    cy.get('[data-cy=Number]').contains('98');
  });

  it('When the input is less than 1, show "1~100 までの数字を入力してください。".', () => {
    cy.get('[data-cy=inputNumber]').type('test');
    cy.get('[data-cy=checkButton]').click();
    cy.get('[data-cy=Number]', { timeout: 0 }).should('not.exist');
    cy.get('.v-messages__message').contains(
      '1~100 までの数字を入力してください。'
    );
  });

  it('When the input is less than 1, show "1 より大きい数字を入力してください。".', () => {
    cy.get('[data-cy=inputNumber]').type('-1').should('have.value', '-1');
    cy.get('[data-cy=checkButton]').click();
    cy.get('[data-cy=Number]', { timeout: 0 }).should('not.exist');
    cy.get('.v-messages__message').contains(
      '1 より大きい数字を入力してください。'
    );
  });

  it('When the input is less than 1, show "100 より小さい数字を入力してください。".', () => {
    cy.get('[data-cy=inputNumber]').type('101').should('have.value', '101');
    cy.get('[data-cy=checkButton]').click();
    cy.get('[data-cy=Number]', { timeout: 0 }).should('not.exist');
    cy.get('.v-messages__message').contains(
      '100 より小さい数字を入力してください。'
    );
  });
});

Coverage を確認します。
image.png
All ⛳️ですね!

Tips

babel.config.js の設定

Cypress で Coverage を測定するために babel-plugin-istanbul の plugin を読み込む必要があります。
ただ Jest にも instrument が組み込まれており、babel-plugin-istanbul とぶつかります。
今回は env を分けることで対応しました。
ちなみに Jest は初期設定で NODE_ENV = test で実行されます

babel.config.js
const plugins = [];
if (process.env.NODE_ENV === 'Coverage') {
  plugins.push([
    'babel-plugin-istanbul',
    {
      extension: ['.js', '.ts', '.vue'],
    },
  ]);
}

module.exports = {
  presets: [
    ['@vue/cli-plugin-babel/preset'],
    ['@babel/preset-env', { targets: { node: 'current' }, modules: false }],
    '@babel/preset-typescript',
  ],
  env: {
    test: {
      presets: [
        ['@babel/preset-env', { targets: { node: 'current' }, modules: false }],
      ],
    },
  },
  plugins,
};

Vue の input を number 型と認識させる方法

参考

今回のコードでは入力を number 型にしています。
が、HTML5 の input 要素は文字列を返してきます。
parse を挟んでも良いのですが、v-model.number で回避しました。

ちなみに入力が 0 のとき false と判定されてしまい、「1 より大きい数字を入力してください。」ではなく、「1~100 までの数字を入力してください。」が表示されます。
(やっぱり、別に作った方が良いかもしれない...)

Cypress の Coverage が測定されない時の対処

たまに Cypress の Coverage が測定できない時がありました。
理由は対象の .vue ファイルが instrument されていないからなのですが、原因を特定できませんでした。
以下に、instrument されなかったときに試した対処法を載せます。
(多分、babel が原因だとは思います)

あれ?と思ったら、開発者ツールで window.__coverage__ を確認するのが良いと思います。

まとめ

環境整備が一番大変でした。
かっちりコード書いてくのは楽しいのでオススメです。
Cypress の CodeCoverage はイマイチわからんので、公式の記事を読み直すべきかもしれない...

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

【Vue.js】Vue.js最初の一歩

はじめに

最近Vue.jsを勉強しています。
自分の備忘を兼ねて、初歩的な内容を書いていきます。

基本

<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width">
        <title>Sample</title>
        <meta name="description" content="sample">
        <!-- Vue.jsをインストール -->
        <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script>
    </head>

    <body>
        <!-- Vue.jsを適用する場所 -->
        <div id="app">

        </div>

        <script>
            //最初にVueクラスをインスタンス化
            new Vue({

                //elオプション:Vueを適用する場所を指定
                el: '#app',

                //dataオプション:Vueで使用するデータを用意する
                data: {
                    message: 'Hello Vue!'
                }
            });
        </script>
    </body>
</html>

まずVueクラスをインスタンス化します。
そして、elオプションにより、Vueを適用する場所を指定します。今回はappというidを持つdiv要素に対して適用するので、el: '#app'としています。
最後に、dataオプションで、使用するデータを用意します。「プロパティ:値」のように記述するので、今回は'Hello Vue!'という文字列をmessageプロパティにセットしたことになります。
このデータを表示させてみます。
HTML内で{{プロパティ名}}のように記述すると、該当するプロパティの値が呼び出され、表示されます。

    <body>
        <div id="app">
            <!-- messageデータを呼び出し -->
            {{message}}
        </div>

        <!-- Vue適用範囲外なので呼び出されない -->
        {{message}}

        <script>
            new Vue({
                el: '#app',
                data: {
                    message: 'Hello Vue!'
                }
            });
        </script>
    </body>

結果

messageプロパティの値が呼び出され、表示されました。
2段目はVueが適用されるdiv要素の外なので、データが呼び出されず、そのまま表示されています。

v-forディレクティブ

Vueでは、ディレクティブというものを使って、表示に関する様々な制御を行えます。
v-forディレクティブは、for文のような繰り返し処理を行います。

    <body>
        <div id="app">

            <ul>
                <!-- 配列itemsから要素itemを繰り返し取り出す -->
                <li v-for="item in items">

                    <!-- 取り出したitemのname属性を表示 -->
                    {{item.name}}
                </li>
            </ul>
        </div>

        <script>
            new Vue({
                el: '#app',
                data: {
                    //表示する要素を格納した配列
                    items: [
                        {name: 'item1'},
                        {name: 'item2'},
                        {name: 'item3'}
                    ]
                }
            });
        </script>
    </body>

まずdataオプションで、複数の要素を格納した配列を用意しておきます。
次に繰り返し表示したいHTML要素のタグにv-for="item in items"といった記述を追加します。
そしてその要素の中に{{item.name}}と記述します。
これで、配列の要素を繰り返し取り出し、それらを次々に表示させています。

状況によってリストの内容が変化する場合等に便利そうですね。

v-onディレクティブ

v-onディレクティブはイベント制御を行うことができます。

    <body>
        <div id="app">
            <!-- クリックしたらcountを1増加させる -->
            <button v-on:click="count += 1">ボタン</button>
            {{count}}

        </div>

        <script>
            new Vue({
                el: '#app',
                data: {
                    //ボタンが押されるたびに1ずつ増える
                    count: 0
                }
            });
        </script>
    </body>

ボタンにv-on:click="count += 1"と記述することにより、クリックすると変数countを1増加させるという処理を付加できます。
変数countは画面に表示されているので。増加していく様子を見ることができます。

v-ifディレクティブ

v-ifディレクティブを使うと、条件によって表示させる内容を変化させることができます。

    <body>
        <div id="app">
            <button v-on:click="count += 1">ボタン</button>
            {{count}}

            <!-- 条件によって表示内容を変化させる -->
            <div v-if="count % 2 === 0">偶数</div>
            <div v-else>奇数</div>
        </div>

        <script>
            new Vue({
                el: '#app',
                data: {
                    //ボタンが押されるたびに1ずつ増える
                    count: 0
                }
            });
        </script>
    </body>

divタグにv-if="count % 2 === 0"と記述することで、この条件を満たす場合に要素が表示されるようにしました。
これにより、countが偶数か奇数かを判定して表示するようにしています。ボタンを押すたびに表示が切り替わります。

v-showディレクティブ

v-ifディレクティブと似ています。記載内容がtrueなら要素が表示されます。

    <body>
        <div id="app">
            <button v-on:click="show = !show">ボタン</button>
            {{show}}

            <!-- 値がtrueなら表示される -->
            <div v-show="show">表示</div>
            <!-- 値がtrueでなければ表示される -->
            <div v-show="!show">非表示</div>
        </div>

        <script>
            new Vue({
                el: '#app',
                data: {
                    show: true
                }
            });
        </script>
    </body>

ボタンを押すと、showについて、truefalseの切り替えが行われます。
これにより、表示される要素が切り替わります。

おわりに

Vue.jsは、HTMLを自在に操作できて便利そうですが、なかなか奥が深いようです。頑張って勉強を続けます。
(参考)
この本で勉強しています。
Webデザインの現場で使えるVue-jsの教科書

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

【Rails + Webpacker】assetsの画像を使いたい! Vue.jsで画像を表示できるまで

RailsとWebpackerで開発している時に、画像どうやって読み込むのかなと思って調べてみた記録です。

WebpackerのREADMEをのぞいてみる

WebpackerのREADMEの Paths > resolved を参照してみると、assetsをもつアプリとimagesを共有したいときは、webpacker.ymlresolved_pathsと書いて読み込める、とあります。今回はこれに習って実装しています。

config/webpacker.yml
resolved_paths: ['app/assets']

ただし、コンパイルの速度に影響アリ

以下にあるように…

Please be careful when adding paths here otherwise it will make the compilation slow, consider adding specific paths instead of whole parent directory if you just need to reference one or two modules

でかいディレクトリ単位で読み込むとコンパイルが遅くなる、ということのようです。表示速度を気にする場合は注意したいです。

実装例

webpacker.ymlにpathをかく

上でみたように webpacker.ymlに記述していきます。今回はassets/images配下しか必要としていないので、先ほどの注意点も考慮してimagesまでpathを指定してみました。

config/webpacker.yml
  resolved_paths: ['app/assets/images']

Vue.jsで使うには

今回は、Vue.jsのコンポーネントで読み込みたかったので、一緒に書いておきます。

sample.vue
<template>
  <div>
    <!-- imgタグでsrcに"~"をつけてimportしたファイルを指定する -->
    <img src="~logo.svg" /> 
  </div>
</template>

<script>
  import 'logo.svg'; // ここでimport
</script>

こうやって書いてみるととってもシンプルですね。

以上、少しでも参考になれば嬉しいです。

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

【Netlify】SPAサイトで簡単にOGPを付ける。

私は普段Nuxt.js(Vue)を使ってサイトを書いていますが、SPAサイトの一番大きな問題はTwitterでOGPがつかない事だと思っています。しかしながらOGPを付けるという目的のためにNuxt.jsでSSR(サーバーサイドレンダリング)する事は間違っていると考えています。理由としては、コストや安定性です。
その中でかなり悩んでいましたが、NetlifyにPrerenderingという機能がありました。今回はこの機能を用いてSPAサイトにOGPを付けていきたいと思います。無料です。

結果

まず初めに実際の結果です。かなり良いのではないでしょうか。

Netlify Prerenderについて

Netlifyはgit pushでホスティングできる事でお馴染みのサービスです。HerokuやGithubPagesと同じ感じです。Netlifyの機能としてPrerender機能が提供されていました。Prerenderとは、予めNetlifyがjavascriptも実行させた静的キャッシュを取っておくことで、SPAの動的OGPも表示させるものです。
もしbot(Twitterクローラなど)の場合はprerenderさせたキャッシュしたページ、人間がアクセスした時は普通にページを表示させます。現在Netlifyではbeta版として提供されています。料金は無料です。その他のprerenderについての詳細は公式ドキュメントを見てください。
prerender-example.png

実装方法

コード側の設定

Nuxtでの動的OGPの例です。特に難しい事はせず、headerのogpを書き換えているだけです。ここについては他の記事を見てください。SSRでOGP表示する例とかもそのまま流用出来るはずです。

index.vue
(抜粋)
  head() {
    return {
      meta: [
        {
          hid: "og:image",
          property: "og:image",
          content: "https://www.example.com/ogp/" + this.img + ".png",
        },
      ],
    };
  },

Netlify側の設定

NetlifyのSettingにおいて、PrerenderingをEnableにします。
2021-01-15_23h08_52.png

これで表示されるはずです。TwitterValidatorで確かめてみましょう。

所感

もうSPA時代のOGPはこれで良いなと思いました。firebaseFunction使ったり,SSRしたりと他にも方法はありますが、圧倒的にこの方法が楽でした。無料ですし。
気になる点は
・キャッシュのタイミングがわからない。(ブラックボックス)
・キャッシュを手動削除できない。(OGP画像を変えても前の画像が残る)
という点です。まぁこのくらいは目をつぶれるかなぁという感じです。無料ですし。

是非素敵なOGPライフを!

あ、もし時間があったら、AI/人間見分けるテストやってみて。OGPちゃんとつきますし。

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