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

Vuexで状態管理しながらメモアプリを作ってみた

メモアプリを Vuex で管理する

index.vue ファイルとコンポーネントだけで作れる簡単なアプリですが、 Vuex による状態管理まで理解したかったので今回 Vuex を取り入れることにしました。
小さいアプリケーションだとデータの流れが分かりやすいので、これから Vuex を学びたい人におすすめです。

前提条件

  • Nuxt.js をインストール済 バージョン 6.4.1 (npm -vで確認)

  • Nuxt.js 公式ドキュメントのチュートリアルを参考に、新しいプロジェクト名で 「create-nuxt-app」 の設定をします。作業はターミナル上で、ルートで行います(新しいプロジェクト名のディレクトリがルートに出来上がります)。これで簡単に開発の準備が整います。

  • 何もない状態(スクラッチ)から始める場合も上記ドキュメントの中に、続けて記載がありますのでそちら参考にしてみてください。

ディレクトリができ、エディタを開くと↓写真のようなフォルダ一覧があります。http://llocalhost:3000で確認すると、 pages フォルダの中の index.vue の内容になっています。 h1 タグのプロジェクト名の下に自動で description の文字が入っている場合があるので、変更するときは package.json を編集します。(写真の下から3番目)
スクリーンショット 2019-07-30 13.00.22.png

Flux の考え方 → Vuex を利用する場面

Flux についての説明は省きますが、 Vuex とは、ドキュメントによると

Vuex は Vue.js アプリケーションのための 状態管理パターン + ライブラリです。

Vuex は一箇所にアプリケーションの状態(ステート)がまとまっています。つまり、"Single source of truth"を保てます。情報源を信頼できるものにしようという事で、下記のように流れは一方向です。
State を変更できるのは Mutations のみであり、 Actions は「コミット」しかできません。

スクリーンショット 2019-07-30 16.23.31.png
スクリーンショット 2019-07-30 16.25.52.png

必要なファイルを揃える

さっそく作っていきますが、必要なファイルを3つ用意します。ファイルは template タグでくるみます。私のエディタは Emmet が入っておりscaffoldと入力するとテンプレートが用意されます。

  • pages/index.vue (最初に既にあるはずです)
  • components/MemoList.vue (文字はパスカルケースで作成/ 再利用可能な部品のようなもので Model 。)
  • store/memo.js ( state, mutations, actionsを今後記述します)

フォームを作る

input タグを作るときは、v-modelで Model, View の双方向バインディングを可能にします。片方変更したら、( input の中身について DOM やデータに変更があれば)もう片方も同じように変更されます。
@clickとはv-onのイベントハンドラの省略。
props でコンポーネントに値を渡す際は、ケバブケースmemo-contentsで渡します。コンポーネント側で受け取る時の属性はキャメルケース。

index.vue
<div class="input_container">
  <p>
    <input
      v-model="inputText" 
      type="text"
     >
  </p>
  <button @click="addMemo()">Save</button>
</div>

<!-- コンポーネントに分けた部分について記述-->
<!-- memo-contents が属性で、" memoContents "はここでは変数です-->
<!-- @delete は、ただのきっかけで memo.js アクションの deleteMemo メソッドに伝えているだけ-->
<memo-list
  :memo-contents="memoContents"
  @delete="deleteMemo()"
/>

コンポーネントを作る

input 入力したものは一つ一つがメモリストです。何度も使うのでコンポーネントとして MemoList.vue ファイルに記述します。

ここでulタグの中でv-forを用いてリストを複数生成しています。
先にscriptタグのprops:を見てください。memoContentsがあり、ここで配列を作っています。
先程のリストの複数生成時にはここのことを指しており、 memoContents に入っている分だけインデックスを付けてアイテム( リスト )を生成します。

Memolist.vue
<template>
  <div>
    <ul
      v-for="(item, index) in memoContents"
      :key="index"
    >
      <li>
        <span>{{ item.text }}</span> <!-- ここについては後ほど -->
        <span>{{ item.date | dateConvert}}</span> <!-- ここについては後ほど -->
        <button @click="handleDelete(index)">x</button>
      </li>
    </ul>
  </div>
</template>

<script>
  export default {  //DOMに描き出すためにこのように書きます
    props: {    //親である index.vue から受け取るときはこの中に入ります
      memoContents: {  // memoContents として受け取り、値を一つずつ配列[]に入れます
        type: Array,
        default: () => []
      }
    },

index.vue に追記

コンポーネントを組み込む際には import する必要があります。
まだ Vuex に関する memo.js は記述していませんが、ここで呼び出します。methodsの中addMemo()については連想配列の階層がコンポーネントで書いたものと一致する必要があります。
computedでは後述の memo.js の state の memoContents を参照。若干ややこしいですが、後述の memo.js と照らし合わせてみてください。

index.vue
<script>
  // MemoList.vue を import したら、 exportdefault の中に compornent として記述
  import MemoList from '~/components/MemoList'
  import { mapState, mapActions } from 'vuex' // このように記述する

  export default {
    components: {  //ここでも描き出すためにMemoListを書きます
      MemoList
    },
    data: () => ({
      inputText: ""
    }),
    // 算出プロパティといいますが、 DOM へ表示されるものが computed です
    computed: {
      ...mapState({
        // どう index.vue で呼ぶか宣言して、 state の中の memo.js の中の memoContents を参照
        memoContents: state => state.memo.memoContents
      }),
      // ...mapState('memo', ['memoContents'])とも書けます
    },
    methods: {
      ...mapActions('memo', ['addMemoContents','deleteMemoContents']),
      addMemo() {
        // methods 内の関数として呼び出し
        this.addMemoContents({  //ここがコンポーネント側で{{ item.text }}で書いたところと階層が一致するはず
          text: this.inputText,
          date: new Date()
        })
        this.inputText = ""
      },
      // memoContens を消すものなので deleteMemo メソッドは index.vue にあるべき
      // 子( MemoList.vue からの)の index を受け取っている
      deleteMemo(index) {
        this.deleteMemoContents(index)
      }
    }
  }
</script>

MemoList.vue に追記

ここで重要なものがもうひとつ、子から親へ値を渡す$emitです。

MemoList.vue
<script>
  export default {   
  // component 内で完結するもの
    filters: {
      dateConvert: function (value) {
        const date = new Date(value)
        return `${date.getFullYear()}-${date.getMonth()+1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}`
      }
    },
  // 親のメソッドを発火させるためだけのもの
  methods: {
    //上記のクリックイベントの index を受け取って $emit で親に受け渡す
    handleDelete(index) {
      this.$emit('delete', index)
    }
  }

memo.js に記述

ようやくここまで来ました。今回重要なポイントは、「 mutations 以外は直接 state とやり取りしない」ことです。mutations が唯一 state を更新できるという事を踏まえて見ていきます。

memo.js
// state  memoContentsが入っている状態
export const state = () => ({
  memoContents: []
})

//mutations データを更新できる
export const mutations = {
  //引数に state と書く。 memo は actions から渡される
  updateMemoContents(state, memo) {
    state.memoContents.push(memo)
  },
  deleteMemoItem(state, index) {
    state.memoContents.splice(index,1)
  }
}

// actions ・・ mutations を発動する
export const actions = {
  // { commit }と書くことで mutations に渡せる
  // index.vue からの memoDate を受け取ってから mutations に commit
  addMemoContents({ commit }, memoData) {
    commit('updateMemoContents', memoData)
  },
  deleteMemoContents({ commit }, index) {
    commit('deleteMemoItem', index)
  }
}

まとめ

今回初めて Vuex を勉強しましたが、まだまだ理解が足りない部分が多く、冗長になってしまいました。 Vuex を使うべきアプリではありませんが、小さいアプリで全体像が捉えられたのは良かったです。

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

Vueプロジェクトにおいて、クラスにプロパティを持たせようとした時のエラー

vue-cliのプロジェクトにおいて、

class AuthService extends EventEmitter {
  idToken = null // ←これがエラーになる
  profile = null
  tokenExpiry = null

  // Starts the user login flow
  login (customState) {
    webAuth.authorize({
      appState: customState
    })
  }
...
}

こういう書き方をすると、

Syntax Error: Unexpected token (9:10)

   7 | 
   8 | class AuthService extends EventEmitter {
>  9 |   idToken = null;
     |           ^
  10 |   profile = null;
  11 |   tokenExpiry = null;
  12 | 

こんな文法エラーぽいのがでた。

package.json

  ...
  "devDependencies": {
    "@babel/polyfill": "^7.4.4",
    "@vue/test-utils": "^1.0.0-beta.29",
    "autoprefixer": "^7.1.2",
    "babel-core": "^6.22.1",
    "babel-eslint": "^8.2.1",
    "babel-helper-vue-jsx-merge-props": "^2.0.3",
    "babel-jest": "^24.8.0",
    "babel-loader": "^7.1.1",
    "babel-plugin-istanbul": "^4.1.1",
    "babel-plugin-syntax-jsx": "^6.18.0",
+   "babel-plugin-transform-class-properties": "^6.24.1",
    "babel-plugin-transform-runtime": "^6.22.0",
    "babel-plugin-transform-vue-jsx": "^3.5.0",

.babelrc

+   "plugins": ["transform-runtime", "transform-class-properties"],

でエラーが解消された。

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

Vueインスタンスとコンポーネントの関係

はじめに

オブジェクトリテラルの観点でVueインスタンス、コンポーネントを整理してみる。
これによって、

  • ルートVueインスタンス
  • グローバルコンポーネント
  • ローカルコンポーネント

3つのVueインスタンスがそれぞれ何故そういう書き方になるのかがクリアになる。また、単一ファイルコンポーネント等のより発展的な内容についても理解の助けになる。本記事では、「ルートVueインスタンスとは?」「コンポーネントとは?」といった、そもそものところについては触れないけれども、それらについてなんかしっくりこない感じがあるひとに対しては何かしら役に立つかもしれない。

  • 本記事では、Vueインスタンス、コンポーネントの生成時に記述されるオブジェクトリテラルを便宜上Vueオブジェクトと呼ぶことにしています。
  • 記事内のサンプルコードはCDNでVueを読み込めば動く内容です。
  • サンプルコードの結果はルートVueインスタンス、グローバルコンポーネント、ローカルコンポーネントで、ほぼ同じです。

Vueオブジェクトによる整理

まずは、ルートVueインスタンス、グローバルコンポーネント、ローカルコンポーネントについて、それぞれ導入的な書き方からVueオブジェクトの観点で整理した書き方に変更する。

ルートVueインスタンス

よくある導入的な書き方。

<div id="container">
    <p>{{ comments }}.</p>
</div>
// ルートVueインスタンス
new Vue({
    el: '#container',
    data: {
        comments: "hello in root"
    }
});
# ブラウザ出力
hello in root.

ルートVueインスタンスを生成するnew Vue()の引数はオブジェクトリテラル。これをVueオブジェクトとして外に出す。

// ルートVueオブジェクト
// new Vue()の引数を外に出した
let rootVueObj = {
    el: '#container',
    data: {
        comments: "hello in root"
    }
};
// ルートVueインスタンス
// rootVueObjを登録
new Vue(rootVueObj);

グローバルコンポーネント

よくある導入的な書き方。

<div id="container">
    <g-component></g-component>
</div>
// グローバルコンポーネント
Vue.component('g-component', {
    data: function () {
        return {
            comments: "hello in global component"
        }
    }
    , template: '<p>{{ comments }}.</p>'
});

// ルートVueインスタンス
new Vue({ el: '#container' });
# ブラウザ出力
hello in global component.

ルートVueオブジェクトについては前章同様。

グローバルコンポーネントを生成するVue.component()の第二引数はオブジェクトリテラル。これもコンポーネントVueオブジェクトとして外に出す。

// コンポーネントVueオブジェクト
// Vue.component()の第二引数の内容を外に出した
let componentVueObj = {
    data: function () {
        return model
    }
    , template: '<p>{{ comments }}.</p>'
};

// ルートVueオブジェクト
// new Vue()の引数を外に出した
let rootVueObj = {
    el: '#container'
};

// グローバルコンポーネント
// componentVueObjを登録
Vue.component('g-component', componentVueObj);

// ルートVueインスタンス
// rootVueObjを登録
new Vue({rootVueObj);

ローカルコンポーネント

よくある導入的な書き方。

<div id="container">
    <l-component></l-component>
</div>
// コンポーネントVueオブジェクト
let componentVueObj = {
    data: function () {
        return {
            comments: "hello in local component"
        }
    }
    , template: '<p>{{ comments }}.</p>'
};

// ルートVueインスタンス
new Vue( {
    el: '#container', 
    components: {
        'l-component': componentVueObj
    }
});    
# ブラウザ出力
hello in local component.

ルートVueオブジェクトについてこれまで同様外に出す。

let componentVueObj = {
    data: function () {
        return {
            comments: "hello in local component"
        }
    }
    , template: '<p>{{ comments }}.</p>'
};

// ルートVueオブジェクト
// new Vue()の引数を外に出した
let rootVueObj = {
    el: '#container', 
    components: {
        'l-component': componentVueObj
    }
};

// ルートVueインスタンス
// rootVueObjを登録
new Vue(rootVueObj);    

Vueオブジェクトの定義と登録

ここまでの整理で、Vueオブジェクト定義と登録によって成立していることがはっきりしたので、今度はルートVueインスタンス、グローバルコンポーネント、ローカルコンポーネント、それぞれの定義と登録を比較してみる。

定義

// ルートVueインスタンスでのVueオブジェクト記述形式
let vueObj = {
    el: 'DOMセレクタ',
    data: {}
};
// グローバルコンポーネント/ローカルコンポーネントでのVueオブジェクト記述形式
let vueObj = {
    data: function () {
        return {}
    }
    // , template: 'テンプレート'

まず、グローバルコンポーネントとローカルコンポーネントのVueオブジェクトは全く同じである。ルートVueインスタンスは全く同じでは無いが、かなり似ている(templateオプションについては、後で触れます)。

登録

// ルートVueインスタンスでのVueオブジェクトの登録
new Vue(rootVueObj);
// グローバルコンポーネントでのVueオブジェクトの登録
Vue.component('g-component', componentVueObj);
// ルートVueインスタンス
new Vue({rootVueObj);
// ローカルコンポーネントでのVueオブジェクトの登録
let rootVueObj = {
    el: '#container', 
    components: {
        'l-component': componentVueObj
    }
};
// ルートVueインスタンス
new Vue(rootVueObj);    

Vueオブジェクトという観点から整理すると、
- 定義の書き方は2種類
- 登録方法はルートVueインスタンス、グローバルコンポーネント、ローカルコンポーネントによってそれぞれ異なる

これだけでもかなり見通しがよくなりませんか?

考察

ルートVueインスタンスとコンポーネントの違い

elオプション

ルートVueインスタンスとコンポーネントの大きな違いはHTML要素へ紐付けする際の対象の違い。ルートVueインスタンスは'#container'という既存のHTML要素id属性で紐付けしている。それに対して、グローバルコンポーネントの'g-component'もローカルコンポーネントの'l-component'カスタムタグへの紐付けである。Vue独自ルールであるカスタムタグは素のHTMLのままでは使用することはできない。そのため既存のHTML要素の接点としてルートVueインスタンスが存在している。そう考えると、ルートVueインスタンスにだけ'elオプション'がある理由が理解できる(唯一の例外は el のようなルート固有のオプションです)。

dataオプション

ルートVueインスタンスでは直接文字列や数値が入るのに対し、コンポーネントでは関数経由でそれらを指定する。その理由はインスタンスごとの値を保持するためということだが(data は関数でなければなりません)、関数にするとなぜそれが可能なのかはわからない(何となくはわかる気もするが、、)。誰か教えていただけると嬉しい。

templateオプション

templateオプションはルートVueインスタンスに登録するVueオブジェクトにも記述できる(あまりそういう書き方をは目にしないが、、)。なので、実はこの点に関しては両者に違いはない。
逆に、本記事のようような書き方をする場合、HTML側ににテンプレートの内容を書くことはできない。X-テンプレートを使うとできるようなので、気になる人はそちらを試してみると良いかもしれない(X- テンプレート)。
X-テンプレートの他には、単一ファイルコンポーネントという形式でVueオブジェクトからテンプレートの記述を分割できる。プロジェクトがある程度大きくなれば単一ファイルコンポーネントを使うことが必須になるはず。本記事では触れないけれども、本記事の内容が理解の助けになると思う(そもそも、それが本記事の目的の一つ)。

グローバルコンポーネントとローカルコンポーネントの違い

両者の違いは名前通りグローバルとローカルの違いだが、それは記述にも表れている。グローバルコンポーネントの登録は直接カスタムタグの名前が紐付けられている。それに対し、ローカルコンポーネントの登録はルートVueインスタンス(親)のcomponentsオブションに対してである(親VueオブジェクトはルートVueインスタンスである必要はない)。これによってローカル化を実現している。
違いではなく共通点に目を向けると、どちらもルートVueインスタンスで紐付けされたHTML要素<div id='container'>)のなかで、コンポーネントのカスタムDOMが記述されている。繰り返しになるが、ルートVueインスタンスは既存のHTML要素の接点なので、グローバルコンポーネントであってもその外には出られない。

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

日本史上初! 速読ウェブアプリ 時間を生み出す最強スキル

日本史上初! 速読ウェブアプリ 時間を生み出す最強スキル

LINE、SNS、ネット小説、実用書
ちまたには文字があふれている
読むべきものと読まないものを見極める知性と
速く読む力
この二つが現代社会でうまく文字とつきあうマストスキルだ

だれかの知恵は真似できなくても
自分の速読は鍛えられる

速く読めれば、時間が生まれる
時間が生まれれば、多くを読める
多くを学び、自由な時間をもっていれば、
私たちは全然違う人間になれる

Sokdok.net

目次

  • 1 結論
  • 2 細事
  • 2-1 開発の動機
  • 2-2 日本史上初とは
  • 2-3 ロジック
  • 3 おわりに

1 結論

Sokdokは、速読訓練用のウェブアプリです。

  • 好きなテキストで練習可能です(ニュース、ブログ、小説を読もう、好きなサイトから文章をコピー&ペーストするだけ)
  • 好きなスピードで速読の訓練をすることができます
  • 一度に表示する語数を自由に調整できます

image.png

2 細事

2-1 開発の動機

海外に目を向ければ、Spreederという速読用ウェブアプリがあります。
好きなテキストを好きなスピードで速読できるサービスです。
Spreederを使っているうちに、日本語でも練習したいと思うようになりました。
ところが日本の速読用サイトで、Spreederのようなアプリは見つけれず。
自分で作りました。

2-2 日本史上初とは

従来の日本語用速読アプリは、事前に用意されたテキストでしか練習できなかったようです。
Sokdokは、ユーザが自由にテキストを用意できる、日本で最初の無料のウェブサービスです。

2-3 ロジック

英語の文章を分割するのは容易です。

English sentence has blank space between each words, so it's easy to divide a sentence into a list of words.
(英語の文は単語の間にスペースがあるから、単語のリストに分割するのは簡単だよ)

日本語の文章を速読用に分割することが難しいから、従来のアプリは、事前に用意されたテキストでしか練習できなかったのかと思われます。
例えば、「宝石商リチャード氏の謎鑑定」という文言を文節に区切るとすると
「宝石商,リチャード氏の,謎鑑定」ですが、プログラミングで区切ると難しい。

なぜなら、辞書データ「石、鑑定、氏、商、宝、謎、の、宝石、宝石商、リチャード」に対して、「宝、石、商、リチャード、氏の、謎、鑑定」と区切ってしまうかもしれないです。
それに、実際は辞書はもっと重いので、長文の全文字を辞書検索していると、時間がかかって仕方ありません。

そこで人工知能で文の形を解析されている方もいます。

でも私は人工知能の知識がありません。
そして幸いにも、速読という山を登るのに人工知能という飛行機はいりません。

日本語には、主に漢字と平仮名とカタカナしかないからです。

「漢字とカタカナと平仮名、3種類も文字があって、日本語はとても難しいと言うが、漢字がなければどれだけ読みづらいか気づかないわけじゃないでしょう。すくなくとも あいだに すぺーすが ないと ごらんこんなによみにくいじゃあないか」

漢字:〇 平仮名:× カタカナ:△ として変換すると…

「〇×△×〇、3〇×〇×、〇×〇×〇×、〇×〇×〇×。× × × × ×」

だったら、漢字+平仮名、カタカナ+平仮名で速読用の文章は作れます。
つまり「宝石商、リチャード氏の、謎鑑定」です。

analyzeJapanese.js
function analyze(targetText) {
    var reg = /「?[ァ-ヶー]+[\u4E00-\u9FFF]+[ぁ-んー]+[、。;:!?!?.,;::  …...・]?[!?]?[?!]?[!?]?[?!]??|「?[A-Za-z0-9/-:.,;]+[\u4E00-\u9FFF]+[a-zA-Z0-9]?[ぁ-んー]+[、。;:!?!?.,;::  …...・]?[!?]?[?!]?[!?]?[?!]??|「?[\u4E00-\u9FFF]+[a-zA-Z0-9]?[ぁ-んー]+[、。;:!?!?.,;::  …...・]?[!?]?[?!]?[!?]?[?!]??|「?[ァ-ヶー]+[0-9]?[ぁ-んー]+[、。;:!?!?.,;::  …...・]?[!?]?[?!]?[!?]?[?!]??|「?[0-9a-zA-Z]+[ぁ-んー]+[、。;:!?!?.,;::  …...・]?[!?]?[?!]?[!?]?[?!]??|「?[a-zA-Z0-9]+[ァ-ヶー]+[、。;:!?!?.,;::  …...・]?[!?]?[?!]?[!?]?[?!]??|「?[ぁ-んー]+[、。;:!?!?.,;::  …...・]?[!?]?[?!]?[!?]?[?!]??|[\u4E00-\u9FFF]+[、。;:!?!?.,;::  …...・]?[!?]?[?!]?[!?]?[?!]??/g;
    return targetText.match(reg)
}

鬼のように長い馬鹿な正規表現になっておりますが、以下の組み合わせを |(または) でつないで、
〇×、△〇などのかたまりを見つけているだけです。

分類 正規表現 (文字コードはutf-8)
漢字 [\u4E00-\u9FFF]
平仮名 [ぁ-んー]
カタカナ [ァ-ヶー]

おわりに

お読みいただきありがとうございます
速読は筋トレみたいなもので、才能とは関係なしに伸ばせるらしいです
私も、英文で400wpmから始めて、2週間で480wpmまで伸びました
Sokdokは日本語で練習できるので、チェックしてみてください!

みなさんの速さや速読に関するコメントお待ちしております↓

日本人の人生の自由な時間が増えますよう
かしこ

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

Javascriptの関数の引数でよく見る({ id })ってどういう意味?

VueやReactを書いていると、こんな感じの関数をよく見ます。

sample.vue
methods: {
  userData({ id }) {
    // 処理
  }
}

Vuexのactionでも({ commit })という記述をよく見ますよね。

正直、初めて見たときは意味不明でした。引数に波括弧ってどういうこと?みたいな。

同じように思っている人も多い(はず)なので、以下でこの独特な記法について解説していきます。

上記の書き方はES6の分割代入の一種っぽい

分割代入とは、以下のようなものです。

const data = {
  id: 1,
  name: 'taro'
}

const { id, name } = data;

console.log(id); // 1
console.log(name); // taro

dataオブジェクトはidとnameというプロパティを持っています。代入したい変数名とプロパティ名が一致している場合、上記のように書くことで1行にまとめることが可能です。

関数の引数に出てくる波括弧もよく似たもので、上の考え方を少し応用すれば理解できるかと思います。

sample.vue
<template>
  <div>
    {{ displayUserData(userData) }} // taroと表示される
  </div>
</template>

<script>
export default {
  data() {
    return {
      userData: {
        id: 1,
        name: 'taro'
      }
    }
  },

  methods: {
    displayUserData({ name }) {
      return name
    }
  }
}
</script>

displayUserDataメソッドの引数は({ name })となっています。これは、引数として渡されたオブジェクトのプロパティにnameがあれば、それを利用するという意味です。

このメソッドを使う場合、上記のようにuserDataオフジェクトをそのまま放り込みます。userDataはnameプロパティを持っているので、この場合は'taro'が表示されます。

これは以下の書き方と同義です。

sample.vue
<template>
  <div>
    {{ displayUserData(userData.name) }} // taroと表示される
  </div>
</template>

<script>
export default {
  data() {
    return {
      userData: {
        id: 1,
        name: 'taro'
      }
    }
  },

  methods: {
    displayUserData(name) {
      return name
    }
  }
}
</script>

displayUserDataの引数から波括弧がなくなり、加えて呼び出し時にuserData.nameと、プロパティを指定しています。

ということは、({ commit })はどういう意味?

Vuexのactionsを定義する際、下記のような書き方をすることが多いかと思います。

sample.js
actions = {
  somethingAction({ commit }) {
    commit('somethingMutation')
  }
}

実は、上記についてはこのように書くこともできます。

sample.js
actions = {
  somethingAction(context) {
    context.commit('somethingMutation')
  }
}

Vuexのactionsはdispatchされると、contextオブジェクトを受け取ります。そしてcontextオブジェクトは、以下のようなプロパティを持っています。

{
  state,
  rootState,
  commit,
  dispatch,
  getters,
  rootGetters
}

ただ、commitしか使わないケースが大半なので、({ commit })と書くことで簡略化することが多いかと思います。

上記の通り、contextオブジェクトはdispatchプロパティも持っているので、以下のような感じにすればactionから別のactionを呼び出すことも可能です。

actions = {
  somethingActions({ commit, dispatch }) {
    commit('somethingMutation')
    dispatch('otherAction')
  }
}

公式ドキュメントにより詳しく書かれているので、もっと詳しく知りたい人はそちらを見てください。。

vuex公式ドキュメント

まとめ

jsフレームワークのconfigファイルとかでもよく出てくる記法なので、理解しておくと便利です。

これらについては、「呼びされるときに呼び出し元からオブジェクトを渡される」ということを知っておくと、より理解しやすいかなと思います。

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

javascript vue.js object 配列を検索

vue.jsで配列を検索したい。
しかし、なぜか find を使うと googlebot が エラーを吐く。
これはSEO的にまずい。

ということで、 filter を使って要素を取得したり、存在チェックしたりします。

まずは元になるデータ

var arr = [
    {
        id:100,
        name: '一郎',
        age: 25
    },
    {
        id:102,
        name: '二郎',
        age: 21
    },
    {
        id:153,
        name: '三郎',
        age: 18
    }
];


ID153のデータはある?

var tmp = arr.filter(e => e.id == 153);

if(tmp.length > 0){
    console.log("ID153のデータはあります");
} else {
    console.log("ユーザーが見つかりません");
}


データを絞り込む

//指定した条件のデータを抽出
var tmp = arr.filter(e => e.age >= 20);

// 結果
// 0: {id: 100, name: "一郎", age: 25}
// 1: {id: 102, name: "二郎", age: 21}

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

【Vue.js】v-forの中で外部リンクと内部リンクを混ぜて表示する方法

本記事の内容

v-forを使って要素を繰り返して表示するのはわかったけど、その中身に外部リンクと内部リンクが混ざっている時の実装で少し悩んだので、同じような悩みを抱えている人の助けになる記事です。

前提

・Vue.jsとNuxt.jsを使ったアプリケーションでの実装
・Vuetifyを利用

です。

実装

大元のvueファイルの内容(一部抜粋)

(色々省略)

<v-btn
 v-for="link in links"
 :key="link"
 color="white"
 flat
 round
 :to="link.url"
>
 {{ link.name }}
</v-btn>

(色々省略)

<script>
  export default {
    data: () => ({
      links: [
        { url: 'hoge', name: '外部リンクへ' },
        { url: 'privacy-policy', name: 'プライバシーポリシー'},
        { url: 'service-policy', name: '利用規約'},
        { url: 'contact', name: 'お問い合わせ'},
        { url: 'for_user', name: '出品したい方'},
      ]
    })
  }
</script>

上記の内容では。:to="それぞれのurl"が入ってくる形になっていて、下の4つについてはそれで問題なかったのですが、hogeの部分を https://google.com のような外部リンクに飛ばしたかったので、ここの部分がちゃんと動作するように変更することにしました。

手順としては下記になります。

・もともとあった:to="link.url"を削除
・クリックした時にurlを判定して、特定のものについては外部リンクに遷移するように変更

この手順で実装したものが下記になります。

変更後のvueファイルの内容(一部抜粋)

(色々省略)
<v-btn
 v-for="link in links"
 :key="link"
 color="white"
 flat
 round
 @click="getCreateUrl(link.url)"
>
(色々省略)
<script>
  export default {
    data: () => ({
      links: [
        { url: 'hoge', name: '外部リンクへ' },
        { url: 'privacy-policy', name: 'プライバシーポリシー'},
        { url: 'service-policy', name: '利用規約'},
        { url: 'contact', name: 'お問い合わせ'},
        { url: 'for_user', name: '出品したい方'},
      ]
    }),
    methods: {
      getCreateUrl(url) {
        if(url==='hoge') {
          location.href="https://google.com/"
        } else {
          location.href=`${url}`
        }
      }
    }
  }
</script>

これで、無事に動いてくれました。

※他にいいやり方があったらご指摘ください。

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

MEMO[Vue.js]

Vue.jsのメモ

データの反映

作成されたVueインスタンスは、プロパティ値が変更されると、関連付いているメソッドの値も変更される。

example
// 定義
var data = { a: 1 }
var vm = new Vue({
  data: data
})

// プロパティ操作
vm.a = 2
data.a // 2

プロパティ値を変更されたくない場合

以下を適用する。

example
Object.freeze(obj)

プロパティへのアクセス

接頭辞「$」を使用して呼び出す事が可能。

example
var data = {'data' : 1}
var vm = ner Vue ({
    el = $example,
    data = data
})

// 呼び出す
vm.$data
vm.$el

ライフサイクルフック

インスタンス生成処理に、特定の処理を挟むことが出来る機能が、備わっている。
以下は、インスタンス生成後への処理追加方法。

example
new ({
    data : {a:1},
    created : function (){
        console.log('インスタンス完了')
    },
})

その他にも関数が用意されている。

テンプレート

HTMLパースを適切に行うために。

初歩

基本的には、Mustache構文を利用する。
特にプロパティを指定しない場合には、要素の操作で、データバインディング箇所は、可変式になっている。

example
//表示データが変わってもいい場合
<span> Message: {{ msg }} </span> // 画面上 -> Message: example!
vm.msg = 'change!'                //  画面上 -> Message: change!

// 表示データを変えられたく無い場合
<span v-once> Message: {{ msg }} </span> // 画面上 -> Message: example!
vm.msg = 'change!'                    //  画面上 -> Message: example!

属性

Mustacheは属性値では使用できない。
代わりに、v-bindディレクティブを使用する。

example
<div v-bind:id="example"></div>

desable設定時は挙動が変わる。
以下は、値が存在する時には、ボタンが非活性にする記述。

example
var vm = new Vue ({ 
    el  :'#example',
    data: {
      example: 'desable',
    }
})
example
<button id="example" v-bind:disabled="example">Button {{ example }}</button>

javascriptの処理を活用

バインディング内で、javascriptを利用できる。

example
{{ number + 1 }}                        // 定義した値に1を足す。文字列の場合には連結。
{{ ok ? 'YES' : 'NO' }}                    // 条件式
{{ message.split('').reverse().join('') }} // scriptを適用

適用されない記述方式もあるので、仕様を把握するのがよい。
三項演算子でしか適用されな等。

ディレクティブ

接頭辞「v-」で定義した箇所へ、動的な処理を施せる。

引数

引数を既述することにより、要素を動的に扱うことが出来る。

example
var vm = new Vue({
  el: '#example',
  data: {
    bool: false,
    url: 'http://',
  },
  methods: {
    go:function(e){
      alert('Hello ' + this.name + '!')
    },
  },
})

表示例。

example
<div id="example">
    以下要素は表示されない
    <p v-if="bool">Now you see me</p>
    href属性にurl変数展開
    <a v-bind:href="url"> ... </a>
    go関数実行
    <button v-on:click="go"> DODODO </button>
</div>

<p class="example" v-if="bool">Now you see me</p>

href属性にurl変数展開
<a class="example" v-bind:href="url"> ... </a>

動的引数

鉤括弧にて、動的引数を利用して、値の展開が可能。

example
var vm = new Vue({
  el: '#example',
  data: {
    el: 'class',
    clsnm : 'example',
  },
})
example
<div id="example">
    el変数展開、clsnm変数展開
    <p v-bind:[el]="clsnm"></p>
  イベントハンドラも動的記述可能
  <a v-on:[eventName]="doSomething"> ... </a>
</div>

引数は、stringが扱われることを想定されている。
nullはバインディングを削除する場合に活用できる。

スペースや、引用符を既述すると、正しく動作しないので注意。
また、eventNameのようなキャメルケースは、ブラウザが全て小文字にするので注意。

省略記法

冗長さを避けたい人へ。
こちら

算出プロパティ

テンプレート内に、ロジックを埋め込めるのは便利だけど、保守、運用の観点から、あまり良く無いため、算出プロパティを利用する。

基本的な例

example
var vm = new Vue ({ 
    el:'#example',
    data:{msg : 'Hello'},
    computed: {
        joinStr: function(e){
            return this.msg + 'World'
        },
    },
})
example
<div id="example">
    出力 -> HelloWorld
    <p>{{ joinStr }}</p>
</div>

プロパティ操作も適用される。

example
vm.msg = 'small' // 出力 ->  smallWorld

算出プロパティとメソッドの違い

メソッド記法により、同様の結果を得られることは可能。
しかし、処理のパフォーマンスに違いが生まれる。

算出プロパティは、依存関係が変化しなければ、キャッシュされ続ける特質を持っている。
すなわち、プロパティの変更が行われなければ、再度計算を行われず、保持された値を返すのみなので、アプリケーション負荷が低くなる利点がある。

必要で無い限りは、算出プロパティの使用をこころがけよう。

要素のclassを動的に操作

利用者の操作によって、見た目を変えたい時の処理。
v-bind:classを使用する。
有効化したい場合は、

example
var vm = new Vue ({ 
    el:'#example',
    data: {
      is_active: true,
      is_a     : false,
    },
})
example
<div id="example" v-bind:class="{'active':is_active, 'cls_a':is_a}"></div><div id="example" class="active"></div>

算出プロパティの利用も可能。

example
var vm = new Vue ({ 
    el  :'#example',
    data: {
        'el_activ': true,
        'error': true,
    },
    computed: {
        ExampleObj: function(){
            return {
                'active': this.el_activ,
                'error_cls' : this.error,
            }
        }
    }
})
example
<div id="example" v-bind:class="ExampleObj"></div><div id="example" class="active error_cls"></div>

条件付きレンダリング

v-if

真偽値、条件結果を利用して、画面描写結果を変化させる。
要素自体を削除してくれるので、アクセス制限、表示制限等に利用すると良さそう。

入力項目の制御

input等に関しては、条件操作によって、要素の表示操作を行ったとしても、入力項目が切り替わらない場合がある。
その際には、一意の属性をもたせると良い。

example
<div v-if="login_type == 'usr_id'">
  <input placeholder="username" key="username-input">
</div>
<div v-if="login_type == 'email'">
  <input placeholder="mail addless" key="email-input">
</div>

v-show

display要素により、表示、非常時を切り替える。
template要素、v-else要素に対応していないので、注意する。

v-ifとv-else

特性を理解して、適切に扱う。
v-ifは、描画時に要素の操作を、スクリプトによってシステム的に処理するため、比較的、動作が遅くなる。
v-elseは、要素を非表示にするだけ。
入力項目等の、不正操作による、不具合、アプリケーションの脆弱性を緩和することができそう。

v-ifとv-for

同時に利用することは推奨されていない。
こちら

リストレンダリング

類似した形式の情報を一覧で表示する場合には、v-forが利用できる。
pythonのforと似ているの。
inの代わりにofを使用できる。

状態の維持

リストを並び替える処理を行った際に、情報が正しく保持されない場合がある。
例えば、インプットの値等。
適切に保持させるためには、key属性を適用するとよい。

example
<div v-for="item in items" v-bind:key="item.id"></div>

keyが必要のない場合には、付与しなくてもいいが、基本的には適用しておけば、不具合を回避できる。

リストの状態を操作する時

変更メソッドが用意されているので、適宜活用しよう。

注意点
Vueが検知できない場合がある。

  • インデックスを利用した、直接な値操作
    • vm.items[key] =
  • 配列の長さの操作
    • vm.items.length =

追記中

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

nuxt generate + S3 + CloudFront + Lambda Edge で静的サイト構築&ハマりポイントと解決法

Repsona LLCの代表兼エンジニア(ひとり)の、ガッシーです。ひとりで、「理想のタスク管理ツール」Repsona(レプソナ)を作っています。

前回の記事(Nuxt + Sails + TypeScript + Fargateでタスク管理ツールを作ったら快適だった話)でRepsona本体側の全体像をざっくりと書きました。今回はウェブサイトのについてです。

Nuxt は静的ウェブサイト制作にも断然おすすめ

https://repsona.com
image.png
Repsona では本体のアプリケーションに Nuxt を採用していますが、ウェブサイトにも Nuxt を使っています。小規模なサイトをサクッと作りたい人にとってもおすすめです。

  • 公式ドキュメントのままやれば簡単に始められる
  • SPC のおかげで HTML を書いている感覚でコンポーネント化できる
  • おかげで パーツの使い回しがきく
  • おかげで CSS を多少ムチャしても破綻しにくい
  • 「JavaScriptでやりたーい」的な見せ方はライブラリでだいたいできる
  • jQuery も組み込んじゃえば使える
  • webpack とかJavaScript界の色々を考えなくていい(組み込まれてる)
  • ビルドは nuxt generate だけでいい

そして快適に作ったサイトを、S3 + CloudFront でデプロイしたい!と思う方もいるでしょう。今回はその手順と、ハマったところと解決法を共有したいと思います。

前提

  • ドメイン取得済み、Route 53 設定済み、ACM設定済み
  • Nuxt環境構築済み、サイト作成済み、nuxt generateできる

アーキテクチャ

image.png

  • nuxt generateで静的ファイル生成
  • gulpでS3にデプロイ & CloudFront invalidate
  • Lambda Edge でオリジンパスをハンドリング

構築手順

公式ドキュメント通りやればよし!なんですが、公式が「シークレットキーを記載したdeploy.shを作って.gitignoreする」というなんだか微妙なかんじなので、ちょっとアレンジした手元の手順を紹介します。

  • S3 バケットを作成する
  • CloudFront distribution を作成する
  • Route 53 を設定する
  • Lambda Edge でオリジンパスのハンドリングを設定する
  • セキュリティアクセスを設定する
  • ビルドスクリプトを作成する
  • CloudFront invalidate のスクリプトを修正する
  • デプロイして確認する

S3 バケットを作成する

バケットを作ります。全部デフォルト設定のままでよかったはず。CloudFront経由のアクセスのみ許可するので、静的ウェブホスティング無効でOKです。
image.png

CloudFront distribution を作成する

  • Create Distribution > Web - Get Started
  • Origin Domain Name > さっき作ったバケットを選択 (プルダウンにでてくる)
  • Origin Path > (空白)
  • Origin ID > (勝手に入る値)
  • Restrict Bucket Access > Yes
  • Origin Access Identity > 初めてなら Create a New Identity
  • Grant Read Permissions on Bucket > Yes, Update Bucket Policy
  • Alternate Domain Names > ドメイン名
Repsonaの場合
repsona.com
www.repsona.com
  • SSL Certificate > Custom SSL Certificate (example.com): (プルダウンにでてくる)
    • まだ作ってなければ Learn more about using ACM.
  • 他項目は各自の都合に合わせてください(デフォルトでもよかったはず)。

Route 53 を設定する

管理下にあるドメインに「レコードセットの作成」から下記の設定を入れて、CloudFront distribution にまわしてやります。

Repsonaの場合
repsona.com.
タイプ: A
エイリアス先: さっき作った CloudFront distribution (プルダウンにでてくる)
Repsonaの場合
www.repsona.com.
タイプ: A
エイリアス先: さっき作った CloudFront distribution (プルダウンにでてくる)

アクセスできるか確認する

ここまでで、青で囲んだ部分が通っているはずです。

image.png

S3バケット直下にindex.htmlを置いて、https://ドメイン名/index.htmlでアクセスできるか確認しておきましょう。

Lambda Edge でオリジンパスのハンドリングを設定する

CloudFront には S3 でいうところのインデックスドキュメントにあたるものがありません。Default Root Objectを設定すれば、以下のindex.htmlナシはいけますが

◯: https://ドメイン名/index.html
◯: https://ドメイン名/
◯: https://ドメイン名

以下のindex.htmlナシはいけません。

◯: https://ドメイン名/foo/index.html
×: https://ドメイン名/foo/
×: https://ドメイン名/foo

そこで、Lambda Edge を、イベントタイプ: origin-request に仕掛けます。

index-handler/index.js
// arranged: https://github.com/CloudUnder/lambda-edge-nice-urls

const config = {
  suffix: '/index.html',
  appendToDirs: 'index.html',
}

const regexSuffixless = /\/[^/.]+$/ // e.g. "/some/page" but not "/", "/some/" or "/some.jpg"
const regexTrailingSlash = /.+\/$/ // e.g. "/some/" or "/some/page/" but not root "/"

exports.handler = function handler (event, context, callback) {
  const {request} = event.Records[0].cf
  const {uri} = request
  const {suffix, appendToDirs} = config

  if (suffix && uri.match(regexSuffixless)) {
    request.uri = uri + suffix
    callback(null, request)
    return
  }

  if (appendToDirs && uri.match(regexTrailingSlash)) {
    request.uri = uri + appendToDirs
    callback(null, request)
    return
  }

  callback(null, request)
}

Lambda Function のARN(arn:aws:lambda:us-east-1:000000000000:function:index-handller:1みないなの)をコピっておいて、CloudFront > Behaviors の下の方にある、 Lambda Function Associations の Lambda Function ARN にセットします。$LATESTは使えません。

アクセスできるか確認する

ここまでで、青枠全部いけました。
image.png
S3バケットにfoo/index.htmlを置いて、スラありスラなしなど含めて、アクセス確認をしてみます。

セキュリティアクセスを設定する

デプロイ用のユーザーに「バケットへのファイル配置」と「CloudFrontのキャッシュ削除」の権限を与えます。以下のポリシーを作成し、デプロイを実行するユーザーにアタッチしてください。ユーザーがない場合はここで作成し、アクセスキーとシークレットキーを取得してください。
※ 公式ママだとうまくいかず、すこし変更しています。

{
    "Version": "2012-10-17",
    "Statement": [ {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObjectAcl",
                "s3:GetObject",
                "s3:AbortMultipartUpload",
                "s3:ListBucket",
                "s3:DeleteObject",
                "s3:PutObjectAcl",
                "s3:ListMultipartUploadParts"
            ],
            "Resource": [
                "arn:aws:s3:::さっき作ったS3バケット名/*",
                "arn:aws:s3:::さっき作ったS3バケット名"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "cloudfront:ListInvalidations",
                "cloudfront:GetInvalidation",
                "cloudfront:CreateInvalidation"
            ],
            "Resource": "*"
        }
    ]
}

ビルドスクリプトを作成する

Gulp をインストールする

npmの場合
npm install --save-dev gulp gulp-awspublish gulp-cloudfront-invalidate-aws-publish concurrent-transform
npm install -g gulp
yarnの場合
yarn add -D gulp gulp-awspublish gulp-cloudfront-invalidate-aws-publish concurrent-transform
yarn global add gulp

gulpfile.js を作成する

※ 公式ママだとうまくいかず、すこし変更しています。

const gulp = require('gulp')
const awspublish = require('gulp-awspublish')
const cloudfront = require('./modules/gulp-cloudfront-invalidate-aws-publish')
const parallelize = require('concurrent-transform')

// https://docs.aws.amazon.com/cli/latest/userguide/cli-environment.html

const config = {

  // 必須
  params: {Bucket: process.env.AWS_BUCKET_NAME},
  accessKeyId: process.env.AWS_ACCESS_KEY_ID,
  secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,

  // 任意
  deleteOldVersions: false,                 // PRODUCTION で使用しない
  distribution: process.env.AWS_CLOUDFRONT, // CloudFront distribution ID
  region: process.env.AWS_DEFAULT_REGION,
  headers: {'x-amz-acl': 'private' /*'Cache-Control': 'max-age=315360000, no-transform, public',*/},

  // 適切なデフォルト値 - これらのファイル及びディレクトリは gitignore されている
  distDir: 'dist',
  indexRootPath: true,
  cacheFileName: '.awspublish.' + environment,
  concurrentUploads: 10,
  wait: true,  // CloudFront のキャッシュ削除が完了するまでの時間(約30〜60秒)
}

gulp.task('deploy', function () {
  // S3 オプションを使用して新しい publisher を作成する
  // http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#constructor-property
  const publisher = awspublish.create(config, config)
  // console.log(publisher)

  let g = gulp.src('./' + config.distDir + '/**')
  // publisher は、上記で指定した Content-Length、Content-Type、および他のヘッダーを追加する
  // 指定しない場合、はデフォルトで x-amz-acl が public-read に設定される
  g = g.pipe(parallelize(publisher.publish(config.headers), config.concurrentUploads))

  // CDN のキャッシュを削除する
  if (config.distribution) {
    console.log('Configured with CloudFront distribution')
    g = g.pipe(cloudfront(config))
  } else {
    console.log('No CloudFront distribution configured - skipping CDN invalidation')
  }

  // 削除したファイルを同期する
  if (config.deleteOldVersions) {
    g = g.pipe(publisher.sync())
  }
  // 連続したアップロードを高速化するためにキャッシュファイルを作成する
  g = g.pipe(publisher.cache())
  // アップロードの更新をコンソールに出力する
  g = g.pipe(awspublish.reporter())
  return g
})

package.json に追記する

yarn run deploy でproductionデプロイできるようにscriptsを追加する。

{
  "scripts": {
    "deploy": "rm -r ./dist; cross-env NODE_ENV=production nuxt generate; cross-env NODE_ENV=production gulp deploy",
  },

環境変数を設定する

.env等
AWS_BUCKET_NAME = バケット名
AWS_CLOUDFRONT = 14文字の大文字のID
AWS_ACCESS_KEY_ID = アクセスキー
AWS_SECRET_ACCESS_KEY = シークレットキー
AWS_DEFAULT_REGION = リージョン(us-east-1みたいなの)

CloudFront invalidate のスクリプトを修正する

Lambda Edge でオリジンパスのハンドリングをするおかげで、スラありスラなしでアクセス可能ですが、全て別々のリソースとしてCloudFrontにキャッシュされてしまいます。デプロイ時に同時にinvalidateしたいところですが、ここで使っているライブラリgulp-cloudfront-invalidate-aws-publishindexRootPaths: trueは、スラなしinvalidateをリクエストしてくれないので、本家に手を加えて利用しています(プルリク)。

modules/gulp-cloudfront-invalidate-aws-publish/index.js
// https://github.com/lpender/gulp-cloudfront-invalidate-aws-publish/blob/master/index.js

var PluginError = require('plugin-error')
  , log = require('fancy-log')
  , through = require('through2')
  , aws = require('aws-sdk')

module.exports = function (options) {
  options.wait = !!options.wait
  options.indexRootPath = !!options.indexRootPath

  var cloudfront = new aws.CloudFront()

  if ('credentials' in options) {
    cloudfront.config.update({
      credentials: options.credentials
    })
  } else {
    cloudfront.config.update({
      accessKeyId: options.accessKeyId || process.env.AWS_ACCESS_KEY_ID,
      secretAccessKey: options.secretAccessKey || process.env.AWS_SECRET_ACCESS_KEY,
      sessionToken: options.sessionToken || process.env.AWS_SESSION_TOKEN
    })
  }

  var files = []

  var complain = function (err, msg, callback) {
    callback(false)
    throw new PluginError('gulp-cloudfront-invalidate', msg + ': ' + err)
  }

  var check = function (id, callback) {
    cloudfront.getInvalidation({
      DistributionId: options.distribution,
      Id: id
    }, function (err, res) {
      if (err) {
        return complain(err, 'Could not check on invalidation', callback)
      }

      if (res.Invalidation.Status === 'Completed') {
        return callback()
      } else {
        setTimeout(function () {
          check(id, callback)
        }, 1000)
      }
    })
  }

  var processFile = function (file, encoding, callback) {
    // https://github.com/pgherveou/gulp-awspublish/blob/master/lib/log-reporter.js
    // var state

    if (!file.s3) {
      return callback(null, file)
    }
    if (!file.s3.state) {
      return callback(null, file)
    }
    if (options.states &&
      options.states.indexOf(file.s3.state) === -1) {
      return callback(null, file)
    }

    switch (file.s3.state) {
      case 'update':
      case 'create':
      case 'delete': {
        let path = file.s3.path

        if (options.originPath) {
          const originRegex = new RegExp(options.originPath.replace(/^\//, '') + '/?')
          path = path.replace(originRegex, '')
        }

        files.push(path)
        if (options.indexRootPath && /index\.html$/.test(path)) {
          files.push(path.replace(/index\.html$/, ''))
          files.push(path.replace(/\/index\.html$/, '')) // スラなしも invalidate してほしい
        }
        break
      }
      case 'cache':
      case 'skip':
        break
      default:
        log('Unknown state: ' + file.s3.state)
        break
    }

    return callback(null, file)
  }

  var invalidate = function (callback) {
    if (files.length == 0) {
      return callback()
    }

    files = files.map(function (file) {
      return '/' + file
    })

    cloudfront.createInvalidation({
      DistributionId: options.distribution,
      InvalidationBatch: {
        CallerReference: Date.now().toString(),
        Paths: {
          Quantity: files.length,
          Items: files
        }
      }
    }, function (err, res) {
      if (err) {
        return complain(err, 'Could not invalidate cloudfront', callback)
      }

      log('Cloudfront invalidation created: ' + res.Invalidation.Id)

      if (!options.wait) {
        return callback()
      }

      check(res.Invalidation.Id, callback)
    })
  }

  return through.obj(processFile, invalidate)
}

デプロイして確認する

それではいってみます!

yarn run deploy

おめでとう!デプロイがうまくいけば、指定したドメインで、成果物にアクセスできるようになっているはずです。うまく動作したかどうかは、公式によると、下記らしいです。

NOTE: CloudFront invalidation created:XXXX は CloudFront invalidation を行う npm パッケージからの唯一の出力です。それが表示されない場合は、動作していません。

ハマりポイントと解決法

ポリシーがうまく適用されない

公式通りのポリシーをあてているはずなのにうまく行かず、VisualEditorでいろいろといじりながらなんとかうまくいく設定にたどり着きました。"cloudfront:UnknownOperation"がダメだったのかな。現状動いていますが、s3:ListBucketはほんとはリソースわけなきゃいかんかも。

デプロイ実行したらでたエラー

[12:32:01] Using gulpfile ~/xxxxxx/repsona-website/gulpfile.js
[12:32:01] Starting 'deploy'...
Configured with CloudFront distribution
[12:32:02] 'deploy' errored after 1.51 s
[12:32:02] AccessDenied: Access Denied
    at Request.extractError (/Users/xxxxxx/xxxxxx/repsona-website/node_modules/aws-sdk/lib/services/s3.js:585:35)
    at Request.callListeners (/Users/xxxxxx/xxxxxx/repsona-website/node_modules/aws-sdk/lib/sequential_executor.js:106:20)
    at Request.emit (/Users/xxxxxx/xxxxxx/repsona-website/node_modules/aws-sdk/lib/sequential_executor.js:78:10)
    at Request.emit (/Users/xxxxxx/xxxxxx/repsona-website/node_modules/aws-sdk/lib/request.js:683:14)
    at Request.transition (/Users/xxxxxx/xxxxxx/repsona-website/node_modules/aws-sdk/lib/request.js:22:10)
    at AcceptorStateMachine.runTo (/Users/xxxxxx/xxxxxx/repsona-website/node_modules/aws-sdk/lib/state_machine.js:14:12)
    at /Users/xxxxxx/xxxxxx/repsona-website/node_modules/aws-sdk/lib/state_machine.js:26:10
    at Request.<anonymous> (/Users/xxxxxx/xxxxxx/repsona-website/node_modules/aws-sdk/lib/request.js:38:9)
    at Request.<anonymous> (/Users/xxxxxx/xxxxxx/repsona-website/node_modules/aws-sdk/lib/request.js:685:12)
    at Request.callListeners (/Users/xxxxxx/xxxxxx/repsona-website/node_modules/aws-sdk/lib/sequential_executor.js:116:18)
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

Access が Denied であること以外なんもわからん!ポリシーも無事適用できてput objectもできていただけにかなりハマりました。どうやらS3のアクセス権限設定で、いつからか、ブロックパブリックアクセスがすべてブロックがデフォルトになったようで、そうすると、publicなファイルは配置できません。それで、gulpfile.jsheaders: {'x-amz-acl': 'private'}の記述を追加して、privateとしてputするようにしました。

https://ホスト名/index.html じゃないと AccessDenied

上述の通り、Lambda Edgeで回避しました。

まとめ

  • どこでコケてるかわかりにくいので、確認できるポイント毎に確認すべし
  • 構築は結構手間だけど、一度通ってしまえばすごく楽
  • nuxt generateで静的ウェブサイトにもコンポーネントの概念を・・すごくイイ
  • 静的サイト生成なので当たり前だけど、ものすごくはやい

という感じで快適に開発しています。ぜひお試しください。

そして、Repsonaもぜひお試しください。ベータ期間中、制限なく無料で使えます。
チームのための理想のタスク管理ツール | Repsona

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

自分の勉強記録を分析しよう!~新米エンジニアの個人開発~

自分の勉強記録を分析しよう!~新米エンジニアの個人開発~

プログラマー歴1年目のじょーだいです。

プログラマーになって一年、そろそろ新しいことを学んでみたかったので、使ったことのない技術を使用して簡単なwebアプリケーションを開発してみました。

どんなアプリ?

僕は勉強したことをTwitterでつぶやいています。

他にも多くの方々が自分の勉強したことをつぶやいているのをよく見かけます。

せっかく勉強しているならその記録をしっかり残したい。。。

そこで今回は自分の勉強を記録しておくことのできる「Stacker」というアプリを開発しました。

使用している技術は?

  • Laravel
  • Vue.js
  • Chart.js
  • MySQL
  • Docker
  • XSERVER
  • Git

各画面の説明

ダッシュボード

01.png

この画面の作成が一番時間をとられました。

上部に1週間のグラフ、下部に日毎の入力したデータが表示されます。

また右上のボタンによって、1週間のグラフを総合計のグラフに切り替えることでカテゴリーごとの合計時間を知ることができます。

勉強している日、してない日がすぐわかるので結構気に入ってます。

カテゴリ―毎の詳細

02.png

ダッシュボードの左側のカテゴリー一覧からカテゴリ―を選択すると画像のような画面が開きます。

今まで入力したコメントが一覧表示されるので、どんなことを勉強してきたのか一目瞭然です。

カテゴリ―詳細ページは他のユーザーが覗くこともできます。

知らない語句や勉強の順序、気になるエンジニアがどんなことを学んでいるのかを知ることができます。

ホーム

02.png

各ユーザーの投稿が一覧表示されています。

とても簡易的なSNSのような感じです。

フォロー機能等は実装していませんが、他のユーザーの勉強量を知ることで、モチベーションを上げることができます。

現在会社の後輩とこのアプリを使用しているのですが、とても会話が盛り上がります。

特に相手が何を勉強しているのかがわかるので、気になったことをすぐに聞けるし、話のきっかけづくりにもなります。

感想

一応完成はしたものの、まだまだ実装したい機能が盛りだくさんです。
例えば
 フォロー機能
 、円グラフでの分析
 、カテゴリ―毎の表示順序、色の切り替え
など、他にも細かいところでまだまだたくさんあります。

他にもリファクタリングやテストコードの作成などすることは盛りだくさん。

これからも勉強を記録していくためにこのアプリを使用し、改善を続けていきます。

自分自身が毎日使用していくので、改善、メンテナンスを無理なく続けていくことができると思います。

個人開発でも作りっぱなしではなく、しっかりと運用していくので、よければ皆様も使ってみてください。

一言

コードが汚すぎる。リーダブルコード読んだはずなんだけどな~

➡Stackerはこちら

➡Twitterはこちら

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

Nuxt + TypeScript で 2つ以上の値をコンポーネントに渡して変更を受け取る方法

nuxt-property-decorator (vue-property-decorator) を使用しているとき、
親コンポーネントから、子コンポーネントに2つ以上の値を渡し、その変更を受け取る方法。

意外と解決するのに手間取ったので、備忘録として。

Nuxt (Vue) + TypeScript

  • 親コンポーネント
Parent.vue
<template>
  <div>
    <p>Value A: {{ valueA }}</p>
    <p>Value B: {{ valueB }}</p>
    <Child
      :value-a.sync="valueA"
      :value-b.sync="valueB"
    />
  </div>
</template>
<script lang="ts">
import { Vue, Component } from 'nuxt-property-decorator'
import Child from '~/components/Child.vue'

@Component({
  components: {
    Child,
  },
})
export default class Parent extends Vue {
  valueA: string = ''

  valueB: string = ''
}
</script>
  • 子コンポーネント
Child.vue
<template>
  <div>
    <input v-model="computedValueA"> -
    <input v-model="computedValueB">
  </div>
</template>
<script lang="ts">
import { Vue, Component, Prop } from 'nuxt-property-decorator'

@Component
export default class Parent extends Vue {
  @Prop({ required: true })
  valueA: string

  @Prop({ required: true })
  valueB: string

  get computedValueA() {
    return this.valueA
  }

  set computedValueA(value) {
    this.$emit('update:valueA', value)
  }

  get computedValueB() {
    return this.valueB
  }

  set computedValueB(value) {
    this.$emit('update:valueB', value)
  }
}
</script>

対応するJavaScriptでの書き方(Child.vue)

export default {
  props: {
    valueA: {
      type: String,
      required: true,
    },
    valueB: {
      type: String,
      required: true,
    },
  },
  computed: {
    computedValueA: {
      get() {
        return this.valueA
      },
      set(value) {
        this.$emit('update:valueA', value)
      },
    },
    computedValueB: {
      get() {
        return this.valueB
      },
      set(value) {
        this.$emit('update:valueB', value)
      },
    },
  },
}

構成

package.json
  "dependencies": {
    ...
    "@nuxt/config": "^2.8.1",
    "@nuxt/typescript": "^2.8.1",
    "nuxt": "^2.8.1",
    "nuxt-property-decorator": "^2.3.0",
    "ts-node": "^8.2.0"
  },
  "devDependencies": {
    ...
    "@babel/runtime-corejs3": "^7.4.5",
    "@types/node": "^12.0.7",
    "@types/webpack-env": "^1.13.9",
    "babel-core": "7.0.0-bridge.0",
    "babel-preset-vue": "^2.0.2",
    "webpack-merge": "^4.2.1",
  }

Nuxt関係ないね。Vueだね。

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

Node.js、Vue.js インストール(Windows)

Node.js インストール ~ 起動(node:v10.16.0, npm:v6.9.0)

  • インストーラー取得 https://nodejs.org/en/download/
  • インストーラー実行
    画面に従い進める、許可などは確認して同意、はいを選択
  • インストール確認
    コマンドプロンプトで以下を実行してバージョンが表示されればOK
    node --version
    npm --version
  • プロジェクトフォルダを作成
    コマンドプロンプトで以下を実行して${フォルダパス}に node_modules, package.json, package-lock.json あればOK
    cd ${フォルダパス}
    npm install express --save ※WARNでるが気にしない
    npm init -y ※ -y を指定しない場合、対話形式で進む
  • 実行用のapp.js, index.html を作成
    ${フォルダパス}の直下に以下を作成、両方とも utf8
app.js
var express = require('express');
var app = express();
app.use(express.static('./'));
var port = 8080;
app.listen(port,function(){
  console.log("express server port %d", port)
});
index.html
<!DOCTYPE html>
<html>
<head>
<title>タイトル</title>
</head>
<body>
こんにちは Nodejs
</body>
</html>
  • server 実行
    コマンドプロンプトで以下を実行して起動ログでればOK
    node app.js
  • server アクセス http://localhost:8080/

Vue.js (vue:v3.9.3)

  • Node.js のインストーラー実行までやっておく
  • Vue CLI をインストール
    コマンドプロンプトで以下を実行
    npm install -g @vue/cli ※-g を指定することでどこでも使えるようになる
    npm install -g @vue/cli-service-global ※-g は上記同様
  • インストール確認
    コマンドプロンプトで以下を実行してバージョンが表示されればOK
    vue --version
  • プロジェクトフォルダを作成
    コマンドプロンプトで以下を実行してプロジェクトフォルダができればOK
    cd ${プロジェクトルートパス}
    vue create -d ${プロジェクトフォルダ名} ※-d を指定しない場合、対話形式で設定
  • プロジェクトフォルダに移動して server 起動
    コマンドプロンプトで以下を実行して npm のプロンプトあがればOK
    cd ${プロジェクトルートパス}\${プロジェクトフォルダ名}
    npm run serve
  • server アクセス http://localhost:8080/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む