20200126のvue.jsに関する記事は12件です。

AmplifyのStorageと認証まわり

AmplifyのStorageと認証まわり

はじめに

サーバーレスWebアプリ開発を進めてくなかで、AmplifyのStorageと認証まわりの理解が進んできて、完全に理解したなと思ってJAWS-UG浜松で発表しようとしたら全然できませんでした。
できなかったというか、話し始めようとしたら話せるほど自分のなかで整理できてないことに気付いてギブアップしました。発表内容はこれだけじゃなかったとはいえ、まぁ失礼な話ですよね。本当すみません。
リベンジすべく、整理するために記事を書きます。

Guest許可と認証ユーザーのみ

AmplifyのStorageをセットアップする際の質問のなかに以下のような質問があって、選択できます。

? Who should have access: (Use arrow keys)
  Auth users only 
  Auth and guest users 
  • Auth user only
    Storageへアクセスするためにユーザー認証(ログイン)が必要。
  • Auth and guest users
    ユーザー認証していない状態でもStorageへアクセスできる。

ファイルアップロード(put)

Amplifyを利用してファイルをS3にアップロードするJavaScriptのコード例

  :
import { Storage } from 'aws-amplify';
  :
      Storage.put(filePath, image).then(result => {
        console.log(result);
      }).catch(err => console.log(err));

例えばfilePath = "a/b/c/test.jpg"とした場合、
resultには key : a/b/c/test.jpg のような結果(要はS3 Object key)が返ってきます。
そしてバケットには以下のようにpublic/a/b/c/test.jpgとしてファイルがアップロードされます。
Screenshot 2020-01-25 at 22.22.43.png
publicが先頭についてますが、これはアクセスレベルです。

AmplifyのStorageには3つのアクセスレベルがあります。

  • public : 全員が読み書きできる。
  • protected : 全員が読めるが、書けるのは自分だけ。
  • private : 自分しか読み書きできない。

protectedの場合は以下のように書きます。

      Storage.put(filePath, image, {
          level: 'protected'
      }).then(result => {
        console.log(result);
      }).catch(err => console.log(err));

privateの場合は、protectedをprivateにすればいいだけです。

protectedやprivateの場合、アップロードされるパスの先頭には、アクセスレベルに続きCognito Identity IDが付きます。
ink (7).png

なお、Guestでprotectedやprivate指定した場合、以下の例外とともに HTTP403(Forbidden(閲覧禁止))が返ってきました。
AWSS3Provider - error uploading AccessDenied: Access Denied

ファイルダウンロード(アドレス取得)(get)

Amplifyを利用してS3にあるファイルの参照URLを取得するJavaScriptのコード例

      Storage.get(s3key).then(result => {
        this.imageURL = result;
      }).catch(err => console.log(err));

s3key = "a/b/c/test.jpg"このように、s3keyにはアクセスレベル名やCognito Identity IDを付けないことに注意。

resultに以下のようなURLが取得できます。
https://sample-vue-project-bucket-work.s3.ap-northeast-1.amazonaws.com/public/a/b/c/test.jpg?X-Amz-Algorithm=xxx&X-Amz-Credential=xxx&X-Amz-Date=xxx&X-Amz-Expires=900&X-Amz-Security-Token=xxx&X-Amz-Signature=xxx&X-Amz-SignedHeaders=host
後ろに認証のための色々なパラメータが付いてますね。これがないとアクセスが拒否されます。
また、デフォルトで900秒(15分)という有効期限も付いています。

任意の有効期限を指定することもできます。

      let s3key = "a/b/c/test.jpg"
      let dataExpireSeconds = (30 * 60);
      Storage.get(s3key, { expires: dataExpireSeconds }).then(result => {
        this.imageURL = result;
      }).catch(err => console.log(err));

適切な認証パラメータではなかったり、有効期限が切れた後にgetすると、HTTP403(Forbidden(閲覧禁止))とともにエラーが返ってきます。

<Error>
  <Code>AccessDenied</Code>
  <Message>Access Denied</Message>
  <RequestId>D43YC2E5080A822C</RequestId>
  <HostId>
    w7zzd5NRpm8Ih0DN3hmjZT8sDQT5hcV7FhqbUaW6ZG7SpsepV3UspzDUMDVKRkPesaUpyUSEGC0=
  </HostId>
</Error>

<Error>
  <Code>AccessDenied</Code>
  <Message>Request has expired</Message>
  <X-Amz-Expires>900</X-Amz-Expires>
  <Expires>2020-01-26T03:07:16Z</Expires>
  <ServerTime>2020-01-26T03:20:13Z</ServerTime>
  <RequestId>9F14A2CA61AE75F2</RequestId>
  <HostId>
    wru7usdhgiI2MXWsdFGh59x8JYl1TbOzPDMs2L5LZ/IwDD9tueInxK7bfh/rKftjJZCCf9CY7SE=
  </HostId>
</Error>

AmplifyのAuthによる認証

ゲストを許可した場合は不要ですが、そうではない場合は認証をパスした状態でputやgetをする必要があります。

ユーザーにサインインしてもらう

Amplifyを利用した一般的な実現方法としては、AmplifyのAuthの基本機能を利用して、ユーザーにアカウントを作ってもらったうえでサインインしてもらう形になるかと思います。
Screenshot 2020-01-26 at 13.15.46.png
こんな感じの画面でユーザー登録や認証の基盤を構築することがサクッと実現できちゃいます。
AmplifyでAuthをセットアップをする手順などの詳細は、この記事では割愛します。

プログラムで自動的に認証する

ユーザーがログインしなくてもサービスを利用できるようにしたい場合、一般的にはゲストを許可する設定でセットアップして、ゲストとして処理してあげればいいと思います。
ただ、あえてゲストは利用せず、サービス側が予め用意したアカウントで自動的(ユーザーに意識させず)に認証をパスするということもできます。
細かな手順などはこちらの記事に書いてありますので、やはりこの記事では割愛します。
Cognitoユーザーを作成したうえで、JavaScriptで自動的にログインをします。

あとがき

まずは何か動くものを作る。座学よりも効率的に学ぶことができると思っています。
それを人に説明しようとした場合、やはりある程度は整理してまとめるという作業が必要になってきますね。改めて実感しました。
これからはちゃんと準備してから話をしようと思います。

さて今回はStorage観点での認証の話でしたが、次は認証そのもの、その中でも特にソーシャルログインについて学びたいと考えています。
アカウント作成って心理的ハードルが高く、また、手間がかかると思っていて、とはいえユーザー毎の情報を保護することも必要だと思ってます。
多少でも心理的ハードルを下げ、また、ユーザー認証の手間を少なくしてくれるソーシャルログインは、サービスにとって必須の機能ではないでしょうか。と、思うわけなんです。

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

jQueryとVue.jsの比較(入力バインディング編)

jQueryで現状フロントの値の監視や操作、DOMの表示制御などを行ってる。
Vue.jsに置き換えるとイベント発火制御が少なくて済んだりコンポーネント化ができたりと、メリットあるなぁ〜というフンワリ理解してる部分を、実際に同じものをjQueryとVue.jsで作って違いを理解してみます。

今回は入力バインディング編です。

前提

Vue.jsはCDN版の方を使います。
jQueryについても同様にCDN版です。

<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" />
<!-- jQuery + Popper.js + Bootstrap -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.bundle.min.js"></script>
<!-- Vue.js -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

※見た目を簡単にキレイにしたいのでBootstrapも使ってます。

フォーム入力バインディング

<input type="text">で入力された値を<p>タグ内に同期で表示させるというシンプルなものです。

画面

スクリーンショット 2020-01-25 22.59.25.png

jQueryの場合

HTMLのBODY部分は以下の通りです。

<div class="container">
  <div class="mt-5"></div>
  <div class="card">
    <div class="card-header">
      jQueryの場合
    </div>
    <div class="card-body">
      <div class="form-group">
        <label for="text1">Text:</label>
        <input type="text" id="text1" class="form-control" value="hoge" />
      </div>
      <h5 class="card-title">値を反映</h5>
      <p class="card-text" id="result1"></p>
    </div>
  </div>
</div>

input type="text"#text1に入力された値をp#result1に反映させます。
まず初期値を反映させるために以下が必要です。

$(function() {
    // 初期値反映
    $("#result1").html($("#text1").val());
});

そして、入力された値が変更されたことをトリガーにするのであれば以下のようなchangeイベントトリガーでもいいのですがchangeイベントだと入力エリアからフォーカスが外れたときにしか発火されないので

$("#text1").change(function() {
    // changeイベントだと入力エリアからフォーカスが外れたときにだけ発火
    $("#result1").html($(this).val());
});

今回は値の入力と同期させたいのでkeyupイベントトリガーが正しいです。

$("#text1").keyup(function() {
    // keyupイベントは入力時にキーを離した瞬間に発火
    $("#result1").html($(this).val());
});

結果

以下のような動きをする画面ができました。
jquery.gif

では次にVue.jsの場合です。

Vue.jsの場合

HTMLのBODY部分は以下の通りです。

<div class="container">
    <div class="mt-5"></div>
    <div class="card" id="vue">
        <div class="card-header">
            Vue.jsの場合
        </div>
        <div class="card-body">
            <div class="form-group">
            <label for="text2">Text:</label>
            <input type="text" id="text2" class="form-control" v-model="result2"/>
            </div>
            <h5 class="card-title">値を反映</h5>
            <p class="card-text" id="result2">{{ result2 }}</p>
        </div>
    </div>
</div>
  • <div class="card" id="vue">とVueが操作するエリアと指定させるためにid="vue"としました
  • inputタグにv-model="result2"が追加されています。
  • pタグには{{ result2 }}が追加されてます。

javascriptは何をするかというと以下のみです。

new Vue({
    el: "#vue",
    data: {
        result2: "hoge"
    }
});

やっていることは以下のみです。
- new VueでVueのインスタンスを作成
- elで固有のルートを指定(今回は#vue
- dataresult2を定義し、初期値に"hoge"を入れる

HTML部分と組み合わせると・・・

  • #vue配下はVueで操作するエリアとして指定されて、result2というデータを持っている
  • inputタグに入力された値はv-modeldataresult2に値を管理させるようにしている
  • {{ result2 }}datareslut2を表示している

ということになります。

結果

vue.gif

まとめ

Vue.jsの入力バインディング1つとってもjQueryだと初期値を考慮して、イベントを監視して、イベント発火したら特定のタグ要素に値を反映して〜と、1つづつ明示的に指定していく必要がある印象です。
他にも色々と違いは出てくると思うのでネタが思いつき次第まとめていきます。

また、個人的にですがQiitaの投稿が久しぶりなので、リハビリ兼ねてライトな部分からですがjQueryよりVueのココがいいよって理解するためにも継続していけるようがんばります。

追記

jQueryとVue.jsの比較(算出プロパティ編)を書きました。

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

簡単なForm作成から学ぶVue(TypeScript)の書き方 その3

その他のディレクレィブ etc

〜contents〜

  • v-for
  • v-if v-else v-else-if
  • v-show
  • 算術プロパティ

RadioButtonコンポーネントから学ぶv-forディレクティブ

フォームの性別選択のラジオボタンを作ってみる。

v-forディレクティブは、オブジェクトや配列を受け取り、エイリアスを用いて各要素にアクセスできる。

構文
<div v-for="item in items">
  {{item.attr}}   //itemsの一要素を取得できる。
</div>

今回はラジオボタンの項目をリストで受け取りその項目数forループでラジオボタンを表示する。
componentsディレクトリ下にRadioButton.vueを作成する。

はじめにラジオボタンの項目を詰めたオブジェクトに型名をつけておく。

components/RadioAlternative.ts
export interface RadioAlternative {
  value: string;
  alternative: string;
}

前回学んだことと組み合わせると親コンポーネントから配列を受け取り、v-forでラジオボタンを配置して選択が変更されるたびに親にEmitすることができる。

親コンポーネント(Form)
<template>
  <div>
    <RadioButton :alts="sexList" name="sex" @updateSelected="updateSelected" />
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import RadioButton from '@/components/RadioButton.vue'
import { RadioAlternative } from '@/components/RadioAlternative'

@Component({
  components:{
    RadioButton
  }
})
export default class Form extends Vue {
  private readonly sexList: Array<RadioAlternative> = [
    {
      value: "male",
      alternative: ""
    }, {
      value: "female",
      alternative: ""
    }
  ]

  private updateSelected(selected: string){
    console.log("parent:" + selected)
  }
}
</script>
子コンポーネント(RadioButton)
<template>
  <div>
    <span v-for="alt in alts" :key="alt.value">
      <label><input type="radio" :name="name" :value="alt.value" @change="changeSelected" >{{alt.alternative}}</label>
    </span>
  </div>
</template>

<script lang="ts">
import { Component, Prop, Vue, Emit } from 'vue-property-decorator';
import { RadioAlternative } from './RadioAlternative'

@Component
export default class TextBox extends Vue {
  private alt: RadioAlternative

  @Prop()
  private alts: Array<RadioAlternative>

  @Prop()
  private name: string

  private changeSelected() :void {
    if (event!.target instanceof HTMLInputElement) {
      console.log(event!.target.value)
      this.updateSelected(event!.target.value)
    }
  }

  @Emit('updateSelected')
  private updateSelected(selected: string): void{
  }
}
</script>

この場合初めからどちらかを選択した状態にするcheckedはつけられないのだろうか。。

コンテンツの表示/非表示を切り替える条件分岐

ラジオボタンの選択に応じて表示するコンテンツを変更する機能を実装する。
今回は上のラジオボタンで性別により異なるコンテンツが表示されるように分岐してみる。

  • v-if
  • v-show

v-ifディレクティブ

v-ifディレクティブは値がtrueであれば表示、falseであれば非表示とするもの。
v-elsev-else-ifもだいたい予想がつく通り。なお、v-ifの直後に配置しないと認識されない。

<template>
  <div>
    <RadioButton :alts="sexList" name="sex" @updateSelected="updateSelectedSex" />
    <div v-if="sex === 'male'">**ここに何かしらのコンテンツ**</div>
    <div v-else>**何か別のコンテンツ**</div>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import RadioButton from '@/components/RadioButton.vue';
import { RadioAlternative } from '../components/RadioAlternative';

@Component({
  components:{
    RadioButton
  }
})
export default class Form extends Vue {
  private readonly sexList: Array<RadioAlternative> = [
    {
      value: "male",
      alternative: ""
    }, {
      value: "female",
      alternative: ""
    }
  ]

  private sex: string = ""

  private updateSelectedSex(selected: string){
    console.log("parent:" + selected)
    this.sex = selected
  }
}
</script>

v-showディレクティブ

v-showは同じく値がtrueの時のみ表示し、falseでは非表示とするもの。
こちらは分岐させることはできない。

    <div v-if="sex === 'male'">**ここに何かしらのコンテンツ**</div>

v-ifv-showの使い分けは表示を切り替える頻度を参考にする。

切り替えの頻度が低ければv-if
切り替えの頻度が高ければv-show

というのもv-showはDOM自体は保持しているが、CSSで表示をオフにしているだけなので、表示/非表示を何度も切り替えても描画コストが低い。一方で、v-iffalseであればDOM自体が消失するため、表示切り替えをするたびにDOMを構成し直す必要があり、描画コストが高い。そのため、何度も表示/非表示が切り替わるようなコンテンツには不向きである。

算術プロパティ

上記の例では表示の条件判定の際に条件式sex === 'male'を直接書き込んでいた。
しかし、もし、条件が複雑となった場合、この一行に記述するのはひどく可読性を損なう。
そこで、算術プロパティを使用する。

算術プロパティは関数のように記述し、内部で使用するデータ(変数)の値が変更されると勝手に再計算され、値がreturnされる。計算結果をreturnするgetterのようなものと考える。
なお、一般の関数methodと異なる点は、算術プロパティは内部のデータに変更があるまでは以前の値をキャッシュし、それをreturnし続ける点である。

注意点としては初期化されていない変数は追跡されない(変更が検出されない)こと。算術プロパティ内部に分岐処理があり初期の経路では通らない処理にのみ置かれた変数も同様に追跡の対象とならない。

jsではComputed:として記述される。

    <div v-if="isMale">**ここに何かしらのコンテンツ**</div>
親コンポーネントに追記
  private get isMale(): boolean{
    if (this.sex === "male")
      return true
    else
      return false
  }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

簡単なForm作成から学ぶVue(TypeScript)の書き方 その2

Vueコンポーネント同士を連携する。

〜contents〜

  • v-model
  • Emit(引数付)

TextBoxコンポーネントから学ぶv-modelディレクティブ

親コンポーネント(Form)
<template>
  <div>
    <TextBox placeholder="山田 太郎" :message="message" @updatedInputValue="checkInputValue" />
    <Button value="決定" :disabled="disabled" @Clicked="click"/>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import TextBox from '@/components/TextBox.vue'
import Button from '@/components/Button.vue'

@Component({
  components:{
    TextBox,
    Button,
    RadioButton
  }
})
export default class Form extends Vue {
  private name: string = ""
  private disabled: boolean = false
  private message: string = ""

  private click(): void{
    this.disabled = true
    console.log("I'm parent")
  }

  private checkInputValue(val: string){
    this.name = val
    console.log("入力:" + this.name)
    if (this.name.length > 10 || this.name.length < 1){
      this.message = "ユーザ名は1〜10文字です。"
      this.disabled = true
    } else {
      this.message = ""
      this.disabled = false
    }
  }
}
</script>
子コンポーネント(TextBox)
<template>
  <div>
    <input type="text" :placeholder="placeholder" v-model="inputValue">
  </div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';

@Component
export default class TextBox extends Vue {
  private inputValue: string = ""

  @Prop()
  private placeholder: string

</script>

v-model

双方向バインディング。v-modelとは、v-bindv-onをまとめて記述した糖衣構文(まとめただけで等価であるということ)である。すなわち、v-modelに指定したvalueは、<script>で定義された変数と連動する。
今回のようにで使用すると、<script>内で受けたialueの変更は入力欄へも反映され、逆に入力欄への入力は<script>の変数valueにも変更を与える。

糖衣構文(上と下では同じこと)
<input type="text" v-model="inputValue">

<input type="text" v-bind:value="inputValue" v-on:input="inputValue = $event.target.value">

今回のようにテキスト入力された情報を子コンポーネントだけで持っていてもしょうがないので、変更とともに随時親コンポーネントに通知するようにしたい。ここで、v-onの方は式であり自由に書き換えられることに着目する。
inputイベントを受けて親コンポーネントへEmitする関数をコールし、引数として親へ入力内容を渡そう。

ただし、typescriptではjsのように$event.target.valueで入力を拾おうとするとエラーがでる。次のように1step踏んで解決*1。ちなみに、event!としているのはそうしないとundefindの可能性があるよとエラーが出る(?)ので*2

参考1) https://yuutookun.hatenablog.com/entry/2018/07/21/084912
参考2) https://teratail.com/questions/130598

Emitする際に引数を渡す。

親コンポーネント(Form)
<template>
  <div>
    <TextBox @updatedInputValue="checkInputValue" />
    <Button value="決定" :disabled="disabled" @Clicked="click"/>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import TextBox from '@/components/TextBox.vue'
import Button from '@/components/Button.vue'

@Component({
  components:{
    TextBox,
    Button
  }
})
export default class Form extends Vue {
  private inputValue: string = ""

  private checkInputValue(val: string){
    this.inputValue = val
    console.log("入力:" + this.inputValue)
  }
}
</script>
子コンポーネント(TextBox)
<template>
  <div>
    <input type="text" :value="inputValue" @input="updateValue" >
  </div>
</template>

<script lang="ts">
import { Component, Vue, Emit } from 'vue-property-decorator';

@Component
export default class TextBox extends Vue {
  private inputValue: string = ""

  //同名であるがなぜか省略すると親と連携できなかった。
  @Emit('updatedInputValue')
  private updatedInputValue(val: string): void{
  }

  private updateValue():void{
    if (event!.target instanceof HTMLInputElement) {
      this.inputValue = event!.target.value;
      this.updatedInputValue(this.inputValue);
    }
  }
}
</script>

親コンポーネントはEmitで指定したイベントにコールする関数を紐づけることで連携できる。
上記の流れで、<input>に入力されるとinputイベントが発火し、続いてEmitで指定するイベントが発火、親コンポーネントの関数がコールされるといった流れ。

各コンポーネントを結合する。

最後に2つのコンポーネントを連携する。
TextBoxへの入力のバリデーションチェックを行い、決定不可のケースではdisabledフラグをtrueにし、Buttonを使用不可な状態に切り替える。

Form.vue(親)
<template>
  <div>
    <TextBox placeholder="ユーザ名" :message="message" @updatedInputValue="checkInputValue" />
    <Button value="決定" :disabled="disabled" @Clicked="click"/>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import TextBox from '@/components/TextBox.vue'
import Button from '@/components/Button.vue'

@Component({
  components:{
    TextBox,
    Button
  }
})
export default class Form extends Vue {
  private inputValue: string = ""
  private disabled: boolean = false
  private message: string = ""
  private click(): void{
    this.disabled = true
    console.log("I'm parent")
  }

  private checkInputValue(val: string){
    this.inputValue = val
    console.log("入力:" + this.inputValue)
    if (this.inputValue.length > 10 || this.inputValue.length < 1){
      this.message = "ユーザ名は1〜10文字です。"
      this.disabled = true
    } else {
      this.message = ""
      this.disabled = false
    }
  }
}
</script>
TextBox.vue(子)
<template>
  <div>
    <input type="text" :placeholder="placeholder" :value="inputValue" @input="updateValue" >
    <p>{{message}}</p>
  </div>
</template>

<script lang="ts">
import { Component, Prop, Vue, Emit } from 'vue-property-decorator';

@Component
export default class TextBox extends Vue {
  private inputValue: string = ""

  @Prop()
  private placeholder: string

  @Prop()
  private message: string

  @Emit('updatedInputValue')
  private updatedInputValue(val: string): void{
  }

  private updateValue():void{
    if (event!.target instanceof HTMLInputElement) {
      this.inputValue = event!.target.value;
      this.updatedInputValue(this.inputValue);
    }
  }
}
</script>
Button.vue(子)
<template>
  <div>
    <button class="button" :class="{disabled}" @click="onClick">{{value}}</button>
  </div>
</template>

<script lang="ts">
import { Component, Prop, Vue, Emit } from 'vue-property-decorator';

@Component
export default class Button extends Vue {
  @Prop()
  private value: string

  @Prop()
  private disabled: boolean

  private onClick():void {
    if (!(this.disabled)){
      this.Clicked()
    }
  }
  //Emitの引数のイベント名はメソッド名と同じ場合は省略可能。この場合省可能
  @Emit('Clicked')
  private Clicked():void {
    console.log("I'm child")
  }

}
</script>

<style scoped lang="scss">
.button{
  text-align : center;
  background-color : #f0b434;
  cursor : pointer;
  border : solid 1px;
  float : center;
}
.disabled{
  background-color : #7d837d;
}
</style>

完成品

image.png

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

簡単なForm作成から学ぶVue(TypeScript)の書き方 その1

vueコンポーネントを作成する。

〜contents〜

  • PropとEmit
  • v-bindとv-on

コンポーネントとは?

Vueでは、各機能の最小単位の部品をコンポーネントとして創り、必要な各箇所でその部品を組み合わせてページを作っていく。
今回のFormの例では、子コンポーネントはテキスト入力の部品コンポーネントやボタンの部品コンポーネントが相当し、これらを束ねるFormコンポーネントが親コンポーネントという関係になる。

image.png

vueファイルのざっくりした理解。

<template>
  <!--HTMLを書く空間-->
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'; //おまじない

@Component //おまじない
export default class コンポーネント名 extends Vue {
  /////////////コンポーネント内で使用する変数、関数の定義//////////////
}
</script>

<style scoped lang="scss">
  /////////////スタイルを書くところ//////////////
</style>

vue-property-decoratorは、TypeScriptで書く用のツールらしい。

各コンポーネントは自身の変数の値や関数コールを知ることはできるが、このままでは外部の値を知ることはできない。
つまり、テキスト入力のコンポーネントに何を入力されても、親のFormコンポーネントからは知れないし、ボタンのコンポーネントは入力値に基づいた活性非活性を分岐することはできない。

そこで、各コンポーネント間で値の授受や、関数コールを行う仕組みを利用する。それがPropEmitである。

重要なことはこれらは一方向の関係であること。
Propは親コンポーネントから子コンポーネントへ値を渡す時に使用し、Emitはこコンポーネントから親コンポーネントの関数をコールする時に使用する。(逆に、子は親の変数を直接変えないし、親が子の関数は動かさない。)

まとめ

Prop:親→子へのデータ受け渡し
Emit:子→親へのデータ受け渡し

つくってみる

ここでは、TextBoxに入力があったらボタンを活性にするFormを作る。

image.png

流れは、TextBoxコンポーネントで入力されたことをトリガにFormコンポーネントへEmitして値が入力されたことを通知する。次に、FormコンポーネントではPropを用いてButtonコンポーネントの活性を変更する。

まずは、Propv-bindの扱い方。

Form(親)からButton(子)へ活性非活性を指定する。

親コンポーネント
<template>
  <div>
    <Button v-bind:disabled="disabled"/>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import Button from '@/components/Button.vue'

@Component({
  components:{
    Button
  }
})
export default class Form extends Vue {
  private disabled: boolean = false
}
</script>
子コンポーネント
<template>
  <div>
    <button class="button" :class="{disabled: disabled}">決定</button>
  </div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';

@Component
export default class Button extends Vue {
  @Prop()
  private disabled: boolean
}
</script>

<style scoped lang="scss">
.button{
  text-align : center;
  background-color : #f0b434; //活性の色
  cursor : pointer;
  border : solid 1px;
  float : center;
}
.disabled{
  background-color : #7d837d; //非活性の色
}
</style>

子コンポーネントでの@Propは親コンポーネントから受け取るデータとして宣言している。
これにより変数disableは親コンポーネントから代入できる。

  @Prop()
  private disabled: boolean //子コンポーネントの変数。

では親からはどのように代入するか?
まずは、Buttonコンポーネントが使えるようにならないといけないのでインポートする。

親コンポーネント(Form)
//インポート
import Button from '@/components/Button.vue'

//コンポーネントの定義
@Component({
  components:{
    Button
  }
})
export default class Form extends Vue {
  private disabled: boolean = false //親コンポーンネントの変数。
}

親コンポーネントからButtonコンポーネントのdisableに値を渡してあげる。この時、渡す対象の子コンポーネントの値にはv-bind:を付して、呼び出す際に値を指定する。

親コンポーネント(Form)
    <Button v-bind:disabled="disabled"/>

上記のケースでは、ボタンが使用可能かを示すdisabledv-bind:が付され、代入される「disable」は定数ではなく、親コンポーネントのdisabledという変数である。なお、v-bind:は省略して:と表記することもできる。

親から指定されるとdisableにより子コンポーネントのclassdisabled: false指定され、disableスタイルが外れる。

<button class="button" :class="{disabled: disabled}">決定</button>

なお、<button class="button" :class="{disabled}">決定</button>としても同じ。

まとめ

変数を渡す方法
<コンポーネント名 v-bind:子コンポーネントの変数="親コンポーネントの変数" />

変数を受け取る方法
@Prop()
//子コンポーネントの変数定義

次にEmitv-onについて

Button(子)からForm(親)へクリックされたことを通知する。

親コンポーネント
<template>
  <div>
    <Button v-bind:disabled="disabled" v-on:Clicked="click"/>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import Button from '@/components/Button.vue'

@Component({
  components:{
    Button
  }
})
export default class Form extends Vue {
  private disabled: boolean = false
  private click(): void{
    console.log("I'm parent")
  }
}
</script>
子コンポーネント(スタイル省略)
<template>
  <div>
    <button class="button" :class="{disabled: disabled}" v-on:click="Clicked">決定</button>
  </div>
</template>

<script lang="ts">
import { Component, Prop, Vue, Emit } from 'vue-property-decorator';

@Component
export default class Button extends Vue {
  @Prop()
  private disabled: boolean

  //Emitの引数のイベント名はメソッド名と同じ場合は省略可能。この場合省可能
  @Emit('Clicked')
  private Clicked():void {
    console.log("I'm child")
  }
}
</script>

@Emitは子コンポーネントから親コンポーネントへ連絡する際に用いる。書式は以下。

構文
@Emit('親側で関数を指定する時のイベント名')
private 子の中で呼ぶ時のメソッド名(): 戻り値{
  //処理
}

トリガされる方では以下。

構文
v-on:イベント名="呼び出す親自身の関数"

上記の例では、子コンポーネントのv-on:click="Clicked"によりクリックイベントが発生した際に子コンポーネント自身の関数Clickedが呼び出される。さらに、この関数ClickedはEmitが指定されており、この関数Clickedが呼ばれると、引数に指定した親コンポーネントのClickedイベントを発生させる。
親コンポーネント側ではv-on:Clicked="click"によりClickedイベント発生をトリガにして親自身の関数clickが呼ばれる。

このようにして、このボタンコンポーネントをクリックすると以下のようにコンソールに出力される。

I'm child
I'm parent

なお、上の例のようにイベント名=メソッド名の場合にはEmitの引数を省略することができる。
また、大文字小文字は区別されず、キャメルケースとケバブケースは勝手に変換され、バインドできる。

v-bind同様に略記法があり、v-on:は省略して@と表記することもできる。

Appendix

Property '~' has no initializer and is not definitely assigned in the constructor

実行時に上のエラーが鬱陶しい場合、tsconfig.jsonに以下を追記すると解消する。

"strictPropertyInitialization": false

お世話になった参考サイト

https://orizuru.io/blog/vue-js/vue_emit-props/

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

簡単なForm作成から学ぶVue(TypeScript)の書き方 その0 環境構築編

Dockerを用いてVue環境を作り、いい感じのフォームを作成しながらVueを学習していく。

Dockerざっくりした理解

Dockerコンテナ

→ アプリケーションの実行環境
ホストOS上に論理的な区画を作ったもの。OSやIPアドレスなどのシステムリソースをコンテナが見かけ上占有できる。
コンテナに必要なモジュールのみを詰めてアプリケーションを実行できる。

Dockerイメージ

→ コンテナを構成するための設計図。
DockerHubを介して種々のイメージを共有できる。

Dockerコマンドの利用

よくあるDockerの練習としてnginxを用いたサーバー構築がある。

nginxでサーバー構築
$ docker pull nginx                  //DockerHubからイメージ取得
$ docker run --name webserver -d -p 80:80 nginx //起動。デーモン化。ブラウザからアクセスできるようになる。
$ docker ps                       //プロセス確認
$ docker stop webserver                //停止
$ docker rm webserver                  //削除

Dockerfile

→ DockerfileはDockerイメージを構成するための構成情報ファイル。
上の練習ではDockerHub上のイメージを取得し、そのまま起動させるものだが、実際の開発では、内部の構成に手を加え、独自のイメージを創り、コンテナを生成することとなる。
コマンドで環境を構築してもよいが、本格的な開発となった場合に以下のような構成情報を別途ドキュメントとして残す必要が出てしまう。

  • 元となるDockerイメージ
  • コンテナ内で実行するコマンド
  • 環境変数などの設定値

こうした構成情報を記述しておくファイルがDockerファイルであり、docker buildコマンドを用いると、このDockerfileを元にしてDockerイメージを作成できるため無駄やミスがない。

Docker Compose

これまでのDockerは1つのコンテナを用いる話だった。先述の通りコンテナは論理的に1区画を創るものであり、複数のコンテナを生成することもできる。複数のコンテナを生成し、それらをコンテナ内部のネットワークで繋ぐこともできる。

これらを束ねるのがdocker-composeであり、その設定ファイルがdocker-compose.ymlである。

vue環境の構築

参考→https://cloudpack.media/43078

正直この通りやっていけばできる。複数選択のものはスペースで選択できる。
設定は少し変更あり。

Dockerfile
FROM node:10.8.0-stretch

RUN npm install --global @vue/cli

WORKDIR /projects
docker-compose.yml
version: '3'

services:
  app:
    build: .
    ports:
      - "8080:8080"
    volumes:
      - ".:/projects"
    tty: true
$ docker-compose up -d
$ docker-compose exec app bash
設定部分
# vue create app

Vue CLI v4.0.5
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, TS, PWA, Router, Vuex, CSS Pre-processors, Linter, Unit, E2E
? Use class-style component syntax? Yes
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? Yes
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS (with dart-sass)
? Pick a linter / formatter config: Basic(一番目を選択)
? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)Lint on save
? Pick a unit testing solution: Mocha
? Pick a E2E testing solution: Cypress
? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? No
? Pick the package manager to use when installing dependencies: NPM

構築が終わったらappディレクトリに移行して下のコマンドを実行。ブラウザからlocalhost:8080にアクセスして確認。

 # cd app
 # npm run serve

ctrl+Cで終了し、exitでDockerから抜けた後$ docker-compose downでコンテナごと終了。

お世話になった参考サイト

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

【徒然なるままに】2020年最新、WEB勢力図を三国志にしてみた

今は昔

一昔前はTwitterのScala/FacebookのReactが猛威を振るっていた時期がありましたが、
イキリ勢力の消耗戦で、最近はVue勢がかなり盛り上がっている印象を受けます。

かつてのJQuery、それがVue

そんな予感がひしひしとします。

そこで、最新のWEB勢力図を三国志風にしてみました。
三国志の知識はマンガとゲームとWikiです、すみません。

全く知らないキッズは中田さんのYoutube大学をご覧ください。
【三国志】第一話〜英雄たちの夜明け〜ついに授業リクエストNo.1の超大作

三国志a.png

  • 曹操(PHP) … 何でもあり最強、女を侍らせる。
  • 夏侯惇(Laravel) … ロマンチスト
  • 司馬懿(Vue) … 超現実主義、パクリもありあり

  • 孫権(Ruby) … なんとなく
  • 陸遜(Rails) … 堅実な感じ
  • 周瑜(Angular) … 超優秀だがぱっとしない

  • 劉備(Scala) … 形式(礼)に拘る、前漢(Java)の末裔
  • 関羽(Playframework) … RESTを世に広めた貢献者
  • 孔明(React) … 革新者、新時代を拓く智彗者

後漢、晋は図の通りです

面白そうなので、図はクリエイティブ・コモンズ(表示—非営利—継承)にしときます。
勝手に改編(罵倒)してCCでお願いします。

Reactはreact-routerとhooksでものすごい簡単になったので、
是非、新時代を体験してください。

新型Reactとは

簡単に書くと、これだけです。
このアトムを融合していくだけです。

const 関数 = () => {

  ~ステート式ES6スクリプト~

  render (
    <div></div>
  )

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

【徒然なるままに】2020年最新、WEB技術の勢力図を三国志にしてみた

今は昔

一昔前はTwitterのScala/FacebookのReactが猛威を振るっていた時期がありましたが、
イキリ勢力の消耗戦で、最近はVue勢がかなり盛り上がっている印象を受けます。

かつてのJQuery、それがVue

そんな予感がひしひしとします。

そこで、最新のWEB勢力図を三国志風にしてみました。
三国志の知識はマンガとゲームとWikiです、すみません。

全く知らないキッズは中田さんのYoutube大学をご覧ください。
【三国志】第一話〜英雄たちの夜明け〜ついに授業リクエストNo.1の超大作

三国志a.png

  • 曹操(PHP) … 何でもあり最強、女を侍らせる。
  • 夏侯惇(Laravel) … ロマンチスト
  • 司馬懿(Vue) … 超現実主義、パクリもありあり

  • 孫権(Ruby) … なんとなく
  • 陸遜(Rails) … 堅実な感じ
  • 周瑜(Angular) … 超優秀だがぱっとしない

  • 劉備(Scala) … 形式(礼)に拘る、前漢(Java)の末裔
  • 関羽(Playframework) … RESTを世に広めた貢献者
  • 孔明(React) … 革新者、新時代を拓く智彗者

後漢、晋は図の通りです

面白そうなので、図はクリエイティブ・コモンズ(表示—非営利—継承)にしときます。
勝手に改編(罵倒)してCCでお願いします。

Reactはreact-routerとhooksでものすごい簡単になったので、
是非、新時代を体験してください。

新型Reactとは

簡単に書くと、これだけです。
このアトムを融合していくだけです。

const 関数 = () => {

  ~ステート式ES6スクリプト~

  render (
    <div></div>
  )

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

dataの値をクラス名に使用する(Vue)

 <div :class='`${sample.id}`'>サンプル</div>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsをサブディレクトリで運用する時の注意

ちょっと調べることがあったのでメモ書き

vue.config.js というファイルを作りその中で

module.exports = {
  publicPath: '/subdirectory'
}

publicPathを設定するとbuild時にうまく書き出してくれるようです。
また画像のパスも相対パスにするなどで間違いのないようにしましょう。

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

vue リンク先にパラメータ渡す

ルータ

{ 
  path: '/user/:id',
  name: 'Detail',
  component: Detail 
},

リンク元

<td class="name"><router-link :to="{ name : 'Detail', params : { id: grp.user.id }}">{{grp.user.last_name}} {{grp.user.first_name}}</router-link></td>

受け取る

methods: {
    getParam() {
        return this.$route.params.id
    },
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vue.jsのscope id計算方法

全く別のvueモジュールとscoped cssのID(data-v-xxxx の様な値)が被ったので調べた。

2020/01/26時点ではここが計算元
https://github.com/vuejs/vue-loader/blob/74febfc280/lib/index.js#L94

  const id = hash(
    isProduction
      ? (shortFilePath + '\n' + source)
      : shortFilePath
  )

shortFilePathはsrc/index.vueの様な値。この値の場合、ハッシュ値は2964abc9になる。

image.png

Productionビルドにするとソースコードも含まれるので被ることは無さそう。自分用だとdevelopmentビルドをそのまま使っていたので引っかかってしまった。困った

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