20200329のvue.jsに関する記事は18件です。

Vue.js + AWS の組み合わせ

基本的なこと

Vue.jsの開発はVue-CLIを使うのが基本

まず前提として、Vue.jsは今となってはVue-CLI無しでの開発は"かなりキツイ"と捉えてください。
実際それほどでもないと言えばそうなんですが、Vue-CLIがかなり便利になってきたので、その恩恵を受けられない構成での開発を基本フロントは嫌がります。

Laravel + Vue という悪習

そういうことなので、Laravelに入っていたVueを雛形にしてそのまま使ってしまっている場合は将来切り離すスケジュールを立てるべきです。
この構成は、通常LaravelMixでコンパイルするのでVue-CLIが使えません。移行には具体的に以下の作業が発生します。

  • リポジトリの切り分け
  • vue-routerを用いたSPAの対応
  • APIのCORS対応

元々Laravelが担当していた機能は /home → /about のように『ルートが切り替わったら別のHTMLを表示する』という事をしていただけなので、ページ切替がフロントで管理できるようになった今となってはバックエンドのwebルートは完全に不要になりました。

恩恵として、フロント側でページ遷移した場合はリロードが発生しません。
リロードしてしまうとJSの変数に格納したデータが全て消えてなくなってしまうのですが、SPAは基本的にリロードが無いので、ページ遷移の度に同じデータをダウンロードしなくてもページ間でデータを共有できるようになり、サイトの高速化に繋がります。

デプロイ環境

デプロイ環境に関してはサーバーもNode.js環境も必要ありません。S3だけで事足ります。
ブラウザがコンパイル済みのindex.htmlとそこでインポートされているJSやCSSファイルを読み出せればOKです。
完全に静的なファイルを置いておくだけで大丈夫なんです。
コンパイルにNode.js環境を必要としますが、後で出てくるAmplifyのビルド環境にNode.jsが入っていますので心配しなくて大丈夫です。

やっちゃいけないこと

  • Laravel+Vue.jsのようなバックエンドのリポジトリにVue.jsが付属する構成は難儀するので、LaravelはAPI専用にして後方に配置し、LaravelとVue.jsは別々のリポジトリで管理しましょう。
  • とりあえずCodeStarでやろうとするのはNG。正解は全然別の所にあるAmplifyの方です(UI統一してくれ..)。似たようなものですが、より実践的な設定が簡単にできるのでAmplifyを使いましょう。

使用するAWSのサービス

  • Amplify

Amplify

ほぼ対象のリポジトリを選択するだけで簡単に『リポジトリの更新を検知→自動テスト→自動デプロイ』のパイプラインが出来上がります。
たぶんSPA前提なのでS3のサーバーレス環境にデプロイされます。
AmplifyにはFrontセクションとBackendセクションがあり、上記がFrontセクションに該当します。
Backendセクションでは、Cognito、AppSync、Lambda、S3などを用いて自在にバックエンド開発ができます。
『Amplify = AWSフレームワーク』に近いものなので、基本的な部分はAmplifyで作成してしまい、追加サービスとして他のAPIを付け足していくという感じになっていくと思います。

Amplifyについて詳しくはまた次回に書こうと思います。

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

Nuxt TypeScript + Vuex によるカウンターのサンプルコード

概要

  • Nuxt TypeScript + Vuex を使用して「+」「-」ボタンで数値が増減するカウンターを作成する

今回の環境

  • Node.js 13.12.0
  • Nuxt.js 2.12.1
  • TypeScript 3.8.3
  • Nuxt TypeScript (Nuxt.js 向け TypeScript サポート)

Nuxt TypeScript + Vuex によるカウンターのサンプルコード

ソースコード一覧

├── nuxt.config.ts
├── package.json
├── pages
│   └── counter.vue
├── store
│   └── counter.ts
├── tsconfig.json
└── vue-shim.d.ts

nuxt.config.ts

nuxt.config.js の TypeScript 版。

import { Configuration } from '@nuxt/types'

const config: Configuration = {
  buildModules: ['@nuxt/typescript-build']
}
export default config

package.json

必要なライブラリの記述等。

{
  "name": "my-app",
  "scripts": {
    "dev": "nuxt-ts",
    "build": "nuxt-ts build",
    "generate": "nuxt-ts generate",
    "start": "nuxt-ts start"
  },
  "dependencies": {
    "@nuxt/typescript-runtime": "0.4.1",
    "nuxt": "2.12.1",
    "nuxt-property-decorator": "2.5.1"
  },
  "devDependencies": {
    "@nuxt/typescript-build": "0.6.1"
  }
}

pages/counter.vue

カウンター表示用のページコンポーネント。

<template>
  <div>
    <p>{{ count }}</p>
    <p>
      <button @click="increment">+</button>
      <button @click="decrement">-</button>
    </p>
  </div>
</template>

<script lang="ts">

import { Component, Vue } from 'nuxt-property-decorator'
import { CounterState } from '~/store/counter'

@Component
export default class CounterComponent extends Vue {

  get count () : number {
    console.log('Call the computed count')
    return (this.$store.state.counter as CounterState).count
  }

  // 「+」ボタンクリック時に呼ばれる
  increment () : void {
    console.log('Call the methods increment')
    this.$store.commit('counter/increment')
  }

  // 「-」ボタンクリック時に呼ばれる
  decrement () : void {
    console.log('Call the methods decrement')
    this.$store.commit('counter/decrement')
  }
}
</script>

store/counter.ts

カウンターの値を管理するストア。

import { MutationTree } from 'vuex'

export const state = () => ({
  count: 0
})

export type CounterState = ReturnType<typeof state>

export const mutations: MutationTree<CounterState> = {
  increment: (state) => {
    console.log('Call the mutations increment')
    state.count++
  },
  decrement: (state) => {
    console.log('Call the mutations decrement')
    state.count--
  }
}

tsconfig.json

ほぼ Nuxt TypeScript の公式資料通りだが、Nuxt Property Decorator (nuxt-property-decorator) を使うため experimentalDecorators の設定を追加している。

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "target": "es2018",
    "module": "esnext",
    "moduleResolution": "node",
    "lib": [
      "esnext",
      "esnext.asynciterable",
      "dom"
    ],
    "esModuleInterop": true,
    "allowJs": true,
    "sourceMap": true,
    "strict": true,
    "noEmit": true,
    "baseUrl": ".",
    "paths": {
      "~/*": [
        "./*"
      ],
      "@/*": [
        "./*"
      ]
    },
    "types": [
      "@types/node",
      "@nuxt/types"
    ]
  },
  "exclude": [
    "node_modules"
  ]
}

vue-shim.d.ts

Nuxt TypeScript の公式資料通り。
Vue ファイルの型を提供するための型宣言。

declare module "*.vue" {
  import Vue from 'vue'
  export default Vue
}

Node.js サーバを起動

package.json に記述したライブラリをインストール。

$ npm install

Node.js サーバを起動。

$ npm run dev

> my-app@ dev /Users/foobar/my-app
> nuxt-ts


   ╭─────────────────────────────────────────────╮
   │                                             │
   │   Nuxt.js v2.12.1                           │
   │   Running in development mode (universal)   │
   │                                             │
   │   Listening on: http://localhost:3000/      │
   │                                             │
   ╰─────────────────────────────────────────────╯

ℹ Preparing project for development
ℹ Initial build may take a while
✔ Builder initialized
✔ Nuxt files generated
ℹ Starting type checking service...

✔ Client
  Compiled successfully in 8.15s

✔ Server
  Compiled successfully in 6.50s

ℹ Type checking in progress...
ℹ Waiting for file changes
ℹ Memory usage: 230 MB (RSS: 311 MB)
ℹ Listening on: http://localhost:3000/

Web ブラウザで http://localhost:3000/counter にアクセスすると「+」「-」ボタンと数値が増減するカウンターが表示される。

参考資料

環境構築

Nuxt Property Decorator (nuxt-property-decorator)

GitHub - nuxt-community/nuxt-property-decorator: Property decorators for Nuxt (base on vue-property-decorator)

Handy ES / TypeScript decorators for class-style Vue components in Nuxt (based on Vue class component) and Property decorators for Vue (bases on Vue Property Decorator) and Vuex (based on Vuex Class)

This library fully depends on vue-class-component.

Decorators · TypeScript

To enable experimental support for decorators, you must enable the experimentalDecorators compiler option either on the command line or in your tsconfig.json

機能実装

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

Vue.jsでアコーディオン

n番煎じですが試行錯誤した結果を残しておきます。
いろんなやり方があると思いますが、個人的に一番しっくり来たやり方です。

結論

<template>
  <div class="a-accordion">
    <button @click="opened = !opened">
      Click
    </button>
    <transition
      @before-enter="beforeEnter"
      @enter="enter"
      @before-leave="beforeLeave"
      @leave="leave"
    >
      <div v-if="opened" class="a-accordion-inner">
        Accordion
      </div>
    </transition>
  </div>
</template>

<script>
export default {
  data() {
    return {
      opened: false
    }
  },

  methods: {
    beforeEnter(el) {
      el.style.height = '0'
    },

    enter(el) {
      el.style.height = el.scrollHeight + 'px'
    },

    beforeLeave(el) {
      el.style.height = el.scrollHeight + 'px'
    },

    leave(el) {
      el.style.height = '0'
    }
  }
}
</script>

<style scoped lang="scss">
.a-accordion {
  .a-accordion-inner {
    overflow: hidden; // 閉じるときに他要素に被らないように必須。
    transition: height 0.2s ease-in-out; // 高さの変更に対して連続的に変化させる。
  }
}
</style>

ポイントは

  • 開閉する要素に overflow, transition を指定する。
  • transion のフックで height を変更する。

の2つです!

Kapture 2020-03-29 at 20.41.43.gif

参考

https://jp.vuejs.org/v2/guide/transitions.html
https://developer.mozilla.org/ja/docs/Web/CSS/CSS_Transitions/Using_CSS_transitions
http://lab.astamuse.co.jp/entry/2018/10/15/154737
https://qiita.com/mimoe/items/7ad8d9ea9fc4299da6bc

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

nuxt.jsインストール手順備忘録

nuxt.jsインストール備忘録

  • バージョン 2.12.1

  • CDでインストールするディレクトリに移動
  • 以下のコマンドでインストール
  • project-name はプロジェクトの名前を入れる

npxを使う場合

npx create-nuxt-app <project-name>

yarnを使う場合

yarn create nuxt-app <project-name>

? Project name

? Project name xxx
  • プロジェクト名を入力

? Project description

  • 説明を入力
? Project description xxxxxx

Author name

  • 作者の名前を入力
? Author name xxx

Choose programming language

  • TypeScriptを使うか選択
? Choose programming language (Use arrow keys)
❯ JavaScript
  TypeScript

Choose the package manage

  • YarnとNpmのどちらかを使うか選択
? Choose the package manager (Use arrow keys)
❯ Yarn
  Npm

Choose UI framework

  • UIフレームワーク使うか選択
? Choose UI framework (Use arrow keys)
❯ None
  Ant Design Vue
  Bootstrap Vue
  Buefy
  Bulma
  Element
  Framevuerk
  iView
  Tachyons
  Tailwind CSS
  Vuesax
  Vuetify.js

Choose custom server framework

  • サーバーサイドのフレームワークを選択
? Choose custom server framework (Use arrow keys)
❯ None (Recommended)
  AdonisJs
  Express
  Fastify
  Feathers
  hapi
  Koa
  Micro

Choose the runtime for TypeScript

? Choose the runtime for TypeScript (Use arrow keys)
❯ Default
  @nuxt/typescript-runtime

Choose Nuxt.js module

  • Axios と PWA を使うか選択
? Choose Nuxt.js modules (Press <space> to select, <a> to toggle all, <i> to inv
ert selection)
❯◯ Axios
 ◯ Progressive Web App (PWA) Support
 ◯ DotEnv

Choose linting tools

  • 静的解析の選択
? Choose linting tools (Press <space> to select, <a> to toggle all, <i> to inver
t selection)
❯◯ ESLint
 ◯ Prettier
 ◯ Lint staged files
 ◯ StyleLint

Choose test framework

  • ユニットテストの選択
? Choose test framework (Use arrow keys)
❯ None
  Jest
  AVA

Choose rendering mod

  • SSRかSPAの選択
? Choose rendering mode (Use arrow keys)
❯ Universal (SSR)
  Single Page App

Choose development tool

  • jsconfig.jsonSemantic Pull Requestを入れるか選択
  • jsconfig.jsonVS Code向けのツール
  • Semantic Pull RequestはGitHub向けのツール
? Choose development tools (Press <space> to select, <a> to toggle all, <i> to i
nvert selection)
❯◯ jsconfig.json (Recommended for VS Code)
 ◯ Semantic Pull Requests

インストール完了

スクリーンショット 2020-03-29 15.09.55.png

起動する

yarnの場合

yarn run dev

npmの場合

npm run dev

ビルド

  • dist 以下にファイルが出力される
yarn run build

generate

  • dist 以下にHTMLの静的ファイルが出力させる
yarn run generate

その他

Sasaを使う

  • Sassをインストール
npm install --save-dev node-sass sass-loader
  • vueファイルでlang="scssまたはlang="sassにするとSassが使える
<style lang="scss" scoped>

Pugやcoffeeも可能

Pug
npm install --save-dev pug@2.0.3 pug-plain-loader
<template lang="pug">
  h1.red Hello {{ name }}!
</template>
CoffeeScript
npm install --save-dev coffeescript coffee-loader
<script lang="coffee">
export default data: ->
  { name: 'World' }
</script>

コンポーネント以外の独自で作るCSSを読み込む

  • nuxt.config.js のcssの部分にファイル名を入れる
css: [
  '@/assets/css/main.scss'
],

ルーティング

  • this.$route でURLが取れる
  • リンクを貼るときはnuxt-link :to=""を使う
  • パラメーターで動的に使うには _id.vue のようにファイル名にアンダースコアを付ける
<nuxt-link :to="'/xxx">リンク</nuxt-link>
  • generateするときに吐き出したいファイルを指定する時はnuxt.config.jsに設定する
  • /controller/actionの時
generate: {
  routes: [
    '/controller/action'
  ]
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vueコンポーネント内でsassを使用するときにディープセレクタを指定する方法

vue コンポーネント内の style を scss にして、その中でディープセレクタを使用したかったのですが、 >>> を使用すると動かなかったので、調べた内容についてまとめました。

<style lang="scss" scoped>
// ここで >>> を使いたい!
>>> .hoge {
// 
}
</style>

ディープセレクタについて

scoped スタイルのセレクタを "deep" にしたい、つまり子コンポーネントに及ぼしたい場合は、>>> コンビネータを使用することができます

::v-deep を使用する

vue-loaderのディープセレクタに下記の記載がありました。

SASS のようないくつかのプリプロセッサは、>>> を適切に解析できないかもしれません。そのようなケースでは /deep/ コンビネータを代わりに使用することができます。それは、>>> のエイリアスで、全く同じような動作します。

ただ、下記の issue によると、実際には /deep/ を使用すると chrome で warning が出るそうで、 ::v-deep を使用するのが良いようです。実際に試したところ、warning ではなくて error が出ました。
https://github.com/vuejs/vue-loader/issues/913

<style lang="scss" scoped>
// >>> をそのまま ::v-deep に変更するだけでOK
::v-deep .hoge {
// 
}
</style>

::v-deep の具体例

::v-deep を使う具体例をみたい場合には、こちらのPRで /deep/ から ::v-deep へ変更を行なっているため、参考になると思います。
https://github.com/aeternity/aepp-base/pull/1347/files

ドキュメントの修正状況

ドキュメントの修正に関しては、変更の提案の issue はあがっていたので、そのうち修正されると思います。
Remove /deep/ from documentation for future releases for Deep Selectors

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

Vue.jsでTo-Do Listを作ってみた。

Vue.jsの学習を本日から始めました!
めちゃくちゃ面白かったので、記事にします。

やったこと

「今日のTo-Do List」を作成しました。
普段はUdemyや本を参考に学習することが多いのですが、今回は久しぶりにドットインストールを使ってみました。
ドットインストールさん、分かりやすかったです。ありがとうございます。回し者ではありません。

作ったものはこんな感じです。
スクリーンショット 2020-03-29 16.54.22.png

ドットインストールを参考に作ったので、見た目はほぼドットインストールのままですが、ToDoをする予定時刻を自分で入れてみました。
これからもっと手のこんだアプリケーションをVue.jsで作っていきたいです。

学んだこと

導入が楽

scriptタグをhtmlファイルに記載するだけで使えました。
開発環境の構築は大変な時が多々ありますが、Vue.jsは非常に楽でした。

双方向データバインディング

テキストボックスに入力した途端に出力される文字が変わる、といったことがこの双方向データバインディングに例になると思います。
これを最初学んだときは驚きました。しかも、それが数行のコードですぐ簡単に実装できてしまうのです。

学びやすい

学習コストが少し低いように感じました。まだ最初だからだと思いますが、最初にしてはとっつきやすかったです。
これからVue Routerを使ってSPAを作ろうと思っておりますが、おそらくその過程で苦戦することでしょう。。。

これから

初めてJavaScriptのフレームワークを本格的に学習してみましたが、非常に興味深かったです。
上でも書いたようにこれからはVue.jsでシングルページアプリケーションを作ってみます。
その際にはまた記事にします!

参考

ドットインストール Vue.js入門

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

Vue Composition API の computed についてまとめてみた

はじめに

Vue Composition API の reactive と ref についてまとめてみた の続きになります。

ref, reactive は値の検知を監視してリアクティブにレンダリングコンテキストを変更する便利なオブジェクトでした。

今回は

メソッドであり、リアクティブなオブジェクトしての機能も保持している computed ( 算出プロパティ )

についてまとめていきます。

computed

【前提知識】 なぜ使うのか ?

テンプレート内に式を書けるのはとても便利ですが、非常に簡単な操作しかできません。テンプレート内に多くのロジックを詰め込むと、コードが肥大化し、メンテナンスが難しくなります ( Vue.js 公式より )

公式に書かれていますが、vue はテンプレート内部に簡単な比較や計算などを直接記述することができます。

しかし、複雑な処理をテンプレート内部で記述できるため、同様の処理を至る所に書いてしまいがちです。
ここで、仕様が変更されたとすると複数箇所を修正しなければいけなくなり、保守性を低下させてしまいます。

そこで利用するのが computed ( 算出プロパティ ) です。

【基本】 呼び出し方

computed メソッドを利用して生成します。
これまでの computed オブジェクト内への記述とは異なります。

書き方は 2 通りあります。

computed-型定義(1)
// computed に渡す引数
interface Option<T> {
    get: () => T,
    set: (value: T) => void
}

// getter として使うとき
export declare function computed<T>(getter: Option<T>['get']): Readonly<Ref<Readonly<T>>>;
// getter, setter として使うとき
export declare function computed<T>(options: Option<T>): Ref<Readonly<T>>;

getter のみを利用するときは引数なしの コールバック を用いることで、処理を記述することができます。

computed-getter(2-1)
    const cost = ref<number>(100);
    const tax = 0.5;

    // computed メソッドで生成する
    const calcPrice = computed(() => cost.value * tax);
    // 値を取得するとき
    const price = calcPrice.value;

getter, setter どちらの機能も利用したいときは、get, set を持つオブジェクトを渡す必要があります。
getter で利用している リアクティブな値が変わった場合は再度処理が行われ、新たな値を返します。

computed-getter-setter(2-2)
<template>
  <div>
    <div>{{calcPrice}}</div>
  </div>
</template>

<script lang='ts'>
import { ref, computed } from "@vue/composition-api";

export default {
  setup() {
    const cost = ref<number>(100);
    const tax = 0.5;

    // computed メソッドで生成する
    const calcPrice = computed({
      get: () => cost.value * tax,
      set: (value: number) => (cost.value = value)
    });

    // 値を取得するとき
    const price = calcPrice.value;
    // 値をセットするとき
    calcPrice.value += 100;
    // 200
    console.log(cost.value);

    return { calcPrice };
  }
};
</script>

【基本】 readonly

TypeScript だとクラス構文で利用される readonly があるので、馴染み深いものかと思います。
リアクティブなオブジェクトに対しての値変更の不可を設定できます。

readonly(1)
const readOnlyValue = readonly(100);

getter のみの computed は readonly で生成されるため 直接の値変更は不可 になります。
処理内で利用しているリアクティブ変数を書き換えることで間接的に値を変更できます。

【提案】 Computed or Method ?

こちらは vue 2.0 系統での記事を多く見かけるので細かい説明は省こうと思います。
2 つの使い分けは キャッシュさせたいかそうでないか で判断すると良いです。

どちらもロジックを綺麗にまとめることができるため、使わないという選択は無いと思います。

computed

  • computed はキャッシュされる ( ブラウザのメモリの節約効果 )
  • キャッシュされるので 2 度目以降の画面表示は早い
  • リアクティブな値を用いる場合 はこちらを利用

method

  • UI イベントによる処理 は、キャッシュのデータによる誤送信が考えられるため method を利用する

終わりに

今回は、ref, reactive と同様にリアクティブな値を取得できる computed についてまとめました。

繰り返しになりますが、今後も随時 vue Composition API について記事を書いていこうと思います。
是非、引き続き追っていただけると嬉しいです。

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

よそ様のgithubリポジトリを初めて使ってみたら、最高の開発者体験ができた話(vue + githubActions + github-pages)

対象読者

vuecliやnuxtなど、静的サイトを生成するツールで静的サイトをローカルでビルドし、githubにプッシュすることでgithub pagesでホスティングしている人すべて

やること

github actionsを利用して、ローカルでのビルド時間を0秒にする

背景

コロナの影響で時間ができたため、サークルを企業に紹介する静的サイトを作成しようと思い、github pagesをテスト環境にしてメンバーからのフィードバックを得るということをしていました。使用していたのはvuecliだったので、開発中は

npm run serve

このコマンドでホットリローディングしながら常時localhost:8080にアクセスしてコーディングしていました。
サークルメンバーも同様にコロナの影響で暇しているので、フィードバックはとても早く「これってこんな感じがいい?」と実際にコードを書きながら確認することが多くありましたが、ここに若干めんどうなところがありました。
github-pagesに公開する際は、

npm run build

してからリモートに上げることでgithub-pagesの内容を更新していました。ところがこのコマンドを叩くには一旦Ctrl + Cでタスクキルする必要があり、ビルド後再度npm run serveを打たなくてはいけませんでした。これは非常に面倒です。

解決策

調べたところ、github actionsという、github上での様々なイベントをトリガーにlinux,Windows,Macのインスタンス(コンテナの方があっているのでしょうか)を無料で利用でき、そのうえで自由に仕事をさせることができることを知りました。これを設定しておけば、以下のように改善することができます。

before.sh
npm run serve # hot reloading
Ctrl + C
npm run build # 一分ほどTwitterを見に行く
# ビルド結果をgithub-pagesにプッシュ => 公開
npm run serve
after.sh
npm run serve # hot reloading
# ソースコードをgithubにプッシュ => github上でビルド => 公開

gitの操作はGithub Desktopを利用しているため、最初にホットリローディングを開始すれば、開発中途切れさせる必要はありませんでした。ビルドとサーブの開始でTwitterを見に行く必要はもうありません。

github actionsに先駆者がいた。

本題です。
もともとはこの部分を「よし!僕がいっちょ作って公開してQiitaにアウトプットしたら大ヒットや!」と考えいろいろ調べていたところ、同じ考えをした(しかも後続にやさしい)先駆者がいました。
https://github.com/peaceiris/actions-gh-pages
Qiitaで日本語記事も書いてくださっています(神)
https://qiita.com/peaceiris/items/d401f2e5724fdcb0759d

github actionsには、再利用可能にパッケージ化されたワークフローが公開されており、この作者peaceiris様も公開してくださっていました。
このアクションはビルドされた静的コンテンツを入れたディレクトリのパスを渡すだけで、あとはgithub pagesに公開してくれるものです。

え?ビルド結果を特定のディレクトリに入れて公開するだけなら簡単そう

と僕と同じように思う方も多いと思いますが、github actionsで生成されたものはActionsの終了と同時にすべて破棄されてしまいます。また、リモートにコミットして反映させる手法も確かに取れますが、この作業が非常にだるく、このコミットによってループにならないようにしたり、そもそも権限を付与してあげないといけなかったりと、とにかくめんどう!その点、このワークフローはビルド結果のディレクトリと指定されたトークンやシークレットを渡すだけで公開してくれるため、神がかっているのです。

それぞれの方法については作者様のgithubリポジトリに紹介されているので、ご参照ください。私も今回は
https://github.com/marketplace/actions/github-pages-action#%EF%B8%8F-vue-and-nuxt
こちらをコピペして、npm run generatenpm run buildに書き換えた程度でした。(package.jsonに定義したビルドコマンドに合わせただけ)
細かいことも、ここで下手に説明するより上記URLから参照したほうがよろしいかと思いますので省きます。

詰まったところ

github pagesの設定をもともとmaster ブランチのdocsディレクトリに設定していたため、ビルド結果が反映されず、あれ?となりました。設定ページでgh-pagesブランチに設定しなおしたところちゃんと反映されましたので、導入前からgithub-pagesを利用していたが、なぜかビルド結果が反映されないというかたは参考になるかもしれません。

最後に

nodejsやaxiosといった大手のOSSを利用することはあれど、個人開発のリポジトリを利用するというのは初めてで、最初は「動くのかなあ」とか「動かなかったらデバッグめんどくさそうだ...」と消極的でした。しかし利用し始めてみるとドキュメントも充実していて使いやすく、また、原因不明な不具合なども発生しませんでした。いいプロジェクトに出会えたから、という理由も大きいとは思いますが、githubでよそ様の作ったものを取り入れてみようかなとも思ったというお話でした。

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

Vuexについて勉強したこと

まずはVuexをダウンロード

npm install vuex

main.js内のVueインスタンスに含まれている全てのコンポーネントでVuexにアクセスできるようにするまでの流れ

  1. store.jsを作る
  2. store.jsでVuexでVuexをimportする
  3. Vue.use(Vuex)でVuexというプラグインを使えるようにする
  4. new Vue.Store({ })のなかに、Vue.js全体で使える、グローバル変数のようなものを用意する
  5. main.jsで読み込むために、export defaultとする
  6. main.jsでstoreを読み込む
  7. main.jsのnew Vueインスタンス内で、store:storeとする(es6なので、store,でも可)
store.js
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);
export default new Vue.Store({
 state:
  count:2
})
main.js
import store from "./store";

new Vue ({
 store:store
}).$mount("#app")

例えばHome.vueというコンポーネントから$storeにアクセスしたい時

  • Home.vueのどこに書いてもいいけど、store.jsの中身が変わったときに、実際にそれを計算してDOMを更新する必要があるのかどうかとかを賢くやってくれるのがcomputedプロパティなので、computedに書く
Home.vue
<template>
 <div> 
  <p>{{ count }}</p>
 </div>
</template>

<script>
export default {
 computed:{
//⬇︎名前はなんでもいいけどcount()にしとく
  counte(){
//$storeは、store.jsのVue.sotreの部分
//stateはstore.jsのそのあとのstate:の部分
   return this.$store.state.count
  }
 }
}
</script>

例えば、また他のページで、カウントしたい時

Header.vue
<template>
 <div>
  <button @click="increment">+1</button>
  <button @click="decrement">-1</button>
 </div>
</template>

<script>
 export default {
  methods: {
   increment(){
     this.$store.state.count++;
     },
   decrement(){
     this.$store.state.count--;      
   }
 }

こんな感じでHome.vueと同じように使えます

gettersについて

gettersとは

  • Vuex用の算出プロパティ
算出プロパティはcomputedプロパティですよね。

computedプロパティとは

  • どのデータを使って、別の計算をするか

それがVuexにもあるという感じ

gettersの使い所

  • 式が複雑化してきたときに、その式をいちいち書くことなく、Vuexにまとめることができる どうするかというと、gettersを使う

gettersの使い方

  • 下のように書くと、Vuex側で、カウントを2倍するという処理ができる
store.js
export default new Vuex.Store({
 state:{
  count: 2
 },
 getters: {
//stateを引数にとる
//state.cuontとすることで、上のcountが取れる
  doubleCounter: state => state.count * 2
 }
});

例えば、Home.vueにて

先ほどはこのように書いていましたが、

Home.vue
<script>
export default {
 computed:{
//⬇︎名前はなんでもいいけどcount()にしとく
  counte(){
//$storeは、store.jsのVue.sotreの部分
//stateはstore.jsのそのあとのstate:の部分
   return this.$store.state.count
  }
 }
}
</script>

gettersを使用すると、

Home.vue
<script>
improt { mapGetters } from "vuex"

export default {
 computed:{
//ここから
  counte(){
   return this.$store.getters.doubleCount;
  }
 }
//ここまで
}
</script>

と、書くことで、countを算出することができる

mapGettersヘルパーについて

computedのなかにgettersを書いてると思うのですが、もっとコンパクトに書きたい時は、
vuexから、mapGettersというものをインポートする。

さっきこのように書いてあった、computedの部分を、、

Home.vue
<script>
improt { mapGetters } from "vuex"

export default {
 computed:{
//ここから
  //doubleとtripleに計算を増やしてみました
  doubleCounte(){
   return this.$store.getters.doubleCount;
  },
  tripleCount(){
   return this.$store.getters.tripleCount;
 }
 }
//ここまで
}
</script>

一旦がさっと削除して、代わりにmapGetters、その中に配列で一つ目にdoubleCounttripleCountとすると、

Home.vue
<script>
improt { mapGetters } from "vuex"

export default {
 computed:mapGetters(["doubleCount","tripleCount"])
}
</script>

という感じで一行で終わるコンパクトさになります

mapGettersの部分を、下記のようにオブジェクトで書いたりもできる

Home.vue
<script>
improt { mapGetters } from "vuex"

export default {
 computed:mapGetters({
   //キーはなんでもよいが、バリューに`doubleCount`をかく
  myComponentDoubleCount:"doubleCount"     
 })
}
</script>

computedの部分に他にも書きたい、、という場合

  • ES6のスプレッド演算子
  • ...と書くと、このオブジェクトの中にうまく組み込んでくれる
  • ...と書くことで、他に書いたcomputedも動く
Home.vue
<script>
improt { mapGetters } from "vuex"

export default {
 computed:{
    //computedプロパティにオブジェクトではかけないので
   ...mapGetters([myComponentDoubleCount:"doubleCount"])
},
}
</script>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vuexを初めから丁寧に(1)~Vuexを理解するために必須の前提知識~

はじめに

この記事を読むと

  • Vuexを理解するために必要な知識を習得できます
  • Vuexを学ぶためのマイルストーンが明確となります

想定読者

  • Vue.js や Nuxt.js の初級〜中級者
  • Vuex を何となく雰囲気で使っている

前提知識

JavaScript 及び Vue についての基本知識があることは前提とします。
(Vue の基本知識がない方はこちらが入門書として最も最適です。)
『Vue.js 超入門』(掌田津耶乃/秀和システム)

またJavaScriptにおいては特に、オブジェクトの使い方にも慣れておくとスムーズでしょう。
(こちらの第9章が最も良い説明だと思います。)
『初めてのJavaScript 第3版 ―ES2015以降の最新ウェブ開発』(Ethan Brown, 武舎広幸,武舎るみ/オライリージャパン)

Vuex の理解が難しい原因

なぜ Vuex が難しいと感じるのでしょうか?
私の場合は専門用語の意味が省略されていることに起因していました。
さらに問題なのは、 「Vuexを理解するためのキーとなる用語」が、全く違う意味で使われているにも関わらず見た目は一般的な日本語と一緒なのでなんとなくわかった気になり、「何が分からないのか分からない」状況に陥ることです。
例えば Vuex における「状態」
「アプリケーションが保持するデータ」
のことを指します。
なので、「Vuex は状態管理ライブラリである」「Vuex は状態を管理するために単方向データフローを採用している」といった説明や図解※を見ても、肝心の「状態」が分からないので、文章の意味が消化できないまま頭を素通りしていくだけでした。

しかし逆に言うと、用語の意味さえ押さえておけば Vuex はスラスラ理解できます。

※Vuexデータフローの図解
Vuex図解(Vuex公式ドキュメントより)

Vuex を理解するためのツボ

さて、前置きが長くなりましたが本題です。
たった 4 つだけです。

  • 用語を正確に理解する

    • 「状態」
    • 「データフロー」
  • 「データフローの設計」と「状態管理」の意義を理解する

    • 信頼できる唯一の情報源(Single Source of Truth)
    • 単方向フロー(one-way data flow)
    • 情報と取得のカプセル化(Encapsulation of sorce and receiving)
  • Vuex の構成要素の役割と使い方を理解する

    • State
    • Getters
    • Mutations
    • Actions
  • ※「ストアのモジュール分割」は一旦省略します

Vuex に入る前に

いきなり Vuex に入るより、まず状態管理やデータフローの基本知識を押さえておくと、スムーズに理解が進みます。

「状態」とは

状態とは
「アプリケーションが保持するデータ」
のことです。
ユーザーの操作やイベントの発生などによってその値が更新されていきます。

例えば、EC サイトのショッピングカートです。カートは何も入っていない空の状態から始まり、ユーザーが商品をカートに入れる操作を行うことでカートは空の状態に戻り、購入処理が完了します。

規模が大きいアプリケーションは保持する状態の数、それぞれの組み合わせの数も多くなり、そのままでは扱いきれなくなります。

繰り返しになりますが、Vuex において「状態」は普段の日本語とは異なる特別な意味がある言葉なので注意してください。

データフローとは

「データフロー」とは
「状態を含む、アプリケーションが持つデータの流れ」
のことを指します。
具体的には、どこにデータを保持し、データを読み込む時や更新するときはどこからどのように行うのかという点を表すことが多いです。

データフローの設計において、以下の三つのプラクティスが重要です。

信頼できる唯一の情報源

「信頼できる唯一の情報源」(single source)とは、「管理する対象のデータを一箇所に集約することで管理を容易にすることを目的とする設計のパターン」です。

  • どのコンポーネントも同一のデータを参照するため、データや表示の不整合が発生しづらい
  • 複数のデータを組み合わせた処理を比較できる容易に実装できる
  • データの変更のログ出力、現在のデータの確認などの開発に便利なツールを作りやすい

「状態の取得・更新」のカプセル化

「状態の取得・更新」のカプセル化を行うことで、状態管理のコストを下げることができます。
例えばカウンターアプリの例では更新処理を store 内に記述することでカプセル化しており、コンポーネント側からは具体的にどのような実装がされているかは隠されています。

  • 状態の取得・更新のロジックを様々な場所から利用できる
  • 詳細な実装をビューから隠すことで、データ構造や取得、更新処理の変更の影響範囲を小さくする
  • デバッグ時に確認する場所が限られるため、デバッグが容易になる

単方向データフロー

単方向データフローにすることで、状態の取得、更新のコードが簡潔になります。
データが単方向でないと、データの取得と更新の両方を同時にできてしまい、より複雑な処理になり理解が難しくなってしまいます。

  • データを取得しつつ更新するといったようなことができなくなり、実装やデバッグが単純になる
  • データを取得、更新するために何をするかの選択肢が絞られて、理解が容易なコードをかきやすい

まとめ

ここまでデータフローの三つのプラクティスを見てきましたが、実はVuex は先ほど紹介したデータフローのプラクティスを全て満たします。

まず、Vuex はアプリケーションの状態やそれに付随するロジックが一つの場所(ストア)にまとまるように設計されているため、「信頼できる唯一の情報源」を満たします。

また、Vuex において状態の更新はミューテーションでのみ行うことができ、取得に関してもゲッターという機能で詳細な実装は隠蔽できるため「状態の取得と更新」のカプセル化も満たします。

さらに、状態の取得と更新の窓口が異なるため(冒頭の図解をもう一度参照ください)、強制的に実装が単方向データフローになります。

おわりに

いかがだったでしょうか。VueやNuxtで開発を行う方が、Vuexを理解するための助けになれば幸いです。
「状態管理」「データフロー」についてはバッチリですか?
次の記事ではいよいよ Vuex による状態管理について見ていきます。

参考文献

『Vue.js入門 基礎から実践アプリケーション開発まで』(川口和也, 喜多啓介, 野田陽平, 手島拓也, 片山真也/技術評論社)
Vue.jsについての書籍は増えてきていますが、問題なのはその殆どがVuexについての説明を省略していることです。Vue.jsやNuxt.jsを用いた実際の開発においてVuexによる状態管理は必須ですが、学習の障壁になるとして避けてしまっているのでしょう。私が読んだ中で唯一、Vuexについて丁寧に説明していたのが本書です。Vuex以外の内容も素晴らしいの一言。本書はVue.js・Nuxt.jsの開発に関わるエンジニアや組織にとって必携です。保存用・実用用・観賞用に3冊購入しましょう。あるいは、あなたが経営者の場合はぜひエンジニアに対して一人一冊ずつ買い与えてください。
ただし、全くVueについて未経験という方への第一歩としては内容が本格的すぎるかもしれません。その場合は『Vue.js 超入門』がおすすめです。

『Vue.js 超入門』(掌田津耶乃/秀和システム)
とにかく分かりやすく、まず概要を把握するために最適の一冊です。「なんとなくで良いので概要を把握する」⇨「より詳細で厳密な理解する」という流れで学ぶとスムーズです。

『初めてのJavaScript 第3版 ―ES2015以降の最新ウェブ開発』(Ethan Brown, 武舎広幸,武舎るみ/オライリージャパン)
JavaScriptの根本的な理解ができる、革命的な良書です。分厚いので手強そうに見えますが、実際はとても親切で分かりやすい作りです。本書も一人一冊は欲しいところです。

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

Vuetifyスクラッチインストール時にiconを使いたい

概要

vue-cliなどを使わず、スクラッチインスールした場合、iconがない。

app.ts

Vuetify アイコンを参考に設定する。

以下具体例。

app.ts
import Vue from "vue";
import Vuetify from "vuetify";
import '@mdi/font/css/materialdesignicons.css' // Ensure you are using css-loader
import "vuetify/dist/vuetify.min.css";
import App from "./components/Main.vue";
import router from './router'

Vue.use(Vuetify);
new Vuetify({
  icons: {
    iconfont: 'mdi',
  }
});
new Vue({
  router: router,
  render: h => h(App),
  vuetify: new Vuetify()
}).$mount('#app')

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

サーバーレスWebアプリにメールフォームを追加実装する 〜 フロントエンド編 〜

サーバーレスWebアプリにメールフォームを追加実装する 〜 フロントエンド編 〜

はじめに

AWSを活用したサーバーレスWebアプリの制作で作ったWebアプリにメールフォームを追加実装します。
フロントエンド・バックエンドの2部構成にしています。
バックエンド編はこちら。
サーバーレスWebアプリにメールフォームを追加実装する 〜 バックエンド編 〜

フロントエンド

Vue.jsのWebアプリにメールフォーム用のページを追加します。

VeeValidateの利用

メールフォームの各入力値チェックのために、VeeValidateというものを利用しました。
VeeValidateとは、Vue.js用のバリデーションコンポーネントライブラリです。

利用するために、まずはVeeValidateをプロジェクトにインストールする必要があります。

npm i vee-validate

使い方などの詳細は以下のページを参照してください。
https://logaretm.github.io/vee-validate/overview.html#getting-started

コンポーネントの実装

VeeValidateを利用してバリデーションを効かせたメールフォーム画面のコンポーネントを実装します。
デザインコンポーネントは例によってVuetifyを利用しています。
必要な入力項目をすべて適切に入力しないとSUBMITボタンが有効にならないようにバリデーションを効かせ、ただ、SUBMITボタンを押下しても入力内容をアラートするだけにしておきます。(バックエンドの呼び出しは後で実装します。)

src/components/Mail.vue
<template>
  <v-container>
    <p>Mail Form</p>

    <ValidationObserver ref="observer" v-slot="{ validate, reset, invalid }">
      <ValidationProvider v-slot="{ errors }" name="name" rules="required|max:25">
        <v-text-field
          label="Name"
          v-model="name"
          :counter="25"
          :error-messages="errors"
          required
        ></v-text-field>
      </ValidationProvider>

      <ValidationProvider v-slot="{ errors }" name="email" rules="required|email">
        <v-text-field
          label="E-mail"
          v-model="email"
          :error-messages="errors"
          required
        ></v-text-field>
      </ValidationProvider>

      <ValidationProvider v-slot="{ errors }" name="contens" rules="required|max:300">
        <v-textarea
          label="Message"
          v-model="message"
          :error-messages="errors"
          :counter="300"
          rows="8"
          required
        ></v-textarea>
      </ValidationProvider>

      <v-btn @click="onSubmit" :disabled="invalid">submit</v-btn>
      <v-btn @click="onValidate">validate</v-btn>
      <v-btn @click="onClear">clear</v-btn>
    </ValidationObserver>

  </v-container>
</template>

<script>
import { required, email, max } from "vee-validate/dist/rules"
import { extend, ValidationObserver, ValidationProvider, setInteractionMode } from "vee-validate"

setInteractionMode("eager");

extend("required", {
  ...required,
  message: "{_field_} can not be empty",
});

extend("max", {
  ...max,
  message: "{_field_} may not be greater than {length} characters",
});

extend("email", {
  ...email,
  message: "Email must be valid",
});

export default {
  name: "Mail",

  components: {
    ValidationProvider,
    ValidationObserver,
  },

  data: () => ({
    name: "", 
    email: "", 
    message: "", 
  }), 

  methods:{
    async onSubmit(){
      alert(this.name + " / " + this.email + " / " + this.message);
    }, 
    onValidate(){
      this.$refs.observer.validate();      
    }, 
    onClear(){
      this.name = "";
      this.email = "";
      this.message = "";
      this.$refs.observer.reset();
    }
  }
}
</script>

実行結果

Screenshot 2020-03-29 at 08.05.17.png

バックエンド

バックエンド編へ記載しています。
終わったら戻ってきてください。

フロントエンドからAppSyncの呼び出し

バックエンドが済んだら、最後にWebアプリからの呼び出しです。
バックエンド編でAppSyncに追加したIF(processSendMail)をWebアプリからリクエストします。

src/graphql/mutations.js
export const processSendMail = `
    mutation processSendMail($input: ProcessSendMailInput!) {
        processSendMail(input: $input) {
            statusCode
            body
        }
    }
`;
src/components/Mail.vue
 :
<script>
import { required, email, max } from "vee-validate/dist/rules"
import { extend, ValidationObserver, ValidationProvider, setInteractionMode } from "vee-validate"
import { Auth, API, graphqlOperation } from 'aws-amplify';
import { processSendMail } from "../graphql/mutations";
 :
  methods:{
    async onSubmit() {
      let apiResult = await API.graphql(graphqlOperation(processSendMail, 
        {input : {name: this.name, email: this.email, message: this.message}})
      ).catch(error => {
        console.error(error);
      });
    }, 

実行して、フォームに入力し、SUBMITボタン押下によりメールが届くことを確認してください。

あとがき

バックエンド編へまとめて書きます。

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

Vue.js×TypeScriptでのテキストコピー(iOS対応)

Vue.js×TypeScriptで「ワンタップでテキストをコピーする」ボタンを作ったらハマりポイントがたくさんありました。
生jsやjQueryでの解決策はたくさん見つかりましたが、Vue.js×TSは見つからなかったのでメモです。

やりたいこと

フォームに文字入力した時に、飾り文字を追加した文章を出力して、ワンタップでコピーできるようにする。

recipe formatter qiita.gif

完成したコード

    <template lang="pug">
      .CopyText
        button(@click.prevent="copyTexts")
          span.copy-message クリップボードにコピー
        .formatted-text
          span.recipe
            span#copy-text {{formattedTitle}}<br>
    </template>
    <script lang="ts">
    import { Recipe } from "../../components/molecules/RecipeTitle.vue";
    import Vue, { PropType } from "vue";
    export default Vue.extend({
      props: {
        recipe: {
          type: Object as PropType<Recipe>,
          default: {}
        }
      },
      computed: {
        //inputで入力した内容ではなく、ここでフォーマットしたテキストがコピー対象
        formattedTitle(): string {
          return this.recipe.title ? `【${this.recipe.title}】` : "";
        }
      },
      methods: {
        //iOSの判定
        isIOS() {
          const agent = window.navigator.userAgent;
          return agent.indexOf("iPhone") != -1 || agent.indexOf("iPad") != -1;
        },
        //コピー
        copyTexts(): void {
          if (this.isIOS()) {
            //iOSの場合
            const doc: HTMLInputElement = document.getElementById(
              "copy-text"
            ) as HTMLInputElement;
            const selected = window.getSelection();
            const range = document.createRange();
            range.selectNodeContents(doc);
            selected!.removeAllRanges();
            selected!.addRange(range);
            document.execCommand("copy");
          } else {
            //それ以外
            const formattedText = `${this.formattedTitle}`;
            navigator.clipboard.writeText(formattedText);
          }
        }
      }
    });
    </script>

参考:Javascriptによるコピー機能(クロスブラウザ対応)

ハマった部分の解説

iOSでのコピー

jsでコピーをしようと思ったらnavigator.clipboardを使用するのが一般的かと思います。

ユーザーエージェントなどの情報を扱うNavigatorインターフェイスにclipboardプロパティを追加して、writeText()メソッドを呼び出すことで、テキストがコピーできます。

    navigator.clipboard.writeText(text);

しかし、このnavigator.clipboardはiOSの10以降、textareaなど一部のタグからしかコピーできないなど仕様が変わっています。

今回はinputに入力した文字ではなく、フォーマットをかけたテキストをコピーするため、まさにこの条件に引っかかり、iOSのsafariとchromeで動作しませんでした。

参考: Copy to clipboard using Javascript in iOS

そのため、iOSとそれ以外でコピーの処理を変える必要があります。

iOSかどうかの判定

  isIOS() {
    const agent = window.navigator.userAgent;
    return agent.indexOf("iPhone") != -1 || agent.indexOf("iPad") != -1 || agent.indexOf("iPod") != -1;
        }

navigator.userAgentを使います。

今回はブラウザではなくiOSかどうかだけ判定するので、上記のようにしてみました。

iOS用のコピー

iOSのコピーは、コピーしたい文章を選択→コピーの実行という流れで行います。

 const doc = document.getElementById("copy-text");
 const selected = window.getSelection();

 const range = document.createRange();
 range.selectNodeContents(doc);

 selected.removeAllRanges();
 selected.addRange(range);

 document.execCommand("copy");

ユーザーはワンタップするだけですが、内部の動作はマウスなどで文章選択→コピーをするのと同じです。

2行目のwindow.getSelectionはselectionオブジェクトを取得するものです。

selectionオブジェクトは、ユーザーが選択した範囲のDOMに関する情報を持つことができます。

3行目のcreateRangeはdocument中のテキストやノードに関する情報を持つrangeオブジェクトを作成します。

rangeオブジェクトを作成しただけでは何も情報を持っていないため、4行目のrange.selectNodeContents(doc)で、最初に取得した要素を渡します。

5行目は2行目に取得したselectionオブジェクトが現在持っているrangeに関する情報をあらかじめ削除する処理です。文章がすでに選択されてselectionオブジェクトに情報が設定されている場合、この後の処理が無視されるので先に削除してしまいます。

これにより、6行目でselectionオブジェクトに作成したrangeオブジェクトを追加することができます。

最後のdocument.execCommand()はhtmlのdocumentオブジェクトを操作するコマンドを実行します。copyは選択範囲をクリップボードにコピーするコマンドです。

これでiOSでもテキストコピーができるようになりました!

参考: memo: テキスト全選択の JavaScript コードが動かなくなったので修正した

TSで"Argument of type 'HTMLElement | null' is not assignable to parameter of type 'Node'"

上記のコードはType Scriptを使うと以下の部分でエラーを吐きます。

  //Argument of type 'HTMLElement | null' is not assignable to parameter of type 'Node'
  const doc = document.getElementById("copy-text");

document.getElementById() はHTMLElement型もしくはnullを返しますが、nullを返す可能性があるとTSがエラーを出すようです。

そのため、返り値がHTMLElement型であることを明示的に示します。

  const doc: HTMLInputElement = document.getElementById("copy-text") as HTMLInputElement;

参考: Typescript で TS2322 の対処方法

TSでObject is possibly 'null'エラー

rangeの削除、追加部分でも型エラーが出ます。

  selected.removeAllRanges(); //Object is possibly 'null'
  selected.addRange(range); //Object is possibly 'null'

これはselectedの部分がnullの可能性があることで出るエラーです。

そこで、!をつけて、selectedがnullでもundefinedでもないことを推論させます。ただ、この方法はESlintで"Forbidden non-null assertion"の警告が出ます。

参考: 非nullアサーション演算子(Non-Null Assertion Operator)

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

宮崎県COVID-19対策サイトを見つけ、出身者として何かしたいと思ったから、した。

出身の宮崎県用の対策サイトが出来、同時に感染者数が3人に増えていたことを知った。何かできる事は無いかと思ったので、Qiitaに記録しつつ、色々やってみた。

Introduction

References

Who I am

  • 宮崎県出身
  • PRしたことない人
  • Vue, TypeScript何も分らん
  • 現在、住所不定無職。再就職活動中

Environment

  • Ubuntu 18.04 ( vm with Vagrant
    • Node.js: v12.16. (>= 10.19.0
    • yarn: 1.22.4

Environment Setup

リポジトリのREADME.md中の開発者向け情報->環境構築の手順に従う

terminal
# 対象をforkしておく
git clone https://github.com/oriverk/covid19.git
cd covid19
terminal
# yarnが入ってなかったのでinstallした後。
yarn install
yarn dev

私にとっては、ここから未知の領域。(yarn devとは :rolling_eyes:
image.png

yarn devの処理?が終わった後、http://localhost:3000/にアクセスすると
image.png

What I did

First

最初に書いた通り、Vue(と言うかJS)何もわからない人であり、コード修正は厳しいと考えたので、表示される自然言語の修正をする事にした。因みにどのファイルがどこにあるかも判らなかったので、commit履歴から探しました(^^;

Main

  1. CODE_OF_CONDUCT.mdの修正
  2. image.png

東京都verから宮崎県verにした際の地域表記変更の漏れであり、1文字の修正だった。

CODE_OF_CONDUCT.md
# 33行目:都庁の人だけではなく
=> 県庁の人だけでなく
terminal
git add .
# pre-commit.shでエラーが出たので(触ってない
git commit -m "都庁を県庁に修正" --no-verify
git push コピーしてきたURL development

how to pull request

無職&&個人で勉強なのでPRの機会は初めてで、ここをよく見ながらしましたが、怖かった:cry:

やってることは同じなので割愛。こんな感じで書いた。
image.png

無事にpull requestがmergeされ、OSS活動(?)の実績解除となりました。:blush:
image.png

What I wanna do form now

  • 表示自然言語の部分を中心に修正改善
  • Warningと出ている部分の修正
    • 恐らくTypescriptの型由来の警告なので、ドキュメントと格闘しながら。

序に

PCを触ってない時は、これを動かしてます。

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

クライアントサイド系を歴史も踏まえて振り返る

概要

  • クライアントサイド系の技術を改めて背景・歴史振り返る
    • クロスプラットフォーム系とJSフレームワーク系を中心に記載
    • 気になっている技術はFlutter/Electron
      • Angular/Vueは引き続きウォッチ

背景や歴史

スマホが出てくる前はマルチブラウザ対応が種だったが、Android/iOSも含めてクロスプラットフォームでの開発が必須な時代で、その状況に準じた製品が様々出てきている。

JQueryみたいなプリミティブなものは2010年前後に主流だった記憶があるが、その後AngularJSなどのデータバインティングとか少しずつ大きめの機能を持つようなJSフレームワークが出てきたように感じる。

※なお、RIA(Rich Internet Application)と言う言葉で総称され、Apach FlexやJavaFXやSilverlightなど様々なクライアントサイド技術の流行もあった。

クロスプラットフォームフレームワーク

こちらの記事を参考にさせていただきました。個人的にはElectronも気になっている。

  • Flutter
  • React Native
  • Xamarin
  • Unity

JSフレームワーク

  • 2014年頃の御三家イメージ
    • Angular
    • Backbone
    • Knockout
  • 2020年における御三家イメージ※参考:比較ページ
    • Aunglar
    • Vue
    • React

JSフレームワーク事情2020年始めThe State of JavaScript 2019を参考

長期政権のAngular

クライアントサイドといえば最大3年もあれば、次の技術になっているのが通例なイメージがある一方で、Angularは2012/6にAngularJS v1.0.0がリリース。その後2016/9に1系から2系になったかと思えば、リリースサイクルが変わり、2020/2にバージョン9が出ている。

多大な貢献のJQuery

併せてJQueryも以前は随分お世話になった。個人的には最近は触っていないが、利用しているプロジェクトもまだまだあるのではないかなと想像。

AltJS

もう最近はこの言い方をしないのかもしれないけれど、コンパイルするとJavaScriptが出力される言語。言わずもがなTypeScriptが前提となるケースが多い。例えば、AngularもTypeScriptが推奨となっている。CoffeScriptなども流行した。

AltJSはJavaScriptの人気を示していると感じる。少しずれるがJVM言語系のScalaやGroovyも同様にJavaの人気を示しているなと。

サーバーサイドJavaScript

今回主としては取り上げないが、node.jsなどのサーバサイドJavaScriptも流行しており、JavaScriptの人気を改めて示している。

まとめ

現時点では以下の形でまとめられることが多いように感じるけれど、時代背景に応じて数年後にはまた違った形になっていくかもしれない。例えば、今流行しているAIとかその辺の技術につられて何か出てくるとか。

  • クロスプラットフォーム
  • JSフレームワーク
  • AltJS
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails + heroku App でpage speed insightをあげる

レンダリングを妨げるリソースの除外

全部のjavascriptにレンダリングしてるところにdefer: trueをつけた
3秒から0.45秒に

<%= javascript_pack_tag 'faq', defer: true %>

オフスクリーン画像の遅延読み込み

遅延読み込みという技術は、Webサイトに表示される画像を一度に読み込まず、必要に応じて必要な分だけ読み込むというものです。不必要な画像の読み込みを後回しにして、画像以外のCSSやJSファイルの読み込みが先に行われます。そうすることで、表示速度を速くすることができます。
6秒消えた

yarn add v-lazy-image

  <v-lazy-image :src="item.image_url" />


  import VLazyImage from "v-lazy-image"
  export default {
    components: {
      VLazyImage
    }
  }

テキスト圧縮の有効化

https://qiita.com/Oakbow/items/ec13c3a57327cc5f3197

gem 'heroku-deflater'
bundle install

静的なアセットと効率的なキャッシュポリシーの配信

https://catnose99.com/rails-heroku-fastly/

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

Vue Routerでquery書き換えができなかった(凡ミス)

試行錯誤の段階を記載しているので急いでいる方は下へ。

問題

Vue Routerを使用してqueryを書き換えようとすると次のように怒られた。

Uncaught (in promise)
NavigationDuplicated {
    _name: "NavigationDuplicated",
    name: "NavigationDuplicated",
    message: "Navigating to current location ("/blog?page=3") is not allowed",
    stack: "Error↵    at new NavigationDuplicated (webpack-int…node_modules/vue/dist/vue.runtime.esm.js:1853:26)"
}

Error
    at new NavigationDuplicated (webpack-internal:///./node_modules/vue-router/dist/vue-router.esm.js:2013:14)
    at HTML5History.confirmTransition (webpack-internal:///./node_modules/vue-router/dist/vue-router.esm.js:2129:18)
    at HTML5History.transitionTo (webpack-internal:///./node_modules/vue-router/dist/vue-router.esm.js:2073:8)
    at HTML5History.replace (webpack-internal:///./node_modules/vue-router/dist/vue-router.esm.js:2416:10)
    at eval (webpack-internal:///./node_modules/vue-router/dist/vue-router.esm.js:2829:22)
    at new Promise (<anonymous>)
    at VueRouter.replace (webpack-internal:///./node_modules/vue-router/dist/vue-router.esm.js:2828:12)
    at Proxy.set (webpack-internal:///./node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/ts-loader/index.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/views/blog/article_list.vue?vue&type=script&lang=ts&:190:20)
    at callback (eval at ./node_modules/cache-loader/dist/cjs.js?{"cacheDirectory":"node_modules/.cache/vue-loader","cacheIdentifier":"900baf1e-vue-loader-template"}!./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/views/blog/article_list.vue?vue&type=template&id=ee03622c&scoped=true& (http://localhost:8080/js/article_list.js:23:1), <anonymous>:115:29)
    at invokeWithErrorHandling (webpack-internal:///./node_modules/vue/dist/vue.runtime.esm.js:1853:26)"
 at invokeWithErrorHandling (webpack-internal:///./node_modules/vue/dist/vue.runtime.esm.js:1853:26)"

どうやら同じrouteに遷移しようとしていると思われているらしい。
実際はちゃんと別routeへ動こうとしているので、何かがおかしい。

ソース

問題のソースはこれ。(抜粋)(変更)

let query = this.$route.query;
query["page"] = page.toString();
this.$router.push({ query });

GitHubを調査

https://github.com/vuejs/vue-router/issues/2872
それっぽいissueをみつけた。
解決方法としては、Exceptionを握りつぶすというもの。
(わざと同じルートへ動こうとしたときのissueだろうか?)

ただ、同じルートへ遷移しようとすると無視するという仕様のもとでエラーを無視したところで得られるものは何もなく…

ソースを観察

stacktraceをもとに元凶であると思われるファイルへ。(node_modules/vue-router/dist/vue-router.esm.js:2129)

vue-router.esm.js
  if (
    isSameRoute(route, current) &&
    // in the case the route map has been dynamically appended to
    route.matched.length === current.matched.length
  ) {
    this.ensureURL();
    return abort(new NavigationDuplicated(route))
  }

isSameRouteの定義を見にいく。

vue-router.esm.js
function isSameRoute (a, b) {
  if (b === START) {
    return a === b
  } else if (!b) {
    return false
  } else if (a.path && b.path) {
    return (
      a.path.replace(trailingSlashRE, '') === b.path.replace(trailingSlashRE, '') &&
      a.hash === b.hash &&
      isObjectEqual(a.query, b.query)
    )
  } else if (a.name && b.name) {
    return (
      a.name === b.name &&
      a.hash === b.hash &&
      isObjectEqual(a.query, b.query) &&
      isObjectEqual(a.params, b.params)
    )
  } else {
    return false
  }
}

しっかりqueryが異なることを確認しているように見える。

実験

先のisSameRouteの冒頭でa,bの中身をconsole.logで確認する。

a: {
  name: "article_list",
  meta: {},
  path: "/blog",
  hash: "",
  query: {page: "3"},
  params: {},
  fullPath: "/blog?page=3",
  matched: [{}],
  __proto__: Object
}

b: {
  name: "article_list",
  meta: {},
  path: "/blog",
  hash: "",
  query: {page: "3"},
  params: {},
  fullPath: "/blog?page=2",
  matched: [{}],
  __proto__: Object
}

queryとfullPathで齟齬が出ていることがわかった。
原因を考えてみると、問題のソースの1行目、オブジェクトを参照でコピーしていることに気づいた。

let query = this.$route.query;

これを次のように変更することで修正できた。

let query = Object.assign({}, this.$route.query);

まとめ

JSのオブジェクトの代入には細心の注意を。参照渡しで痛い目にあいます。

(問題の核心はVue-routerとは少し離れますが、関係はしていたのでタグ付けしました)

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

忙しい人向けVue.jsプロジェクト作成

開発環境

Visual Studio Code (Onlineでも良い)
拡張機能 Vetur(便利です。)

叩くコマンド

プロジェクト作成

>vue create <project名>

⇒必要なオプションを選択する(今回はTypeScriptや画面遷移させるRouter等を追加)
※追記 tsc -v でバージョン見るとなぜか古いが、内部的には入ってるっぽい。

>C:\Users\<username>>npm ls -g typescript
C:\Users\<username>\AppData\Roaming\npm
`-- @vue/cli@4.2.3
  `-- @vue/cli-ui@4.2.3
    `-- typescript@3.7.5

image.png

ここで内部的にnpmからモジュール持ってきたり、設定したり全部自動でやってくれている。(テストに出ます!)

開発サーバーの起動(出てきたURLに飛べばVueの初期画面が表示されているはずです)

>npm run serve

本番用ビルド

>npm run build

Vue CLI プラグインが超優秀らしい

やってくれること
 ・npmモジュールの導入
 ・設定ファイル等の追加
 ・Vueインスタンスへのロードetc

例えば HTTP クライアントであるaxiosの導入(APIが作れる)
これだけでasyncとかawaitとかもう使える

>vue add axios

UIをカッコよくしたい場合に使うライブラリ
 ・Vuetify - マテリアルデザイン、コンポーネントが多い。
 ・Element - 管理画面向けコンポーネントが豊富
 ・Buefy - BulmaというCSSフレームワークがベースで軽い
 ・Onsen UI - モバイルアプリ向け

Gitにあげときましょう。

Githubに新規リポジトリを追加
https://qiita.com/sodaihirai/items/caf8d39d314fa53db4db

参考

株式会社ゼンアーキテクツCTO三宅さんのセッション

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