20190828のvue.jsに関する記事は14件です。

【Vue.js】ローカル環境構築(Vue CLI v3.10.0)インストール手順

開発環境
?Mac OS Mojave

CodePenやJSFiddleでCDN(インターネット経由)でも開発できますが、
ローカル環境での構築手順をまとめます。

Vue.jsの公式サイト
http://vuejs.org/
↑英語

Vue.jsの公式 日本語訳されたサイト
http://jp.vuejs.org/
↑日本語

【Vue.js】Vuejsをchromeブラウザでデバッグする方法
https://qiita.com/nonkapibara/items/8b587013b6b817d6dfc4

1.nodebrewをインストールする

brew install nodebrew

下記エラーが出た。><
xcrun: error: active developer path ("/Applications/Xcode.app/Contents/Developer") does not exist
Use sudo xcode-select --switch path/to/Xcode.app to specify the Xcode that you wish to use for command line developer tools, or use xcode-select --install to install the standalone command line developer tools.
See man xcode-select for more details.
Error: An exception occurred within a child process:
CompilerSelectionError: nodebrew cannot be built with any available compilers.
Install GNU's GCC:
brew install gcc

001.png

エラー内容を見ると、CLTをインストールしたら良いっぽいので、
CLTをインストールする

  brew install gcc

CLTをインストールできたので
再度、Nodebrewをインストールする

brew install nodebrew

インストール確認をする

nodebrew -v

インストール成功
nodebrew 1.0.1

002.png

2.Node.jsをインストールする

nodebrew/src のディレクトリを作る

mkdir -p ~/.nodebrew/src

Node.jsをインストールする

nodebrew install-binary latest

Installed successfully
が表示され、インストール成功

バージョンを確認する

nodebrew ls

v12.8.0
が表示される

使用するバージョンを指定する。

nodebrew use v12.8.0

3.パスを通す

bash_profileを開いて

vi ~/.bash_profile

以下の内容を追加する

export PATH=$HOME/.nodebrew/current/bin:$PATH

vimで書き込みを行うときは、iを押します。
一番下の行に-- INSERT --と出て、入力モードになります。
書き込みが済んだら、escを押して入力モードを解除します。

この状態で、:wqと打つと保存してvimを終了してくれます。
(保存しないで終了したい場合は:q)

確認する

cat ~/.bash_profile

4.設定ファイルを読み直す。

exec $SHELL -l

5.ここまでの確認をする

node -v

v12.8.0
が表示されたOK

pm -v

6.10.2
が表示されたOK

6.Vue CLI3のインストールする

インタラクティブなプロジェクトのひな型作成 - @vue/cli
設定なしに即使えるプロトタイプ作成 - @vue/cli + @vue/cli-service-global

npm install -g @vue/cli
npm install -g @vue/cli-service-global

バージョンの確認

vue --version

3.10.0
が表示されOK

7.Vue.js Devtoolsをインストール
Vue.js Devtoolsは、Vue.jsの開発をサポートする Chromeブラウザの拡張機能

Vue.js devtools
https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd

003.png

8.新しいプロジェクトを作成する

vue create testone-app

下記表示がされた
? Your connection to the default npm registry seems to be slow.
Use https://registry.npm.taobao.org for faster installation? Yes
004.png

翻訳すると、デフォルトのnpmレジストリへの接続が遅いようです。
とりあえずYでEnter

下記表示がされた
Vue CLI v3.10.0
? Please pick a preset: default (babel, eslint)

005.png
とりあえずEnter

これで、プロジェクトの作成完了
006.png

9.アプリケーションの起動

cd testone-app

サーバー起動

npm run serve

下記が表示された
DONE Compiled successfully in 2670ms

App running at:
- Local: http://localhost:8080/
- Network: http://10.38.109.192:8080/

Note that the development build is not optimized.
To create a production build, run npm run build.
007.png

下記にアクセスする
http://localhost:8080/

成功
008.png

★開発サーバを停止する時は ctrl + c

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

Vue.js の v-slot でスロットのスコープ変数をなんとか推論させる

Vue.js の v-slot でスロットのスコープ変数をなんとか推論させる

最近の Vetur では <template> 配下でも変数の型を確認できるようになってきましたが
スロットのスコープ変数については any になりがちです。

child
<template>
  <div>
    child
    <slot :id="id" :namae="namae" />
  </div>
</template>

<script>
export default {
  data() {
    return {
      id: 1,
      namae: 'Taro',
    };
  },
};
</script>
parent
<template>
  <div>
    parent
    <child>
      <template v-slot="slotProps">
        child 使用
        {{ slotProps }}
      </template>
    </child>
  </div>
</template>

<script>
import Child from './child.vue';

export default {
  components: {
    Child,
  },
};
</script>

child.jpg

child 自身では template 中の変数の型を確認可能だが・・・

parent.jpg

parent 側で使用した際の変数では any

browser.jpg

実際の画面表示では slotProps の中身は id, namaechild にて指定した通り

これについて parent 側にて id などの型の確認ができないかなどをやってみます。

スロット側で変数を明確にする関数を定義して export

スロットを持つ child 側にて「何もしない」が型を明示する関数を用意してみます。

child-vs
<script>
/**
 * @typedef {Object<string, any>} SlotProps
 * @property {number} id
 * @property {string} namae
 */
/**
 * @param {SlotProps} slotProps
 * @return {SlotProps}
 */
export const vs = slotProps => slotProps;

export default {
  data() {
    return {
      id: 1,
      namae: 'Taro',
    };
  },
};
</script>

定義された関数 vs は引数に渡された値をそのまま返すだけのアロー関数です。
@typedef により型定義した SlotProps はそれぞれ child での <slot> に渡している要素と同一の構造のオブジェクトの定義となります。

使用側でスロット側で定義した変数用関数を適用

上記の vsparent にて適用すると・・・

parent-vs
<template>
  <div>
    parent
    <child>
      <template v-slot="slotProps">
        child 使用
        {{ vs(slotProps) }}
        {{ vs(slotProps).id }}
        {{ vs(slotProps).namae }}
      </template>
    </child>
  </div>
</template>

<script>
import Child, { vs } from './child.vue';

export default {
  components: {
    Child,
  },
  methods: {
    vs,
  },
};
</script>

child.vue より vsimport して parentmethods に追加しています。
これをエディタ上で確認してみると以下のようになります。

slotProps.jpg

slotProps は相変わらず any

vs.jpg

child より import した vs は定義した通りで推論されます。

id-inferred.jpg

vs(slotProps) の結果より生やした idJSDoc で定義した通り number で確認できました。
(残念ながら vs(slotProps) の結果から .id などは候補としては取得できませんでした・・・)

not-exist.jpg

JSDoc にて定義していない abababab を指定してみたところ
SlotProps 型に存在しない旨のメッセージが表示され、想定しないプロパティへのアクセスであることも確認できました。

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

【Vue.js】CodePenでVue.jsを実装する方法

2019.08.28時点

CodePen
https://codepen.io/

1.CodePenにログインし、「Pen」を選択する
スクリーンショット 2019-08-28 19.57.23.png

2.「Settings」を選択する
スクリーンショット 2019-08-28 19.58.01.png

3.「JavaScript」を選択する
スクリーンショット 2019-08-28 19.58.47.png

4.Vue.jsのCDNを追加する
「Add External Scripts/Pens」のサーチエリアに「vue」と入力する
スクリーンショット 2019-08-28 20.00.16.png

vueを選択する
スクリーンショット 2019-08-28 20.01.04.png

こんな感じで、Vue.jsのCDNが追加される。
スクリーンショット 2019-08-28 22.16.51.png

5.APIリクエストで「axios」を使用する場合は、
「Add External Scripts/Pens」のサーチエリアに「axios」と入力する
スクリーンショット 2019-08-28 22.03.34.png

6.axiosを選択する
スクリーンショット 2019-08-28 22.04.17.png

7.保存する
スクリーンショット 2019-08-28 22.04.50.png

nonVuejsAxiosAPI by nonkapibara (@nonkapibara) on CodePen.

8.HTMLにdivから書く事ができる。

index.html
  <div id="app">
    {{ info }}
  </div>
<script src="./main.js"></script>
main.js
new Vue({
  el: '#app',
  data() {
    return {
      info: null
    }
  },
  mounted () {
    axios
      .get('https://api.coindesk.com/v1/bpi/currentprice.json',{ responseType: 'arraybuffer' })
      .then(response => (this.info = response))
  }
})

スクリーンショット 2019-08-28 22.27.02.png

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

【Vue.js】CodePenでVue.jsを実装する方法とaxiosでAPIリクエストやってみた

2019.08.28時点

Vue.js サンプル axiosでAPIリクエスト


See the Pen
nonVuejsAxiosAPI
by noriko fujita (@nonkapibara)
on CodePen.




■CodePenのサイト
https://codepen.io/

1.CodePenにログインし、「Pen」を選択する
スクリーンショット 2019-08-28 19.57.23.png

2.「Settings」を選択する
スクリーンショット 2019-08-28 19.58.01.png

3.「JavaScript」を選択する
スクリーンショット 2019-08-28 19.58.47.png

4.Vue.jsのCDNを追加する
「Add External Scripts/Pens」のサーチエリアに「vue」と入力する
スクリーンショット 2019-08-28 20.00.16.png

vueを選択する
スクリーンショット 2019-08-28 20.01.04.png

こんな感じで、Vue.jsのCDNが追加される。
スクリーンショット 2019-08-28 22.16.51.png

5.APIリクエストで「axios」を使用する場合は、
「Add External Scripts/Pens」のサーチエリアに「axios」と入力する
スクリーンショット 2019-08-28 22.03.34.png

6.axiosを選択する
スクリーンショット 2019-08-28 22.04.17.png

7.保存する
スクリーンショット 2019-08-28 22.04.50.png

8.HTMLにdivから書く事ができる。

index.html
  <div id="app">
    {{ info }}
  </div>
<script src="./main.js"></script>
main.js
new Vue({
  el: '#app',
  data() {
    return {
      info: null
    }
  },
  mounted () {
    axios
      .get('https://api.coindesk.com/v1/bpi/currentprice.json',{ responseType: 'arraybuffer' })
      .then(response => (this.info = response))
  }
})

スクリーンショット 2019-08-28 22.27.02.png

Fullだとこんな感じ

index.html
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>Vue.js API</title>
    <!-- 開発バージョン -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>

<body>
  <div id="app">
    {{ info }}
  </div>
    <script src="./main.js"></script>
</body>

</html>

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

Vue.js:Vuexのエラー:「[vuex] Do not mutate vuex store state outside mutation handlers」の原因と対応例

原因

  • コンポーネントの「v-model」に、stateの値を設定していたため。
  • v-modelは参照と設定を同時に行うため、タイトルのエラーが発生する。
issue.vue
<template>
 <!-- v-modelにstateの値を設定 ->
 <v-test-comp v-model="theValue"></v-test-comp>
</template>

<script>
import { mapState } from "vuex";

export default {
  computed: {
    //stateの値にアクセスできるよう設定
    ...mapState(["theValue"])
  }
}
</script>

対応

  • v-modelに設定する値を算出プロパティに置き換える。
  • 算出プロパティのgetでstateの値を返却し、set内で、storeのactionを呼び出す。
resolve.vue
<template>
 <!-- v-modelにstateの値を設定 ->
 <v-test-comp v-model="theValue"></v-test-comp>
</template>

<script>
import { mapState } from "vuex";

export default {
  computed: {
    theValue: {
      get() {
        return this.$store.theValue
      },
      set(value) {
        this.$store.dispatch("setTheValue", value)
      }
    }
  }
}
</script>

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

【Vue.js】Vue.js サンプル「カウントアップ」

↓↓完成図
https://twitter.com/nonnonkapibara/status/1166711295384510465

Vue.jsで、ハート型のボタン「Count Up」をTapする度に、カウントアップしていくサンプル。

See the Pen nonVueJsCountUp by nonkapibara (@nonkapibara) on CodePen.

index.html
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>Vue.js カウントアップ</title>
    <!-- 開発バージョン -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <!-- 本番バージョン -->
    <!-- <script src="https://cdn.jsdelivr.net/npm/vue"></script>-->
    <link rel="stylesheet" type="text/css" href="./style.css">
</head>

<body bgcolor="#FFF8DC">
    <div id="app">
        <center>
            <div class="titleStyle">Vue.js カウントアップ</div>
            <div class="sytle001">{{count_number}}回</div><br>
          <input v-on:click="click_count_up" type="image" class="heartButton" src="pic/button.png">
        </center>
    </div>
    <script src="./main.js"></script>
</body>

</html>

main.js
var app = new Vue({
  el: '#app',
  data:{
    count_number: 1
  },
  methods:{
    click_count_up: function(){
      this.count_number++;
    }
  }
});


style.css
.titleStyle{
    font-size: 30px;
    color: #c71585;
    font-family: 'Hiragino Maru Gothic Pro','ヒラギノ丸ゴ Pro W4', sans-serif;
}
.sytle001{
    font-size: 40px;
    color: #ff69b4;
    font-family: 'Hiragino Maru Gothic Pro','ヒラギノ丸ゴ Pro W4', sans-serif;
}

.heartButton {
  border: 1px solid #ddd;
  width: 80px;
  height: 70px;
  margin: 0 auto;
  overflow: hidden;
  cursor: pointer;
}

.heartButton:hover {
  box-shadow: 5px 5px 5px rgba(0,0,0,0.5);
  transform: translateY(-10px);
  transition-duration: 0.5s;
}

.heartButton:active {
  transform: scale(1.2) rotate(0deg);
  transition-duration: 0.0s;
}

CodePenを埋め込むには、右下の「Embed」を選択する
スクリーンショット 2019-08-28 22.34.55.png

HTMLタブを選択して、内容をコピーすれば、Qiitaに埋め込める
スクリーンショット 2019-08-28 22.36.51.png

はてなブログに、CodePenを埋め込むには、「iframe」を貼り付けたらできました。
スクリーンショット 2019-08-28 22.40.17.png

「はてなブログ」にCodePenが埋め込まれています。
かぴばらさんの覚書ブログ nonkapibara

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

typescriptでbuefyのtoastやmodalを使う

buefybulmaをベースとしたvue.js用のUIコンポーネントです。
もともとbuefyをよく使っていたのですが、最近typescriptでも使ってみたところ、少し詰まったところがあったのでまとめました。

vue.jsでmodalやtoastを使用する場合

公式のドキュメントのサンプルコードをそのままコピペするだけで、このようなtoastが作成できます。

Image from Gyazo

実際にscript内に書くコードはこれだけなのですが、typescriptを使う場合にこのままだと動きませんでした。

this.$buefy.toast.open('Something happened')

参考
https://buefy.org/documentation/toast

typescriptで使う場合

先ほどのコードをそのまま使うと下記のようなエラーが出てしまいます。
TS2339: Property '$buefy' does not exist on type 'App'.

typescriptで使用する場合には、下記の書き方をすれば使用できるようです。

this.$toast.open('Something happened');

同様にmodalなんかを使う場合には、vue.jsだとこのように書くので

this.$buefy.modal.open(
    `<p class="image is-4by3">
        <img src="https://buefy.org/static/img/placeholder-1280x960.png">
    </p>`
)

typescriptで使う場合には、これで使えます。

this.$modal.open(
    `<p class="image is-4by3">
        <img src="https://buefy.org/static/img/placeholder-1280x960.png">
    </p>`
)

参考
https://buefy.org/documentation/modal

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

Rails & Nuxt.jsのアプリケーションにGraphQLを導入する

前提

Rails & Nuxt.jsのDocker環境をalpineイメージで構築する

こちらのポストの環境をもとに進めるので、Dockerのサービス名等は適宜読み替えていただくようにお願いします。

ディレクトリ構成

後述のコマンドでは、Railsは backend、Nuxtは frontend がDockerのサービス名になっています。

.
├── backend <- Ruby on Rails
│   ├── Dockerfile
│   ├── Gemfile
│   ├── Gemfile.lock
│   (中略)
│   
├── frontend <- Nuxt.js
│   ├── Dockerfile
│   ├── README.md
│   ├── nuxt.config.js
│   ├── package-lock.json
│   ├── package.json
│  (中略)
│
├── docker-compose.yml
└── .env

ライブラリ追加

Rails

./backend/Gemfile を修正し、 bundle install します。

## (中略) ##

gem 'graphql' #added

group :development do
  gem 'listen', '>= 3.0.5', '< 3.2'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'

  gem 'graphiql-rails' #added
end

## (中略) ##
$ docker-compose exec backend bundle install

Nuxt

こちらのライブラリをインストールします。

本記事では graphql-tag は使いません。

$ docker-compose exec frontend yarn add @nuxtjs/apollo

実装

Rails

generator で雛形を作成します。

$ docker-compose exec backend rails g graphql:install

graphiql-railsの Readme にしたがって、Railsのconfigファイルを修正します。

./backend/config/routes.rb

GraphiQLエンジンをマウントし、ブラウザからアクセスできるようにします。

Rails.application.routes.draw do
  post "/graphql", to: "graphql#execute" #generatorでinsertされる
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html

  #added
  if Rails.env.development?
    mount GraphiQL::Rails::Engine, at: '/graphiql', graphql_path: '/graphql'
  end
end

./backend/config/application.rb

APIモードの場合に必要な修正です。

## (中略) ##

- # require "sprockets/railtie"
+ require "sprockets/railtie"

## (中略) ##

GraphiQLで動作確認

dockerコンテナを再起動後、ブラウザで http://localhost:3000/graphiql にアクセスし、GraphiQLを開きます。

./backend/app/graphql/types/query_type.rb のサンプルを利用して、下記のようにqueryの結果が返ってくればOKです。

$ docker-compose restart backend

graphiql.png

Nuxt

Nuxtアプリのルートに、下記のディレクトリ、ファイルを追加、編集します。

今回はmutationは使いませんが、あわせて作成しておきます。

.
└── frontend 
    ├── nuxt.config.js
    │
    ├── pages
    │   └── index.vue
    │
    └── apollo
        ├── client-configs
        │   └── default.js
        └── gqls
            ├── mutations
            └── queries
                └── getTestField.gql

./frontend/nuxt.config.js

Nuxtでapolloクライアントを使用するための設定を追加します。

default.js を読み込まず、nuxt.config.jsに直書きしてもOKです。

export default {

  /* (中略) */

  modules: [
    '@nuxtjs/apollo', //added
  ],

  /* (中略) */

  apollo: {
    clientConfigs: {
      default: '~/apollo/client-configs/default.js'
    }
  }
}

./frontend/apollo/client-configs/default.js

apolloには様々なオプションありますがが、今回はqueryの実行が確認できればよいので最低限です。
uriのホスト名は、DockerのRailsアプリのサービス名 backend になります。

import { HttpLink } from 'apollo-link-http'

export default () => {
  const httpLink = new HttpLink({ uri: 'http://backend:3000/graphql' })
  return {
    link: httpLink
  }
}

./frontend/apollo/gqls/queries/testField.gql

GraphiQLで実行したものです。

query {
  testField
}

./frontend/pages/index.vue

queryを実行した結果を表示します。

<template>
  <p>{{ testField }}</p>
</template>

<script>
import testField from '~/apollo/gqls/queries/testField';

export default {
  data() {
    return {
      testField: {}
    }
  },
  apollo: {
    testField: {
      query: testField
    }
  }
}
</script>

nuxt.png

お疲れさまでした。

(簡略化シすぎた感)

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

Textareaで未確定文字がvalueに勝手にinsertされてしまう現象を修正した

概要

社内アプリケーションにていつからか、Windowsユーザの方から
「未確定文字がtextareaに勝手にはいって、かつ、未確定文字は残っている」
という不思議な現象が起きるようになった。

結論

input eventを使えばいい

解消した方法

Vue.jsを使っており、textareaにて、様々なイベントをつけていた。
その一部で keyup eventを使っていたが、それを input event に変更した。

<template>
    <textarea
            v-focus
            rows="8"
            cols="40"
            accesskey="g"
            :value="value"
            @keyup.esc="anything"
            @keypress.enter.exact="something"
            @keyup="update"></textarea>
</template>

<template>
    <textarea
            v-focus
            rows="8"
            cols="40"
            accesskey="g"
            :value="value"
            @keyup.esc="anything"
            @keypress.enter.exact="something"
            @input="update"></textarea>
</template>

原因

正直わからない。(調べ方がわからない)
あるkeyによって起きるということではなく、たまに起きる。ということ、おそらくIMEの問題ではないかと思っている。
詳しい人がいれば教えて下さい。

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

分からねえ!!

Node.jsをインストールしたはいいが、おかしい。

$ npm install -g vue-cli

って打ち込んでEnterしたら

npm ERR! code ENOTFOUND
npm ERR! errno ENOTFOUND
以下略。。

って表示が出て何やらできてないっぽい。。。
なんだ?国会図書館のWifi使ったからか?

そこのあなた助けてくだざい。。。

(追記)
家のwifi繋いだら無事できました。
見てくれた皆さまお騒がせしてすいません。
ありがとうございました。

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

【Vue・React・Elm】ビューの書き方を比べてみた

Vue.jsの場合


Vueのビューはこんな感じ

<template>
  <div>
    <button v-on:click="decrement">-1</button>
    <div>{{ count }}</div>
    <button v-on:click="increment">+1</button>
  </div>
</template>

ほぼHTMLですね。見やすいです。


Vueでの条件分岐や繰り返し

v-ifv-forなどのディレクティブというものが用意されていて、それをHTMLの属性みたいに書きます。

【例】記事タイトル一覧

記事があれば、その分だけ繰り返し表示するし、1件もなければ「記事がありません。」と表示する例です。

<template>
  <ul v-if="items.length">
    <li v-for="item in items">
      {{ item.title }}
    </li>
  </ul>
  <p v-else>記事がありません。</p>
</template>

v-ifとかv-forとか色々覚えないといけませんが、個人的には割と分かりやすいと思います。


Reactの場合


Reactのビューはこんな感じ

render() {
  return (
    <div>
      <button onClick={decrement}>-1</button>
      <div>{ count }</div>
      <button onClick={increment}>+1</button>
    </div>
  )
}

Babelというトランスパイラを使うことで、ReactのビューもHTMLみたいに書けます。
これはJSXという書き方なんですが、これが一部の方からは「何か気持ち悪い」と不評みたいです。。。
個人的には、ほぼHTMLそのままで読みやすいと思します。

ちなみにJSX記法を使わない場合は、以下の様にReact.createElementメソッドを使用します。

render() {
  return (
    React.createElement('div', null, 
      React.createElement('button', { onClick: decrement }, '-1'),
      React.createElement('div', null, count),
      React.createElement('button', { onClick: increment }, '+1')
    )
  )
}

Reactでの条件分岐や繰り返し

JSXの中では、if文やfor文は使えません。
そのため三項演算子や配列のmapメソッドを使って記述します。

【例】記事タイトル一覧

render() {
  return (
    <div>
      { items.length > 0 ? (
        <ul>
          { items.map((item) => {
            return <li>{ item.title }</li>;
          })}
        </ul>
      ) : (
        <p>記事がありません。</p>
      )}
    </div>
  )
}

JSの構文と混じると、少し読みづらいかもしれません。。。


Elmの場合


賢い人は考えた

「HTML要素って、要はタグ名属性中に入ってる子要素たち・・・」
「この3つから構成されているやん?」

<div class="container" id="hoge">
  <span>子要素</span>
</div>

「じゃあ、divとかspanっていう関数を作って・・・」
「第一引数には[ class "containor", id "hoge" ]みたいに属性のリストを渡して」
「第二引数には子要素のリストを渡せばええやん」

div [ class "container", id "hoge" ] [
    span [] [ text "子要素" ]
]

「↑こう書いたらHTML要素を返してくれるような・・・」
「各タグ名に対応したHTML関数たちを作ればええんや!」
「ただの関数やから、ループさせるのに(v-ifとかv-forのような)専用の書き方とか要らんで」
「ElmのifList.map関数とかと、シームレスに組み合わせられるんや!」


Elmではdivspanすらも関数

Elmでは、各HTML要素に対応した関数があらかじめ用意されています。
(header, section, p 等々・・・)

このHTML関数たちを使って、ビュー部分プログラム部分をシームレスかつ柔軟に書くことができます。

「配列の分だけループして表示させるための特別な構文」もありませんし、
「属性値を動的に生成するための特別な書き方」もありません。


ということで、Elmのビューはこんな感じ

view model =
    div []
        [ button [ onClick Increment ] [ text "+1" ]
        , div [] [ text <| String.fromInt model.count ]
        , button [ onClick Decrement ] [ text "-1" ]
        ]

Elmでの条件分岐や繰り返し

条件分岐や繰り返しに関して、ビュー専用の構文はありません。
普通にElmのList.map関数とかifなどを使用します。

【例】記事タイトル一覧

view model =
    div []
        [ if List.length model.items > 0 then
            ul [] (model.items |> List.map liComponent)
            -- 記事(items)が1件以上あれば、
            -- その分だけliComponentを呼び出して表示する

          else
            p [] [ text "記事がありません。" ]
        ]


-- ただ関数を作ればコンポーネントのように利用できる
liComponent title =
    li [] [ text title ]


シンプルでいい感じ!


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

【Vue.js】子から親コンポーネントのイベント実行・データ受け渡し

子から親コンポーネントのデータを更新

初心者用記事です。Vue.jsに少し慣れたので忘れないうちに記事にします。

今回、二つの方法に触れます。
$emitで子から親コンポーネントの処理を呼び出す
$emitで子から親コンポーネントへ値を渡す

以下の記事を参考に追加機能付きで作成しました。
https://dev83.com/vue-emit/
分かりやすい記事なのでお勧めです。

親から子の受け渡しを行うアプリで、ボタンを押すと数が増減する簡単なアプリ作っていきます。
↓ 例
image.png

実際に作ってもいいですが、JSFiddleで動作テストしてもいいと思います。

<div id="app">
</div>

new Vue({
        el: '#app',
        data: {
          // 下部に出てる合計個数
          totalCount: 0,
          items: [
            {
              name: 'りんご',
              price: 100,
              imgUrl: 'https://4.bp.blogspot.com/-uY6ko43-ABE/VD3RiIglszI/AAAAAAAAoEA/kI39usefO44/s800/fruit_ringo.png'
            },
            {
              name: 'バナナ',
              price: 200,
             imgUrl: 'https://1.bp.blogspot.com/-JaRzIloEZw4/UgSMOL-UzYI/AAAAAAAAW_A/vOiX6Tz5oO4/s800/fruit_banana.png'
            },
          ],
        },
      });

style部分

  .item-wrap {
    list-style-type: none;
    display: flex;
    flex-wrap: wrap;
    margin: 0;
    padding: 0;
  }
  .item-wrap li {
    padding: 1em;
  }
  .item-wrap img {
    border-radius: 50%;
  }
  .total-count {
    padding: 1em;
    background-color: #222;
    color: #fff;
  }

現状では何も表示されません。

簡単アプリを追加

本体に追加

<div id="app">
  <ul class="item-wrap">
    <item v-for="item in items" :item="item"></item>
  </ul>
  <div class="total-count">合計:{{ totalCount }}個</div>
</div>

子コンポーネント追加。ファイル読み込んだ方がいいですが今回はjsFiddle使って確認したので一つにまとめて書いてます。

Vue.component('item', {
        // 親から値もらう
        props: ['item'],
        // 子コンポーネントのテンプレート部分
        template: `
          <li>
            <img :src="item.imgUrl" style="height: 200px;">
            <div>{{ item.name }} = {{ childTotal }}個</div>
            <button @click="addCart">増やす</button>
            <button @click="removeCart">減らす</button>
          </li>
        `,
        // 子コンポーネントデータ
        data: function () {
          return {
            childTotal: 0
          };
        },
        // ボタン押すと発火する処理
        methods: {
          addCart: function () {
            this.childTotal += 1;
          },
          removeCart: function () {
            if (this.childTotal > 0) {
              this.childTotal -= 1;
            }
          },
        },
      });

new Vue({
        // 省略。さっき書いたところ
      });

ここまでの状態だと子コンポーネントから親コンポーネントに値を渡していないので
「合計:個」には変化が起きません。

親コンポーネントのデータを更新

子コンポーネントのデータが更新されたら親コンポーネントのデータが更新されるようにします。

子コンポーネントのmethodsを修正。

methods: {
          addCart: function () {
            this.childTotal += 1;
            // ↓追加。親の合計個数増やす
            this.$emit('addbtn');
          },
          removeCart: function () {
            if (this.childTotal > 0) {
              this.childTotal -= 1;
              // ↓追加。親の合計個数減らす
              this.$emit('removebtn');
            }
          },
        },

親コンポーネントを修正
itemコンポーネント部分
methods追加

<div id="app">
  <ul class="item-wrap">
    <item
          v-for="item in items"
          :item="item"
          @addbtn="addParentTotal"
          @removebtn="removeParentTotal"
          >
    </item>
  </ul>
  <div class="total-count">合計:{{ totalCount }}個</div>
</div>
new Vue({
        el: '#app',
        data: {
          totalCount: 0,
          items: [
            {
              name: 'RedApple',
              imgUrl: 'https://4.bp.blogspot.com/-uY6ko43-ABE/VD3RiIglszI/AAAAAAAAoEA/kI39usefO44/s800/fruit_ringo.png'
            },
            {
              name: 'GreenApple',
              imgUrl: 'https://1.bp.blogspot.com/-JaRzIloEZw4/UgSMOL-UzYI/AAAAAAAAW_A/vOiX6Tz5oO4/s800/fruit_banana.png'
            },
          ],
        },
        methods: {
          addParentTotal: function () {
            this.totalCount += 1;
          },
          removeParentTotal: function () {
            this.totalCount -= 1;
          },
        },
      });

最終的な子コンポーネント

      Vue.component('item', {
        props: ['item'],
        template: `
          <li>
            <img :src="item.imgUrl" style="height: 200px;">
            <div>{{ item.name }} = {{ childTotal }}個</div>
            <button @click="addCart">増やす</button>
            <button @click="removeCart">減らす</button>
          </li>
        `,
        data: function () {
          return {
            childTotal: 0
          };
        },
        methods: {
          addCart: function () {
            this.childTotal += 1;
            this.$emit('addbtn')
          },
          removeCart: function () {
            if (this.childTotal > 0) {
              this.childTotal -= 1;
              this.$emit('removebtn');
            }
          },
        },
      });

ひとまず完成

vue-emit.gif

@addbtn=”addParentTotal”
@removebtn=”removeParentTotal”

親コンポーネントの<item>~~~</item>に追加したのはこのふたつ。

子コンポーネントのthis.$emit('addbtn')
もしくはthis.$emit('removebtn');が発火したら
親コンポーネントのmethodsにあるaddParentTotal removeParentTotal
を呼び出してくださいという処理です。

子から親に値を受け渡す

現状だと親コンポーネントで合計を計算していますが、子コンポーネントからの値を直接受け取り、計算したい場合もあると思います。

サンプル:値段を計算

親コンポーネントに値段を定義して、子コンポーネントから受け取った個数をもとに合計金額を計算する機能を追加します

受け渡しは引数を追加してやります。

this.$emit('addbtn', this.childTotal, name)

この場合だとthis.childTotalとnameを渡しています。
親の受け取る側

addParentTotal: function (total, name){
}

これで子から親へデータを渡すことができます。

サンプルを作ったのですが、 改造してたら意外とシンプルにならずに申し訳ないです…

<div id="app">
  <ul class="item-wrap">
    <item
          v-for="item in items"
          :item="item"
          @addbtn="addParentTotal"
          @removebtn="removeParentTotal"
          >
    </item>
  </ul>
  <div class="total-count">合計:{{ totalCount }}個</div>
  <p class="total-count" v-for="item in items">
    {{ item.name }}の合計金額:¥{{ item.totalYen }}
  </p>
</div>

子コンポーネント

Vue.component('item', {
  props: ['item'],
  template: `
    <li>
      <img :src="item.imgUrl" style="height: 200px;">
      <div>{{ item.name }} = {{ childTotal }}個</div>
      <button @click="addCart(item.name)">増やす</button>
      <button @click="removeCart(item.name)">減らす</button>
    </li>
  `,
  data: function () {
    return {
      childTotal: 0
    };
  },
  methods: {
    addCart: function (name) {
      this.childTotal += 1;
      this.$emit('addbtn', this.childTotal, name)
    },
    removeCart: function (name) {
      if (this.childTotal > 0) {
        this.childTotal -= 1;
        this.$emit('removebtn', this.childTotal, name);
      }
    },
  },
});

親コンポーネント

new Vue({
        el: '#app',
        data: {
          totalCount: 0,
          items: [
            {
              name: 'リンゴ',
              price: 100,
              imgUrl: 'https://4.bp.blogspot.com/-uY6ko43-ABE/VD3RiIglszI/AAAAAAAAoEA/kI39usefO44/s800/fruit_ringo.png',
              number: 0,
              totalYen: 0
            },
            {
              name: 'バナナ',
              price: 200,
              imgUrl: 'https://1.bp.blogspot.com/-JaRzIloEZw4/UgSMOL-UzYI/AAAAAAAAW_A/vOiX6Tz5oO4/s800/fruit_banana.png',
              number: 0,
              totalYen: 0
            },
          ],
        },
        methods: {
          addParentTotal: function (total, name) {
            this.totalCount += 1;
            // 名前一致する該当の要素をオブジェクトから入れる
            const item = this.items.find(ele => ele.name === name)
            item.number = total
            item.totalYen = total * item.price
          },
          removeParentTotal: function (total, name) {
            this.totalCount -= 1;
            // 名前一致する該当の要素をオブジェクトから入れる
            const item = this.items.find(ele => ele.name === name)
            item.number = total
            item.totalYen = total * item.price
          },
        },
      });
  .item-wrap {
    list-style-type: none;
    display: flex;
    flex-wrap: wrap;
    margin: 0;
    padding: 0;
  }
  .item-wrap li {
    padding: 1em;
  }
  .item-wrap img {
    border-radius: 50%;
  }
  .total-count {
    padding: 1em;
    background-color: #222;
    color: #fff;
  }

それぞれのオブジェクトに個数を操作。
また、1個当たりの値段から金額を計算するアプリ完成。
vue-emit2.gif

これなにに使えるの?

色々ありますけど、モーダルなどが分かりやすい例です。
モーダルを子コンポーネントにして、フォームの入力をしてもらうと親の値も変わるみたいな感じです。

それについては次の機会に

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

Vue CLI で VSCode+TypeScript+ESLint+Prettierの環境構築

はじめに

TypeScriptの場合も、これからはESLintらしい。

Vue CLIのV3を使って、TypeScript+ESLintの初期構築手順をまとめる。

手順

vue-cliのインストール

# npm i -g @vue-cli
# vue -V
3.11.0

Vue CLIによるプロジェクト作成

# vue create myproject

マニュアルを選択

? Please pick a preset: 
  default (babel, eslint) 
❯ Manually select features 

インストールしたい機能を選択する。
TypeScriptLinter / Formatterを有効化。その他はお好みで。

? Check the features needed for your project: 
 ◉ Babel
 ◉ TypeScript
 ◯ Progressive Web App (PWA) Support
 ◉ Router
 ◉ Vuex
 ◉ CSS Pre-processors
❯◉ Linter / Formatter
 ◯ Unit Testing
 ◯ E2E Testing

以降は有効化した機能に関する質問が続く。

Linterとフォーマッタに関する質問で、ESLint + Prettierを選択。

? Pick a linter / formatter config: 
  TSLint 
  ESLint with error prevention only 
  ESLint + Airbnb config 
  ESLint + Standard config 
❯ ESLint + Prettier

それ以外の質問はお好みでOK。

VSCodeの設定追加

ESLintの拡張機能を追加。
ESLint

さらに、プロジェクト設定を追加。

setting.json
{
  "eslint.autoFixOnSave": true,
  "eslint.validate": [
    "javascript",
    {
      "language": "typescript",
      "autoFix": true
    },
    {
      "language": "vue",
      "autoFix": true
    }
  ],
}

Prettierの設定追加

.prettierrcをルートディレクトリに追加する。

{
  "tabWidth": 2,
  "semi": true,
  "singleQuote": true
}

.eslintrc.jsのrulesに追加してもOK

eslintrc.js
module.exports = {
  root: true,
  env: {
    node: true
  },
  extends: ["plugin:vue/essential", "@vue/prettier", "@vue/typescript"],
  rules: {
    "no-console": process.env.NODE_ENV === "production" ? "error" : "off",
    "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off",
    // 以下を追加
    "prettier/prettier": [
      "error",
      {
        "singleQuote": true,
        "semi": false
      }
    ]
  },
  parserOptions: {
    parser: "@typescript-eslint/parser"
  }
};

ESLintの実行

デフォルトの設定だと、Linterが勝手に修正してしまう。
https://teratail.com/questions/186587

無効化したい場合は、package.jsonを修正する

package.json
{
  "name": "myproject",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    // --no-fixを追加
    "lint": "vue-cli-service lint --no-fix",
    // 追加
    "lint:fix": "vue-cli-service lint"
  },
  "dependencies": {
    // 各種ライブラリ
  },
  "devDependencies": {
    // 各種ライブラリ
  }

ESLintを実行

# npm run lint

警告が多数検出されたら、正常に動作している。

warning: Replace `"Avenir"` with `'Avenir'` (prettier/prettier) at src/App.vue:13:16:

補足

既存のプロジェクトをTSLintからESLintに切り替える場合は、色々ライブラリを追加する必要があるみたい
Vue.jsでtypescript-eslint + Prettierを導入する

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

【Vue初心者向け】超シンプルなお買い物リストアプリを作る

Vue.jsで作ったシンプルなサンプルコードです。

対象者は、Vue.jsを始めたところで、色々サンプルコードをいじってみたい人です。

商品名、個数を入力して登録すると買い物リストが表示されます。重要な機能のコードが分かるよう、エラー処理はしていません。

実際に動作はこちらで確認できます。
お買い物リストアプリ

<template>
    <div class="shopping_component">
        <section>
            <div class="input_data">
                <p>買うもの:<input v-model="name" type="text" placeholder="買うもの"></p>
                <p>個数:<input v-model="number" type="text" placeholder="個数"></p>
            </div>
            <button @click="input">登録する</button>
        </section>
        <section>
            <div class="shopping_list">
                <li v-for="(item, index) in shoppingList" :key="index">
                    {{item.name}}{{item.number}}個
                    <button @click="deleteItem(index)">削除</button>
                </li>
            </div>
        </section>
    </div>

</template>

<script>
export default {
    data(){
        return{
            name: '',
            number: 1,
            shoppingList: []
        }
    },
    methods:{
        input(){
            var item = {name:this.name, number: this.number}
            this.shoppingList.push(item)
            this.name = ''
        },
        deleteItem(index){
            this.shoppingList.splice(index, 1)
        }
    }
}
</script>

<style scoped>

</style>

ポイントは、配列をv-forで表示する時にindexにつけることです。indexをつけておけば、削除ボダンにこのindexを渡すことで簡単に該当する項目を削除できます。
splice(index,1)は「index番目から始める要素を1つ削除してね」という意味です。

宣伝:ブログやっています→ ブログ

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